UNPKG

@devicecloud.dev/dcd

Version:

Better cloud maestro testing

215 lines (214 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SupabaseGateway = void 0; const supabase_js_1 = require("@supabase/supabase-js"); const tus = require("tus-js-client"); class SupabaseGateway { static SB = { dev: { SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ', SUPABASE_URL: 'https://lbmsowehtjwnqlurpemb.supabase.co', }, prod: { SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8', SUPABASE_URL: 'https://cloud.devicecloud.dev', }, }; static getSupabaseKeys(env) { return this.SB[env]; } /** * Upload to Supabase using resumable uploads (TUS protocol) * Uploads to staging location (uploads/{id}/) using anon key * File is later moved to final location by API after finalization * @param env - Environment (dev or prod) * @param path - Staging storage path (uploads/{id}/file.ext) * @param file - File to upload * @param debug - Enable debug logging * @param onProgress - Optional callback for upload progress (bytesUploaded, bytesTotal) * @returns Promise that resolves when upload completes */ static async uploadResumable(env, path, file, debug = false, onProgress) { const { SUPABASE_PUBLIC_KEY, SUPABASE_URL } = this.getSupabaseKeys(env); // Extract project ID from Supabase URL for the storage endpoint // Format: https://project-id.supabase.co or https://cloud.devicecloud.dev (custom domain) let projectId; if (SUPABASE_URL.includes('.supabase.co')) { projectId = SUPABASE_URL.replace('https://', '').split('.')[0]; } else { // For custom domains like cloud.devicecloud.dev, we need the project ref // This is the dev environment project ref projectId = env === 'dev' ? 'lbmsowehtjwnqlurpemb' : 'pgydnphbimetinsgfkbo'; } const storageUrl = `https://${projectId}.storage.supabase.co`; if (debug) { console.log(`[DEBUG] Resumable upload starting...`); console.log(`[DEBUG] Storage URL: ${storageUrl}`); console.log(`[DEBUG] Upload path: ${path}`); console.log(`[DEBUG] File name: ${file.name}`); console.log(`[DEBUG] File size: ${(file.size / 1024 / 1024).toFixed(2)} MB`); } // Convert File to Buffer for Node.js tus-js-client // In Node.js environment, tus-js-client expects Buffer or Readable stream, not File const fileBuffer = Buffer.from(await file.arrayBuffer()); if (debug) { console.log(`[DEBUG] Converted File to Buffer (${fileBuffer.length} bytes)`); } return new Promise((resolve, reject) => { const upload = new tus.Upload(fileBuffer, { // TUS endpoint for Supabase Storage endpoint: `${storageUrl}/storage/v1/upload/resumable`, // Retry configuration - will retry 5 times with increasing delays retryDelays: [0, 3000, 5000, 10_000, 20_000], // Authentication and headers // Use anon key for staging uploads to uploads/* folder headers: { authorization: `Bearer ${SUPABASE_PUBLIC_KEY}`, 'x-upsert': 'true', // Allow overwriting existing files }, // Upload metadata metadata: { bucketName: 'organizations', objectName: path, contentType: file.type || 'application/octet-stream', cacheControl: '3600', }, // Chunk size must be 6MB for Supabase chunkSize: 6 * 1024 * 1024, // Remove fingerprint on success to allow re-uploading identical files removeFingerprintOnSuccess: true, // Upload data during creation for better performance uploadDataDuringCreation: true, // Progress callback onProgress(bytesUploaded, bytesTotal) { if (debug) { const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); console.log(`[DEBUG] Upload progress: ${(bytesUploaded / 1024 / 1024).toFixed(2)} MB / ${(bytesTotal / 1024 / 1024).toFixed(2)} MB (${percentage}%)`); } // Call user-provided progress callback if available if (onProgress) { onProgress(bytesUploaded, bytesTotal); } }, // Error handler onError(error) { if (debug) { console.error(`[DEBUG] === RESUMABLE UPLOAD ERROR ===`); console.error(`[DEBUG] Error:`, error); console.error(`[DEBUG] Error message: ${error.message}`); if (error.stack) { console.error(`[DEBUG] Stack trace:\n${error.stack}`); } } reject(new Error(`Resumable upload failed: ${error.message}`)); }, // Success handler onSuccess() { if (debug) { console.log(`[DEBUG] Resumable upload completed successfully`); console.log(`[DEBUG] Upload path: ${path}`); } resolve(); }, }); // Start the upload if (debug) { console.log(`[DEBUG] Starting TUS upload...`); } upload.start(); }); } static async uploadToSignedUrl(env, path, token, file, debug = false) { const { SUPABASE_PUBLIC_KEY, SUPABASE_URL } = this.getSupabaseKeys(env); if (debug) { console.log(`[DEBUG] Supabase upload starting...`); console.log(`[DEBUG] Supabase URL: ${SUPABASE_URL}`); console.log(`[DEBUG] Upload path: ${path}`); console.log(`[DEBUG] File name: ${file.name}`); console.log(`[DEBUG] File type: ${file.type}`); } try { const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_PUBLIC_KEY); const uploadToUrl = await supabase.storage .from('organizations') .uploadToSignedUrl(path, token, file); if (uploadToUrl.error) { const error = uploadToUrl.error; const errorMessage = typeof error === 'string' ? error : error?.message || 'Upload failed'; if (debug) { console.error(`[DEBUG] Supabase upload error:`, error); if (typeof error === 'object' && 'statusCode' in error) { console.error(`[DEBUG] HTTP Status Code: ${error.statusCode}`); } } throw new Error(errorMessage); } if (debug) { console.log(`[DEBUG] Supabase upload successful`); console.log(`[DEBUG] Upload result path: ${uploadToUrl.data?.path || 'N/A'}`); } } catch (error) { if (debug) { this.logUploadException(error, env, SUPABASE_URL, path, file); } // Re-throw with additional context const errorMsg = error instanceof Error ? error.message : String(error); throw new Error(`Supabase upload error: ${errorMsg}`); } } /** * Logs network error details for debugging * @param error - Error object to analyze for network-related issues * @returns void */ static logNetworkError(error) { const errorMsg = error.message.toLowerCase(); if (errorMsg.includes('econnrefused') || errorMsg.includes('connection refused')) { console.error(`[DEBUG] Network error: Connection refused - check internet connectivity`); } else if (errorMsg.includes('etimedout') || errorMsg.includes('timeout')) { console.error(`[DEBUG] Network error: Connection timeout - check internet connectivity or try again`); } else if (errorMsg.includes('enotfound') || errorMsg.includes('not found')) { console.error(`[DEBUG] Network error: DNS lookup failed - check internet connectivity`); } else if (errorMsg.includes('econnreset') || errorMsg.includes('connection reset') || errorMsg.includes('connection lost')) { console.error(`[DEBUG] Network error: Connection reset/lost - network unstable or interrupted`); } else if (errorMsg.includes('network')) { console.error(`[DEBUG] Network-related error detected - check internet connectivity`); } } /** * Logs upload exception details for debugging * @param error - Exception that occurred during upload * @param env - Environment (dev or prod) * @param supabaseUrl - Supabase URL being used * @param path - Upload path * @param file - File being uploaded * @returns void */ static logUploadException(error, env, supabaseUrl, path, file) { console.error(`[DEBUG] === SUPABASE UPLOAD EXCEPTION ===`); console.error(`[DEBUG] Exception caught:`, error); console.error(`[DEBUG] Error type: ${error instanceof Error ? error.name : typeof error}`); console.error(`[DEBUG] Error message: ${error instanceof Error ? error.message : String(error)}`); if (error instanceof Error && error.stack) { console.error(`[DEBUG] Error stack:\n${error.stack}`); } // Log additional context console.error(`[DEBUG] Upload context:`); console.error(`[DEBUG] - Environment: ${env}`); console.error(`[DEBUG] - Supabase URL: ${supabaseUrl}`); console.error(`[DEBUG] - Upload path: ${path}`); console.error(`[DEBUG] - File name: ${file.name}`); console.error(`[DEBUG] - File size: ${file.size} bytes`); // Check for common network errors if (error instanceof Error) { this.logNetworkError(error); } } } exports.SupabaseGateway = SupabaseGateway;