Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register and handle cyd:// URLs #372

Merged
merged 22 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f3dbc7b
Register and handle cyd:// URLs
micahflee Jan 20, 2025
ac59b88
Switch from protocol to deep links
micahflee Jan 20, 2025
8b21e22
Handle deep links for both Mac and Windows/Linux
micahflee Jan 20, 2025
ab385f1
Queue cyd:// links and process them after that app is ready
micahflee Jan 20, 2025
c0fd8bc
Open the app with cyd:// path /
micahflee Jan 20, 2025
61582ab
Update packaging to support cyd:// and cyd-dev:// URLs
micahflee Jan 20, 2025
7cd8530
Move logging to the top of main.ts
micahflee Jan 20, 2025
5a2d45e
Actually, move logging to below loading config and setting the app name
micahflee Jan 20, 2025
29b255e
Add a debug log
micahflee Jan 20, 2025
c7ac8f2
Automatically log to disk when running not prod
micahflee Jan 20, 2025
fcd78d4
Add Cyd URLs to queue from both open-url event (macOS) and second-ins…
micahflee Jan 20, 2025
5287dbe
If the last arg is cyd:// URL, pass it into app.setAsDefaultProtocolC…
micahflee Jan 20, 2025
edc21d7
Try manually calling initial openCydURL in Windows and Linux if necce…
micahflee Jan 20, 2025
2742eb9
Try adding protocol.registerSchemesAsPrivileged to see if Windows wil…
micahflee Jan 20, 2025
a3e61c9
Remove protocol.registerSchemesAsPrivileged because it does not help …
micahflee Jan 21, 2025
0bcf611
Fix lint
micahflee Jan 21, 2025
a1fa1cc
Set logging to debug for everything but prod
micahflee Jan 21, 2025
0a78539
Update src/main.ts
micahflee Jan 21, 2025
77b1416
Skip code signing when making a macOS build that will not be published
micahflee Jan 21, 2025
9acc031
Support the two Cyd URLs cyd://open/ and cyd://social.cyd.api/atproto…
micahflee Jan 21, 2025
1e41e2e
Merge branch '370-protocol-handler' of github.com:lockdown-systems/cy…
micahflee Jan 21, 2025
d8e8e6c
Merge branch 'main' into 370-protocol-handler
micahflee Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ function removeCodeSignatures(dir: string) {
});
}

// For cyd:// and cyd-dev:// URLs
const protocols = [];
if (process.env.CYD_ENV == 'prod') {
protocols.push({
"name": "Cyd",
"schemes": ["cyd"]
});
} else {
protocols.push({
"name": "Cyd Dev",
"schemes": ["cyd-dev"]
});
}
const mimeTypeScheme = process.env.CYD_ENV == 'prod' ? 'x-scheme-handler/cyd' : 'x-scheme-handler/cyd-dev';

const config: ForgeConfig = {
packagerConfig: {
name: process.env.CYD_ENV == 'prod' ? 'Cyd' : 'Cyd Dev',
Expand All @@ -115,6 +130,7 @@ const config: ForgeConfig = {
path.join(buildPath, 'config.json'),
path.join(assetsPath, 'icon.png'),
],
protocols: protocols,
},
rebuildConfig: {},
makers: [
Expand Down Expand Up @@ -165,6 +181,7 @@ const config: ForgeConfig = {
productName: process.env.CYD_ENV == 'prod' ? "Cyd" : "Cyd Dev",
bin: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
name: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
mimeType: [mimeTypeScheme],
}),
// Linux Debian
new MakerDeb({
Expand All @@ -177,6 +194,7 @@ const config: ForgeConfig = {
productName: process.env.CYD_ENV == 'prod' ? "Cyd" : "Cyd Dev",
bin: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
name: process.env.CYD_ENV == 'prod' ? "cyd" : "cyd-dev",
mimeType: [mimeTypeScheme],
}
})
],
Expand Down Expand Up @@ -215,7 +233,7 @@ const config: ForgeConfig = {

// macOS codesign here because osxSign seems totally broken
preMake: async (_forgeConfig) => {
if (os.platform() !== 'darwin') {
if (os.platform() !== 'darwin' || process.env.MACOS_RELEASE !== 'true') {
return;
}

Expand Down Expand Up @@ -285,7 +303,7 @@ const config: ForgeConfig = {

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

Expand Down
1 change: 1 addition & 0 deletions scripts/make-dev-macos.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
export CYD_ENV=dev
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
export MACOS_RELEASE=false

./scripts/clean.sh
electron-forge make --arch universal
1 change: 1 addition & 0 deletions scripts/make-local-macos.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
export CYD_ENV=local
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
export MACOS_RELEASE=false

./scripts/clean.sh
electron-forge make --arch universal
1 change: 1 addition & 0 deletions scripts/make-prod-macos.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
export CYD_ENV=prod
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
export MACOS_RELEASE=false

./scripts/clean.sh
electron-forge make --arch universal
1 change: 1 addition & 0 deletions scripts/publish-dev-macos.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
export CYD_ENV=dev
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
export MACOS_RELEASE=true

./scripts/clean.sh
electron-forge publish --arch universal
1 change: 1 addition & 0 deletions scripts/publish-prod-macos.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
export CYD_ENV=prod
export DEBUG=electron-packager,electron-universal,electron-forge*,electron-installer*
export MACOS_RELEASE=true

./scripts/clean.sh
electron-forge publish --arch universal
122 changes: 106 additions & 16 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ interface Config {
plausibleDomain: string;
}

let isAppReady = false;

// Queue of cyd:// URLs to handle, in case the app is not ready
const cydURLQueue: string[] = [];

// Load the config
const configPath = path.join(getResourcesPath(), 'config.json');
if (!fs.existsSync(configPath)) {
Expand All @@ -66,17 +71,69 @@ if (require('electron-squirrel-startup')) {
app.quit();
}

if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}

// Initialize the logger
log.initialize();
log.transports.file.level = false; // Disable file logging
log.transports.file.level = config.mode == "prod" ? false : "debug"; // Disable file logging in prod mode
log.info('Cyd version:', app.getVersion());
log.info('User data folder is at:', app.getPath('userData'));

// Handle cyd:// URLs (or cyd-dev:// in dev mode)
const openCydURL = async (cydURL: string) => {
if (!isAppReady) {
log.debug('Adding cyd:// URL to queue:', cydURL);
cydURLQueue.push(cydURL);
return;
}

const url = new URL(cydURL);
log.info(`Opening URL: ${url.toString()}`);

// If there's no main window, open one
if (BrowserWindow.getAllWindows().length === 0) {
await createWindow();
}

// If hostname is "open", this just means open Cyd
if (url.hostname == "open") {
// Success!
return;
}

// Check for Bluesky OAuth redirect
const blueskyHostname = config.mode == "prod" ? 'social.cyd.api' : 'social.cyd.dev-api';
if (url.hostname == blueskyHostname && url.pathname == "/atproto-oauth-callback") {
dialog.showMessageBoxSync({
title: "Cyd",
message: `Bluesky OAuth is not implemented yet.`,
type: 'info',
});
return;
}

// For all other paths, show an error
dialog.showMessageBoxSync({
title: "Cyd",
message: `Invalid Cyd URL: ${url.toString()}.`,
type: 'info',
});
return;
}

// Register the cyd:// (or cyd-dev://) protocol
const protocolString = config.mode == "prod" ? "cyd" : "cyd-dev";
app.setAsDefaultProtocolClient(protocolString)

// In Linux and Windows, handle cyd:// URLs passed in via the CLI
const lastArg = process.argv.length >= 2 ? process.argv[process.argv.length - 1] : "";
if ((process.platform == 'linux' || process.platform == 'win32') && lastArg.startsWith(protocolString + "://")) {
openCydURL(lastArg);
}

// In macOS, handle the cyd:// URLs
app.on('open-url', (event, url) => {
openCydURL(url);
})

const cydDevMode = process.env.CYD_DEV === "1";

async function initializeApp() {
Expand Down Expand Up @@ -106,7 +163,7 @@ async function initializeApp() {
}

// Set the log level
if (config.mode == "open" || config.mode == "dev" || config.mode == "local") {
if (config.mode != "prod") {
log.transports.console.level = "debug";
} else {
log.transports.console.level = "info";
Expand Down Expand Up @@ -164,10 +221,11 @@ async function initializeApp() {
await createWindow();
}

let win: BrowserWindow | null = null;
async function createWindow() {
// Create the browser window
const icon = nativeImage.createFromPath(path.join(getResourcesPath(), 'icon.png'));
const win = new BrowserWindow({
win = new BrowserWindow({
width: 1000,
height: 850,
minWidth: 900,
Expand All @@ -179,12 +237,20 @@ async function createWindow() {
icon: icon,
});

// Handle power monitor events
// Mark the app as ready
isAppReady = true;

// Handle any cyd:// URLs that came in before the app was ready
log.debug('Handling cyd:// URLs in queue:', cydURLQueue);
for (const url of cydURLQueue) {
openCydURL(url);
}

// Handle power monitor events
powerMonitor.on('suspend', () => {
log.info('System is suspending');
try {
win.webContents.send('powerMonitor:suspend');
win?.webContents.send('powerMonitor:suspend');
} catch (error) {
log.error('Failed to send powerMonitor:suspend to renderer:', error);
}
Expand All @@ -193,7 +259,7 @@ async function createWindow() {
powerMonitor.on('resume', () => {
log.info('System has resumed');
try {
win.webContents.send('powerMonitor:resume');
win?.webContents.send('powerMonitor:resume');
} catch (error) {
log.error('Failed to send powerMonitor:resume to renderer:', error);
}
Expand Down Expand Up @@ -376,6 +442,9 @@ async function createWindow() {
}

try {
if (!win) {
throw new Error("Window not initialized");
}
const result = dialog.showOpenDialogSync(win, options);
if (result && result.length > 0) {
return result[0];
Expand Down Expand Up @@ -483,15 +552,36 @@ async function createWindow() {

// When devtools opens, make sure the window is wide enough
win.webContents.on('devtools-opened', () => {
const [width, height] = win.getSize();
if (width < 1500) {
win.setSize(1500, height);
if (win) {
const [width, height] = win.getSize();
if (width < 1500) {
win.setSize(1500, height);
}
}
});

return win;
}

// Make sure there's only one instance of the app running
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
} else {
app.on('second-instance', (event, commandLine, _) => {
// Someone tried to run a second instance, focus the window
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
}
// commandLine is array of strings in which last element is deep link URL
const cydURL = commandLine.pop()
if (cydURL) {
openCydURL(cydURL);
}
})
}

app.enableSandbox();
app.on('ready', initializeApp);

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

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