diff --git a/public/electron.js b/public/electron.js index cc3c60d..3e2012b 100644 --- a/public/electron.js +++ b/public/electron.js @@ -8,7 +8,9 @@ import { createWindow, setupMainWindowIPC, setupMainWindowAppEvents, - setupDevAuthServer + setupDevAuthServer, + setupSingleInstanceLock, + handleDeepLinkFromArgv } from './mainWindow.js' // --- Keytar-backed auth session storage (main process) --- @@ -27,14 +29,19 @@ try { const KEYTAR_SERVICE = app.name || 'Farm Control' const KEYTAR_ACCOUNT = 'authSession' -app.whenReady().then(() => { - createWindow() - registerGlobalShortcuts() - setupSpotlightIPC() - setupMainWindowIPC() - setupMainWindowAppEvents(app) - setupDevAuthServer() -}) +const gotTheLock = setupSingleInstanceLock(app) + +if (gotTheLock) { + app.whenReady().then(() => { + createWindow() + registerGlobalShortcuts() + setupSpotlightIPC() + setupMainWindowIPC() + setupMainWindowAppEvents(app) + setupDevAuthServer() + handleDeepLinkFromArgv() + }) +} app.on('will-quit', () => { globalShortcut.unregisterAll() diff --git a/public/mainWindow.js b/public/mainWindow.js index cab5b2b..4ffa913 100644 --- a/public/mainWindow.js +++ b/public/mainWindow.js @@ -7,6 +7,72 @@ const __dirname = dirname(__filename) let win +const PROTOCOL_PREFIX = 'farmcontrol://' + +function findProtocolUrl(args) { + return args.find( + (arg) => typeof arg === 'string' && arg.startsWith(PROTOCOL_PREFIX) + ) +} + +function sendNavigateToRenderer(redirectPath) { + const deliver = () => { + win.webContents.send('navigate', redirectPath) + win.show() + win.focus() + } + + if (!win || win.isDestroyed()) { + createWindow() + win.webContents.once('did-finish-load', () => { + setTimeout(deliver, 100) + }) + return + } + + if (win.webContents.isLoading()) { + win.webContents.once('did-finish-load', () => { + setTimeout(deliver, 100) + }) + return + } + + deliver() +} + +export function handleDeepLink(url) { + if (!url?.startsWith(`${PROTOCOL_PREFIX}app`)) return + const redirectPath = url.replace(`${PROTOCOL_PREFIX}app`, '') || '/' + sendNavigateToRenderer(redirectPath) +} + +export function handleDeepLinkFromArgv() { + if (process.platform === 'darwin') return + const url = findProtocolUrl(process.argv) + if (url) handleDeepLink(url) +} + +export function setupSingleInstanceLock(app) { + const gotTheLock = app.requestSingleInstanceLock() + if (!gotTheLock) { + app.quit() + return false + } + + app.on('second-instance', (_event, commandLine) => { + const url = findProtocolUrl(commandLine) + if (url) { + handleDeepLink(url) + } else if (win && !win.isDestroyed()) { + if (win.isMinimized()) win.restore() + win.show() + win.focus() + } + }) + + return true +} + function attachKeyboardShortcuts(browserWindow) { if (!browserWindow) return // Keyboard shortcuts for the main window can be added here if needed @@ -155,13 +221,7 @@ export function setupMainWindowAppEvents(app) { app.on('open-url', (event, url) => { event.preventDefault() - if (url.startsWith('farmcontrol://app')) { - // Extract the path/query after 'farmcontrol://app' - const redirectPath = url.replace('farmcontrol://app', '') || '/' - if (win && win.webContents) { - win.webContents.send('navigate', redirectPath) - } - } + handleDeepLink(url) }) }