add proper deeplinking on desktop
The desktop app was merely focusing before, but now it will navigate to the proper route
This commit is contained in:
@@ -52,6 +52,19 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function DeepLinkNavigationListener() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
window.electronDeepLink.getPending().then((path) => {
|
||||
if (path) navigate(path);
|
||||
});
|
||||
return window.electronDeepLink.onNavigate((path) => navigate(path));
|
||||
}, [navigate]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function AutoplayNavigationListener() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -68,6 +81,7 @@ function AuthenticatedApp() {
|
||||
return (
|
||||
<RouterShell>
|
||||
<AutoplayNavigationListener />
|
||||
<DeepLinkNavigationListener />
|
||||
<InAppAutoplayCard />
|
||||
<RouteErrorBoundary>
|
||||
<Routes>
|
||||
|
||||
Vendored
+4
@@ -44,6 +44,10 @@ declare global {
|
||||
stop: () => void;
|
||||
onInit: (callback: () => void) => () => void;
|
||||
};
|
||||
electronDeepLink: {
|
||||
getPending: () => Promise<string | null>;
|
||||
onNavigate: (callback: (path: string) => void) => () => void;
|
||||
};
|
||||
electronLink: {
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
};
|
||||
|
||||
+58
-9
@@ -53,6 +53,8 @@ if (!app.isDefaultProtocolClient('llink')) {
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let autoplayWindow: BrowserWindow | null = null;
|
||||
let pendingDeepLink: string | null = null;
|
||||
let rendererReady = false;
|
||||
let huddleWindow: BrowserWindow | null = null;
|
||||
let screenRecordWindow: BrowserWindow | null = null;
|
||||
|
||||
@@ -423,9 +425,46 @@ ipcMain.on(
|
||||
},
|
||||
);
|
||||
|
||||
// Extracts the in-app route path from a llink:// URL.
|
||||
// llink://networkId/streamId → /networkId/streamId
|
||||
// llink:// or llink://open → / (root)
|
||||
function deepLinkPath(url: string): string | null {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (parsed.protocol !== 'llink:') return null;
|
||||
const host = parsed.hostname;
|
||||
if (!host || host === 'open') return '/';
|
||||
return `/${host}${parsed.pathname}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function tryNavigateToDeepLink(url: string) {
|
||||
// Before the renderer has mounted (cold start), there's no onNavigate
|
||||
// listener yet — stash the link so the renderer can pull it via getPending.
|
||||
if (!rendererReady || !mainWindow) {
|
||||
pendingDeepLink = url;
|
||||
return;
|
||||
}
|
||||
|
||||
const path = deepLinkPath(url);
|
||||
if (!path) return;
|
||||
|
||||
focusMainWindow();
|
||||
mainWindow.webContents.send('deep-link:navigate', path);
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
createWindow();
|
||||
createAutoplayWindow();
|
||||
|
||||
// On Windows/Linux, a cold-start llink:// click passes the URL as a process argument.
|
||||
// macOS cold start is handled via open-url, which fires after ready.
|
||||
const coldStartUrl = process.argv.find((arg) => arg.startsWith('llink://'));
|
||||
if (coldStartUrl) {
|
||||
tryNavigateToDeepLink(coldStartUrl);
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
@@ -448,17 +487,27 @@ function focusMainWindow() {
|
||||
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) => {
|
||||
// macOS: fired on cold start and when app is already running.
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
focusMainWindow();
|
||||
tryNavigateToDeepLink(url); // stashes if the renderer isn't ready yet (cold start) or pushes live otherwise.
|
||||
});
|
||||
|
||||
// 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();
|
||||
// single-instance lock diverts it here, executed on the primary instance main process.
|
||||
app.on('second-instance', (_event, argv) => {
|
||||
const url = argv.find((arg) => arg.startsWith('llink://'));
|
||||
if (url) {
|
||||
tryNavigateToDeepLink(url);
|
||||
} else {
|
||||
focusMainWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// Renderer pulls any pending deep link on mount (cold-start case).
|
||||
safeHandle('deep-link:get-pending', () => {
|
||||
rendererReady = true;
|
||||
const url = pendingDeepLink;
|
||||
pendingDeepLink = null;
|
||||
return url ? deepLinkPath(url) : null;
|
||||
});
|
||||
|
||||
@@ -83,6 +83,19 @@ contextBridge.exposeInMainWorld('electronScreenRecord', {
|
||||
},
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('electronDeepLink', {
|
||||
getPending: () =>
|
||||
ipcRenderer.invoke('deep-link:get-pending') as Promise<string | null>,
|
||||
onNavigate: (callback: (path: string) => void) => {
|
||||
const handler = (_event: Electron.IpcRendererEvent, path: string) =>
|
||||
callback(path);
|
||||
ipcRenderer.on('deep-link:navigate', handler);
|
||||
return () => {
|
||||
ipcRenderer.removeListener('deep-link:navigate', handler);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('electronLink', {
|
||||
openExternal: (url: string) => ipcRenderer.invoke('link:open-external', url),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user