diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 3470fc9296..bde22a1aaf 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -445,77 +445,65 @@ if (process.platform !== 'darwin') { } } -let firstOpenWindow: BrowserWindow; -let pendingDeepLink: string | null = null; +const pendingDeepLinks = new Map(); // windowId -> deep link URL let openUrlHandledLaunch = false; async function handleProtocolUrl(url: string) { if (!url) return; - pendingDeepLink = url; - const parsedUrl = new URL(url); const recentDirs = loadRecentDirs(); const openDir = recentDirs.length > 0 ? recentDirs[0] : null; if (parsedUrl.hostname === 'new-session') { await createChat(app, { dir: openDir || undefined }); - pendingDeepLink = null; return; } else if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { - // For bot/recipe URLs, get existing window or create new one const existingWindows = BrowserWindow.getAllWindows(); const targetWindow = existingWindows.length > 0 ? existingWindows[0] : await createChat(app, { dir: openDir || undefined }); - await processProtocolUrl(parsedUrl, targetWindow); + await processProtocolUrl(url, parsedUrl, targetWindow); } else { - // For other URL types, reuse existing window if available const existingWindows = BrowserWindow.getAllWindows(); + let targetWindow: BrowserWindow; if (existingWindows.length > 0) { - firstOpenWindow = existingWindows[0]; - if (firstOpenWindow.isMinimized()) { - firstOpenWindow.restore(); + targetWindow = existingWindows[0]; + if (targetWindow.isMinimized()) { + targetWindow.restore(); } - firstOpenWindow.focus(); + targetWindow.focus(); } else { - firstOpenWindow = await createChat(app, { dir: openDir || undefined }); + targetWindow = await createChat(app, { dir: openDir || undefined }); } - if (firstOpenWindow) { - const webContents = firstOpenWindow.webContents; - if (webContents.isLoadingMainFrame()) { - webContents.once('did-finish-load', async () => { - await processProtocolUrl(parsedUrl, firstOpenWindow); - }); - } else { - await processProtocolUrl(parsedUrl, firstOpenWindow); - } + if (targetWindow.webContents.isLoadingMainFrame()) { + pendingDeepLinks.set(targetWindow.id, url); + } else { + await processProtocolUrl(url, parsedUrl, targetWindow); } } } -async function processProtocolUrl(parsedUrl: URL, window: BrowserWindow) { +async function processProtocolUrl(url: string, parsedUrl: URL, window: BrowserWindow) { const recentDirs = loadRecentDirs(); const openDir = recentDirs.length > 0 ? recentDirs[0] : null; if (parsedUrl.hostname === 'extension') { - window.webContents.send('add-extension', pendingDeepLink); + window.webContents.send('add-extension', url); } else if (parsedUrl.hostname === 'sessions') { - window.webContents.send('open-shared-session', pendingDeepLink); + window.webContents.send('open-shared-session', url); } else if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { - const deeplinkData = parseRecipeDeeplink(pendingDeepLink ?? parsedUrl.toString()); + const deeplinkData = parseRecipeDeeplink(url); const scheduledJobId = parsedUrl.searchParams.get('scheduledJob'); - // Create a new window and ignore the passed-in window await createChat(app, { dir: openDir || undefined, recipeDeeplink: deeplinkData?.config, scheduledJobId: scheduledJobId || undefined, recipeParameters: deeplinkData?.parameters, }); - pendingDeepLink = null; } } @@ -560,25 +548,21 @@ app.on('open-url', async (_event, url) => { return; } - // For extension/session URLs, store the deep link for processing after React is ready - pendingDeepLink = url; - log.info('[Main] Stored pending deep link for processing after React ready:', url.includes('key=') ? url.replace(/key=[^&]+/, 'key=REDACTED') : url); - + // For extension/session URLs, send to existing window or store pending for new one const existingWindows = BrowserWindow.getAllWindows(); if (existingWindows.length > 0) { - firstOpenWindow = existingWindows[0]; - if (firstOpenWindow.isMinimized()) firstOpenWindow.restore(); - firstOpenWindow.focus(); + const targetWindow = existingWindows[0]; + if (targetWindow.isMinimized()) targetWindow.restore(); + targetWindow.focus(); if (parsedUrl.hostname === 'extension') { - firstOpenWindow.webContents.send('add-extension', pendingDeepLink); - pendingDeepLink = null; + targetWindow.webContents.send('add-extension', url); } else if (parsedUrl.hostname === 'sessions') { - firstOpenWindow.webContents.send('open-shared-session', pendingDeepLink); - pendingDeepLink = null; + targetWindow.webContents.send('open-shared-session', url); } } else { openUrlHandledLaunch = true; - firstOpenWindow = await createChat(app, { dir: openDir || undefined }); + const newWindow = await createChat(app, { dir: openDir || undefined }); + pendingDeepLinks.set(newWindow.id, url); } } }); @@ -1150,8 +1134,8 @@ const createChat = async (app: App, options: CreateChatOptions = {}) => { mainWindow.on('closed', () => { windowMap.delete(windowId); - // Clean up pending initial message pendingInitialMessages.delete(windowId); + pendingDeepLinks.delete(windowId); if (windowPowerSaveBlockers.has(windowId)) { const blockerId = windowPowerSaveBlockers.get(windowId)!; @@ -1518,27 +1502,21 @@ ipcMain.on('react-ready', (event) => { pendingInitialMessages.delete(windowId); } - if (pendingDeepLink && window) { - log.info('Processing pending deep link:', pendingDeepLink); + if (windowId && pendingDeepLinks.has(windowId) && window) { + const deepLinkUrl = pendingDeepLinks.get(windowId)!; + pendingDeepLinks.delete(windowId); + log.info('Processing pending deep link for window:', windowId); try { - const parsedUrl = new URL(pendingDeepLink); + const parsedUrl = new URL(deepLinkUrl); if (parsedUrl.hostname === 'extension') { - log.info('Sending add-extension IPC to ready window'); - window.webContents.send('add-extension', pendingDeepLink); + window.webContents.send('add-extension', deepLinkUrl); } else if (parsedUrl.hostname === 'sessions') { - log.info('Sending open-shared-session IPC to ready window'); - window.webContents.send('open-shared-session', pendingDeepLink); + window.webContents.send('open-shared-session', deepLinkUrl); } - pendingDeepLink = null; } catch (error) { log.error('Error processing pending deep link:', error); - pendingDeepLink = null; } - } else { - log.info('No pending deep link to process'); } - - log.info('React ready - window is prepared for deep links'); }); ipcMain.handle('open-external', async (_event, url: string) => {