From 3f0e62bc81f94d6736f43b6333745e022d9794c7 Mon Sep 17 00:00:00 2001 From: harrykamboj1 Date: Sun, 21 Jun 2026 13:46:39 +0530 Subject: [PATCH 1/4] fix(desktop): dedupe Nostr session deep link imports Signed-off-by: harrykamboj1 --- ui/desktop/src/App.tsx | 68 +++++++++++++++++++++++++++++++----------- ui/desktop/src/main.ts | 62 ++++++++++++++++++++++++++++++++------ 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 00d6878cb5d5..b25ecd2311b2 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -347,10 +347,14 @@ const ExtensionsRoute = () => { ); }; +const NOSTR_IMPORT_SUCCESS_DEDUP_MS = 5000; + export function AppInner() { const [fatalError, setFatalError] = useState(null); const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false); const [sharedSessionError, setSharedSessionError] = useState(null); + const nostrImportInFlight = useRef(null); + const recentSuccessfulNostrImports = useRef>(new Map()); const navigate = useNavigate(); const setView = useNavigation(); @@ -440,17 +444,52 @@ export function AppInner() { }, []); useEffect(() => { + const shouldSkipNostrDeepLinkImport = (link: string): boolean => { + if (nostrImportInFlight.current === link) { + return true; + } + const importedAt = recentSuccessfulNostrImports.current.get(link); + return importedAt !== undefined && Date.now() - importedAt < NOSTR_IMPORT_SUCCESS_DEDUP_MS; + }; + const handleOpenSharedSession = async (_event: IpcRendererEvent, ...args: unknown[]) => { const link = args[0] as string; window.electron.logInfo(`Opening shared session from deep link ${link}`); - setIsLoadingSharedSession(true); - setSharedSessionError(null); - try { - if (link.startsWith('goose://sessions/nostr')) { + + if (link.startsWith('goose://sessions/nostr')) { + if (shouldSkipNostrDeepLinkImport(link)) { + window.electron.logInfo('Skipping duplicate Nostr deep link import'); + return; + } + + nostrImportInFlight.current = link; + setIsLoadingSharedSession(true); + setSharedSessionError(null); + try { await importNostrSessionFromDeepLink(link); + recentSuccessfulNostrImports.current.set(link, Date.now()); navigate('/sessions'); - return; + } catch (error) { + console.error('Unexpected error opening shared session:', error); + trackErrorWithContext(error, { + component: 'AppInner', + action: 'open_shared_session', + recoverable: true, + }); + toast.error(`Failed to import Nostr session: ${errorMessage(error, 'Unknown error')}`); + navigate('/sessions'); + } finally { + if (nostrImportInFlight.current === link) { + nostrImportInFlight.current = null; + } + setIsLoadingSharedSession(false); } + return; + } + + setIsLoadingSharedSession(true); + setSharedSessionError(null); + try { await openSharedSessionFromDeepLink(link, (_view: View, options?: ViewOptions) => { navigate('/shared-session', { state: options }); }); @@ -461,18 +500,13 @@ export function AppInner() { action: 'open_shared_session', recoverable: true, }); - if (link.startsWith('goose://sessions/nostr')) { - toast.error(`Failed to import Nostr session: ${errorMessage(error, 'Unknown error')}`); - navigate('/sessions'); - } else { - const shareToken = link.replace('goose://sessions/', ''); - const options = { - sessionDetails: null, - error: errorMessage(error, 'Unknown error'), - shareToken, - }; - navigate('/shared-session', { state: options }); - } + const shareToken = link.replace('goose://sessions/', ''); + const options = { + sessionDetails: null, + error: errorMessage(error, 'Unknown error'), + shareToken, + }; + navigate('/shared-session', { state: options }); } finally { setIsLoadingSharedSession(false); } diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index cd8b69ac3fcf..2de282b8d70c 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -492,7 +492,49 @@ if (process.platform !== 'darwin') { } } -const pendingDeepLinks = new Map(); // windowId -> deep link URL +const pendingDeepLinks = new Map(); + +const DEEPLINK_BURST_DEDUP_MS = 2000; +let lastSentSessionDeepLink: { url: string; at: number } | null = null; + +function isBurstDuplicateSessionDeepLink(url: string): boolean { + if (!lastSentSessionDeepLink || lastSentSessionDeepLink.url !== url) { + return false; + } + return Date.now() - lastSentSessionDeepLink.at < DEEPLINK_BURST_DEDUP_MS; +} + +function sendOpenSharedSession( + window: BrowserWindow, + url: string, + options?: { skipBurstDedup?: boolean } +): void { + if (!options?.skipBurstDedup && isBurstDuplicateSessionDeepLink(url)) { + log.info('[Main] Ignoring burst duplicate session deep link'); + return; + } + lastSentSessionDeepLink = { url, at: Date.now() }; + window.webContents.send('open-shared-session', url); +} + +function deliverExtensionOrSessionDeepLink( + url: string, + parsedUrl: URL, + targetWindow: BrowserWindow +): void { + if (targetWindow.webContents.isLoadingMainFrame()) { + if (!pendingDeepLinks.has(targetWindow.id)) { + pendingDeepLinks.set(targetWindow.id, url); + } + return; + } + + if (parsedUrl.hostname === 'extension') { + targetWindow.webContents.send('add-extension', url); + } else if (parsedUrl.hostname === 'sessions') { + sendOpenSharedSession(targetWindow, url); + } +} function getResumeSessionId(parsedUrl: URL): string | null { try { @@ -552,7 +594,9 @@ async function handleProtocolUrl(url: string, parsedUrl: URL) { } if (targetWindow.webContents.isLoadingMainFrame()) { - pendingDeepLinks.set(targetWindow.id, url); + if (!pendingDeepLinks.has(targetWindow.id)) { + pendingDeepLinks.set(targetWindow.id, url); + } } else { await processProtocolUrl(url, parsedUrl, targetWindow); } @@ -566,7 +610,7 @@ async function processProtocolUrl(url: string, parsedUrl: URL, window: BrowserWi if (parsedUrl.hostname === 'extension') { window.webContents.send('add-extension', url); } else if (parsedUrl.hostname === 'sessions') { - window.webContents.send('open-shared-session', url); + sendOpenSharedSession(window, url); } else if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { const deeplinkData = parseRecipeDeeplink(url); const scheduledJobId = parsedUrl.searchParams.get('scheduledJob'); @@ -641,15 +685,15 @@ app.on('open-url', async (_event, url) => { const targetWindow = existingWindows[0]; if (targetWindow.isMinimized()) targetWindow.restore(); targetWindow.focus(); - if (parsedUrl.hostname === 'extension') { - targetWindow.webContents.send('add-extension', url); - } else if (parsedUrl.hostname === 'sessions') { - targetWindow.webContents.send('open-shared-session', url); + if (parsedUrl.hostname === 'extension' || parsedUrl.hostname === 'sessions') { + deliverExtensionOrSessionDeepLink(url, parsedUrl, targetWindow); } } else { openUrlHandledLaunch = true; const newWindow = await createChat(app, { dir: openDir || undefined }); - pendingDeepLinks.set(newWindow.id, url); + if (!pendingDeepLinks.has(newWindow.id)) { + pendingDeepLinks.set(newWindow.id, url); + } } } }); @@ -1675,7 +1719,7 @@ ipcMain.on('react-ready', (event) => { if (parsedUrl.hostname === 'extension') { window.webContents.send('add-extension', deepLinkUrl); } else if (parsedUrl.hostname === 'sessions') { - window.webContents.send('open-shared-session', deepLinkUrl); + sendOpenSharedSession(window, deepLinkUrl, { skipBurstDedup: true }); } } catch (error) { log.error('Error processing pending deep link:', error); From 0a5ec0946675a7a8485adcff926f8f8cab11639b Mon Sep 17 00:00:00 2001 From: harrykamboj1 Date: Wed, 24 Jun 2026 08:26:45 +0530 Subject: [PATCH 2/4] fix(desktop): address review feedback on Nostr deep link dedup Signed-off-by: harrykamboj1 --- ui/desktop/src/App.tsx | 14 +------------- ui/desktop/src/main.ts | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index a257e50fcf8c..615e5ed89aa9 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -352,14 +352,11 @@ const ExtensionsRoute = () => { ); }; -const NOSTR_IMPORT_SUCCESS_DEDUP_MS = 5000; - export function AppInner() { const [fatalError, setFatalError] = useState(null); const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false); const [sharedSessionError, setSharedSessionError] = useState(null); const nostrImportInFlight = useRef(null); - const recentSuccessfulNostrImports = useRef>(new Map()); const navigate = useNavigate(); const setView = useNavigation(); @@ -449,20 +446,12 @@ export function AppInner() { }, []); useEffect(() => { - const shouldSkipNostrDeepLinkImport = (link: string): boolean => { - if (nostrImportInFlight.current === link) { - return true; - } - const importedAt = recentSuccessfulNostrImports.current.get(link); - return importedAt !== undefined && Date.now() - importedAt < NOSTR_IMPORT_SUCCESS_DEDUP_MS; - }; - const handleOpenSharedSession = async (_event: IpcRendererEvent, ...args: unknown[]) => { const link = args[0] as string; window.electron.logInfo(`Opening shared session from deep link ${link}`); if (link.startsWith('goose://sessions/nostr')) { - if (shouldSkipNostrDeepLinkImport(link)) { + if (nostrImportInFlight.current === link) { window.electron.logInfo('Skipping duplicate Nostr deep link import'); return; } @@ -472,7 +461,6 @@ export function AppInner() { setSharedSessionError(null); try { await importNostrSessionFromDeepLink(link); - recentSuccessfulNostrImports.current.set(link, Date.now()); navigate('/sessions'); } catch (error) { console.error('Unexpected error opening shared session:', error); diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 2de282b8d70c..b59f75cb1cb5 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -494,6 +494,13 @@ if (process.platform !== 'darwin') { const pendingDeepLinks = new Map(); +function queuePendingDeepLink(windowId: number, url: string): void { + if (pendingDeepLinks.get(windowId) === url) { + return; + } + pendingDeepLinks.set(windowId, url); +} + const DEEPLINK_BURST_DEDUP_MS = 2000; let lastSentSessionDeepLink: { url: string; at: number } | null = null; @@ -523,9 +530,7 @@ function deliverExtensionOrSessionDeepLink( targetWindow: BrowserWindow ): void { if (targetWindow.webContents.isLoadingMainFrame()) { - if (!pendingDeepLinks.has(targetWindow.id)) { - pendingDeepLinks.set(targetWindow.id, url); - } + queuePendingDeepLink(targetWindow.id, url); return; } @@ -594,9 +599,7 @@ async function handleProtocolUrl(url: string, parsedUrl: URL) { } if (targetWindow.webContents.isLoadingMainFrame()) { - if (!pendingDeepLinks.has(targetWindow.id)) { - pendingDeepLinks.set(targetWindow.id, url); - } + queuePendingDeepLink(targetWindow.id, url); } else { await processProtocolUrl(url, parsedUrl, targetWindow); } @@ -691,9 +694,7 @@ app.on('open-url', async (_event, url) => { } else { openUrlHandledLaunch = true; const newWindow = await createChat(app, { dir: openDir || undefined }); - if (!pendingDeepLinks.has(newWindow.id)) { - pendingDeepLinks.set(newWindow.id, url); - } + queuePendingDeepLink(newWindow.id, url); } } }); From 7f5957bf5e13f7b74a135fe7e586baf0c23a2d8c Mon Sep 17 00:00:00 2001 From: harrykamboj1 Date: Wed, 24 Jun 2026 20:56:07 +0530 Subject: [PATCH 3/4] fix(desktop): key burst dedup by URL for session deep links Signed-off-by: harrykamboj1 --- ui/desktop/src/main.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index b59f75cb1cb5..fa7080c41daf 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -502,13 +502,27 @@ function queuePendingDeepLink(windowId: number, url: string): void { } const DEEPLINK_BURST_DEDUP_MS = 2000; -let lastSentSessionDeepLink: { url: string; at: number } | null = null; +const recentSessionDeepLinkSends = new Map(); -function isBurstDuplicateSessionDeepLink(url: string): boolean { - if (!lastSentSessionDeepLink || lastSentSessionDeepLink.url !== url) { - return false; +function pruneExpiredSessionDeepLinkSends(now: number): void { + for (const [url, sentAt] of recentSessionDeepLinkSends) { + if (now - sentAt >= DEEPLINK_BURST_DEDUP_MS) { + recentSessionDeepLinkSends.delete(url); + } } - return Date.now() - lastSentSessionDeepLink.at < DEEPLINK_BURST_DEDUP_MS; +} + +function isBurstDuplicateSessionDeepLink(url: string): boolean { + const now = Date.now(); + pruneExpiredSessionDeepLinkSends(now); + const sentAt = recentSessionDeepLinkSends.get(url); + return sentAt !== undefined && now - sentAt < DEEPLINK_BURST_DEDUP_MS; +} + +function recordSessionDeepLinkSend(url: string): void { + const now = Date.now(); + recentSessionDeepLinkSends.set(url, now); + pruneExpiredSessionDeepLinkSends(now); } function sendOpenSharedSession( @@ -520,7 +534,7 @@ function sendOpenSharedSession( log.info('[Main] Ignoring burst duplicate session deep link'); return; } - lastSentSessionDeepLink = { url, at: Date.now() }; + recordSessionDeepLinkSend(url); window.webContents.send('open-shared-session', url); } From 7807277bad06c293e78af356bb81bb5d7caaf99f Mon Sep 17 00:00:00 2001 From: harrykamboj1 Date: Sat, 27 Jun 2026 23:27:26 +0530 Subject: [PATCH 4/4] fix(desktop): restore in-flight guard and defer deep-link dedup to renderer readiness Signed-off-by: harrykamboj1 --- ui/desktop/src/App.tsx | 25 +++++++++++++++++++------ ui/desktop/src/main.ts | 20 ++++++++++++-------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 19a9fa303265..e067c3302a78 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -306,6 +306,8 @@ const ExtensionsRoute = () => { export function AppInner() { const [fatalError, setFatalError] = useState(null); + const nostrImportInFlight = useRef(null); + const navigate = useNavigate(); const setView = useNavigation(); @@ -397,15 +399,22 @@ export function AppInner() { const handleOpenSessionShare = async (_event: IpcRendererEvent, ...args: unknown[]) => { const link = args[0] as string; window.electron.logInfo('Opening session share link'); - try { - if (link.startsWith('goose://sessions/nostr')) { - await importNostrSessionFromDeepLink(link); - navigate('/sessions'); - return; - } + if (!link.startsWith('goose://sessions/nostr')) { toast.error('Unsupported session share link'); navigate('/sessions'); + return; + } + + if (nostrImportInFlight.current === link) { + window.electron.logInfo('Skipping duplicate Nostr deep link import'); + return; + } + nostrImportInFlight.current = link; + + try { + await importNostrSessionFromDeepLink(link); + navigate('/sessions'); } catch (error) { console.error('Unexpected error opening Nostr session share:', error); trackErrorWithContext(error, { @@ -415,6 +424,10 @@ export function AppInner() { }); toast.error(`Failed to import Nostr session: ${errorMessage(error, 'Unknown error')}`); navigate('/sessions'); + } finally { + if (nostrImportInFlight.current === link) { + nostrImportInFlight.current = null; + } } }; window.electron.on('open-shared-session', handleOpenSessionShare); diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index aaf955752b0e..854b89b1f696 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -496,6 +496,8 @@ function queuePendingDeepLink(windowId: number, url: string): void { pendingDeepLinks.set(windowId, url); } +const reactReadyWindows = new Set(); + const DEEPLINK_BURST_DEDUP_MS = 2000; const recentSessionDeepLinkSends = new Map(); @@ -520,12 +522,8 @@ function recordSessionDeepLinkSend(url: string): void { pruneExpiredSessionDeepLinkSends(now); } -function sendOpenSharedSession( - window: BrowserWindow, - url: string, - options?: { skipBurstDedup?: boolean } -): void { - if (!options?.skipBurstDedup && isBurstDuplicateSessionDeepLink(url)) { +function sendOpenSharedSession(window: BrowserWindow, url: string): void { + if (isBurstDuplicateSessionDeepLink(url)) { log.info('[Main] Ignoring burst duplicate session deep link'); return; } @@ -538,7 +536,7 @@ function deliverExtensionOrSessionDeepLink( parsedUrl: URL, targetWindow: BrowserWindow ): void { - if (targetWindow.webContents.isLoadingMainFrame()) { + if (!reactReadyWindows.has(targetWindow.id) || targetWindow.webContents.isLoadingMainFrame()) { queuePendingDeepLink(targetWindow.id, url); return; } @@ -1334,6 +1332,7 @@ const createChat = async (app: App, options: CreateChatOptions = {}) => { pendingInitialMessages.delete(windowId); pendingDeepLinks.delete(windowId); + reactReadyWindows.delete(windowId); if (windowPowerSaveBlockers.has(windowId)) { const blockerId = windowPowerSaveBlockers.get(windowId)!; @@ -1409,6 +1408,7 @@ const createLauncher = () => { activeLauncherWindow = launcherWindow; launcherWindow.on('closed', () => { + reactReadyWindows.delete(launcherWindow.id); activeLauncherWindow = null; }); @@ -1693,6 +1693,10 @@ ipcMain.on('react-ready', (event) => { const window = BrowserWindow.fromWebContents(event.sender); const windowId = window?.id; + if (windowId !== undefined) { + reactReadyWindows.add(windowId); + } + // Send any pending initial message for this window if (windowId && pendingInitialMessages.has(windowId)) { const initialMessage = pendingInitialMessages.get(windowId)!; @@ -1712,7 +1716,7 @@ ipcMain.on('react-ready', (event) => { if (parsedUrl.hostname === 'extension') { window.webContents.send('add-extension', deepLinkUrl); } else if (parsedUrl.hostname === 'sessions') { - sendOpenSharedSession(window, deepLinkUrl, { skipBurstDedup: true }); + sendOpenSharedSession(window, deepLinkUrl); } } catch (error) { log.error('Error processing pending deep link:', error);