1- const { app, BrowserWindow, protocol, Menu, ipcMain } = require ( 'electron' ) ;
1+ const { app, BrowserWindow, protocol, Menu, ipcMain, net } = require ( 'electron' ) ;
22const path = require ( 'path' ) ;
33const fs = require ( 'fs' ) ;
44
@@ -10,6 +10,21 @@ const { assertTrusted } = require('./ipc-security');
1010const { getWindowOptions, trackWindowState, DEFAULTS } = require ( './window-state' ) ;
1111const { phoenixLoadURL, gaMetricsURL } = require ( './config' ) ;
1212
13+ // Register phtauri:// as a privileged scheme (must be done before app ready)
14+ // This enables standard web features: fetch, localStorage, cookies, etc.
15+ protocol . registerSchemesAsPrivileged ( [
16+ {
17+ scheme : 'phtauri' ,
18+ privileges : {
19+ standard : true ,
20+ secure : true ,
21+ supportFetchAPI : true ,
22+ corsEnabled : true ,
23+ stream : true
24+ }
25+ }
26+ ] ) ;
27+
1328// Request single instance lock - only one instance of the app should run at a time
1429const gotTheLock = app . requestSingleInstanceLock ( ) ;
1530
@@ -194,11 +209,45 @@ app.whenReady().then(async () => {
194209 // Remove default menu bar
195210 Menu . setApplicationMenu ( null ) ;
196211
212+ // Register phtauri:// protocol for serving Phoenix files
213+ // In dev: serves from ../phoenix/ repo
214+ // In packaged: would need different handling (but typically uses https:// in production)
215+ if ( ! app . isPackaged ) {
216+ const phoenixRepoPath = path . resolve ( __dirname , '..' , '..' , 'phoenix' ) ;
217+
218+ protocol . handle ( 'phtauri' , ( request ) => {
219+ try {
220+ const url = new URL ( request . url ) ;
221+ // phtauri://localhost/src/index.html -> ../phoenix/src/index.html
222+ let requestedPath = decodeURIComponent ( url . pathname ) ;
223+
224+ // Serve index.html for directory requests
225+ if ( requestedPath . endsWith ( '/' ) ) {
226+ requestedPath += 'index.html' ;
227+ }
228+
229+ const filePath = path . join ( phoenixRepoPath , requestedPath ) ;
230+ const normalizedFilePath = path . normalize ( filePath ) ;
231+
232+ // Security: Ensure path is under phoenix repo (prevent directory traversal)
233+ if ( ! normalizedFilePath . startsWith ( phoenixRepoPath ) ) {
234+ console . error ( 'phtauri access denied - path not under phoenix repo:' , requestedPath ) ;
235+ return new Response ( 'Access denied' , { status : 403 } ) ;
236+ }
237+
238+ return net . fetch ( `file://${ normalizedFilePath } ` ) ;
239+ } catch ( err ) {
240+ console . error ( 'phtauri protocol error:' , err ) ;
241+ return new Response ( 'Not found' , { status : 404 } ) ;
242+ }
243+ } ) ;
244+ }
245+
197246 // Register asset:// protocol for serving local files from appLocalData/assets/
198247 const appDataDir = getAppDataDir ( ) ;
199248 const assetsDir = path . join ( appDataDir , 'assets' ) ;
200249
201- protocol . registerFileProtocol ( 'asset' , ( request , callback ) => {
250+ protocol . handle ( 'asset' , ( request ) => {
202251 try {
203252 const url = new URL ( request . url ) ;
204253 // Decode the path from URL encoding
@@ -209,14 +258,13 @@ app.whenReady().then(async () => {
209258 // Security: Ensure path is under assets directory (prevent directory traversal)
210259 if ( ! normalizedRequested . startsWith ( normalizedAssetsDir ) ) {
211260 console . error ( 'Asset access denied - path not under assets dir:' , requestedPath ) ;
212- callback ( { error : - 10 } ) ; // net::ERR_ACCESS_DENIED
213- return ;
261+ return new Response ( 'Access denied' , { status : 403 } ) ;
214262 }
215263
216- callback ( { path : normalizedRequested } ) ;
264+ return net . fetch ( `file:// ${ normalizedRequested } ` ) ;
217265 } catch ( err ) {
218266 console . error ( 'Asset protocol error:' , err ) ;
219- callback ( { error : - 2 } ) ; // net::ERR_FAILED
267+ return new Response ( 'Not found' , { status : 404 } ) ;
220268 }
221269 } ) ;
222270
0 commit comments