44 * Use of this source code is governed by an MIT-style license that can be found in the LICENSE file
55 */
66
7- import { app , BrowserWindow } from 'electron' ;
7+ import { app , BrowserWindow , net , protocol } from 'electron' ;
88import path from 'node:path' ;
99import inspector from 'node:inspector' ;
10+ import { pathToFileURL } from 'node:url' ;
1011import started from 'electron-squirrel-startup' ;
1112import { initialize } from '@aptabase/electron/main' ;
1213import { registerIpcHandlers , setAuthManager , emitToRenderer } from './main/ipc-handlers' ;
@@ -27,6 +28,19 @@ import { SseDetectionServer } from './main/server/SseDetectionServer';
2728declare const MAIN_WINDOW_VITE_DEV_SERVER_URL : string | undefined ;
2829declare const MAIN_WINDOW_VITE_NAME : string ;
2930
31+ const RENDERER_PROTOCOL = 'talktofigma-renderer' ;
32+
33+ protocol . registerSchemesAsPrivileged ( [
34+ {
35+ scheme : RENDERER_PROTOCOL ,
36+ privileges : {
37+ standard : true ,
38+ secure : true ,
39+ supportFetchAPI : true ,
40+ } ,
41+ } ,
42+ ] ) ;
43+
3044// Initialize Aptabase before app is ready (must be before any app events)
3145initialize ( APTABASE_APP_KEY ) ;
3246
@@ -65,6 +79,44 @@ let mainWindow: BrowserWindow | null = null;
6579let tray : TalkToFigmaTray | null = null ;
6680let serverManager : TalkToFigmaServerManager | null = null ;
6781let service : TalkToFigmaService | null = null ;
82+ let rendererProtocolRegistered = false ;
83+
84+ const getRendererRoot = ( ) => path . join ( __dirname , `../renderer/${ MAIN_WINDOW_VITE_NAME } ` ) ;
85+
86+ const getPackagedRendererUrl = ( ) => `${ RENDERER_PROTOCOL } ://renderer/index.html` ;
87+
88+ const registerRendererProtocol = ( ) => {
89+ if ( MAIN_WINDOW_VITE_DEV_SERVER_URL || rendererProtocolRegistered ) {
90+ return ;
91+ }
92+
93+ const rendererRoot = getRendererRoot ( ) ;
94+
95+ protocol . handle ( RENDERER_PROTOCOL , ( request ) => {
96+ const requestUrl = new URL ( request . url ) ;
97+ const requestedPath = decodeURIComponent ( requestUrl . pathname ) ;
98+ const relativePath = requestedPath === '/' || requestedPath === ''
99+ ? 'index.html'
100+ : requestedPath . replace ( / ^ \/ + / , '' ) ;
101+ const filePath = path . join ( rendererRoot , relativePath ) ;
102+ const relativeToRoot = path . relative ( rendererRoot , filePath ) ;
103+
104+ if ( relativeToRoot . startsWith ( '..' ) || path . isAbsolute ( relativeToRoot ) ) {
105+ return new Response ( 'Not found' , { status : 404 } ) ;
106+ }
107+
108+ return net . fetch ( pathToFileURL ( filePath ) . toString ( ) ) ;
109+ } ) ;
110+
111+ rendererProtocolRegistered = true ;
112+ logger . info ( `Renderer protocol registered for: ${ rendererRoot } ` ) ;
113+ } ;
114+
115+ const loadRenderer = ( window : BrowserWindow ) => {
116+ const rendererUrl = MAIN_WINDOW_VITE_DEV_SERVER_URL || getPackagedRendererUrl ( ) ;
117+ logger . info ( `Loading renderer: ${ rendererUrl } ` ) ;
118+ return window . loadURL ( rendererUrl ) ;
119+ } ;
68120
69121const createWindow = ( ) => {
70122 logger . info ( 'Creating main window' ) ;
@@ -90,18 +142,22 @@ const createWindow = () => {
90142 // Center window on screen
91143 mainWindow . center ( ) ;
92144
93- // Load the app
94- if ( MAIN_WINDOW_VITE_DEV_SERVER_URL ) {
95- mainWindow . loadURL ( MAIN_WINDOW_VITE_DEV_SERVER_URL ) ;
96- } else {
97- mainWindow . loadFile (
98- path . join ( __dirname , `../renderer/${ MAIN_WINDOW_VITE_NAME } /index.html` ) ,
99- ) ;
100- }
101-
102145 // Renderer lifecycle diagnostics (critical for packaged/TestFlight blank-screen debugging)
146+ let rendererDomReady = false ;
147+
148+ mainWindow . webContents . on ( 'did-start-navigation' , ( _event , url , isInPlace , isMainFrame ) => {
149+ if ( isMainFrame && ! isInPlace ) {
150+ logger . info ( `Renderer did-start-navigation: ${ url } ` ) ;
151+ }
152+ } ) ;
153+
154+ mainWindow . webContents . on ( 'dom-ready' , ( ) => {
155+ rendererDomReady = true ;
156+ logger . info ( `Renderer dom-ready: ${ mainWindow ?. webContents . getURL ( ) || 'unknown' } ` ) ;
157+ } ) ;
158+
103159 mainWindow . webContents . on ( 'did-finish-load' , ( ) => {
104- logger . info ( ' Renderer did-finish-load' ) ;
160+ logger . info ( ` Renderer did-finish-load: ${ mainWindow ?. webContents . getURL ( ) || 'unknown' } ` ) ;
105161 } ) ;
106162
107163 mainWindow . webContents . on (
@@ -113,6 +169,11 @@ const createWindow = () => {
113169 }
114170 ) ;
115171
172+ mainWindow . webContents . on ( 'console-message' , ( _event , level , message , line , sourceId ) => {
173+ const trimmedMessage = message . length > 500 ? `${ message . slice ( 0 , 500 ) } ...` : message ;
174+ logger . info ( `Renderer console[${ level } ] ${ sourceId } :${ line } ${ trimmedMessage } ` ) ;
175+ } ) ;
176+
116177 mainWindow . webContents . on ( 'render-process-gone' , ( _event , details ) => {
117178 logger . error (
118179 `Renderer process gone: reason=${ details . reason } , exitCode=${ details . exitCode } `
@@ -133,6 +194,24 @@ const createWindow = () => {
133194 logger . info ( 'Main window responsive again' ) ;
134195 } ) ;
135196
197+ loadRenderer ( mainWindow ) . catch ( ( error ) => {
198+ logger . error ( `Renderer load failed: ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
199+ } ) ;
200+
201+ setTimeout ( ( ) => {
202+ if ( ! mainWindow || mainWindow . isDestroyed ( ) || rendererDomReady ) {
203+ return ;
204+ }
205+
206+ logger . warn (
207+ `Renderer did not reach dom-ready after 10s; currentURL=${ mainWindow . webContents . getURL ( ) || 'empty' } , loading=${ mainWindow . webContents . isLoading ( ) } . Retrying renderer load.`
208+ ) ;
209+ mainWindow . webContents . stop ( ) ;
210+ loadRenderer ( mainWindow ) . catch ( ( error ) => {
211+ logger . error ( `Renderer retry failed: ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
212+ } ) ;
213+ } , 10_000 ) ;
214+
136215 // Register IPC handlers
137216 registerIpcHandlers ( mainWindow ) ;
138217
@@ -305,6 +384,8 @@ const initializeServers = (window: BrowserWindow) => {
305384app . on ( 'ready' , async ( ) => {
306385 logger . info ( 'App ready, initializing...' ) ;
307386
387+ registerRendererProtocol ( ) ;
388+
308389 // Track app start and user engagement (Kotlin-compatible)
309390 trackAppStart ( ) ;
310391 trackUserEngagement ( ) ;
0 commit comments