diff --git a/js/forge.config.ts b/js/forge.config.ts index 56fb53e..c38cb49 100644 --- a/js/forge.config.ts +++ b/js/forge.config.ts @@ -12,6 +12,9 @@ const config: ForgeConfig = { asar: true, icon: './assets/flowy', appBundleId: 'live.flowy.llink', + protocols: [ + { name: 'llink', schemes: ['llink'] }, + ], extendInfo: { NSMicrophoneUsageDescription: 'llink needs microphone access to record audio messages.', NSCameraUsageDescription: 'llink needs camera access to record video messages.', diff --git a/js/src/main.ts b/js/src/main.ts index 31eefdf..4516720 100644 --- a/js/src/main.ts +++ b/js/src/main.ts @@ -25,6 +25,21 @@ if (process.platform === 'darwin' && !app.isPackaged) { app.dock?.setIcon(path.join(__dirname, '../../assets/icon.png')); } +// Single-instance lock: on Windows/Linux, clicking a llink:// URL launches a new +// process. The lock makes the losing instance quit and fires `second-instance` on +// the primary, so we focus the existing window instead of spawning a duplicate. +// macOS uses `open-url` instead and doesn't need this, but the lock is harmless. +if (!app.requestSingleInstanceLock()) { + app.quit(); +} + +// Register llink:// as the default handler for this app. macOS also gets this +// declaratively via CFBundleURLTypes (forge.config.ts `protocols`); Windows relies +// on this runtime call (Squirrel doesn't register protocols on install). +if (!app.isDefaultProtocolClient('llink')) { + app.setAsDefaultProtocolClient('llink'); +} + let mainWindow: BrowserWindow | null = null; let autoplayWindow: BrowserWindow | null = null; let huddleWindow: BrowserWindow | null = null; @@ -438,6 +453,8 @@ ipcMain.on('app:set-dock-badge', (_event, count: number) => { } }); +ipcMain.handle('app:get-version', () => app.getVersion()); + ipcMain.handle('link:fetch-metadata', async (_event, url: string) => { if (typeof url !== 'string') return null; try { @@ -517,5 +534,27 @@ app.on('activate', () => { } }); +function focusMainWindow() { + if (!mainWindow) return; + if (mainWindow.isMinimized()) mainWindow.restore(); + if (!mainWindow.isVisible()) mainWindow.show(); + mainWindow.focus(); +} + +// macOS delivers llink:// URLs via this event, both when the app is already +// running and on cold start (after `ready`). We prevent the default to silence +// Electron's warning and focus the window — OS-level focus alone won't restore +// a hidden or minimized window. Cold start is handled by createWindow(). +app.on('open-url', (event) => { + event.preventDefault(); + focusMainWindow(); +}); + +// Windows/Linux: the OS launches a second process with the URL in argv; the +// single-instance lock diverts it here on the primary instance. +app.on('second-instance', () => { + focusMainWindow(); +}); + // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and import them here.