From 35da1cf1b4e8f5476388fbce110fcc0689299b61 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 20 Mar 2026 19:55:44 +0200 Subject: [PATCH] refactor(desktop): extract deep link handling into modules/deep-links.ts --- surfsense_desktop/src/main.ts | 71 +++------------------ surfsense_desktop/src/modules/deep-links.ts | 66 +++++++++++++++++++ 2 files changed, 74 insertions(+), 63 deletions(-) create mode 100644 surfsense_desktop/src/modules/deep-links.ts diff --git a/surfsense_desktop/src/main.ts b/surfsense_desktop/src/main.ts index efbee44bb..b61e82008 100644 --- a/surfsense_desktop/src/main.ts +++ b/surfsense_desktop/src/main.ts @@ -1,16 +1,17 @@ import { app, BrowserWindow, shell, ipcMain, dialog, Menu } from 'electron'; -import path from 'path'; import { autoUpdater } from 'electron-updater'; import { registerGlobalErrorHandlers, showErrorDialog } from './modules/errors'; -import { startNextServer, getServerPort } from './modules/server'; -import { createMainWindow, getMainWindow } from './modules/window'; +import { startNextServer } from './modules/server'; +import { createMainWindow } from './modules/window'; +import { setupDeepLinks, handlePendingDeepLink } from './modules/deep-links'; registerGlobalErrorHandlers(); -const isDev = !app.isPackaged; -let deepLinkUrl: string | null = null; +if (!setupDeepLinks()) { + app.quit(); +} -const PROTOCOL = 'surfsense'; +const isDev = !app.isPackaged; // IPC handlers ipcMain.on('open-external', (_event, url: string) => { @@ -28,58 +29,6 @@ ipcMain.handle('get-app-version', () => { return app.getVersion(); }); -// Deep link handling -function handleDeepLink(url: string) { - if (!url.startsWith(`${PROTOCOL}://`)) return; - - deepLinkUrl = url; - - const win = getMainWindow(); - if (!win) return; - - const parsed = new URL(url); - if (parsed.hostname === 'auth' && parsed.pathname === '/callback') { - const params = parsed.searchParams.toString(); - win.loadURL(`http://localhost:${getServerPort()}/auth/callback?${params}`); - } - - win.show(); - win.focus(); -} - -// Single instance lock — second instance passes deep link to first -const gotTheLock = app.requestSingleInstanceLock(); -if (!gotTheLock) { - app.quit(); -} else { - app.on('second-instance', (_event, argv) => { - // Windows/Linux: deep link URL is in argv - const url = argv.find((arg) => arg.startsWith(`${PROTOCOL}://`)); - if (url) handleDeepLink(url); - - const win = getMainWindow(); - if (win) { - if (win.isMinimized()) win.restore(); - win.focus(); - } - }); -} - -// macOS: deep link arrives via open-url event -app.on('open-url', (event, url) => { - event.preventDefault(); - handleDeepLink(url); -}); - -// Register surfsense:// protocol -if (process.defaultApp) { - if (process.argv.length >= 2) { - app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [path.resolve(process.argv[1])]); - } -} else { - app.setAsDefaultProtocolClient(PROTOCOL); -} - function setupAutoUpdater() { if (isDev) return; @@ -136,11 +85,7 @@ app.whenReady().then(async () => { createMainWindow(); setupAutoUpdater(); - // If a deep link was received before the window was ready, handle it now - if (deepLinkUrl) { - handleDeepLink(deepLinkUrl); - deepLinkUrl = null; - } + handlePendingDeepLink(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { diff --git a/surfsense_desktop/src/modules/deep-links.ts b/surfsense_desktop/src/modules/deep-links.ts new file mode 100644 index 000000000..1a2b08395 --- /dev/null +++ b/surfsense_desktop/src/modules/deep-links.ts @@ -0,0 +1,66 @@ +import { app } from 'electron'; +import path from 'path'; +import { getMainWindow } from './window'; +import { getServerPort } from './server'; + +const PROTOCOL = 'surfsense'; + +let deepLinkUrl: string | null = null; + +function handleDeepLink(url: string) { + if (!url.startsWith(`${PROTOCOL}://`)) return; + + deepLinkUrl = url; + + const win = getMainWindow(); + if (!win) return; + + const parsed = new URL(url); + if (parsed.hostname === 'auth' && parsed.pathname === '/callback') { + const params = parsed.searchParams.toString(); + win.loadURL(`http://localhost:${getServerPort()}/auth/callback?${params}`); + } + + win.show(); + win.focus(); +} + +export function setupDeepLinks(): boolean { + const gotTheLock = app.requestSingleInstanceLock(); + if (!gotTheLock) { + return false; + } + + app.on('second-instance', (_event, argv) => { + const url = argv.find((arg) => arg.startsWith(`${PROTOCOL}://`)); + if (url) handleDeepLink(url); + + const win = getMainWindow(); + if (win) { + if (win.isMinimized()) win.restore(); + win.focus(); + } + }); + + app.on('open-url', (event, url) => { + event.preventDefault(); + handleDeepLink(url); + }); + + if (process.defaultApp) { + if (process.argv.length >= 2) { + app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [path.resolve(process.argv[1])]); + } + } else { + app.setAsDefaultProtocolClient(PROTOCOL); + } + + return true; +} + +export function handlePendingDeepLink(): void { + if (deepLinkUrl) { + handleDeepLink(deepLinkUrl); + deepLinkUrl = null; + } +}