Skip to content

Commit 7d5b82e

Browse files
authored
Merge pull request #372 from lockdown-systems/370-protocol-handler
Register and handle cyd:// URLs
2 parents 2a15ddd + d8e8e6c commit 7d5b82e

File tree

7 files changed

+131
-18
lines changed

7 files changed

+131
-18
lines changed

forge.config.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ function removeCodeSignatures(dir: string) {
9292
});
9393
}
9494

95+
// For cyd:// and cyd-dev:// URLs
96+
const protocols = [];
97+
if (process.env.CYD_ENV == 'prod') {
98+
protocols.push({
99+
"name": "Cyd",
100+
"schemes": ["cyd"]
101+
});
102+
} else {
103+
protocols.push({
104+
"name": "Cyd Dev",
105+
"schemes": ["cyd-dev"]
106+
});
107+
}
108+
const mimeTypeScheme = process.env.CYD_ENV == 'prod' ? 'x-scheme-handler/cyd' : 'x-scheme-handler/cyd-dev';
109+
95110
const config: ForgeConfig = {
96111
packagerConfig: {
97112
name: process.env.CYD_ENV == 'prod' ? 'Cyd' : 'Cyd Dev',
@@ -115,6 +130,7 @@ const config: ForgeConfig = {
115130
path.join(buildPath, 'config.json'),
116131
path.join(assetsPath, 'icon.png'),
117132
],
133+
protocols: protocols,
118134
},
119135
rebuildConfig: {},
120136
makers: [
@@ -165,6 +181,7 @@ const config: ForgeConfig = {
165181
productName: process.env.CYD_ENV == 'prod' ? "Cyd" : "Cyd Dev",
166182
bin: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
167183
name: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
184+
mimeType: [mimeTypeScheme],
168185
}),
169186
// Linux Debian
170187
new MakerDeb({
@@ -177,6 +194,7 @@ const config: ForgeConfig = {
177194
productName: process.env.CYD_ENV == 'prod' ? "Cyd" : "Cyd Dev",
178195
bin: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
179196
name: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
197+
mimeType: [mimeTypeScheme],
180198
}
181199
})
182200
],
@@ -215,7 +233,7 @@ const config: ForgeConfig = {
215233

216234
// macOS codesign here because osxSign seems totally broken
217235
preMake: async (_forgeConfig) => {
218-
if (os.platform() !== 'darwin') {
236+
if (os.platform() !== 'darwin' || process.env.MACOS_RELEASE !== 'true') {
219237
return;
220238
}
221239

@@ -285,7 +303,7 @@ const config: ForgeConfig = {
285303

286304
// macOS notarize here because osxNotarize is broken without using osxSign
287305
postMake: async (forgeConfig, makeResults) => {
288-
if (makeResults[0].platform !== 'darwin') {
306+
if (makeResults[0].platform !== 'darwin' || process.env.MACOS_RELEASE !== 'true') {
289307
return makeResults;
290308
}
291309

scripts/make-dev-macos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/bin/sh
22
export CYD_ENV=dev
33
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
4+
export MACOS_RELEASE=false
45

56
./scripts/clean.sh
67
electron-forge make --arch universal

scripts/make-local-macos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/bin/sh
22
export CYD_ENV=local
33
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
4+
export MACOS_RELEASE=false
45

56
./scripts/clean.sh
67
electron-forge make --arch universal

scripts/make-prod-macos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/bin/sh
22
export CYD_ENV=prod
33
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
4+
export MACOS_RELEASE=false
45

56
./scripts/clean.sh
67
electron-forge make --arch universal

scripts/publish-dev-macos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/bin/sh
22
export CYD_ENV=dev
33
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
4+
export MACOS_RELEASE=true
45

56
./scripts/clean.sh
67
electron-forge publish --arch universal

scripts/publish-prod-macos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/bin/sh
22
export CYD_ENV=prod
33
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
4+
export MACOS_RELEASE=true
45

56
./scripts/clean.sh
67
electron-forge publish --arch universal

src/main.ts

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ interface Config {
4242
plausibleDomain: string;
4343
}
4444

45+
let isAppReady = false;
46+
47+
// Queue of cyd:// URLs to handle, in case the app is not ready
48+
const cydURLQueue: string[] = [];
49+
4550
// Load the config
4651
const configPath = path.join(getResourcesPath(), 'config.json');
4752
if (!fs.existsSync(configPath)) {
@@ -66,17 +71,69 @@ if (require('electron-squirrel-startup')) {
6671
app.quit();
6772
}
6873

69-
if (!app.requestSingleInstanceLock()) {
70-
app.quit();
71-
process.exit(0);
72-
}
73-
7474
// Initialize the logger
7575
log.initialize();
76-
log.transports.file.level = false; // Disable file logging
76+
log.transports.file.level = config.mode == "prod" ? false : "debug"; // Disable file logging in prod mode
7777
log.info('Cyd version:', app.getVersion());
7878
log.info('User data folder is at:', app.getPath('userData'));
7979

80+
// Handle cyd:// URLs (or cyd-dev:// in dev mode)
81+
const openCydURL = async (cydURL: string) => {
82+
if (!isAppReady) {
83+
log.debug('Adding cyd:// URL to queue:', cydURL);
84+
cydURLQueue.push(cydURL);
85+
return;
86+
}
87+
88+
const url = new URL(cydURL);
89+
log.info(`Opening URL: ${url.toString()}`);
90+
91+
// If there's no main window, open one
92+
if (BrowserWindow.getAllWindows().length === 0) {
93+
await createWindow();
94+
}
95+
96+
// If hostname is "open", this just means open Cyd
97+
if (url.hostname == "open") {
98+
// Success!
99+
return;
100+
}
101+
102+
// Check for Bluesky OAuth redirect
103+
const blueskyHostname = config.mode == "prod" ? 'social.cyd.api' : 'social.cyd.dev-api';
104+
if (url.hostname == blueskyHostname && url.pathname == "/atproto-oauth-callback") {
105+
dialog.showMessageBoxSync({
106+
title: "Cyd",
107+
message: `Bluesky OAuth is not implemented yet.`,
108+
type: 'info',
109+
});
110+
return;
111+
}
112+
113+
// For all other paths, show an error
114+
dialog.showMessageBoxSync({
115+
title: "Cyd",
116+
message: `Invalid Cyd URL: ${url.toString()}.`,
117+
type: 'info',
118+
});
119+
return;
120+
}
121+
122+
// Register the cyd:// (or cyd-dev://) protocol
123+
const protocolString = config.mode == "prod" ? "cyd" : "cyd-dev";
124+
app.setAsDefaultProtocolClient(protocolString)
125+
126+
// In Linux and Windows, handle cyd:// URLs passed in via the CLI
127+
const lastArg = process.argv.length >= 2 ? process.argv[process.argv.length - 1] : "";
128+
if ((process.platform == 'linux' || process.platform == 'win32') && lastArg.startsWith(protocolString + "://")) {
129+
openCydURL(lastArg);
130+
}
131+
132+
// In macOS, handle the cyd:// URLs
133+
app.on('open-url', (event, url) => {
134+
openCydURL(url);
135+
})
136+
80137
const cydDevMode = process.env.CYD_DEV === "1";
81138

82139
async function initializeApp() {
@@ -106,7 +163,7 @@ async function initializeApp() {
106163
}
107164

108165
// Set the log level
109-
if (config.mode == "open" || config.mode == "dev" || config.mode == "local") {
166+
if (config.mode != "prod") {
110167
log.transports.console.level = "debug";
111168
} else {
112169
log.transports.console.level = "info";
@@ -164,10 +221,11 @@ async function initializeApp() {
164221
await createWindow();
165222
}
166223

224+
let win: BrowserWindow | null = null;
167225
async function createWindow() {
168226
// Create the browser window
169227
const icon = nativeImage.createFromPath(path.join(getResourcesPath(), 'icon.png'));
170-
const win = new BrowserWindow({
228+
win = new BrowserWindow({
171229
width: 1000,
172230
height: 850,
173231
minWidth: 900,
@@ -179,12 +237,20 @@ async function createWindow() {
179237
icon: icon,
180238
});
181239

182-
// Handle power monitor events
240+
// Mark the app as ready
241+
isAppReady = true;
242+
243+
// Handle any cyd:// URLs that came in before the app was ready
244+
log.debug('Handling cyd:// URLs in queue:', cydURLQueue);
245+
for (const url of cydURLQueue) {
246+
openCydURL(url);
247+
}
183248

249+
// Handle power monitor events
184250
powerMonitor.on('suspend', () => {
185251
log.info('System is suspending');
186252
try {
187-
win.webContents.send('powerMonitor:suspend');
253+
win?.webContents.send('powerMonitor:suspend');
188254
} catch (error) {
189255
log.error('Failed to send powerMonitor:suspend to renderer:', error);
190256
}
@@ -193,7 +259,7 @@ async function createWindow() {
193259
powerMonitor.on('resume', () => {
194260
log.info('System has resumed');
195261
try {
196-
win.webContents.send('powerMonitor:resume');
262+
win?.webContents.send('powerMonitor:resume');
197263
} catch (error) {
198264
log.error('Failed to send powerMonitor:resume to renderer:', error);
199265
}
@@ -376,6 +442,9 @@ async function createWindow() {
376442
}
377443

378444
try {
445+
if (!win) {
446+
throw new Error("Window not initialized");
447+
}
379448
const result = dialog.showOpenDialogSync(win, options);
380449
if (result && result.length > 0) {
381450
return result[0];
@@ -483,15 +552,36 @@ async function createWindow() {
483552

484553
// When devtools opens, make sure the window is wide enough
485554
win.webContents.on('devtools-opened', () => {
486-
const [width, height] = win.getSize();
487-
if (width < 1500) {
488-
win.setSize(1500, height);
555+
if (win) {
556+
const [width, height] = win.getSize();
557+
if (width < 1500) {
558+
win.setSize(1500, height);
559+
}
489560
}
490561
});
491562

492563
return win;
493564
}
494565

566+
// Make sure there's only one instance of the app running
567+
if (!app.requestSingleInstanceLock()) {
568+
app.quit();
569+
process.exit(0);
570+
} else {
571+
app.on('second-instance', (event, commandLine, _) => {
572+
// Someone tried to run a second instance, focus the window
573+
if (win) {
574+
if (win.isMinimized()) win.restore()
575+
win.focus()
576+
}
577+
// commandLine is array of strings in which last element is deep link URL
578+
const cydURL = commandLine.pop()
579+
if (cydURL) {
580+
openCydURL(cydURL);
581+
}
582+
})
583+
}
584+
495585
app.enableSandbox();
496586
app.on('ready', initializeApp);
497587

@@ -501,10 +591,10 @@ app.on('window-all-closed', () => {
501591
}
502592
});
503593

504-
app.on('activate', () => {
594+
app.on('activate', async () => {
505595
// On OS X it's common to re-create a window in the app when the
506596
// dock icon is clicked and there are no other windows open.
507597
if (BrowserWindow.getAllWindows().length === 0) {
508-
createWindow();
598+
await createWindow();
509599
}
510600
});

0 commit comments

Comments
 (0)