1
- import { app , BrowserWindow } from 'electron' ;
1
+ import type { BrowserWindowConstructorOptions } from 'electron' ;
2
+ import { app , BrowserWindow , shell } from 'electron' ;
2
3
import path from 'path' ;
3
4
import { migrateToLatest } from '@server/db/migrations' ;
4
5
import router from '@server/router' ;
@@ -11,12 +12,35 @@ if (require('electron-squirrel-startup')) {
11
12
app . quit ( ) ;
12
13
}
13
14
14
- const createWindow = async ( ) => {
15
+ // This method will be called when Electron has finished
16
+ // initialization and is ready to create browser windows.
17
+ // Some APIs can only be used after this event occurs.
18
+ app . on ( 'ready' , onReady ) ;
19
+
20
+ // Quit when all windows are closed, except on macOS. There, it's common
21
+ // for applications and their menu bar to stay active until the user quits
22
+ // explicitly with Cmd + Q.
23
+ app . on ( 'window-all-closed' , ( ) => {
24
+ if ( process . platform !== 'darwin' ) {
25
+ app . quit ( ) ;
26
+ }
27
+ } ) ;
28
+
29
+ app . on ( 'activate' , async ( ) => {
30
+ // On OS X it's common to re-create a window in the app when the
31
+ // dock icon is clicked and there are no other windows open.
32
+ if ( BrowserWindow . getAllWindows ( ) . length === 0 ) {
33
+ await createWindow ( ) ;
34
+ }
35
+ } ) ;
36
+
37
+ function createWindow ( options ?: BrowserWindowConstructorOptions ) {
15
38
// Create the browser window.
16
- const mainWindow = new BrowserWindow ( {
39
+ const window = new BrowserWindow ( {
17
40
width : 1280 ,
18
41
height : 720 ,
19
42
webPreferences : {
43
+ ...( options ?. webPreferences || { } ) ,
20
44
preload : path . join ( __dirname , 'preload.js' ) ,
21
45
nodeIntegration : true ,
22
46
} ,
@@ -25,48 +49,106 @@ const createWindow = async () => {
25
49
26
50
// and load the index.html of the app.
27
51
if ( MAIN_WINDOW_VITE_DEV_SERVER_URL ) {
28
- await mainWindow . loadURL ( MAIN_WINDOW_VITE_DEV_SERVER_URL ) ;
52
+ void window . loadURL ( MAIN_WINDOW_VITE_DEV_SERVER_URL ) ;
29
53
} else {
30
- await mainWindow . loadFile (
54
+ void window . loadFile (
31
55
path . join ( __dirname , `../renderer/${ MAIN_WINDOW_VITE_NAME } /index.html` ) ,
32
56
) ;
33
57
}
34
58
35
59
// Open the DevTools.
36
60
if ( ! app . isPackaged ) {
37
- mainWindow . webContents . openDevTools ( ) ;
61
+ window . webContents . openDevTools ( ) ;
38
62
}
39
- return mainWindow ;
40
- } ;
41
63
42
- const onReady = async ( ) => {
43
- await migrateToLatest ( getUserSettings ( ) . dbPath ) ;
44
- const window = await createWindow ( ) ;
45
- createIPCHandler ( { router, windows : [ window ] } ) ;
46
- setAppMenu ( ) ;
47
- } ;
64
+ window . webContents . setWindowOpenHandler ( ( { url } ) => {
65
+ const newUrl = maybeParseUrl ( url ) ;
66
+ const currentUrl = maybeParseUrl ( window . webContents . getURL ( ) ) ;
48
67
49
- // This method will be called when Electron has finished
50
- // initialization and is ready to create browser windows.
51
- // Some APIs can only be used after this event occurs.
52
- app . on ( 'ready' , onReady ) ;
68
+ if ( ! newUrl || ! currentUrl ) {
69
+ return { action : 'deny' } ;
70
+ }
53
71
54
- // Quit when all windows are closed, except on macOS. There, it's common
55
- // for applications and their menu bar to stay active until the user quits
56
- // explicitly with Cmd + Q.
57
- app . on ( 'window-all-closed' , ( ) => {
58
- if ( process . platform !== 'darwin' ) {
59
- app . quit ( ) ;
72
+ if ( ! isOpeningApp ( currentUrl , newUrl ) ) {
73
+ void openExternalUrl ( url ) ;
74
+ return { action : 'deny' } ;
75
+ }
76
+
77
+ return {
78
+ action : 'allow' ,
79
+ createWindow : ( options ) => {
80
+ const newWindow = createWindow ( options ) ;
81
+ void newWindow . webContents . loadURL ( url ) ;
82
+ window . addTabbedWindow ( newWindow ) ;
83
+ return newWindow . webContents ;
84
+ } ,
85
+ outlivesOpener : true ,
86
+ overrideBrowserWindowOptions : {
87
+ width : 1280 ,
88
+ height : 720 ,
89
+ webPreferences : {
90
+ preload : path . join ( __dirname , 'preload.js' ) ,
91
+ nodeIntegration : true ,
92
+ } ,
93
+ } ,
94
+ } ;
95
+ } ) ;
96
+
97
+ return window ;
98
+ }
99
+
100
+ function isOpeningApp ( currentUrl : URL , newUrl : URL ) {
101
+ if ( app . isPackaged ) {
102
+ return (
103
+ currentUrl . protocol === 'file:' &&
104
+ currentUrl . protocol === newUrl . protocol &&
105
+ currentUrl . pathname === newUrl . pathname
106
+ ) ;
60
107
}
61
- } ) ;
62
108
63
- app . on ( 'activate' , async ( ) => {
64
- // On OS X it's common to re-create a window in the app when the
65
- // dock icon is clicked and there are no other windows open.
66
- if ( BrowserWindow . getAllWindows ( ) . length === 0 ) {
67
- await createWindow ( ) ;
109
+ return (
110
+ currentUrl . hostname === 'localhost' &&
111
+ currentUrl . host === newUrl . host &&
112
+ currentUrl . pathname === newUrl . pathname
113
+ ) ;
114
+ }
115
+
116
+ async function openExternalUrl ( url : string ) {
117
+ const parsedUrl = maybeParseUrl ( url ) ;
118
+ if ( ! parsedUrl ) {
119
+ return ;
68
120
}
69
- } ) ;
121
+
122
+ const { protocol } = parsedUrl ;
123
+ // We could handle all possible link cases here, not only http/https
124
+ if ( protocol === 'http:' || protocol === 'https:' ) {
125
+ try {
126
+ await shell . openExternal ( url ) ;
127
+ } catch ( error : unknown ) {
128
+ console . error ( `Failed to open url: ${ error } ` ) ;
129
+ }
130
+ }
131
+ }
132
+
133
+ function maybeParseUrl ( value : string ) : URL | undefined {
134
+ if ( typeof value === 'string' ) {
135
+ try {
136
+ return new URL ( value ) ;
137
+ } catch ( err ) {
138
+ // Errors are ignored, as we only want to check if the value is a valid url
139
+ console . error ( `Failed to parse url: ${ value } ` ) ;
140
+ }
141
+ }
142
+
143
+ return undefined ;
144
+ }
145
+
146
+ async function onReady ( ) {
147
+ await migrateToLatest ( getUserSettings ( ) . dbPath ) ;
148
+ const window = await createWindow ( ) ;
149
+ createIPCHandler ( { router, windows : [ window ] } ) ;
150
+ setAppMenu ( ) ;
151
+ }
70
152
71
153
// In this file you can include the rest of your app's specific main process
72
154
// code. You can also put them in separate files and import them here.
0 commit comments