11import "reflect-metadata" ;
22import os from "node:os" ;
3- import { app } from "electron" ;
3+ import { app , BrowserWindow } from "electron" ;
44import log from "electron-log/main" ;
55import "./utils/logger" ;
66import "./services/index.js" ;
@@ -19,6 +19,7 @@ import type { NotificationService } from "./services/notification/service";
1919import type { OAuthService } from "./services/oauth/service" ;
2020import {
2121 captureException ,
22+ getPostHogClient ,
2223 initializePostHog ,
2324 trackAppEvent ,
2425} from "./services/posthog-analytics" ;
@@ -43,6 +44,102 @@ if (!gotTheLock) {
4344 process . exit ( 0 ) ;
4445}
4546
47+ const RECOVERABLE_RENDER_REASONS = new Set ( [
48+ "abnormal-exit" ,
49+ "killed" ,
50+ "crashed" ,
51+ "oom" ,
52+ "integrity-failure" ,
53+ "memory-eviction" ,
54+ ] ) ;
55+ const CRASH_LOOP_WINDOW_MS = 30_000 ;
56+ const CRASH_LOOP_THRESHOLD = 3 ;
57+ const recentCrashTimestamps : number [ ] = [ ] ;
58+
59+ function isCrashLoop ( ) : boolean {
60+ const now = Date . now ( ) ;
61+ while (
62+ recentCrashTimestamps . length > 0 &&
63+ now - recentCrashTimestamps [ 0 ] > CRASH_LOOP_WINDOW_MS
64+ ) {
65+ recentCrashTimestamps . shift ( ) ;
66+ }
67+ recentCrashTimestamps . push ( now ) ;
68+ return recentCrashTimestamps . length >= CRASH_LOOP_THRESHOLD ;
69+ }
70+
71+ app . on ( "render-process-gone" , ( _event , webContents , details ) => {
72+ const props = {
73+ source : "main" ,
74+ type : "render-process-gone" ,
75+ reason : details . reason ,
76+ exitCode : String ( details . exitCode ) ,
77+ url : webContents . getURL ( ) ,
78+ title : webContents . getTitle ( ) ,
79+ webContentsId : String ( webContents . id ) ,
80+ } ;
81+ log . error ( "Renderer process gone" , {
82+ ...props ,
83+ chromiumLogTail : readChromiumLogTail ( ) ,
84+ } ) ;
85+ captureException (
86+ new Error ( `Renderer process gone: ${ details . reason } ` ) ,
87+ props ,
88+ ) ;
89+ getPostHogClient ( )
90+ ?. flush ( )
91+ . catch ( ( ) => { } ) ;
92+
93+ if ( RECOVERABLE_RENDER_REASONS . has ( details . reason ) ) {
94+ if ( isCrashLoop ( ) ) {
95+ log . error ( "Crash loop detected, stopping auto-recovery" , {
96+ crashesInWindow : recentCrashTimestamps . length ,
97+ windowMs : CRASH_LOOP_WINDOW_MS ,
98+ } ) ;
99+ return ;
100+ }
101+ log . info ( "Recovering from renderer crash" , { reason : details . reason } ) ;
102+ const win = BrowserWindow . fromWebContents ( webContents ) ;
103+ if ( ! win || win . isDestroyed ( ) ) {
104+ log . warn ( "No window to recover" ) ;
105+ return ;
106+ }
107+ setImmediate ( ( ) => {
108+ if ( win . isDestroyed ( ) ) return ;
109+ log . info ( "Reloading webContents" ) ;
110+ win . webContents . reload ( ) ;
111+ log . info ( "Bringing window to foreground" ) ;
112+ win . show ( ) ;
113+ win . moveTop ( ) ;
114+ win . focus ( ) ;
115+ app . focus ( { steal : true } ) ;
116+ } ) ;
117+ }
118+ } ) ;
119+
120+ app . on ( "child-process-gone" , ( _event , details ) => {
121+ const props = {
122+ source : "main" ,
123+ type : "child-process-gone" ,
124+ processType : details . type ,
125+ reason : details . reason ,
126+ exitCode : String ( details . exitCode ) ,
127+ serviceName : details . serviceName ?? "" ,
128+ name : details . name ?? "" ,
129+ } ;
130+ log . error ( "Child process gone" , {
131+ ...props ,
132+ chromiumLogTail : readChromiumLogTail ( ) ,
133+ } ) ;
134+ captureException (
135+ new Error ( `Child process gone (${ details . type } ): ${ details . reason } ` ) ,
136+ props ,
137+ ) ;
138+ getPostHogClient ( )
139+ ?. flush ( )
140+ . catch ( ( ) => { } ) ;
141+ } ) ;
142+
46143async function initializeServices ( ) : Promise < void > {
47144 container . get < DatabaseService > ( MAIN_TOKENS . DatabaseService ) ;
48145 container . get < OAuthService > ( MAIN_TOKENS . OAuthService ) ;
@@ -111,17 +208,6 @@ app.on("window-all-closed", () => {
111208 app . quit ( ) ;
112209} ) ;
113210
114- app . on ( "child-process-gone" , ( _event , details ) => {
115- log . error ( "Child process gone" , {
116- type : details . type ,
117- reason : details . reason ,
118- exitCode : details . exitCode ,
119- serviceName : details . serviceName ,
120- name : details . name ,
121- chromiumLogTail : readChromiumLogTail ( ) ,
122- } ) ;
123- } ) ;
124-
125211app . on ( "before-quit" , async ( event ) => {
126212 let lifecycleService : AppLifecycleService ;
127213 try {
0 commit comments