2026-03-20 19:55:44 +02:00
|
|
|
import { app } from 'electron';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
import { getMainWindow } from './window';
|
2026-05-25 17:55:03 +05:30
|
|
|
import { getServerOrigin } from './server';
|
2026-04-18 14:35:14 -07:00
|
|
|
import { trackEvent } from './analytics';
|
2026-03-20 19:55:44 +02:00
|
|
|
|
|
|
|
|
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);
|
2026-04-18 14:35:14 -07:00
|
|
|
trackEvent('desktop_deep_link_received', {
|
|
|
|
|
host: parsed.hostname,
|
|
|
|
|
path: parsed.pathname,
|
|
|
|
|
});
|
2026-03-20 19:55:44 +02:00
|
|
|
if (parsed.hostname === 'auth' && parsed.pathname === '/callback') {
|
|
|
|
|
const params = parsed.searchParams.toString();
|
2026-05-25 17:55:03 +05:30
|
|
|
win.loadURL(`${getServerOrigin()}/auth/callback?${params}`);
|
2026-03-20 19:55:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 18:39:22 +02:00
|
|
|
// Cold-start on Windows/Linux: protocol URL arrives via argv of the
|
|
|
|
|
// first instance, not via `second-instance` or `open-url`.
|
|
|
|
|
const cold = process.argv.find((arg) => arg.startsWith(`${PROTOCOL}://`));
|
|
|
|
|
if (cold) handleDeepLink(cold);
|
|
|
|
|
|
2026-03-20 19:55:44 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function handlePendingDeepLink(): void {
|
|
|
|
|
if (deepLinkUrl) {
|
|
|
|
|
handleDeepLink(deepLinkUrl);
|
|
|
|
|
deepLinkUrl = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-20 12:42:06 -07:00
|
|
|
|
|
|
|
|
// True when a deep link arrived before the main window existed. Callers can
|
|
|
|
|
// use this to force-create a window even on a "started hidden" boot, so we
|
|
|
|
|
// don't silently swallow a `surfsense://` URL the user actually clicked on.
|
|
|
|
|
export function hasPendingDeepLink(): boolean {
|
|
|
|
|
return deepLinkUrl !== null;
|
|
|
|
|
}
|