From 452ea10123ae5d97b8e576c40590a63ab1677e24 Mon Sep 17 00:00:00 2001 From: Harnoor Singh <49549662+harrykamboj1@users.noreply.github.com> Date: Wed, 1 Jul 2026 02:45:48 +0530 Subject: [PATCH] fix(desktop): dedupe Nostr session deep link imports (#9918) Signed-off-by: harrykamboj1 --- ui/desktop/src/App.tsx | 25 +++++++++---- ui/desktop/src/main.ts | 81 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 19a9fa303..e067c3302 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 c91bb31ed..18b107f1d 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -495,7 +495,66 @@ if (process.platform !== 'darwin') { } } -const pendingDeepLinks = new Map(); // windowId -> deep link URL +const pendingDeepLinks = new Map(); + +function queuePendingDeepLink(windowId: number, url: string): void { + if (pendingDeepLinks.get(windowId) === url) { + return; + } + pendingDeepLinks.set(windowId, url); +} + +const reactReadyWindows = new Set(); + +const DEEPLINK_BURST_DEDUP_MS = 2000; +const recentSessionDeepLinkSends = new Map(); + +function pruneExpiredSessionDeepLinkSends(now: number): void { + for (const [url, sentAt] of recentSessionDeepLinkSends) { + if (now - sentAt >= DEEPLINK_BURST_DEDUP_MS) { + recentSessionDeepLinkSends.delete(url); + } + } +} + +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(window: BrowserWindow, url: string): void { + if (isBurstDuplicateSessionDeepLink(url)) { + log.info('[Main] Ignoring burst duplicate session deep link'); + return; + } + recordSessionDeepLinkSend(url); + window.webContents.send('open-shared-session', url); +} + +function deliverExtensionOrSessionDeepLink( + url: string, + parsedUrl: URL, + targetWindow: BrowserWindow +): void { + if (!reactReadyWindows.has(targetWindow.id) || targetWindow.webContents.isLoadingMainFrame()) { + queuePendingDeepLink(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 { @@ -555,7 +614,7 @@ async function handleProtocolUrl(url: string, parsedUrl: URL) { } if (targetWindow.webContents.isLoadingMainFrame()) { - pendingDeepLinks.set(targetWindow.id, url); + queuePendingDeepLink(targetWindow.id, url); } else { await processProtocolUrl(url, parsedUrl, targetWindow); } @@ -569,7 +628,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'); @@ -644,15 +703,13 @@ 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); + queuePendingDeepLink(newWindow.id, url); } } }); @@ -1283,6 +1340,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)!; @@ -1358,6 +1416,7 @@ const createLauncher = () => { activeLauncherWindow = launcherWindow; launcherWindow.on('closed', () => { + reactReadyWindows.delete(launcherWindow.id); activeLauncherWindow = null; }); @@ -1642,6 +1701,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)!; @@ -1661,7 +1724,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); } } catch (error) { log.error('Error processing pending deep link:', error);