diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index c9e9dc3a..3d6c37ea 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -74,6 +74,8 @@ export default function App() { return `${cmd} ${args.join(' ')}`.trim(); } + useEffect(() => window.electron.reactReady(), []); + useEffect(() => { const handleAddExtension = (_: any, link: string) => { const command = extractCommand(link); diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 850276c4..3d4de348 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -38,31 +38,25 @@ if (started) app.quit(); app.setAsDefaultProtocolClient('goose'); // Triggered when the user opens "goose://..." links +let firstOpenWindow: BrowserWindow; +let pendingDeepLink = null; // Store deep link if sent before React is ready app.on('open-url', async (event, url) => { - event.preventDefault(); + pendingDeepLink = url; // Get existing window or create new one - let targetWindow: BrowserWindow; const existingWindows = BrowserWindow.getAllWindows(); if (existingWindows.length > 0) { - targetWindow = existingWindows[0]; - if (targetWindow.isMinimized()) targetWindow.restore(); - targetWindow.focus(); + firstOpenWindow = existingWindows[0]; + if (firstOpenWindow.isMinimized()) firstOpenWindow.restore(); + firstOpenWindow.focus(); } else { const recentDirs = loadRecentDirs(); const openDir = recentDirs.length > 0 ? recentDirs[0] : null; - targetWindow = await createChat(app, undefined, openDir); + firstOpenWindow = await createChat(app, undefined, openDir); } - // Wait for window to be ready before sending the extension URL - if (!targetWindow.webContents.isLoading()) { - targetWindow.webContents.send('add-extension', url); - } else { - targetWindow.webContents.once('did-finish-load', () => { - targetWindow.webContents.send('add-extension', url); - }); - } + firstOpenWindow.webContents.send('add-extension', pendingDeepLink); }); declare var MAIN_WINDOW_VITE_DEV_SERVER_URL: string; @@ -332,6 +326,13 @@ process.on('unhandledRejection', (error) => { handleFatalError(error instanceof Error ? error : new Error(String(error))); }); +ipcMain.on('react-ready', (event) => { + if (pendingDeepLink) { + firstOpenWindow.webContents.send('add-extension', pendingDeepLink); + pendingDeepLink = null; + } +}); + // Add file/directory selection handler ipcMain.handle('select-file-or-directory', async () => { const result = await dialog.showOpenDialog({ diff --git a/ui/desktop/src/preload.ts b/ui/desktop/src/preload.ts index a6fe3e3d..a046cf71 100644 --- a/ui/desktop/src/preload.ts +++ b/ui/desktop/src/preload.ts @@ -4,6 +4,7 @@ const config = JSON.parse(process.argv.find((arg) => arg.startsWith('{')) || '{} // Define the API types in a single place type ElectronAPI = { + reactReady: () => void; getConfig: () => Record; hideWindow: () => void; directoryChooser: (replace: string) => void; @@ -44,6 +45,7 @@ type AppConfigAPI = { }; const electronAPI: ElectronAPI = { + reactReady: () => ipcRenderer.send('react-ready'), getConfig: () => config, hideWindow: () => ipcRenderer.send('hide-window'), directoryChooser: (replace: string) => ipcRenderer.send('directory-chooser', replace),