// ===== FUNÇÕES PARA UPLOAD DIRETO AO BUNNYCDN ===== /** * Função para fazer upload direto de arquivo para BunnyCDN * @param {File} file - Arquivo a ser enviado * @param {Function} progressCallback - Callback para atualizar progresso * @returns {Promise} - Resultado do upload com URL */ async function uploadToBunnyCDN(file, progressCallback = null) { console.log('📤 [BUNNYCDN-UPLOAD] Iniciando upload direto para BunnyCDN'); console.log('📤 [BUNNYCDN-UPLOAD] Arquivo:', file.name); console.log('📤 [BUNNYCDN-UPLOAD] Tamanho:', (file.size / (1024 * 1024)).toFixed(2), 'MB'); try { // Obter informações de upload do servidor console.log('🔐 [BUNNYCDN-UPLOAD] Obtendo informações de upload do servidor...'); const uploadInfoResponse = await fetch('/api/bunnycdn/upload-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: file.name, fileType: file.type }) }); if (!uploadInfoResponse.ok) { const errorData = await uploadInfoResponse.json(); throw new Error(errorData.erro || 'Erro ao obter informações de upload do BunnyCDN'); } const uploadInfo = await uploadInfoResponse.json(); console.log('✅ [BUNNYCDN-UPLOAD] Informações de upload obtidas com sucesso'); console.log('✅ [BUNNYCDN-UPLOAD] Nome do arquivo:', uploadInfo.fileName); console.log('✅ [BUNNYCDN-UPLOAD] URL pública:', uploadInfo.publicUrl); // Fazer upload direto para BunnyCDN usando XMLHttpRequest para controle de progresso console.log('📤 [BUNNYCDN-UPLOAD] Iniciando upload para BunnyCDN...'); return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); let heartbeatInterval = null; let lastProgressTime = Date.now(); // Heartbeat para manter conexão viva durante uploads grandes const startHeartbeat = () => { heartbeatInterval = setInterval(() => { const now = Date.now(); const timeSinceLastProgress = now - lastProgressTime; // Se não houve progresso por mais de 30 segundos, enviar heartbeat if (timeSinceLastProgress > 30000) { console.log('💓 [BUNNYCDN-UPLOAD] Enviando heartbeat...'); // Fazer uma pequena requisição para manter conexão viva fetch('/api/bunnycdn/status', { method: 'GET' }).catch(() => {}); lastProgressTime = now; } }, 15000); // Verificar a cada 15 segundos }; const stopHeartbeat = () => { if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; } }; // Configurar progresso xhr.upload.addEventListener('progress', (event) => { lastProgressTime = Date.now(); if (event.lengthComputable && progressCallback) { const percentComplete = Math.round((event.loaded / event.total) * 100); console.log(`📤 [BUNNYCDN-UPLOAD] Progresso: ${percentComplete}%`); progressCallback(percentComplete); } }); // Configurar resposta xhr.addEventListener('load', () => { stopHeartbeat(); if (xhr.status === 200 || xhr.status === 201) { console.log('✅ [BUNNYCDN-UPLOAD] Upload concluído com sucesso!'); console.log('✅ [BUNNYCDN-UPLOAD] URL pública:', uploadInfo.publicUrl); console.log('✅ [BUNNYCDN-UPLOAD] Nome do arquivo:', uploadInfo.fileName); // Retornar dados no formato esperado (compatível com Cloudinary) resolve({ secure_url: uploadInfo.publicUrl, public_id: uploadInfo.fileName, original_filename: uploadInfo.originalName, bytes: file.size, format: file.name.split('.').pop(), resource_type: 'video' }); } else { console.error('❌ [BUNNYCDN-UPLOAD] Erro HTTP:', xhr.status, xhr.statusText); console.error('❌ [BUNNYCDN-UPLOAD] Resposta:', xhr.responseText); reject(new Error(`Erro no upload: ${xhr.status} ${xhr.statusText}`)); } }); // Configurar erro xhr.addEventListener('error', () => { stopHeartbeat(); console.error('❌ [BUNNYCDN-UPLOAD] Erro de rede durante upload'); reject(new Error('Erro de rede durante upload para BunnyCDN')); }); // Configurar timeout xhr.addEventListener('timeout', () => { stopHeartbeat(); console.error('❌ [BUNNYCDN-UPLOAD] Timeout durante upload'); reject(new Error('Timeout durante upload para BunnyCDN')); }); // Configurar e enviar requisição // Construir URL correta para upload direto ao BunnyCDN Storage // O uploadPath já vem no formato correto: /{storageZoneName}/{storagePath}/{fileName} const uploadUrl = `https://${uploadInfo.storageHostname}${uploadInfo.uploadPath}`; console.log(`🔗 [BUNNYCDN-UPLOAD] URL de upload corrigida: ${uploadUrl}`); xhr.open('PUT', uploadUrl); // Headers necessários para BunnyCDN Storage API xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream'); // Adicionar header de autenticação do BunnyCDN xhr.setRequestHeader('AccessKey', uploadInfo.accessKey); // Timeout mais longo para arquivos grandes (2 horas) const timeoutDuration = Math.max(7200000, file.size / 1024 / 1024 * 3000); // 3 segundos por MB, mínimo 2 horas xhr.timeout = timeoutDuration; console.log(`⏱️ [BUNNYCDN-UPLOAD] Timeout configurado para: ${Math.round(timeoutDuration/1000)}s`); console.log(`🔗 [BUNNYCDN-UPLOAD] URL de upload: ${uploadUrl}`); // Iniciar heartbeat para arquivos grandes (>50MB) if (file.size > 50 * 1024 * 1024) { console.log('💓 [BUNNYCDN-UPLOAD] Iniciando heartbeat para arquivo grande'); startHeartbeat(); } xhr.send(file); }); } catch (error) { console.error('❌ [BUNNYCDN-UPLOAD] Erro geral:', error); throw new Error(`Falha no upload para BunnyCDN: ${error.message}`); } } /** * Função alternativa usando fetch para upload via servidor * @param {File} file - Arquivo a ser enviado * @param {Function} progressCallback - Callback para atualizar progresso * @returns {Promise} - Resultado do upload com URL */ async function uploadToBunnyCDNViaServer(file, progressCallback = null) { console.log('📤 [BUNNYCDN-SERVER] Iniciando upload via servidor para BunnyCDN'); console.log('📤 [BUNNYCDN-SERVER] Arquivo:', file.name); console.log('📤 [BUNNYCDN-SERVER] Tamanho:', (file.size / (1024 * 1024)).toFixed(2), 'MB'); try { // Obter informações de upload const uploadInfoResponse = await fetch('/api/bunnycdn/upload-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: file.name, fileType: file.type }) }); if (!uploadInfoResponse.ok) { const errorData = await uploadInfoResponse.json(); throw new Error(errorData.erro || 'Erro ao obter informações de upload'); } const uploadInfo = await uploadInfoResponse.json(); // Converter arquivo para base64 const fileBuffer = await new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => { const base64 = reader.result.split(',')[1]; resolve(base64); }; reader.readAsDataURL(file); }); // Simular progresso se callback fornecido if (progressCallback) { progressCallback(50); } // Fazer upload via servidor const uploadResponse = await fetch('/api/bunnycdn/upload', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ uploadPath: uploadInfo.uploadPath, fileBuffer: fileBuffer, contentType: file.type }) }); if (!uploadResponse.ok) { const errorData = await uploadResponse.json(); throw new Error(errorData.erro || 'Erro no upload'); } if (progressCallback) { progressCallback(100); } console.log('✅ [BUNNYCDN-SERVER] Upload concluído com sucesso!'); // Retornar dados no formato esperado return { secure_url: uploadInfo.publicUrl, public_id: uploadInfo.fileName, original_filename: uploadInfo.originalName, bytes: file.size, format: file.name.split('.').pop(), resource_type: 'video' }; } catch (error) { console.error('❌ [BUNNYCDN-SERVER] Erro:', error); throw new Error(`Falha no upload via servidor: ${error.message}`); } } /** * Função para deletar arquivo do BunnyCDN * @param {String} fileName - Nome do arquivo no BunnyCDN * @returns {Promise} */ async function deleteFromBunnyCDN(fileName) { if (!fileName) { console.warn('🗑️ [BUNNYCDN-DELETE] Nome do arquivo não fornecido'); return; } try { console.log('🗑️ [BUNNYCDN-DELETE] Deletando arquivo:', fileName); const response = await fetch('/api/bunnycdn/delete', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: fileName }) }); if (response.ok) { console.log('✅ [BUNNYCDN-DELETE] Arquivo deletado com sucesso'); } else { console.warn('⚠️ [BUNNYCDN-DELETE] Erro ao deletar (não crítico):', response.status); } } catch (error) { console.warn('⚠️ [BUNNYCDN-DELETE] Erro ao deletar (não crítico):', error.message); // Não propagar erro de limpeza } } /** * Função para verificar se um arquivo é muito grande para Cloudinary * @param {File} file - Arquivo a ser verificado * @returns {boolean} - True se o arquivo é muito grande para Cloudinary */ function isFileTooLargeForCloudinary(file) { const CLOUDINARY_LIMIT = 100 * 1024 * 1024; // 100MB return file.size > CLOUDINARY_LIMIT; } /** * Função para verificar status da conexão com BunnyCDN * @returns {Promise} - Status da conexão */ async function checkBunnyCDNStatus() { try { console.log('🔍 [BUNNYCDN-STATUS] Verificando status...'); const response = await fetch('/api/bunnycdn/status'); const data = await response.json(); if (response.ok) { console.log('✅ [BUNNYCDN-STATUS] Status OK:', data.status); } else { console.warn('⚠️ [BUNNYCDN-STATUS] Status com problema:', data.status); } return data; } catch (error) { console.error('❌ [BUNNYCDN-STATUS] Erro ao verificar status:', error); return { status: 'erro', mensagem: 'Erro ao verificar status', erro: error.message }; } } console.log('✅ [BUNNYCDN-UPLOAD] Módulo de upload BunnyCDN carregado');