fix(stats): harden server lifecycle and verify compiled runtime (#261)

This commit is contained in:
sudacode authored and GitHub committed 2026-09-20 23:19:03 -07:00
1 parent f9c4e892dc
commit 383aed8bad
37 files changed
+1381 -323

No files matched your search

+20 -4
View File
@@ -1,5 +1,6 @@
import { BrowserWindow, dialog, ipcMain } from 'electron';
import * as path from 'path';
import { createLogger } from '../../logger.js';
import type { WindowGeometry } from '../../types.js';
import { IPC_CHANNELS } from '../../shared/ipc/contracts.js';
import {
@@ -26,9 +27,11 @@ import {
} from './stats-window-layer.js';
let statsWindow: BrowserWindow | null = null;
let statsWindowGeneration = 0;
let toggleRegistered = false;
let nativeDialogLayerRegistered = false;
const nativeDialogLayerSuspension = createStatsWindowLayerSuspensionState();
const logger = createLogger('main:stats-window');
export interface StatsWindowOptions {
/** Absolute path to stats/dist/ directory */
@@ -36,7 +39,9 @@ export interface StatsWindowOptions {
/** Absolute path to the compiled preload-stats.js */
preloadPath: string;
/** Resolve the active stats API base URL */
getApiBaseUrl?: () => string;
getApiBaseUrl?: () => Promise<string> | string;
/** Report server startup failure through the configured notification surface. */
onStartupError?: (error: unknown) => void;
/** Resolve the active stats toggle key from config */
getToggleKey: () => string;
/** Resolve the tracked overlay/mpv bounds */
@@ -179,8 +184,16 @@ function registerStatsNativeDialogLayerHandlers(): void {
* Toggle the stats overlay window: create on first call, then show/hide.
* The React app stays mounted across toggles — state is preserved.
*/
export function toggleStatsOverlay(options: StatsWindowOptions): void {
export async function toggleStatsOverlay(options: StatsWindowOptions): Promise<void> {
if (!statsWindow) {
const generation = statsWindowGeneration;
const apiBaseUrl = await Promise.resolve()
.then(() => options.getApiBaseUrl?.())
.catch((error: unknown) => {
options.onStartupError?.(error);
throw error;
});
if (generation !== statsWindowGeneration || statsWindow) return;
statsWindow = new BrowserWindow(
buildStatsWindowOptions({
preloadPath: options.preloadPath,
@@ -195,7 +208,7 @@ export function toggleStatsOverlay(options: StatsWindowOptions): void {
});
const indexPath = path.join(options.staticDir, 'index.html');
statsWindow.loadFile(indexPath, buildStatsWindowLoadFileOptions(options.getApiBaseUrl?.()));
statsWindow.loadFile(indexPath, buildStatsWindowLoadFileOptions(apiBaseUrl));
statsWindow.on('closed', () => {
options.onVisibilityChanged?.(false);
@@ -243,7 +256,9 @@ export function registerStatsOverlayToggle(options: StatsWindowOptions): void {
if (toggleRegistered) return;
toggleRegistered = true;
ipcMain.on(IPC_CHANNELS.command.toggleStatsOverlay, () => {
toggleStatsOverlay(options);
void toggleStatsOverlay(options).catch((error: unknown) => {
logger.error('Failed to open stats overlay:', error);
});
});
}
@@ -252,6 +267,7 @@ export function registerStatsOverlayToggle(options: StatsWindowOptions): void {
* Call during app quit.
*/
export function destroyStatsWindow(): void {
statsWindowGeneration += 1;
if (statsWindow && !statsWindow.isDestroyed()) {
statsWindow.destroy();
statsWindow = null;