Skip to content

Commit 0e63e95

Browse files
committed
Add new protocol registration to app
1 parent 4535f05 commit 0e63e95

3 files changed

Lines changed: 59 additions & 0 deletions

File tree

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
"productName": "BrainWaves",
4949
"appId": "com.electron.brainwaves",
5050
"asar": true,
51+
"protocols": [
52+
{
53+
"name": "BrainWaves",
54+
"schemes": ["brainwaves"]
55+
}
56+
],
5157
"files": [
5258
"out/**/*",
5359
"node_modules/**/*",

src/main/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,45 @@ app.commandLine.appendSwitch(
2424
'true'
2525
);
2626

27+
// Enforce a single app instance — required so the second-instance event fires
28+
// on Windows/Linux when the OS re-launches the app to deliver an OAuth callback.
29+
if (!app.requestSingleInstanceLock()) {
30+
app.quit();
31+
}
32+
33+
// Register brainwaves:// as the OS-level deep-link scheme.
34+
// Redirect URI for OAuth: brainwaves://oauth/callback
35+
app.setAsDefaultProtocolClient('brainwaves');
36+
37+
// Buffer an OAuth callback URL that arrives before the window is ready
38+
// (e.g. the app is cold-launched by the OS to handle the redirect).
39+
let pendingOAuthUrl: string | null = null;
40+
41+
const handleOAuthCallback = (url: string) => {
42+
if (mainWindow) {
43+
mainWindow.webContents.send('oauth:callback', url);
44+
} else {
45+
pendingOAuthUrl = url;
46+
}
47+
};
48+
49+
// macOS: OS fires open-url when a brainwaves:// link is opened.
50+
app.on('open-url', (event, url) => {
51+
event.preventDefault();
52+
handleOAuthCallback(url);
53+
});
54+
55+
// Windows / Linux: OS relaunches the app with the URL as a CLI argument.
56+
// requestSingleInstanceLock() above ensures the existing instance gets this event.
57+
app.on('second-instance', (_event, argv) => {
58+
const url = argv.find((arg) => arg.startsWith('brainwaves://'));
59+
if (url) handleOAuthCallback(url);
60+
if (mainWindow) {
61+
if (mainWindow.isMinimized()) mainWindow.restore();
62+
mainWindow.focus();
63+
}
64+
});
65+
2766
// Register pyodide:// as a privileged custom scheme so web workers can
2867
// fetch() package .whl files from it. Must be called before app.whenReady().
2968
protocol.registerSchemesAsPrivileged([
@@ -460,6 +499,10 @@ const createWindow = async () => {
460499
if (is.dev || process.env.DEBUG_PROD === 'true') {
461500
mainWindow.webContents.openDevTools();
462501
}
502+
if (pendingOAuthUrl) {
503+
mainWindow.webContents.send('oauth:callback', pendingOAuthUrl);
504+
pendingOAuthUrl = null;
505+
}
463506
});
464507

465508
mainWindow.on('closed', () => {

src/preload/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
153153
getResourcePath: (): Promise<string> => ipcRenderer.invoke('getResourcePath'),
154154

155155
getViewerUrl: (): Promise<string> => ipcRenderer.invoke('getViewerUrl'),
156+
157+
// ------------------------------------------------------------------
158+
// OAuth deep-link callback
159+
// ------------------------------------------------------------------
160+
onOAuthCallback: (callback: (url: string) => void) => {
161+
const handler = (_event: Electron.IpcRendererEvent, url: string) =>
162+
callback(url);
163+
ipcRenderer.on('oauth:callback', handler);
164+
return () => ipcRenderer.removeListener('oauth:callback', handler);
165+
},
156166
});

0 commit comments

Comments
 (0)