diff --git a/apps/desktop/src/main/core/browser/Browser.ts b/apps/desktop/src/main/core/browser/Browser.ts index 78fae6b1f3..bce6c60383 100644 --- a/apps/desktop/src/main/core/browser/Browser.ts +++ b/apps/desktop/src/main/core/browser/Browser.ts @@ -374,14 +374,10 @@ export default class Browser { | undefined; logger.info(`Creating new BrowserWindow instance: ${this.identifier}`); logger.debug(`[${this.identifier}] Options for new window: ${JSON.stringify(this.options)}`); - logger.debug( - `[${this.identifier}] Saved window state: ${JSON.stringify(savedState)}`, - ); + logger.debug(`[${this.identifier}] Saved window state: ${JSON.stringify(savedState)}`); const resolvedState = this.resolveWindowState(savedState, { height, width }); - logger.debug( - `[${this.identifier}] Resolved window state: ${JSON.stringify(resolvedState)}`, - ); + logger.debug(`[${this.identifier}] Resolved window state: ${JSON.stringify(resolvedState)}`); const browserWindow = new BrowserWindow({ ...res, @@ -569,33 +565,65 @@ export default class Browser { } /** - * Setup CORS bypass for local file server (127.0.0.1:*) - * This is needed for Electron to access files from the local static file server + * Setup CORS bypass for ALL requests + * In production, the renderer uses app://next protocol which triggers CORS for all external requests + * This completely bypasses CORS by: + * 1. Removing Origin header from requests (prevents OPTIONS preflight) + * 2. Adding proper CORS response headers using the stored origin value */ private setupCORSBypass(browserWindow: BrowserWindow): void { - logger.debug(`[${this.identifier}] Setting up CORS bypass for local file server`); + logger.debug(`[${this.identifier}] Setting up CORS bypass for all requests`); const session = browserWindow.webContents.session; - // Intercept response headers to add CORS headers + // Store origin values for each request ID + const originMap = new Map(); + + // Remove Origin header and store it for later use + session.webRequest.onBeforeSendHeaders((details, callback) => { + const requestHeaders = { ...details.requestHeaders }; + + // Store and remove Origin header to prevent CORS preflight + if (requestHeaders['Origin']) { + originMap.set(details.id, requestHeaders['Origin']); + delete requestHeaders['Origin']; + logger.debug( + `[${this.identifier}] Removed Origin header for: ${details.url} (stored: ${requestHeaders['Origin']})`, + ); + } + + callback({ requestHeaders }); + }); + + // Add CORS headers to ALL responses using stored origin session.webRequest.onHeadersReceived((details, callback) => { - const url = details.url; + const responseHeaders = details.responseHeaders || {}; - // Only modify headers for local file server requests (127.0.0.1) - if (url.includes('127.0.0.1') || url.includes('lobe-desktop-file')) { - const responseHeaders = details.responseHeaders || {}; + // Get the original origin from our map, fallback to default + const origin = originMap.get(details.id) || '*'; - // Add CORS headers - responseHeaders['Access-Control-Allow-Origin'] = ['*']; - responseHeaders['Access-Control-Allow-Methods'] = ['GET, POST, PUT, DELETE, OPTIONS']; - responseHeaders['Access-Control-Allow-Headers'] = ['*']; + // Cannot use '*' when Access-Control-Allow-Credentials is true + responseHeaders['Access-Control-Allow-Origin'] = [origin]; + responseHeaders['Access-Control-Allow-Methods'] = ['GET, POST, PUT, DELETE, OPTIONS, PATCH']; + responseHeaders['Access-Control-Allow-Headers'] = ['*']; + responseHeaders['Access-Control-Allow-Credentials'] = ['true']; + + // Clean up the stored origin after response + originMap.delete(details.id); + + // For OPTIONS requests, add preflight cache and override status + if (details.method === 'OPTIONS') { + responseHeaders['Access-Control-Max-Age'] = ['86400']; // 24 hours + logger.debug(`[${this.identifier}] Adding CORS headers to OPTIONS response`); callback({ responseHeaders, + statusLine: 'HTTP/1.1 200 OK', }); - } else { - callback({ responseHeaders: details.responseHeaders }); + return; } + + callback({ responseHeaders }); }); logger.debug(`[${this.identifier}] CORS bypass setup completed`); diff --git a/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts b/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts index 891bdf7fe7..a1778d5d97 100644 --- a/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +++ b/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts @@ -36,6 +36,7 @@ const { mockBrowserWindow, mockNativeTheme, mockIpcMain, mockScreen, MockBrowser send: vi.fn(), session: { webRequest: { + onBeforeSendHeaders: vi.fn(), onHeadersReceived: vi.fn(), }, },