From 7c4196475c557c51788443fe336b5896ad30ca31 Mon Sep 17 00:00:00 2001 From: "kevinguevaradevia@gmail.com" Date: Tue, 2 Jun 2026 04:44:25 -0500 Subject: [PATCH] fix: 3 bugs in Electron export pipeline Bug 1: RENDERS_DIR mismatch (root cause) - server.ts had hardcoded renders dir, now reads BRADLY_RENDERS_DIR env - renderQueue.ts saves to ~/Library/.../Bradly/renders/ - server.ts now serves from the same directory Bug 2: Upload origin pointing to wrong port - uploadBlobContent.ts used window.location.origin (Vite 5173) - Remotion bundler needs Express origin (3000) to access media - Added getExpressOrigin() helper that detects Electron Bug 3: Batch ZIP export using file-saver (doesn't work in Electron) - Added saveBlobFile IPC method (preload + main) - batchExporter.ts now uses native save dialog in Electron - Web mode falls back to file-saver --- server.ts | 2 +- src/electron/main.ts | 22 ++++++++++++++++++++++ src/electron/preload.ts | 7 +++++++ src/utils/batchExporter.ts | 14 +++++++++++++- src/utils/uploadBlobContent.ts | 20 ++++++++++++++++++-- 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/server.ts b/server.ts index 2c71e9e..0581884 100644 --- a/server.ts +++ b/server.ts @@ -190,7 +190,7 @@ export async function createExpressApp() { }); // ═══ Render Queue ═══ - const RENDERS_DIR = path.join(process.cwd(), "renders"); + const RENDERS_DIR = process.env.BRADLY_RENDERS_DIR || path.join(process.cwd(), "renders"); if (!fs.existsSync(RENDERS_DIR)) { fs.mkdirSync(RENDERS_DIR, { recursive: true }); } diff --git a/src/electron/main.ts b/src/electron/main.ts index d3ab980..1b0237f 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -250,6 +250,28 @@ function setupIPC() { return null; } }); + + // Save raw blob data to user-chosen location (for ZIP exports etc.) + ipcMain.handle('file:saveBlob', async (_event, data: Uint8Array, defaultName: string, filters?: { name: string; extensions: string[] }[]) => { + if (!mainWindow) return null; + + const result = await dialog.showSaveDialog(mainWindow, { + title: 'Guardar archivo', + defaultPath: path.join(app.getPath('downloads'), defaultName), + filters: filters || [{ name: 'Archivo', extensions: ['*'] }], + }); + + if (result.canceled || !result.filePath) return null; + + try { + await fs.promises.writeFile(result.filePath, Buffer.from(data)); + console.log(`✅ Blob saved to: ${result.filePath}`); + return result.filePath; + } catch (err) { + console.error('❌ Failed to save blob:', err); + return null; + } + }); } // ═══ Remotion Browser Pre-download ═══ diff --git a/src/electron/preload.ts b/src/electron/preload.ts index ca1f04b..a6751cf 100644 --- a/src/electron/preload.ts +++ b/src/electron/preload.ts @@ -37,6 +37,13 @@ const electronAPI = { saveRenderedFile: (renderUrl: string, defaultName: string) => ipcRenderer.invoke('file:saveRendered', renderUrl, defaultName) as Promise, + /** + * Save raw binary data (e.g. a ZIP blob) to a user-chosen location. + * Shows native save dialog, then writes the bytes. + */ + saveBlobFile: (data: Uint8Array, defaultName: string, filters?: { name: string; extensions: string[] }[]) => + ipcRenderer.invoke('file:saveBlob', data, defaultName, filters) as Promise, + // ─── App Info ─── getAppInfo: () => ipcRenderer.invoke('app:info') as Promise<{ diff --git a/src/utils/batchExporter.ts b/src/utils/batchExporter.ts index ac08331..2c73fac 100644 --- a/src/utils/batchExporter.ts +++ b/src/utils/batchExporter.ts @@ -198,7 +198,19 @@ export async function exportBatchAsZip( .replace(/\s+/g, '_') .replace(/[^a-zA-Z0-9._-]/g, ''); - saveAs(zipBlob, zipName); + // In Electron, use native save dialog + const electronAPI = (typeof window !== 'undefined') ? (window as any).electronAPI : null; + if (electronAPI?.saveBlobFile) { + const arrayBuffer = await zipBlob.arrayBuffer(); + await electronAPI.saveBlobFile( + new Uint8Array(arrayBuffer), + zipName, + [{ name: 'ZIP Archive', extensions: ['zip'] }], + ); + } else { + // Web fallback + saveAs(zipBlob, zipName); + } onProgress?.({ current: total, total, status: 'done' }); } diff --git a/src/utils/uploadBlobContent.ts b/src/utils/uploadBlobContent.ts index 01d62d9..e30ad9f 100644 --- a/src/utils/uploadBlobContent.ts +++ b/src/utils/uploadBlobContent.ts @@ -27,6 +27,23 @@ async function blobUrlToFile(blobUrl: string, fallbackName: string): Promise { } const data = await res.json(); // Return absolute URL so Remotion's bundler (different port) can reach it - const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'; - return `${origin}${data.url}`; + return `${getExpressOrigin()}${data.url}`; } /**