@@ -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
4651const configPath = path . join ( getResourcesPath ( ) , 'config.json' ) ;
4752if ( ! 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
7575log . 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
7777log . info ( 'Cyd version:' , app . getVersion ( ) ) ;
7878log . 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+
80137const cydDevMode = process . env . CYD_DEV === "1" ;
81138
82139async 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 ;
167225async 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+
495585app . enableSandbox ( ) ;
496586app . 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