@@ -5,6 +5,9 @@ const http = require('http');
55const path = require ( 'path' ) ;
66
77let server ;
8+ let mainWindow ;
9+ let appBaseUrl ;
10+ let pendingNostrUrl = '' ;
811const secureSessionFile = 'secure-private-key-session.dat' ;
912
1013function createStaticServer ( ) {
@@ -41,8 +44,8 @@ function createStaticServer() {
4144}
4245
4346async function createWindow ( ) {
44- const baseUrl = await createStaticServer ( ) ;
45- const window = new BrowserWindow ( {
47+ appBaseUrl = appBaseUrl || ( await createStaticServer ( ) ) ;
48+ mainWindow = new BrowserWindow ( {
4649 width : 1180 ,
4750 height : 840 ,
4851 minWidth : 420 ,
@@ -58,11 +61,53 @@ async function createWindow() {
5861 }
5962 } ) ;
6063
61- window . webContents . setWindowOpenHandler ( ( { url } ) => {
64+ mainWindow . webContents . setWindowOpenHandler ( ( { url } ) => {
6265 void shell . openExternal ( url ) ;
6366 return { action : 'deny' } ;
6467 } ) ;
65- await window . loadURL ( baseUrl ) ;
68+ mainWindow . on ( 'closed' , ( ) => {
69+ mainWindow = undefined ;
70+ } ) ;
71+ await mainWindow . loadURL ( appBaseUrl ) ;
72+ if ( pendingNostrUrl ) {
73+ const url = pendingNostrUrl ;
74+ pendingNostrUrl = '' ;
75+ openNostrUrl ( url ) ;
76+ }
77+ }
78+
79+ function registerNostrProtocol ( ) {
80+ if ( process . defaultApp ) {
81+ if ( process . argv . length >= 2 ) app . setAsDefaultProtocolClient ( 'nostr' , process . execPath , [ path . resolve ( process . argv [ 1 ] ) ] ) ;
82+ } else {
83+ app . setAsDefaultProtocolClient ( 'nostr' ) ;
84+ }
85+ }
86+
87+ function findNostrUrl ( argv = process . argv ) {
88+ return argv . find ( ( argument ) => / ^ n o s t r : / i. test ( argument ) ) || '' ;
89+ }
90+
91+ function nostrIdentifierFromUrl ( url = '' ) {
92+ const clean = String ( url ) . trim ( ) ;
93+ if ( ! / ^ n o s t r : / i. test ( clean ) ) return '' ;
94+ try {
95+ return decodeURIComponent ( clean . replace ( / ^ n o s t r : (?: \/ \/ ) ? / i, '' ) . replace ( / ^ \/ + / , '' ) . split ( / [ ? # ] / , 1 ) [ 0 ] || '' ) . trim ( ) ;
96+ } catch {
97+ return clean . replace ( / ^ n o s t r : (?: \/ \/ ) ? / i, '' ) . replace ( / ^ \/ + / , '' ) . split ( / [ ? # ] / , 1 ) [ 0 ] || '' ;
98+ }
99+ }
100+
101+ function openNostrUrl ( url ) {
102+ const identifier = nostrIdentifierFromUrl ( url ) ;
103+ if ( ! identifier ) return ;
104+ pendingNostrUrl = url ;
105+ if ( ! mainWindow || ! appBaseUrl ) return ;
106+ pendingNostrUrl = '' ;
107+ if ( mainWindow . isMinimized ( ) ) mainWindow . restore ( ) ;
108+ mainWindow . show ( ) ;
109+ mainWindow . focus ( ) ;
110+ void mainWindow . loadURL ( `${ appBaseUrl } /${ encodeURIComponent ( identifier ) } ` ) ;
66111}
67112
68113function contentType ( filePath ) {
@@ -122,10 +167,27 @@ function registerSecureSessionHandlers() {
122167 ipcMain . handle ( 'secure-session:clear' , ( ) => clearSecureSession ( ) ) ;
123168}
124169
125- app . whenReady ( ) . then ( ( ) => {
126- registerSecureSessionHandlers ( ) ;
127- return createWindow ( ) ;
128- } ) ;
170+ registerNostrProtocol ( ) ;
171+ openNostrUrl ( findNostrUrl ( ) ) ;
172+
173+ const gotSingleInstanceLock = app . requestSingleInstanceLock ( ) ;
174+ if ( ! gotSingleInstanceLock ) {
175+ app . quit ( ) ;
176+ } else {
177+ app . on ( 'second-instance' , ( _event , argv ) => {
178+ openNostrUrl ( findNostrUrl ( argv ) ) ;
179+ } ) ;
180+
181+ app . on ( 'open-url' , ( event , url ) => {
182+ event . preventDefault ( ) ;
183+ openNostrUrl ( url ) ;
184+ } ) ;
185+
186+ app . whenReady ( ) . then ( ( ) => {
187+ registerSecureSessionHandlers ( ) ;
188+ return createWindow ( ) ;
189+ } ) ;
190+ }
129191
130192app . on ( 'window-all-closed' , ( ) => {
131193 if ( process . platform !== 'darwin' ) app . quit ( ) ;
0 commit comments