diff --git a/desktop/src/main.ts b/desktop/src/main.ts index aa57e60305..0bb56b4494 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -150,10 +150,13 @@ const registerForEnteLinks = () => app.setAsDefaultProtocolClient("ente"); /** Sibling of {@link registerForEnteLinks}. */ const handleEnteLinks = (mainWindow: BrowserWindow, url: string) => { - // Both (a) our deeplink protocol, and (b) the protocol we're using to serve - // our bundled web app, are the same ("ente://"), so the URL can directly be - // loaded. - mainWindow.webContents.loadURL(url); + // Both + // + // - our deeplink protocol, and + // - the protocol we're using to serve/ our bundled web app + // + // use the same scheme ("ente://"), so the URL can directly be forwarded. + mainWindow.webContents.send("openURL", url); }; /** diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index c6df891dc5..fe449bad38 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -77,6 +77,11 @@ const onMainWindowFocus = (cb?: () => void) => { if (cb) ipcRenderer.on("mainWindowFocus", cb); }; +const onOpenURL = (cb?: (url: string) => void) => { + ipcRenderer.removeAllListeners("openURL"); + if (cb) ipcRenderer.on("openURL", (_, url: string) => cb(url)); +}; + // - App update const onAppUpdateAvailable = ( @@ -307,6 +312,7 @@ contextBridge.exposeInMainWorld("electron", { encryptionKey, saveEncryptionKey, onMainWindowFocus, + onOpenURL, // - App update diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 9c3e199cc0..3d21ee56cf 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -167,6 +167,17 @@ export default function App({ Component, pageProps }: AppProps) { const electron = globalThis.electron; if (!electron) return; + // Attach various listeners for events sent to us by the Node.js layer. + // This is for events that we should listen for always, not just when + // the user is logged in. + + const handleOpenURL = (url: string) => { + // `url` begins with "ente://", which is also the scheme our desktop + // app uses to serve the bundled web app, so it can be directly + // opened. + window.location.href = url; + }; + const showUpdateDialog = (update: AppUpdate) => { if (update.autoUpdatable) { setDialogMessage(getUpdateReadyToInstallMessage(update)); @@ -182,9 +193,14 @@ export default function App({ Component, pageProps }: AppProps) { }); } }; + + electron.onOpenURL(handleOpenURL); electron.onAppUpdateAvailable(showUpdateDialog); - return () => electron.onAppUpdateAvailable(undefined); + return () => { + electron.onOpenURL(undefined); + electron.onAppUpdateAvailable(undefined); + }; }, []); useEffect(() => { diff --git a/web/packages/next/types/ipc.ts b/web/packages/next/types/ipc.ts index 806a00cd5e..23da7221e9 100644 --- a/web/packages/next/types/ipc.ts +++ b/web/packages/next/types/ipc.ts @@ -94,6 +94,23 @@ export interface Electron { */ onMainWindowFocus: (cb?: () => void) => void; + /** + * Set or clear the callback {@link cb} to invoke whenever the app gets + * asked to open a deeplink. This allows the Node.js layer to ask the + * renderer to handle deeplinks and redirect itself to a new location if + * needed. + * + * In particular, this is necessary for handling passkey authentication. + * See: [Note: Passkey verification in the desktop app] + * + * Note: Setting a callback clears any previous callbacks. + * + * @param cb The function to call when the app gets asked to open a + * "ente://" URL. The URL string (a.k.a. "deeplink") we were asked to open + * is passed to the function verbatim. + */ + onOpenURL: (cb?: (url: string) => void) => void; + // - App update /**