@noego/wood 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.cjs +12 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +14 -1
- package/dist/client/index.d.ts +14 -1
- package/dist/client/index.js +11 -0
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/preload_generator.cjs +44 -5
- package/dist/codegen/preload_generator.cjs.map +1 -1
- package/dist/codegen/preload_generator.js +44 -5
- package/dist/codegen/preload_generator.js.map +1 -1
- package/dist/frontend/frontend_surface.d.cts +1 -0
- package/dist/frontend/frontend_surface.d.ts +1 -0
- package/dist/frontend/index.d.cts +1 -0
- package/dist/frontend/index.d.ts +1 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/main/index.cjs +43 -58
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +2 -2
- package/dist/main/index.d.ts +2 -2
- package/dist/main/index.js +48 -58
- package/dist/main/index.js.map +1 -1
- package/dist/main/shutdown_coordinator.cjs +153 -0
- package/dist/main/shutdown_coordinator.cjs.map +1 -0
- package/dist/main/shutdown_coordinator.d.cts +81 -0
- package/dist/main/shutdown_coordinator.d.ts +81 -0
- package/dist/main/shutdown_coordinator.js +127 -0
- package/dist/main/shutdown_coordinator.js.map +1 -0
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +4 -28
- package/dist/navigation/index.d.ts +4 -28
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/layout_preservation.cjs.map +1 -1
- package/dist/navigation/layout_preservation.d.cts +27 -2
- package/dist/navigation/layout_preservation.d.ts +27 -2
- package/dist/navigation/layout_preservation.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +1 -0
- package/dist/testing/create_controller_harness.d.ts +1 -0
- package/dist/testing/index.d.cts +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/test_wood_config.d.cts +1 -0
- package/dist/testing/test_wood_config.d.ts +1 -0
- package/dist/testing/wood_env_renderer.d.cts +1 -0
- package/dist/testing/wood_env_renderer.d.ts +1 -0
- package/package.json +1 -1
- package/src/navigation/layout_preservation.ts +3 -1
package/dist/main/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/main/index.ts"],"sourcesContent":["import path from 'path';\nimport { StartupFailureHandler } from './startup_failure_handler.js';\nimport {\n app as electronApp,\n BrowserWindow,\n ipcMain,\n MessageChannelMain,\n Menu,\n protocol,\n net,\n shell,\n screen,\n} from 'electron';\nimport type {\n BrowserWindowConstructorOptions,\n BrowserWindow as ElectronBrowserWindow,\n Event as ElectronEvent,\n IpcMainInvokeEvent,\n Privileges,\n RenderProcessGoneDetails,\n} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport { cleanupAll } from '../controller/cleanup.js';\nimport { ExecutionContext } from '@noego/ioc';\nimport { runtime as bootRuntime } from '../runtime/runtime.js';\nimport { dispatchRendererTracePayload, type TraceLevel, type WoodTraceBatchReceiver } from '../tracing/index.js';\nimport { setApplicationRoot } from '../runtime/application_root.js';\nimport type { RegisterRuntimeRouter } from '../runtime/runtime.js';\nimport {\n getRendererLogDispatcher,\n RendererLogDispatcher,\n type RendererLogCallback,\n} from '../runtime/renderer_log_dispatcher.js';\nimport {\n setRendererLogRecordFilter,\n type RendererLogRecordFilter,\n} from '../runtime/renderer_log_batch_receiver.js';\nexport {\n setRendererLogRecordFilter,\n type RendererLogRecordFilter,\n} from '../runtime/renderer_log_batch_receiver.js';\nimport {\n setRendererConfigExtension,\n type RendererConfigExtension,\n} from '../runtime/renderer_config_extension.js';\nexport {\n setRendererConfigExtension,\n type RendererConfigExtension,\n} from '../runtime/renderer_config_extension.js';\nimport { Ipc, getIpc } from '../ipc/index.js';\nimport { setIpcPushSender, clearIpcPushSender, type IpcPushOptions } from '../ipc/shared.js';\nimport {\n drainRendererTracesBeforeQuit,\n RendererTraceDrainCoordinator,\n} from './renderer_trace_drain_coordinator.js';\n\ntype CleanupHandler = () => void | Promise<void>;\n\nexport type BootstrapActivationState = 'pending' | 'ready' | 'failed';\n\n/**\n * Owns the startup/activation boundary so Electron activation cannot create a\n * renderer until bootstrap has registered the runtime IPC handlers.\n */\nexport class BootstrapActivationGate {\n private currentState: BootstrapActivationState = 'pending';\n\n constructor(\n private readonly hasWindow: () => boolean,\n private readonly createMainWindow: () => void,\n ) {}\n\n get state(): BootstrapActivationState {\n return this.currentState;\n }\n\n activate(): void {\n if (this.currentState === 'ready' && !this.hasWindow()) {\n this.createMainWindow();\n }\n }\n\n ready(): void {\n if (this.currentState !== 'pending') {\n return;\n }\n\n // createMainWindow may throw. Leave the gate pending so the bootstrap\n // rejection can make the failure terminal rather than exposing readiness.\n this.createMainWindow();\n this.currentState = 'ready';\n }\n\n failed(): void {\n if (this.currentState === 'pending') {\n this.currentState = 'failed';\n }\n }\n}\n\nexport type RendererHealthState =\n | 'ready'\n | 'loading'\n | 'unresponsive'\n | 'gone'\n | 'destroyed';\n\nexport type WoodWindowConfig = {\n title: string;\n width: number;\n height: number;\n defaultRoute?: string;\n frame?: boolean;\n transparent?: boolean;\n resizable?: boolean;\n alwaysOnTop?: boolean;\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n show?: boolean;\n parent?: string;\n modal?: boolean;\n vibrancy?: string;\n backgroundMaterial?: string;\n backgroundColor?: string;\n rememberBounds?: boolean;\n contextMenu?: 'native' | 'custom';\n acceptFirstMouse?: boolean;\n nativeQuitFallback?: boolean;\n titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';\n trafficLightPosition?: { x: number; y: number };\n titleBarOverlay?: boolean | { color?: string; symbolColor?: string; height?: number };\n};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\n /** @deprecated Ignored since 0.6.0: each main runtime owns a fresh root. Use configureContainer. */\n scope?: string;\n configureContainer?: (container: any) => void;\n registerSchemesAsPrivileged?: PrivilegedSchemeRegistration[];\n}\n\nexport interface WoodMainBootContext {\n electron: {\n app: typeof electronApp;\n BrowserWindow: typeof BrowserWindow;\n ipcMain: typeof ipcMain;\n protocol: typeof protocol;\n net: typeof net;\n shell: typeof shell;\n screen: typeof screen;\n Menu: typeof Menu;\n };\n logger: ReturnType<typeof getLogger>;\n}\n\nexport function shouldDeferLastWindowCloseForCleanup(\n trackedWindowCount: number,\n cleanupComplete: boolean,\n): boolean {\n return trackedWindowCount === 1 && !cleanupComplete;\n}\n\nexport interface WoodMainRuntimeContext extends WoodMainBootContext {\n container: any;\n applicationRoot: string;\n getWindow: (windowId: string) => ElectronBrowserWindow | null;\n createWindow: (windowId?: string) => ElectronBrowserWindow;\n navigateWindow: (windowId: string, route: string) => Promise<void>;\n broadcast: (channel: string, data: unknown) => void;\n /**\n * Dispatch a channel through the SAME real WoodRouter the ipcMain handlers\n * wrap — full operations-YAML schema validation, controllers, middleware.\n * Available only after the runtime finishes booting (mainBoot runs before the\n * router is constructed, so callers must invoke this lazily, not during\n * mainBoot). This is the in-main seam the launch-flag-gated test control plane\n * uses for driver `api.call`; production code never calls it.\n */\n dispatch: (channel: string, payload?: unknown) => Promise<unknown>;\n getRendererHealthState: (windowId: string) => RendererHealthState | null;\n setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n /** Filters renderer log records received via the batched __log_batch channel. */\n setRendererLogRecordFilter: (filter: RendererLogRecordFilter | null) => void;\n /**\n * Adds app fields to the `__woodConfig` payload renderers fetch at boot\n * (e.g. an external log sink's port + per-boot token). Wood's own keys win.\n */\n setRendererConfigExtension: (extension: RendererConfigExtension | null) => void;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface WoodRendererHealthEvent {\n windowId: string;\n state: RendererHealthState;\n previousState: RendererHealthState | null;\n webContentsId?: number;\n reason?: string;\n exitCode?: number;\n}\n\nexport type WoodWindowLifecycleContext = WoodMainRuntimeContext;\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\n /** @deprecated Ignored since 0.6.0: each main runtime owns a fresh root. Use configureContainer. */\n scope?: string;\n configureContainer?: (container: any) => void;\n appBoot?: (\n context: WoodMainBootContext,\n ) => void | WoodMainAppBootResult | Promise<void | WoodMainAppBootResult>;\n mainBoot?: (\n appBootResult: unknown,\n context: WoodMainRuntimeContext,\n ) => void | CleanupHandler | Promise<void | CleanupHandler>;\n onBeforeQuit?: (context: WoodMainRuntimeContext) => void | Promise<void>;\n onWindowCreated?: (\n windowId: string,\n win: ElectronBrowserWindow,\n context: WoodMainRuntimeContext,\n ) => void | Promise<void>;\n onRendererHealthChanged?: (\n event: WoodRendererHealthEvent,\n context: WoodMainRuntimeContext,\n ) => void | Promise<void>;\n onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;\n onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;\n traceMinLevel?: TraceLevel;\n rendererTraceEnabled?: boolean;\n rendererTraceMinLevel?: TraceLevel;\n /** Explicit environment-owned durable trace receiver. */\n rendererTraceBatchReceiver?: WoodTraceBatchReceiver;\n /** Opt into process-global observation and push adapters during migration. */\n legacyGlobals?: boolean;\n traceTransport?: 'ipc' | 'messageport';\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: RendererHealthState;\n}\n\nexport interface WoodDebugMetadata {\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: WoodDebugWindowMetadata[];\n}\n\nconst DEFAULT_WOOD_REMOTE_DEBUGGING_PORT = 9333;\n\nexport function resolveWoodDebugMetadataConfiguration(env: NodeJS.ProcessEnv = process.env): {\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n} {\n const isDevelopment = env.WOOD_MODE === 'development';\n if (!isDevelopment) {\n return {\n enabled: false,\n mode: 'production',\n };\n }\n\n const rawPort = env.WOOD_REMOTE_DEBUGGING_PORT;\n const parsedPort = rawPort ? Number.parseInt(rawPort, 10) : DEFAULT_WOOD_REMOTE_DEBUGGING_PORT;\n const port = Number.isFinite(parsedPort) && parsedPort > 0\n ? parsedPort\n : DEFAULT_WOOD_REMOTE_DEBUGGING_PORT;\n\n return {\n enabled: true,\n mode: 'development',\n port,\n };\n}\n\nfunction resolveWindowDef(\n windowDefs: Record<string, WoodWindowConfig>,\n windowId = 'main',\n): WoodWindowConfig {\n const def = windowDefs[windowId];\n if (!def) {\n throw new Error(`Window \"${windowId}\" not defined in generated window defs.`);\n }\n return def;\n}\n\nfunction resolveRuntimePath(relativeOrAbsolutePath: string): string {\n if (path.isAbsolute(relativeOrAbsolutePath)) {\n return relativeOrAbsolutePath;\n }\n return path.join(electronApp.getAppPath(), relativeOrAbsolutePath);\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function startWoodMain(options: StartWoodMainOptions): void {\n const logger = getLogger('wood:main').named('bootstrap');\n const runtimeLogger = getLogger('wood:main').named('runtime');\n const startupFailure = new StartupFailureHandler(\n logger,\n (message) => { process.stderr.write(message); },\n (code) => electronApp.exit(code),\n );\n const debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\n const rendererHealth = new Map<string, RendererHealthState>();\n let mainWindow: ElectronBrowserWindow | null = null;\n let beforeQuitCleanupComplete = false;\n let beforeQuitCleanupPromise: Promise<void> | null = null;\n let runtimeContext: WoodMainRuntimeContext | null = null;\n let mainBootCleanup: CleanupHandler | undefined;\n let runtimeCleanup: CleanupHandler | undefined;\n let nativeQuitFallbackInstalled = false;\n const rendererTraceDrain = new RendererTraceDrainCoordinator(ipcMain);\n rendererTraceDrain.start();\n\n for (const stream of [process.stdout, process.stderr]) {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n return;\n }\n throw error;\n });\n }\n\n if (debugConfig.enabled && debugConfig.port) {\n electronApp.commandLine.appendSwitch('remote-debugging-port', String(debugConfig.port));\n electronApp.commandLine.appendSwitch('remote-debugging-address', '127.0.0.1');\n logger.info('devtools remote debugging enabled', {\n port: debugConfig.port,\n mode: debugConfig.mode,\n });\n }\n\n const bootContext: WoodMainBootContext = {\n electron: {\n app: electronApp,\n BrowserWindow,\n ipcMain,\n protocol,\n net,\n shell,\n screen,\n Menu,\n },\n logger,\n };\n\n const initializeRuntime = async (): Promise<void> => {\n const appBootResult = options.appBoot\n ? await options.appBoot(bootContext)\n : undefined;\n\n const bootOverrides = (appBootResult ?? {}) as WoodMainAppBootResult;\n const configureContainer = bootOverrides.configureContainer ?? options.configureContainer;\n const privilegedSchemes = Array.isArray(bootOverrides.registerSchemesAsPrivileged)\n ? bootOverrides.registerSchemesAsPrivileged\n : [];\n\n if (privilegedSchemes.length > 0) {\n protocol.registerSchemesAsPrivileged(privilegedSchemes);\n logger.info('registered privileged schemes', {\n count: privilegedSchemes.length,\n schemes: privilegedSchemes.map((entry) => entry.scheme),\n });\n }\n\n const outDir = options.outDir ?? 'out';\n const preloadPath = path.join(electronApp.getAppPath(), outDir, 'preload', 'index.js');\n const rendererFile = path.join(electronApp.getAppPath(), outDir, 'renderer', 'index.html');\n\n const rendererLogDispatcher = new RendererLogDispatcher();\n let rendererConfigExtension: RendererConfigExtension | null = null;\n const container = createWoodRoot({\n ...(configureContainer ? { configure: configureContainer } : {}),\n });\n\n const registrationContainer = container as {\n registerFunction?: (\n token: any,\n resolver: (...args: any[]) => unknown,\n options?: Record<string, unknown>,\n ) => void;\n };\n\n if (typeof registrationContainer.registerFunction === 'function') {\n registrationContainer.registerFunction(Ipc, () => new Ipc({ send: dispatchPushEvent }));\n }\n\n const getWindow = (windowId: string): ElectronBrowserWindow | null => {\n const existing = windows.get(windowId);\n if (!existing) {\n return null;\n }\n if (existing.isDestroyed()) {\n windows.delete(windowId);\n return null;\n }\n return existing;\n };\n\n const findWindowIdByWebContents = (webContentsId: number): string | null => {\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n if (win.webContents.id === webContentsId) {\n return windowId;\n }\n }\n return null;\n };\n\n const getRendererHealthState = (windowId: string): RendererHealthState | null => {\n return rendererHealth.get(windowId) ?? null;\n };\n\n const notifyRendererHealthChanged = (event: WoodRendererHealthEvent): void => {\n if (!options.onRendererHealthChanged) {\n return;\n }\n if (!runtimeContext) {\n runtimeLogger.warn('renderer health hook skipped before runtime context', {\n windowId: event.windowId,\n state: event.state,\n });\n return;\n }\n\n Promise.resolve(options.onRendererHealthChanged(event, runtimeContext)).catch((error) => {\n runtimeLogger.warn('renderer health hook failed', {\n windowId: event.windowId,\n state: event.state,\n error: toErrorMessage(error),\n });\n });\n };\n\n const setRendererHealth = (\n windowId: string,\n state: RendererHealthState,\n details: {\n webContentsId?: number;\n reason?: string;\n exitCode?: number;\n } = {},\n ): void => {\n const previousState = rendererHealth.get(windowId) ?? null;\n rendererHealth.set(windowId, state);\n notifyRendererHealthChanged({\n windowId,\n state,\n previousState,\n ...details,\n });\n };\n\n const attachRendererHealth = (windowId: string, win: ElectronBrowserWindow): void => {\n const healthLogger = getLogger('wood:main').named(`renderer-health:${windowId}`);\n const webContentsId = win.webContents.id;\n\n setRendererHealth(windowId, 'loading', { webContentsId });\n\n win.webContents.on('did-start-loading', () => {\n setRendererHealth(windowId, 'loading', { webContentsId });\n healthLogger.info('renderer loading', { windowId, webContentsId });\n });\n\n win.webContents.on('did-finish-load', () => {\n setRendererHealth(windowId, 'ready', { webContentsId });\n healthLogger.info('renderer ready', { windowId, webContentsId });\n });\n\n win.webContents.on('unresponsive', () => {\n setRendererHealth(windowId, 'unresponsive', { webContentsId });\n healthLogger.error('renderer unresponsive', { windowId, webContentsId });\n });\n\n win.webContents.on('responsive', () => {\n setRendererHealth(windowId, 'ready', { webContentsId });\n healthLogger.info('renderer responsive', { windowId, webContentsId });\n });\n\n win.webContents.on('render-process-gone', (_event, details: RenderProcessGoneDetails) => {\n setRendererHealth(windowId, 'gone', {\n webContentsId,\n reason: details.reason,\n exitCode: details.exitCode,\n });\n healthLogger.error('renderer process gone', {\n windowId,\n webContentsId,\n reason: details.reason,\n exitCode: details.exitCode,\n });\n });\n\n win.webContents.on('destroyed', () => {\n setRendererHealth(windowId, 'destroyed');\n healthLogger.warn('renderer destroyed', { windowId, webContentsId });\n });\n\n win.on('closed', () => {\n setRendererHealth(windowId, 'destroyed');\n rendererHealth.delete(windowId);\n });\n };\n\n const canSendToWindow = (win: ElectronBrowserWindow): boolean => {\n if (win.isDestroyed()) {\n return false;\n }\n if (win.webContents.isDestroyed()) {\n return false;\n }\n if (typeof win.webContents.isCrashed === 'function' && win.webContents.isCrashed()) {\n return false;\n }\n return true;\n };\n\n const safeSend = (\n windowId: string,\n win: ElectronBrowserWindow,\n channel: string,\n data: unknown,\n ): boolean => {\n if (!canSendToWindow(win)) {\n runtimeLogger.warn('skipping send to unhealthy renderer', {\n windowId,\n channel,\n rendererHealth: getRendererHealthState(windowId),\n });\n return false;\n }\n\n try {\n win.webContents.send(channel, data);\n return true;\n } catch (error) {\n runtimeLogger.warn('send to renderer failed', {\n windowId,\n channel,\n rendererHealth: getRendererHealthState(windowId),\n error: toErrorMessage(error),\n });\n return false;\n }\n };\n\n const installNativeQuitFallback = (): void => {\n if (nativeQuitFallbackInstalled) {\n return;\n }\n Menu.setApplicationMenu(Menu.buildFromTemplate([\n {\n label: electronApp.name,\n submenu: [\n { role: 'quit' },\n ],\n },\n ]));\n nativeQuitFallbackInstalled = true;\n logger.info('native quit fallback installed');\n };\n\n const waitForWindowReady = async (win: ElectronBrowserWindow): Promise<void> => {\n const webContents = win.webContents as ElectronBrowserWindow['webContents'] & {\n isLoadingMainFrame?: () => boolean;\n };\n const isLoading = typeof webContents.isLoadingMainFrame === 'function'\n ? webContents.isLoadingMainFrame()\n : webContents.isLoading();\n if (!isLoading) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n webContents.once('did-finish-load', () => resolve());\n });\n };\n\n const getDebugMetadata = (): WoodDebugMetadata => {\n const debugWindows: WoodDebugWindowMetadata[] = [];\n\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n\n debugWindows.push({\n windowId,\n title: win.getTitle(),\n webContentsId: win.webContents.id,\n url: win.webContents.getURL(),\n focused: win.isFocused(),\n rendererHealth: getRendererHealthState(windowId) ?? undefined,\n });\n }\n\n return {\n ok: true,\n enabled: debugConfig.enabled,\n mode: debugConfig.mode,\n ...(debugConfig.port ? { port: debugConfig.port } : {}),\n windows: debugWindows,\n };\n };\n\n const createWindow = (windowId = 'main'): ElectronBrowserWindow => {\n const existing = getWindow(windowId);\n if (existing) {\n existing.show();\n existing.focus();\n return existing;\n }\n\n const windowDef = resolveWindowDef(options.windowDefs, windowId);\n const defaultShow = windowDef.show ?? true;\n const parentWindow = windowDef.parent ? getWindow(windowDef.parent) : null;\n\n if (windowDef.nativeQuitFallback === true) {\n installNativeQuitFallback();\n }\n\n windowState.setScreen(screen);\n const savedBounds = windowDef.rememberBounds ? windowState.getBounds(windowId) : null;\n\n const windowOptions: BrowserWindowConstructorOptions = {\n width: savedBounds?.width ?? windowDef.width,\n height: savedBounds?.height ?? windowDef.height,\n ...(savedBounds ? { x: savedBounds.x, y: savedBounds.y } : {}),\n ...(windowDef.minWidth != null ? { minWidth: windowDef.minWidth } : {}),\n ...(windowDef.minHeight != null ? { minHeight: windowDef.minHeight } : {}),\n ...(windowDef.maxWidth != null ? { maxWidth: windowDef.maxWidth } : {}),\n ...(windowDef.maxHeight != null ? { maxHeight: windowDef.maxHeight } : {}),\n ...(typeof windowDef.modal === 'boolean' ? { modal: windowDef.modal } : {}),\n ...(parentWindow ? { parent: parentWindow } : {}),\n title: windowDef.title,\n frame: windowDef.frame ?? true,\n transparent: windowDef.transparent ?? false,\n resizable: windowDef.resizable ?? true,\n alwaysOnTop: windowDef.alwaysOnTop ?? false,\n ...(typeof windowDef.backgroundColor === 'string' ? { backgroundColor: windowDef.backgroundColor } : {}),\n ...(process.platform === 'darwin' && typeof windowDef.vibrancy === 'string'\n ? {\n vibrancy: windowDef.vibrancy as BrowserWindowConstructorOptions['vibrancy'],\n visualEffectState: 'active' as const,\n }\n : {}),\n ...(process.platform === 'win32' && typeof windowDef.backgroundMaterial === 'string'\n ? {\n backgroundMaterial: windowDef.backgroundMaterial as BrowserWindowConstructorOptions['backgroundMaterial'],\n }\n : {}),\n ...(typeof windowDef.acceptFirstMouse === 'boolean' ? { acceptFirstMouse: windowDef.acceptFirstMouse } : {}),\n // Hidden title bar with native window controls: macOS draws the\n // traffic lights (positionable), Windows draws min/max/close via the\n // Window Controls Overlay (always top-right; styled via titleBarOverlay).\n ...(typeof windowDef.titleBarStyle === 'string'\n ? { titleBarStyle: windowDef.titleBarStyle as BrowserWindowConstructorOptions['titleBarStyle'] }\n : {}),\n ...(process.platform === 'darwin' && windowDef.trafficLightPosition\n ? { trafficLightPosition: windowDef.trafficLightPosition }\n : {}),\n ...(process.platform === 'win32' && windowDef.titleBarOverlay != null\n ? { titleBarOverlay: windowDef.titleBarOverlay }\n : {}),\n show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n // Packaged builds never expose DevTools: this disables the default\n // menu's \"Toggle Developer Tools\" shortcut (Cmd/Ctrl+Alt+I, F12)\n // and any openDevTools() call for this window.\n devTools: !electronApp.isPackaged,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(windowId, win);\n attachRendererHealth(windowId, win);\n if (windowId === 'main') {\n mainWindow = win;\n }\n\n if (savedBounds?.isMaximized) {\n win.maximize();\n } else if (savedBounds?.isFullScreen) {\n win.setFullScreen(true);\n }\n\n if (savedBounds && defaultShow) {\n win.show();\n }\n\n if (windowDef.rememberBounds) {\n const captureNormalBounds = () => {\n if (!win.isMaximized() && !win.isFullScreen()) {\n const rect = win.getBounds();\n windowState.updateBoundsDebounced(windowId, {\n ...rect,\n isMaximized: false,\n isFullScreen: false,\n });\n }\n };\n win.on('move', captureNormalBounds);\n win.on('resize', captureNormalBounds);\n win.on('close', () => {\n const rect = win.getBounds();\n windowState.saveBoundsSync(windowId, {\n ...rect,\n isMaximized: win.isMaximized(),\n isFullScreen: win.isFullScreen(),\n });\n });\n }\n\n win.webContents.setWindowOpenHandler(({ url }) => {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n void shell.openExternal(url);\n }\n return { action: 'deny' };\n });\n\n win.webContents.on('will-navigate', (event, url) => {\n if (process.env.ELECTRON_RENDERER_URL && url.startsWith(process.env.ELECTRON_RENDERER_URL)) {\n return;\n }\n if (url.startsWith('http://') || url.startsWith('https://')) {\n event.preventDefault();\n void shell.openExternal(url);\n }\n });\n\n const rendererLogger = getLogger('wood:main').named(`renderer:${windowId}`);\n const levelMap: Record<number, 'debug' | 'info' | 'warn' | 'error'> = {\n 0: 'debug',\n 1: 'info',\n 2: 'warn',\n 3: 'error',\n };\n\n win.webContents.on('console-message', (_event, level, message, line, sourceId) => {\n const method = levelMap[level] ?? 'info';\n rendererLogger[method](message, { source: sourceId, line, windowId });\n });\n\n if (process.env.ELECTRON_RENDERER_URL) {\n void win.loadURL(process.env.ELECTRON_RENDERER_URL);\n } else {\n void win.loadFile(rendererFile);\n }\n\n if (options.traceTransport === 'messageport' && options.rendererTraceEnabled === true) {\n const { port1, port2 } = new MessageChannelMain();\n\n const portHandler = (event: Electron.MessageEvent) => {\n dispatchRendererTracePayload(event.data, {\n windowId,\n webContentsId: win.webContents.id,\n });\n };\n port1.on('message', portHandler);\n port1.start();\n\n win.webContents.once('did-finish-load', () => {\n if (!win.isDestroyed()) {\n win.webContents.postMessage('__trace-port', null, [port2]);\n logger.info('trace messageport sent to renderer', { windowId });\n }\n });\n\n win.on('closed', () => {\n port1.off('message', portHandler);\n port1.close();\n });\n }\n\n win.on('closed', () => {\n windows.delete(windowId);\n rendererHealth.delete(windowId);\n if (windowId === 'main') {\n mainWindow = null;\n }\n if (windows.size === 0) {\n electronApp.quit();\n }\n });\n\n if (options.onWindowCreated) {\n Promise.resolve(options.onWindowCreated(windowId, win, runtimeContext!)).catch((error) => {\n runtimeLogger.warn('window created hook failed', {\n windowId,\n error: toErrorMessage(error),\n });\n });\n }\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const bootstrapActivationGate = new BootstrapActivationGate(\n () => BrowserWindow.getAllWindows().length > 0,\n () => {\n createWindow('main');\n },\n );\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n safeSend(windowId, win, '__event:__navigate', { route });\n win.show();\n win.focus();\n };\n\n const dispatchPushEvent = (\n channel: string,\n data: unknown,\n options?: IpcPushOptions,\n ): void => {\n const eventChannel = channel.startsWith('__event:')\n ? channel\n : `__event:${channel}`;\n\n if (options?.windowId) {\n const target = getWindow(options.windowId);\n if (target) {\n safeSend(options.windowId, target, eventChannel, data);\n }\n return;\n }\n\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n safeSend(windowId, win, eventChannel, data);\n }\n };\n\n if (options.legacyGlobals) setIpcPushSender(dispatchPushEvent);\n\n const broadcast = (channel: string, data: unknown): void => {\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n safeSend(windowId, win, channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n getRendererHealthState,\n setRendererLogCallback: (callback) => {\n rendererLogDispatcher.setCallback(callback);\n },\n getRendererLogCallback: () => rendererLogDispatcher.getCallback(),\n setRendererLogRecordFilter,\n setRendererConfigExtension: (extension) => { rendererConfigExtension = extension; },\n getDebugMetadata,\n // Late-bound after bootRuntime constructs the router (see bootstrap).\n dispatch: async () => {\n throw new Error('WoodMainRuntimeContext.dispatch called before the runtime router was constructed.');\n },\n };\n\n const bootstrap = async () => {\n logger.info('bootstrap starting');\n const applicationRoot = setApplicationRoot(path.join(electronApp.getPath('userData'), 'wood'));\n runtimeContext!.applicationRoot = applicationRoot;\n logger.info('application root configured', { applicationRoot });\n\n if (options.mainBoot) {\n const bootResult = await options.mainBoot(appBootResult, runtimeContext!);\n if (typeof bootResult === 'function') {\n mainBootCleanup = bootResult;\n }\n }\n\n const { router: runtimeRouter, dispose: closeRuntime } = await bootRuntime({\n container,\n ipcMain,\n registerRuntime: options.registerRuntime,\n rendererTraceBatchReceiver: options.rendererTraceBatchReceiver,\n legacyGlobals: options.legacyGlobals,\n rendererLogDispatcher,\n rendererConfigExtension: () => rendererConfigExtension?.() ?? {},\n controllersDir: resolveRuntimePath(options.controllersDir),\n middlewareDir: resolveRuntimePath(options.middlewareDir),\n ...(options.traceMinLevel ? { traceMinLevel: options.traceMinLevel } : {}),\n ...(typeof options.rendererTraceEnabled === 'boolean'\n ? { rendererTraceEnabled: options.rendererTraceEnabled }\n : {}),\n ...(options.rendererTraceMinLevel\n ? { rendererTraceMinLevel: options.rendererTraceMinLevel }\n : {}),\n ...(options.traceTransport === 'messageport' ? { enableRendererTraceBridge: false } : {}),\n onLoad: options.onLoad ?? (async () => {\n throw new Error('Reserved channel \"__load\" is not configured for this app.');\n }),\n onNavigate: async (rawData) => {\n const data = rawData as { windowId?: string; route?: string };\n if (!data.windowId) {\n throw new Error('__navigate requires windowId');\n }\n if (!data.route) {\n throw new Error('__navigate requires route');\n }\n await navigateWindow(data.windowId, data.route);\n return { ok: true };\n },\n onWindow: async (rawData, event) => {\n const data = rawData as { action?: string; windowId?: string };\n const action = data.action ?? '';\n const windowId = data.windowId;\n\n if (action === 'current') {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const senderWindowId = ipcEvent?.sender ? findWindowIdByWebContents(ipcEvent.sender.id) : null;\n if (!senderWindowId) {\n return { ok: false };\n }\n const senderWindowDef = resolveWindowDef(options.windowDefs, senderWindowId);\n return {\n ok: true,\n windowId: senderWindowId,\n defaultRoute: senderWindowDef.defaultRoute,\n };\n }\n\n if (action === 'open') {\n if (!windowId) {\n throw new Error('__window.open requires windowId');\n }\n createWindow(windowId);\n return { ok: true };\n }\n\n const win = windowId ? getWindow(windowId) : mainWindow;\n if (!win) {\n return { ok: false };\n }\n\n switch (action) {\n case 'close':\n win.close();\n return { ok: true };\n case 'focus':\n win.show();\n win.focus();\n return { ok: true };\n case 'minimize':\n win.minimize();\n return { ok: true };\n case 'maximize':\n win.isMaximized() ? win.unmaximize() : win.maximize();\n return { ok: true };\n default:\n return { ok: false };\n }\n },\n onDebug: async (rawData) => {\n const data = rawData as { action?: string } | undefined;\n const action = data?.action ?? '';\n\n if (action === 'current') {\n return getDebugMetadata();\n }\n\n return { ok: false };\n },\n onContextMenu: options.onContextMenu ?? (async (_rawData: unknown, event?: unknown) => {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const senderId = ipcEvent?.sender?.id;\n runtimeLogger.debug('context menu requested', { senderId });\n return { ok: true };\n }),\n resolveRendererTraceContext: (event) => {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const webContentsId = ipcEvent?.sender?.id;\n if (typeof webContentsId !== 'number') {\n return undefined;\n }\n return {\n webContentsId,\n windowId: findWindowIdByWebContents(webContentsId) ?? undefined,\n };\n },\n });\n\n runtimeContext!.dispatch = (channel: string, payload?: unknown) =>\n runtimeRouter.dispatch(channel, payload ?? {});\n runtimeCleanup = closeRuntime;\n\n bootstrapActivationGate.ready();\n logger.info('bootstrap complete');\n };\n\n const handleBootstrapFailure = async (error: unknown): Promise<void> => {\n bootstrapActivationGate.failed();\n await startupFailure.handle('bootstrap failed', error, async () => {\n rendererTraceDrain.stop();\n await cleanupAll([() => runtimeCleanup?.(), () => mainBootCleanup?.(), () => container.dispose()], 'Wood bootstrap rollback failed');\n });\n };\n void electronApp.whenReady().then(bootstrap).catch(handleBootstrapFailure);\n\n const registerApplicationLifecycle = (): void => {\n electronApp.on(\n 'browser-window-created',\n (_event: ElectronEvent, window: ElectronBrowserWindow) => {\n window.on('close', (event) => {\n const isTrackedWindow = [...windows.values()].includes(window);\n if (!isTrackedWindow\n || !shouldDeferLastWindowCloseForCleanup(\n windows.size,\n beforeQuitCleanupComplete,\n )) {\n return;\n }\n event.preventDefault();\n if (!beforeQuitCleanupPromise) {\n logger.debug('deferring last window close for renderer trace drain');\n electronApp.quit();\n }\n });\n },\n );\n\n const destroyWindowsForQuit = (): void => {\n for (const [id, win] of windows) {\n if (!win.isDestroyed()) {\n win.destroy();\n logger.debug('destroyed window on quit', { windowId: id });\n }\n }\n windows.clear();\n };\n\n const runBeforeQuitCleanup = async (): Promise<void> => {\n await cleanupAll([\n () => drainRendererTracesBeforeQuit(rendererTraceDrain, windows.values(), logger, Date.now() + 1_000),\n () => runtimeCleanup?.(),\n () => { if (options.legacyGlobals) clearIpcPushSender(); },\n () => mainBootCleanup?.(),\n () => options.onBeforeQuit?.(runtimeContext!),\n () => ExecutionContext.run(container, () => container.dispose()),\n ], 'Wood main shutdown failed');\n };\n\n electronApp.on('before-quit', (event: ElectronEvent) => {\n if (!runtimeContext) {\n return;\n }\n\n if (beforeQuitCleanupComplete) {\n destroyWindowsForQuit();\n return;\n }\n\n event.preventDefault();\n if (beforeQuitCleanupPromise) {\n return;\n }\n\n beforeQuitCleanupPromise = (async () => {\n try {\n await runBeforeQuitCleanup();\n } catch (error) {\n logger.warn('before-quit handler failed', { error: toErrorMessage(error) });\n } finally {\n beforeQuitCleanupComplete = true;\n beforeQuitCleanupPromise = null;\n }\n\n destroyWindowsForQuit();\n electronApp.quit();\n })();\n });\n\n electronApp.on('window-all-closed', () => {\n electronApp.quit();\n });\n\n electronApp.on('activate', () => {\n bootstrapActivationGate.activate();\n });\n };\n\n registerApplicationLifecycle();\n };\n\n void initializeRuntime().catch((error) => startupFailure.handle(\n 'startup failed before ready',\n error,\n async () => { rendererTraceDrain.stop(); },\n ));\n}\n"],"mappings":"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","names":["setRendererLogRecordFilter","setRendererConfigExtension","options"]}
|
|
1
|
+
{"version":3,"sources":["../../src/main/index.ts"],"sourcesContent":["import path from 'path';\nimport { StartupFailureHandler } from './startup_failure_handler.js';\nimport {\n app as electronApp,\n BrowserWindow,\n ipcMain,\n MessageChannelMain,\n Menu,\n protocol,\n net,\n shell,\n screen,\n} from 'electron';\nimport type {\n BrowserWindowConstructorOptions,\n BrowserWindow as ElectronBrowserWindow,\n Event as ElectronEvent,\n IpcMainInvokeEvent,\n Privileges,\n RenderProcessGoneDetails,\n} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport { cleanupAll } from '../controller/cleanup.js';\nimport { ExecutionContext } from '@noego/ioc';\nimport { runtime as bootRuntime } from '../runtime/runtime.js';\nimport { dispatchRendererTracePayload, type TraceLevel, type WoodTraceBatchReceiver } from '../tracing/index.js';\nimport { setApplicationRoot } from '../runtime/application_root.js';\nimport type { RegisterRuntimeRouter } from '../runtime/runtime.js';\nimport {\n getRendererLogDispatcher,\n RendererLogDispatcher,\n type RendererLogCallback,\n} from '../runtime/renderer_log_dispatcher.js';\nimport {\n setRendererLogRecordFilter,\n type RendererLogRecordFilter,\n} from '../runtime/renderer_log_batch_receiver.js';\nexport {\n setRendererLogRecordFilter,\n type RendererLogRecordFilter,\n} from '../runtime/renderer_log_batch_receiver.js';\nimport {\n setRendererConfigExtension,\n type RendererConfigExtension,\n} from '../runtime/renderer_config_extension.js';\nexport {\n setRendererConfigExtension,\n type RendererConfigExtension,\n} from '../runtime/renderer_config_extension.js';\nimport { Ipc, getIpc } from '../ipc/index.js';\nimport { setIpcPushSender, clearIpcPushSender, type IpcPushOptions } from '../ipc/shared.js';\nimport {\n drainRendererTracesBeforeQuit,\n RendererTraceDrainCoordinator,\n} from './renderer_trace_drain_coordinator.js';\nimport {\n RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS,\n ShutdownCoordinator,\n shouldDeferLastWindowCloseForCleanup,\n} from './shutdown_coordinator.js';\nexport {\n RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS,\n ShutdownCoordinator,\n shouldDeferLastWindowCloseForCleanup,\n type ShutdownCoordinatorDependencies,\n type ShutdownPhase,\n type ShutdownWindow,\n} from './shutdown_coordinator.js';\n\ntype CleanupHandler = () => void | Promise<void>;\n\nexport type BootstrapActivationState = 'pending' | 'ready' | 'failed';\n\n/**\n * Owns the startup/activation boundary so Electron activation cannot create a\n * renderer until bootstrap has registered the runtime IPC handlers.\n */\nexport class BootstrapActivationGate {\n private currentState: BootstrapActivationState = 'pending';\n\n constructor(\n private readonly hasWindow: () => boolean,\n private readonly createMainWindow: () => void,\n ) {}\n\n get state(): BootstrapActivationState {\n return this.currentState;\n }\n\n activate(): void {\n if (this.currentState === 'ready' && !this.hasWindow()) {\n this.createMainWindow();\n }\n }\n\n ready(): void {\n if (this.currentState !== 'pending') {\n return;\n }\n\n // createMainWindow may throw. Leave the gate pending so the bootstrap\n // rejection can make the failure terminal rather than exposing readiness.\n this.createMainWindow();\n this.currentState = 'ready';\n }\n\n failed(): void {\n if (this.currentState === 'pending') {\n this.currentState = 'failed';\n }\n }\n}\n\nexport type RendererHealthState =\n | 'ready'\n | 'loading'\n | 'unresponsive'\n | 'gone'\n | 'destroyed';\n\nexport type WoodWindowConfig = {\n title: string;\n width: number;\n height: number;\n defaultRoute?: string;\n frame?: boolean;\n transparent?: boolean;\n resizable?: boolean;\n alwaysOnTop?: boolean;\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n show?: boolean;\n parent?: string;\n modal?: boolean;\n vibrancy?: string;\n backgroundMaterial?: string;\n backgroundColor?: string;\n rememberBounds?: boolean;\n contextMenu?: 'native' | 'custom';\n acceptFirstMouse?: boolean;\n nativeQuitFallback?: boolean;\n titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';\n trafficLightPosition?: { x: number; y: number };\n titleBarOverlay?: boolean | { color?: string; symbolColor?: string; height?: number };\n};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\n /** @deprecated Ignored since 0.6.0: each main runtime owns a fresh root. Use configureContainer. */\n scope?: string;\n configureContainer?: (container: any) => void;\n registerSchemesAsPrivileged?: PrivilegedSchemeRegistration[];\n}\n\nexport interface WoodMainBootContext {\n electron: {\n app: typeof electronApp;\n BrowserWindow: typeof BrowserWindow;\n ipcMain: typeof ipcMain;\n protocol: typeof protocol;\n net: typeof net;\n shell: typeof shell;\n screen: typeof screen;\n Menu: typeof Menu;\n };\n logger: ReturnType<typeof getLogger>;\n}\n\nexport interface WoodMainRuntimeContext extends WoodMainBootContext {\n container: any;\n applicationRoot: string;\n getWindow: (windowId: string) => ElectronBrowserWindow | null;\n createWindow: (windowId?: string) => ElectronBrowserWindow;\n navigateWindow: (windowId: string, route: string) => Promise<void>;\n broadcast: (channel: string, data: unknown) => void;\n /**\n * Dispatch a channel through the SAME real WoodRouter the ipcMain handlers\n * wrap — full operations-YAML schema validation, controllers, middleware.\n * Available only after the runtime finishes booting (mainBoot runs before the\n * router is constructed, so callers must invoke this lazily, not during\n * mainBoot). This is the in-main seam the launch-flag-gated test control plane\n * uses for driver `api.call`; production code never calls it.\n */\n dispatch: (channel: string, payload?: unknown) => Promise<unknown>;\n getRendererHealthState: (windowId: string) => RendererHealthState | null;\n setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n /** Filters renderer log records received via the batched __log_batch channel. */\n setRendererLogRecordFilter: (filter: RendererLogRecordFilter | null) => void;\n /**\n * Adds app fields to the `__woodConfig` payload renderers fetch at boot\n * (e.g. an external log sink's port + per-boot token). Wood's own keys win.\n */\n setRendererConfigExtension: (extension: RendererConfigExtension | null) => void;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface WoodRendererHealthEvent {\n windowId: string;\n state: RendererHealthState;\n previousState: RendererHealthState | null;\n webContentsId?: number;\n reason?: string;\n exitCode?: number;\n}\n\nexport type WoodWindowLifecycleContext = WoodMainRuntimeContext;\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\n /** @deprecated Ignored since 0.6.0: each main runtime owns a fresh root. Use configureContainer. */\n scope?: string;\n configureContainer?: (container: any) => void;\n appBoot?: (\n context: WoodMainBootContext,\n ) => void | WoodMainAppBootResult | Promise<void | WoodMainAppBootResult>;\n mainBoot?: (\n appBootResult: unknown,\n context: WoodMainRuntimeContext,\n ) => void | CleanupHandler | Promise<void | CleanupHandler>;\n onBeforeQuit?: (context: WoodMainRuntimeContext) => void | Promise<void>;\n onWindowCreated?: (\n windowId: string,\n win: ElectronBrowserWindow,\n context: WoodMainRuntimeContext,\n ) => void | Promise<void>;\n onRendererHealthChanged?: (\n event: WoodRendererHealthEvent,\n context: WoodMainRuntimeContext,\n ) => void | Promise<void>;\n onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;\n onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;\n traceMinLevel?: TraceLevel;\n rendererTraceEnabled?: boolean;\n rendererTraceMinLevel?: TraceLevel;\n /** Explicit environment-owned durable trace receiver. */\n rendererTraceBatchReceiver?: WoodTraceBatchReceiver;\n /** Opt into process-global observation and push adapters during migration. */\n legacyGlobals?: boolean;\n traceTransport?: 'ipc' | 'messageport';\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: RendererHealthState;\n}\n\nexport interface WoodDebugMetadata {\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: WoodDebugWindowMetadata[];\n}\n\nconst DEFAULT_WOOD_REMOTE_DEBUGGING_PORT = 9333;\n\nexport function resolveWoodDebugMetadataConfiguration(env: NodeJS.ProcessEnv = process.env): {\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n} {\n const isDevelopment = env.WOOD_MODE === 'development';\n if (!isDevelopment) {\n return {\n enabled: false,\n mode: 'production',\n };\n }\n\n const rawPort = env.WOOD_REMOTE_DEBUGGING_PORT;\n const parsedPort = rawPort ? Number.parseInt(rawPort, 10) : DEFAULT_WOOD_REMOTE_DEBUGGING_PORT;\n const port = Number.isFinite(parsedPort) && parsedPort > 0\n ? parsedPort\n : DEFAULT_WOOD_REMOTE_DEBUGGING_PORT;\n\n return {\n enabled: true,\n mode: 'development',\n port,\n };\n}\n\nfunction resolveWindowDef(\n windowDefs: Record<string, WoodWindowConfig>,\n windowId = 'main',\n): WoodWindowConfig {\n const def = windowDefs[windowId];\n if (!def) {\n throw new Error(`Window \"${windowId}\" not defined in generated window defs.`);\n }\n return def;\n}\n\nfunction resolveRuntimePath(relativeOrAbsolutePath: string): string {\n if (path.isAbsolute(relativeOrAbsolutePath)) {\n return relativeOrAbsolutePath;\n }\n return path.join(electronApp.getAppPath(), relativeOrAbsolutePath);\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function startWoodMain(options: StartWoodMainOptions): void {\n const logger = getLogger('wood:main').named('bootstrap');\n const runtimeLogger = getLogger('wood:main').named('runtime');\n const startupFailure = new StartupFailureHandler(\n logger,\n (message) => { process.stderr.write(message); },\n (code) => electronApp.exit(code),\n );\n const debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\n const rendererHealth = new Map<string, RendererHealthState>();\n let mainWindow: ElectronBrowserWindow | null = null;\n let shutdownCoordinator: ShutdownCoordinator | null = null;\n let runtimeContext: WoodMainRuntimeContext | null = null;\n let mainBootCleanup: CleanupHandler | undefined;\n let runtimeCleanup: CleanupHandler | undefined;\n let nativeQuitFallbackInstalled = false;\n const rendererTraceDrain = new RendererTraceDrainCoordinator(ipcMain);\n rendererTraceDrain.start();\n\n for (const stream of [process.stdout, process.stderr]) {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n return;\n }\n throw error;\n });\n }\n\n if (debugConfig.enabled && debugConfig.port) {\n electronApp.commandLine.appendSwitch('remote-debugging-port', String(debugConfig.port));\n electronApp.commandLine.appendSwitch('remote-debugging-address', '127.0.0.1');\n logger.info('devtools remote debugging enabled', {\n port: debugConfig.port,\n mode: debugConfig.mode,\n });\n }\n\n const bootContext: WoodMainBootContext = {\n electron: {\n app: electronApp,\n BrowserWindow,\n ipcMain,\n protocol,\n net,\n shell,\n screen,\n Menu,\n },\n logger,\n };\n\n const initializeRuntime = async (): Promise<void> => {\n const appBootResult = options.appBoot\n ? await options.appBoot(bootContext)\n : undefined;\n\n const bootOverrides = (appBootResult ?? {}) as WoodMainAppBootResult;\n const configureContainer = bootOverrides.configureContainer ?? options.configureContainer;\n const privilegedSchemes = Array.isArray(bootOverrides.registerSchemesAsPrivileged)\n ? bootOverrides.registerSchemesAsPrivileged\n : [];\n\n if (privilegedSchemes.length > 0) {\n protocol.registerSchemesAsPrivileged(privilegedSchemes);\n logger.info('registered privileged schemes', {\n count: privilegedSchemes.length,\n schemes: privilegedSchemes.map((entry) => entry.scheme),\n });\n }\n\n const outDir = options.outDir ?? 'out';\n const preloadPath = path.join(electronApp.getAppPath(), outDir, 'preload', 'index.js');\n const rendererFile = path.join(electronApp.getAppPath(), outDir, 'renderer', 'index.html');\n\n const rendererLogDispatcher = new RendererLogDispatcher();\n let rendererConfigExtension: RendererConfigExtension | null = null;\n const container = createWoodRoot({\n ...(configureContainer ? { configure: configureContainer } : {}),\n });\n\n const registrationContainer = container as {\n registerFunction?: (\n token: any,\n resolver: (...args: any[]) => unknown,\n options?: Record<string, unknown>,\n ) => void;\n };\n\n if (typeof registrationContainer.registerFunction === 'function') {\n registrationContainer.registerFunction(Ipc, () => new Ipc({ send: dispatchPushEvent }));\n }\n\n const getWindow = (windowId: string): ElectronBrowserWindow | null => {\n const existing = windows.get(windowId);\n if (!existing) {\n return null;\n }\n if (existing.isDestroyed()) {\n windows.delete(windowId);\n return null;\n }\n return existing;\n };\n\n const findWindowIdByWebContents = (webContentsId: number): string | null => {\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n if (win.webContents.id === webContentsId) {\n return windowId;\n }\n }\n return null;\n };\n\n const getRendererHealthState = (windowId: string): RendererHealthState | null => {\n return rendererHealth.get(windowId) ?? null;\n };\n\n const notifyRendererHealthChanged = (event: WoodRendererHealthEvent): void => {\n if (!options.onRendererHealthChanged) {\n return;\n }\n if (!runtimeContext) {\n runtimeLogger.warn('renderer health hook skipped before runtime context', {\n windowId: event.windowId,\n state: event.state,\n });\n return;\n }\n\n Promise.resolve(options.onRendererHealthChanged(event, runtimeContext)).catch((error) => {\n runtimeLogger.warn('renderer health hook failed', {\n windowId: event.windowId,\n state: event.state,\n error: toErrorMessage(error),\n });\n });\n };\n\n const setRendererHealth = (\n windowId: string,\n state: RendererHealthState,\n details: {\n webContentsId?: number;\n reason?: string;\n exitCode?: number;\n } = {},\n ): void => {\n const previousState = rendererHealth.get(windowId) ?? null;\n rendererHealth.set(windowId, state);\n notifyRendererHealthChanged({\n windowId,\n state,\n previousState,\n ...details,\n });\n };\n\n const attachRendererHealth = (windowId: string, win: ElectronBrowserWindow): void => {\n const healthLogger = getLogger('wood:main').named(`renderer-health:${windowId}`);\n const webContentsId = win.webContents.id;\n\n setRendererHealth(windowId, 'loading', { webContentsId });\n\n win.webContents.on('did-start-loading', () => {\n setRendererHealth(windowId, 'loading', { webContentsId });\n healthLogger.info('renderer loading', { windowId, webContentsId });\n });\n\n win.webContents.on('did-finish-load', () => {\n setRendererHealth(windowId, 'ready', { webContentsId });\n healthLogger.info('renderer ready', { windowId, webContentsId });\n });\n\n win.webContents.on('unresponsive', () => {\n setRendererHealth(windowId, 'unresponsive', { webContentsId });\n healthLogger.error('renderer unresponsive', { windowId, webContentsId });\n });\n\n win.webContents.on('responsive', () => {\n setRendererHealth(windowId, 'ready', { webContentsId });\n healthLogger.info('renderer responsive', { windowId, webContentsId });\n });\n\n win.webContents.on('render-process-gone', (_event, details: RenderProcessGoneDetails) => {\n setRendererHealth(windowId, 'gone', {\n webContentsId,\n reason: details.reason,\n exitCode: details.exitCode,\n });\n healthLogger.error('renderer process gone', {\n windowId,\n webContentsId,\n reason: details.reason,\n exitCode: details.exitCode,\n });\n });\n\n win.webContents.on('destroyed', () => {\n setRendererHealth(windowId, 'destroyed');\n healthLogger.warn('renderer destroyed', { windowId, webContentsId });\n });\n\n win.on('closed', () => {\n setRendererHealth(windowId, 'destroyed');\n rendererHealth.delete(windowId);\n });\n };\n\n const canSendToWindow = (win: ElectronBrowserWindow): boolean => {\n if (win.isDestroyed()) {\n return false;\n }\n if (win.webContents.isDestroyed()) {\n return false;\n }\n if (typeof win.webContents.isCrashed === 'function' && win.webContents.isCrashed()) {\n return false;\n }\n return true;\n };\n\n const safeSend = (\n windowId: string,\n win: ElectronBrowserWindow,\n channel: string,\n data: unknown,\n ): boolean => {\n if (!canSendToWindow(win)) {\n runtimeLogger.warn('skipping send to unhealthy renderer', {\n windowId,\n channel,\n rendererHealth: getRendererHealthState(windowId),\n });\n return false;\n }\n\n try {\n win.webContents.send(channel, data);\n return true;\n } catch (error) {\n runtimeLogger.warn('send to renderer failed', {\n windowId,\n channel,\n rendererHealth: getRendererHealthState(windowId),\n error: toErrorMessage(error),\n });\n return false;\n }\n };\n\n const installNativeQuitFallback = (): void => {\n if (nativeQuitFallbackInstalled) {\n return;\n }\n Menu.setApplicationMenu(Menu.buildFromTemplate([\n {\n label: electronApp.name,\n submenu: [\n { role: 'quit' },\n ],\n },\n ]));\n nativeQuitFallbackInstalled = true;\n logger.info('native quit fallback installed');\n };\n\n const waitForWindowReady = async (win: ElectronBrowserWindow): Promise<void> => {\n const webContents = win.webContents as ElectronBrowserWindow['webContents'] & {\n isLoadingMainFrame?: () => boolean;\n };\n const isLoading = typeof webContents.isLoadingMainFrame === 'function'\n ? webContents.isLoadingMainFrame()\n : webContents.isLoading();\n if (!isLoading) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n webContents.once('did-finish-load', () => resolve());\n });\n };\n\n const getDebugMetadata = (): WoodDebugMetadata => {\n const debugWindows: WoodDebugWindowMetadata[] = [];\n\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n\n debugWindows.push({\n windowId,\n title: win.getTitle(),\n webContentsId: win.webContents.id,\n url: win.webContents.getURL(),\n focused: win.isFocused(),\n rendererHealth: getRendererHealthState(windowId) ?? undefined,\n });\n }\n\n return {\n ok: true,\n enabled: debugConfig.enabled,\n mode: debugConfig.mode,\n ...(debugConfig.port ? { port: debugConfig.port } : {}),\n windows: debugWindows,\n };\n };\n\n const createWindow = (windowId = 'main'): ElectronBrowserWindow => {\n const existing = getWindow(windowId);\n if (existing) {\n existing.show();\n existing.focus();\n return existing;\n }\n\n const windowDef = resolveWindowDef(options.windowDefs, windowId);\n const defaultShow = windowDef.show ?? true;\n const parentWindow = windowDef.parent ? getWindow(windowDef.parent) : null;\n\n if (windowDef.nativeQuitFallback === true) {\n installNativeQuitFallback();\n }\n\n windowState.setScreen(screen);\n const savedBounds = windowDef.rememberBounds ? windowState.getBounds(windowId) : null;\n\n const windowOptions: BrowserWindowConstructorOptions = {\n width: savedBounds?.width ?? windowDef.width,\n height: savedBounds?.height ?? windowDef.height,\n ...(savedBounds ? { x: savedBounds.x, y: savedBounds.y } : {}),\n ...(windowDef.minWidth != null ? { minWidth: windowDef.minWidth } : {}),\n ...(windowDef.minHeight != null ? { minHeight: windowDef.minHeight } : {}),\n ...(windowDef.maxWidth != null ? { maxWidth: windowDef.maxWidth } : {}),\n ...(windowDef.maxHeight != null ? { maxHeight: windowDef.maxHeight } : {}),\n ...(typeof windowDef.modal === 'boolean' ? { modal: windowDef.modal } : {}),\n ...(parentWindow ? { parent: parentWindow } : {}),\n title: windowDef.title,\n frame: windowDef.frame ?? true,\n transparent: windowDef.transparent ?? false,\n resizable: windowDef.resizable ?? true,\n alwaysOnTop: windowDef.alwaysOnTop ?? false,\n ...(typeof windowDef.backgroundColor === 'string' ? { backgroundColor: windowDef.backgroundColor } : {}),\n ...(process.platform === 'darwin' && typeof windowDef.vibrancy === 'string'\n ? {\n vibrancy: windowDef.vibrancy as BrowserWindowConstructorOptions['vibrancy'],\n visualEffectState: 'active' as const,\n }\n : {}),\n ...(process.platform === 'win32' && typeof windowDef.backgroundMaterial === 'string'\n ? {\n backgroundMaterial: windowDef.backgroundMaterial as BrowserWindowConstructorOptions['backgroundMaterial'],\n }\n : {}),\n ...(typeof windowDef.acceptFirstMouse === 'boolean' ? { acceptFirstMouse: windowDef.acceptFirstMouse } : {}),\n // Hidden title bar with native window controls: macOS draws the\n // traffic lights (positionable), Windows draws min/max/close via the\n // Window Controls Overlay (always top-right; styled via titleBarOverlay).\n ...(typeof windowDef.titleBarStyle === 'string'\n ? { titleBarStyle: windowDef.titleBarStyle as BrowserWindowConstructorOptions['titleBarStyle'] }\n : {}),\n ...(process.platform === 'darwin' && windowDef.trafficLightPosition\n ? { trafficLightPosition: windowDef.trafficLightPosition }\n : {}),\n ...(process.platform === 'win32' && windowDef.titleBarOverlay != null\n ? { titleBarOverlay: windowDef.titleBarOverlay }\n : {}),\n show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n // Packaged builds never expose DevTools: this disables the default\n // menu's \"Toggle Developer Tools\" shortcut (Cmd/Ctrl+Alt+I, F12)\n // and any openDevTools() call for this window.\n devTools: !electronApp.isPackaged,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(windowId, win);\n attachRendererHealth(windowId, win);\n if (windowId === 'main') {\n mainWindow = win;\n }\n\n if (savedBounds?.isMaximized) {\n win.maximize();\n } else if (savedBounds?.isFullScreen) {\n win.setFullScreen(true);\n }\n\n if (savedBounds && defaultShow) {\n win.show();\n }\n\n if (windowDef.rememberBounds) {\n const captureNormalBounds = () => {\n if (!win.isMaximized() && !win.isFullScreen()) {\n const rect = win.getBounds();\n windowState.updateBoundsDebounced(windowId, {\n ...rect,\n isMaximized: false,\n isFullScreen: false,\n });\n }\n };\n win.on('move', captureNormalBounds);\n win.on('resize', captureNormalBounds);\n win.on('close', () => {\n const rect = win.getBounds();\n windowState.saveBoundsSync(windowId, {\n ...rect,\n isMaximized: win.isMaximized(),\n isFullScreen: win.isFullScreen(),\n });\n });\n }\n\n win.webContents.setWindowOpenHandler(({ url }) => {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n void shell.openExternal(url);\n }\n return { action: 'deny' };\n });\n\n win.webContents.on('will-navigate', (event, url) => {\n if (process.env.ELECTRON_RENDERER_URL && url.startsWith(process.env.ELECTRON_RENDERER_URL)) {\n return;\n }\n if (url.startsWith('http://') || url.startsWith('https://')) {\n event.preventDefault();\n void shell.openExternal(url);\n }\n });\n\n const rendererLogger = getLogger('wood:main').named(`renderer:${windowId}`);\n const levelMap: Record<number, 'debug' | 'info' | 'warn' | 'error'> = {\n 0: 'debug',\n 1: 'info',\n 2: 'warn',\n 3: 'error',\n };\n\n win.webContents.on('console-message', (_event, level, message, line, sourceId) => {\n const method = levelMap[level] ?? 'info';\n rendererLogger[method](message, { source: sourceId, line, windowId });\n });\n\n if (process.env.ELECTRON_RENDERER_URL) {\n void win.loadURL(process.env.ELECTRON_RENDERER_URL);\n } else {\n void win.loadFile(rendererFile);\n }\n\n if (options.traceTransport === 'messageport' && options.rendererTraceEnabled === true) {\n const { port1, port2 } = new MessageChannelMain();\n\n const portHandler = (event: Electron.MessageEvent) => {\n dispatchRendererTracePayload(event.data, {\n windowId,\n webContentsId: win.webContents.id,\n });\n };\n port1.on('message', portHandler);\n port1.start();\n\n win.webContents.once('did-finish-load', () => {\n if (!win.isDestroyed()) {\n win.webContents.postMessage('__trace-port', null, [port2]);\n logger.info('trace messageport sent to renderer', { windowId });\n }\n });\n\n win.on('closed', () => {\n port1.off('message', portHandler);\n port1.close();\n });\n }\n\n win.on('closed', () => {\n windows.delete(windowId);\n rendererHealth.delete(windowId);\n if (windowId === 'main') {\n mainWindow = null;\n }\n if (windows.size === 0) {\n if (shutdownCoordinator) {\n shutdownCoordinator.handleAllWindowsClosed();\n } else {\n electronApp.quit();\n }\n }\n });\n\n if (options.onWindowCreated) {\n Promise.resolve(options.onWindowCreated(windowId, win, runtimeContext!)).catch((error) => {\n runtimeLogger.warn('window created hook failed', {\n windowId,\n error: toErrorMessage(error),\n });\n });\n }\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const bootstrapActivationGate = new BootstrapActivationGate(\n () => BrowserWindow.getAllWindows().length > 0,\n () => {\n createWindow('main');\n },\n );\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n safeSend(windowId, win, '__event:__navigate', { route });\n win.show();\n win.focus();\n };\n\n const dispatchPushEvent = (\n channel: string,\n data: unknown,\n options?: IpcPushOptions,\n ): void => {\n const eventChannel = channel.startsWith('__event:')\n ? channel\n : `__event:${channel}`;\n\n if (options?.windowId) {\n const target = getWindow(options.windowId);\n if (target) {\n safeSend(options.windowId, target, eventChannel, data);\n }\n return;\n }\n\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n safeSend(windowId, win, eventChannel, data);\n }\n };\n\n if (options.legacyGlobals) setIpcPushSender(dispatchPushEvent);\n\n const broadcast = (channel: string, data: unknown): void => {\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n safeSend(windowId, win, channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n getRendererHealthState,\n setRendererLogCallback: (callback) => {\n rendererLogDispatcher.setCallback(callback);\n },\n getRendererLogCallback: () => rendererLogDispatcher.getCallback(),\n setRendererLogRecordFilter,\n setRendererConfigExtension: (extension) => { rendererConfigExtension = extension; },\n getDebugMetadata,\n // Late-bound after bootRuntime constructs the router (see bootstrap).\n dispatch: async () => {\n throw new Error('WoodMainRuntimeContext.dispatch called before the runtime router was constructed.');\n },\n };\n\n const bootstrap = async () => {\n logger.info('bootstrap starting');\n const applicationRoot = setApplicationRoot(path.join(electronApp.getPath('userData'), 'wood'));\n runtimeContext!.applicationRoot = applicationRoot;\n logger.info('application root configured', { applicationRoot });\n\n if (options.mainBoot) {\n const bootResult = await options.mainBoot(appBootResult, runtimeContext!);\n if (typeof bootResult === 'function') {\n mainBootCleanup = bootResult;\n }\n }\n\n const { router: runtimeRouter, dispose: closeRuntime } = await bootRuntime({\n container,\n ipcMain,\n registerRuntime: options.registerRuntime,\n rendererTraceBatchReceiver: options.rendererTraceBatchReceiver,\n legacyGlobals: options.legacyGlobals,\n rendererLogDispatcher,\n rendererConfigExtension: () => rendererConfigExtension?.() ?? {},\n controllersDir: resolveRuntimePath(options.controllersDir),\n middlewareDir: resolveRuntimePath(options.middlewareDir),\n ...(options.traceMinLevel ? { traceMinLevel: options.traceMinLevel } : {}),\n ...(typeof options.rendererTraceEnabled === 'boolean'\n ? { rendererTraceEnabled: options.rendererTraceEnabled }\n : {}),\n ...(options.rendererTraceMinLevel\n ? { rendererTraceMinLevel: options.rendererTraceMinLevel }\n : {}),\n ...(options.traceTransport === 'messageport' ? { enableRendererTraceBridge: false } : {}),\n onLoad: options.onLoad ?? (async () => {\n throw new Error('Reserved channel \"__load\" is not configured for this app.');\n }),\n onNavigate: async (rawData) => {\n const data = rawData as { windowId?: string; route?: string };\n if (!data.windowId) {\n throw new Error('__navigate requires windowId');\n }\n if (!data.route) {\n throw new Error('__navigate requires route');\n }\n await navigateWindow(data.windowId, data.route);\n return { ok: true };\n },\n onWindow: async (rawData, event) => {\n const data = rawData as { action?: string; windowId?: string };\n const action = data.action ?? '';\n const windowId = data.windowId;\n\n if (action === 'current') {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const senderWindowId = ipcEvent?.sender ? findWindowIdByWebContents(ipcEvent.sender.id) : null;\n if (!senderWindowId) {\n return { ok: false };\n }\n const senderWindowDef = resolveWindowDef(options.windowDefs, senderWindowId);\n return {\n ok: true,\n windowId: senderWindowId,\n defaultRoute: senderWindowDef.defaultRoute,\n };\n }\n\n if (action === 'open') {\n if (!windowId) {\n throw new Error('__window.open requires windowId');\n }\n createWindow(windowId);\n return { ok: true };\n }\n\n const win = windowId ? getWindow(windowId) : mainWindow;\n if (!win) {\n return { ok: false };\n }\n\n switch (action) {\n case 'close':\n win.close();\n return { ok: true };\n case 'focus':\n win.show();\n win.focus();\n return { ok: true };\n case 'minimize':\n win.minimize();\n return { ok: true };\n case 'maximize':\n win.isMaximized() ? win.unmaximize() : win.maximize();\n return { ok: true };\n default:\n return { ok: false };\n }\n },\n onDebug: async (rawData) => {\n const data = rawData as { action?: string } | undefined;\n const action = data?.action ?? '';\n\n if (action === 'current') {\n return getDebugMetadata();\n }\n\n return { ok: false };\n },\n onContextMenu: options.onContextMenu ?? (async (_rawData: unknown, event?: unknown) => {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const senderId = ipcEvent?.sender?.id;\n runtimeLogger.debug('context menu requested', { senderId });\n return { ok: true };\n }),\n resolveRendererTraceContext: (event) => {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const webContentsId = ipcEvent?.sender?.id;\n if (typeof webContentsId !== 'number') {\n return undefined;\n }\n return {\n webContentsId,\n windowId: findWindowIdByWebContents(webContentsId) ?? undefined,\n };\n },\n });\n\n runtimeContext!.dispatch = (channel: string, payload?: unknown) =>\n runtimeRouter.dispatch(channel, payload ?? {});\n runtimeCleanup = closeRuntime;\n\n bootstrapActivationGate.ready();\n logger.info('bootstrap complete');\n };\n\n const handleBootstrapFailure = async (error: unknown): Promise<void> => {\n bootstrapActivationGate.failed();\n await startupFailure.handle('bootstrap failed', error, async () => {\n rendererTraceDrain.stop();\n await cleanupAll([() => runtimeCleanup?.(), () => mainBootCleanup?.(), () => container.dispose()], 'Wood bootstrap rollback failed');\n });\n };\n void electronApp.whenReady().then(bootstrap).catch(handleBootstrapFailure);\n\n const registerApplicationLifecycle = (): void => {\n // Shutdown order: hide windows -> bounded renderer drain -> destroy\n // windows -> backend cleanup -> quit. See ShutdownCoordinator.\n const coordinator = new ShutdownCoordinator({\n trackedWindows: () => windows.entries(),\n clearTrackedWindows: () => { windows.clear(); },\n drainRenderers: () => drainRendererTracesBeforeQuit(\n rendererTraceDrain,\n windows.values(),\n logger,\n Date.now() + RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS,\n ),\n cleanupBackend: () => cleanupAll([\n () => runtimeCleanup?.(),\n () => { if (options.legacyGlobals) clearIpcPushSender(); },\n () => mainBootCleanup?.(),\n () => options.onBeforeQuit?.(runtimeContext!),\n () => ExecutionContext.run(container, () => container.dispose()),\n ], 'Wood main shutdown failed'),\n requestQuit: () => { electronApp.quit(); },\n logger,\n });\n shutdownCoordinator = coordinator;\n\n electronApp.on(\n 'browser-window-created',\n (_event: ElectronEvent, window: ElectronBrowserWindow) => {\n window.on('close', (event) => {\n const isTrackedWindow = [...windows.values()].includes(window);\n if (!isTrackedWindow) {\n return;\n }\n coordinator.handleTrackedWindowClose(event, windows.size);\n });\n },\n );\n\n electronApp.on('before-quit', (event: ElectronEvent) => {\n if (!runtimeContext) {\n return;\n }\n coordinator.handleBeforeQuit(event);\n });\n\n electronApp.on('window-all-closed', () => {\n coordinator.handleAllWindowsClosed();\n });\n\n electronApp.on('activate', () => {\n bootstrapActivationGate.activate();\n });\n };\n\n registerApplicationLifecycle();\n };\n\n void initializeRuntime().catch((error) => startupFailure.handle(\n 'startup failed before ready',\n error,\n async () => { rendererTraceDrain.stop(); },\n ));\n}\n"],"mappings":"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","names":["setRendererLogRecordFilter","setRendererConfigExtension","RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS","ShutdownCoordinator","shouldDeferLastWindowCloseForCleanup","options"]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var shutdown_coordinator_exports = {};
|
|
20
|
+
__export(shutdown_coordinator_exports, {
|
|
21
|
+
RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS: () => RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS,
|
|
22
|
+
ShutdownCoordinator: () => ShutdownCoordinator,
|
|
23
|
+
shouldDeferLastWindowCloseForCleanup: () => shouldDeferLastWindowCloseForCleanup
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(shutdown_coordinator_exports);
|
|
26
|
+
const RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS = 1e3;
|
|
27
|
+
function shouldDeferLastWindowCloseForCleanup(trackedWindowCount, cleanupComplete) {
|
|
28
|
+
return trackedWindowCount === 1 && !cleanupComplete;
|
|
29
|
+
}
|
|
30
|
+
class ShutdownCoordinator {
|
|
31
|
+
constructor(deps) {
|
|
32
|
+
this.deps = deps;
|
|
33
|
+
this.currentPhase = "idle";
|
|
34
|
+
this.shutdown = null;
|
|
35
|
+
}
|
|
36
|
+
get phase() {
|
|
37
|
+
return this.currentPhase;
|
|
38
|
+
}
|
|
39
|
+
get isActive() {
|
|
40
|
+
return this.shutdown !== null;
|
|
41
|
+
}
|
|
42
|
+
get isComplete() {
|
|
43
|
+
return this.currentPhase === "complete";
|
|
44
|
+
}
|
|
45
|
+
/** Resolves when the in-flight (or already finished) shutdown sequence settles. */
|
|
46
|
+
whenSettled() {
|
|
47
|
+
return this.shutdown ?? Promise.resolve();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `close` handler for a tracked BrowserWindow. Returns true when the close
|
|
51
|
+
* was intercepted (last window, or a shutdown is already running); false
|
|
52
|
+
* means the window may close normally.
|
|
53
|
+
*/
|
|
54
|
+
handleTrackedWindowClose(event, trackedWindowCount) {
|
|
55
|
+
if (this.isComplete) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (this.shutdown) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (!shouldDeferLastWindowCloseForCleanup(trackedWindowCount, false)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
this.begin("last-window-close");
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/** `before-quit` handler. Prevents every quit until the sequence completes. */
|
|
70
|
+
handleBeforeQuit(event) {
|
|
71
|
+
if (this.isComplete) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
if (this.shutdown) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.begin("quit");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* `window-all-closed` / last `closed` handler. While a shutdown is in flight
|
|
82
|
+
* the coordinator issues the final quit itself, so the cascade caused by
|
|
83
|
+
* destroying our own windows must not request another one.
|
|
84
|
+
*/
|
|
85
|
+
handleAllWindowsClosed() {
|
|
86
|
+
if (this.shutdown) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.deps.requestQuit();
|
|
90
|
+
}
|
|
91
|
+
begin(reason) {
|
|
92
|
+
this.deps.logger.info("shutdown started", { reason });
|
|
93
|
+
this.shutdown = this.run();
|
|
94
|
+
}
|
|
95
|
+
async run() {
|
|
96
|
+
this.hideAllWindows();
|
|
97
|
+
this.currentPhase = "draining";
|
|
98
|
+
try {
|
|
99
|
+
await this.deps.drainRenderers();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.deps.logger.warn("renderer shutdown drain failed", { error: toErrorMessage(error) });
|
|
102
|
+
}
|
|
103
|
+
this.destroyAllWindows();
|
|
104
|
+
this.currentPhase = "windows-destroyed";
|
|
105
|
+
this.currentPhase = "cleaning-backend";
|
|
106
|
+
try {
|
|
107
|
+
await this.deps.cleanupBackend();
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.deps.logger.warn("backend shutdown cleanup failed", { error: toErrorMessage(error) });
|
|
110
|
+
}
|
|
111
|
+
this.currentPhase = "complete";
|
|
112
|
+
this.deps.logger.info("shutdown complete");
|
|
113
|
+
this.deps.requestQuit();
|
|
114
|
+
}
|
|
115
|
+
hideAllWindows() {
|
|
116
|
+
for (const [id, window] of [...this.deps.trackedWindows()]) {
|
|
117
|
+
this.hideWindow(id, window);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
hideWindow(id, window) {
|
|
121
|
+
try {
|
|
122
|
+
if (!window.isDestroyed()) {
|
|
123
|
+
window.hide();
|
|
124
|
+
this.deps.logger.debug("hid window for shutdown", { windowId: id });
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.deps.logger.warn("hiding window for shutdown failed", { windowId: id, error: toErrorMessage(error) });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
destroyAllWindows() {
|
|
131
|
+
for (const [id, window] of [...this.deps.trackedWindows()]) {
|
|
132
|
+
try {
|
|
133
|
+
if (!window.isDestroyed()) {
|
|
134
|
+
window.destroy();
|
|
135
|
+
this.deps.logger.debug("destroyed window on shutdown", { windowId: id });
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
this.deps.logger.warn("destroying window on shutdown failed", { windowId: id, error: toErrorMessage(error) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
this.deps.clearTrackedWindows();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function toErrorMessage(error) {
|
|
145
|
+
return error instanceof Error ? error.message : String(error);
|
|
146
|
+
}
|
|
147
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
148
|
+
0 && (module.exports = {
|
|
149
|
+
RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS,
|
|
150
|
+
ShutdownCoordinator,
|
|
151
|
+
shouldDeferLastWindowCloseForCleanup
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=shutdown_coordinator.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/main/shutdown_coordinator.ts"],"sourcesContent":["import type { Logger } from '@noego/logger';\n\n/**\n * Bounded time the last renderers get to acknowledge the shutdown drain\n * (trace transport close + app-registered persistence hooks) before their\n * windows are destroyed.\n */\nexport const RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS = 1_000;\n\nexport type ShutdownWindow = Readonly<{\n isDestroyed(): boolean;\n hide(): void;\n destroy(): void;\n}>;\n\nexport type ShutdownPreventableEvent = Readonly<{\n preventDefault(): void;\n}>;\n\nexport type ShutdownReason = 'last-window-close' | 'quit';\n\nexport type ShutdownPhase =\n | 'idle'\n | 'draining'\n | 'windows-destroyed'\n | 'cleaning-backend'\n | 'complete';\n\nexport type ShutdownCoordinatorDependencies = Readonly<{\n /** Live view of the tracked windows; iterated as a snapshot on each use. */\n trackedWindows(): Iterable<readonly [string, ShutdownWindow]>;\n /** Forget every tracked window after destruction. */\n clearTrackedWindows(): void;\n /** Bounded renderer drain (traces + persistence acknowledgements). */\n drainRenderers(): Promise<void>;\n /** Backend runtime/service teardown; runs only after windows are gone. */\n cleanupBackend(): Promise<void>;\n /** Ask Electron to quit; the coordinator calls this exactly once, at the end. */\n requestQuit(): void;\n logger: Pick<Logger, 'debug' | 'info' | 'warn'>;\n}>;\n\nexport function shouldDeferLastWindowCloseForCleanup(\n trackedWindowCount: number,\n cleanupComplete: boolean,\n): boolean {\n return trackedWindowCount === 1 && !cleanupComplete;\n}\n\n/**\n * Owns the Desktop shutdown sequence so windows disappear first and the\n * backend is torn down last:\n *\n * accepted last-window Close or Quit\n * -> hide every tracked window immediately\n * -> bounded renderer drain (trace close + persistence acknowledgements)\n * -> destroy every tracked window\n * -> backend runtime/service cleanup\n * -> app.quit()\n *\n * The sequence runs exactly once. Every Electron quit attempt that arrives\n * while it is in flight (repeated Cmd+Q, the `closed` -> `window-all-closed`\n * cascade caused by our own window destruction, a second Close) is prevented\n * so the process cannot exit before cleanup finishes. Once complete, the final\n * `requestQuit()` is allowed through untouched.\n */\nexport class ShutdownCoordinator {\n private currentPhase: ShutdownPhase = 'idle';\n private shutdown: Promise<void> | null = null;\n\n constructor(private readonly deps: ShutdownCoordinatorDependencies) {}\n\n get phase(): ShutdownPhase {\n return this.currentPhase;\n }\n\n get isActive(): boolean {\n return this.shutdown !== null;\n }\n\n get isComplete(): boolean {\n return this.currentPhase === 'complete';\n }\n\n /** Resolves when the in-flight (or already finished) shutdown sequence settles. */\n whenSettled(): Promise<void> {\n return this.shutdown ?? Promise.resolve();\n }\n\n /**\n * `close` handler for a tracked BrowserWindow. Returns true when the close\n * was intercepted (last window, or a shutdown is already running); false\n * means the window may close normally.\n */\n handleTrackedWindowClose(\n event: ShutdownPreventableEvent,\n trackedWindowCount: number,\n ): boolean {\n if (this.isComplete) {\n return false;\n }\n if (this.shutdown) {\n // Shutdown owns window destruction; do not let a competing close race it.\n event.preventDefault();\n return true;\n }\n if (!shouldDeferLastWindowCloseForCleanup(trackedWindowCount, false)) {\n return false;\n }\n event.preventDefault();\n // begin() hides every tracked window synchronously, including this one.\n this.begin('last-window-close');\n return true;\n }\n\n /** `before-quit` handler. Prevents every quit until the sequence completes. */\n handleBeforeQuit(event: ShutdownPreventableEvent): void {\n if (this.isComplete) {\n return;\n }\n event.preventDefault();\n if (this.shutdown) {\n return;\n }\n this.begin('quit');\n }\n\n /**\n * `window-all-closed` / last `closed` handler. While a shutdown is in flight\n * the coordinator issues the final quit itself, so the cascade caused by\n * destroying our own windows must not request another one.\n */\n handleAllWindowsClosed(): void {\n if (this.shutdown) {\n return;\n }\n this.deps.requestQuit();\n }\n\n private begin(reason: ShutdownReason): void {\n this.deps.logger.info('shutdown started', { reason });\n this.shutdown = this.run();\n }\n\n private async run(): Promise<void> {\n this.hideAllWindows();\n\n this.currentPhase = 'draining';\n try {\n await this.deps.drainRenderers();\n } catch (error) {\n this.deps.logger.warn('renderer shutdown drain failed', { error: toErrorMessage(error) });\n }\n\n this.destroyAllWindows();\n this.currentPhase = 'windows-destroyed';\n\n this.currentPhase = 'cleaning-backend';\n try {\n await this.deps.cleanupBackend();\n } catch (error) {\n this.deps.logger.warn('backend shutdown cleanup failed', { error: toErrorMessage(error) });\n }\n\n this.currentPhase = 'complete';\n this.deps.logger.info('shutdown complete');\n this.deps.requestQuit();\n }\n\n private hideAllWindows(): void {\n for (const [id, window] of [...this.deps.trackedWindows()]) {\n this.hideWindow(id, window);\n }\n }\n\n private hideWindow(id: string, window: ShutdownWindow): void {\n try {\n if (!window.isDestroyed()) {\n window.hide();\n this.deps.logger.debug('hid window for shutdown', { windowId: id });\n }\n } catch (error) {\n this.deps.logger.warn('hiding window for shutdown failed', { windowId: id, error: toErrorMessage(error) });\n }\n }\n\n private destroyAllWindows(): void {\n for (const [id, window] of [...this.deps.trackedWindows()]) {\n try {\n if (!window.isDestroyed()) {\n window.destroy();\n this.deps.logger.debug('destroyed window on shutdown', { windowId: id });\n }\n } catch (error) {\n this.deps.logger.warn('destroying window on shutdown failed', { windowId: id, error: toErrorMessage(error) });\n }\n }\n this.deps.clearTrackedWindows();\n }\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Logger } from '@noego/logger';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bounded time the last renderers get to acknowledge the shutdown drain
|
|
5
|
+
* (trace transport close + app-registered persistence hooks) before their
|
|
6
|
+
* windows are destroyed.
|
|
7
|
+
*/
|
|
8
|
+
declare const RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS = 1000;
|
|
9
|
+
type ShutdownWindow = Readonly<{
|
|
10
|
+
isDestroyed(): boolean;
|
|
11
|
+
hide(): void;
|
|
12
|
+
destroy(): void;
|
|
13
|
+
}>;
|
|
14
|
+
type ShutdownPreventableEvent = Readonly<{
|
|
15
|
+
preventDefault(): void;
|
|
16
|
+
}>;
|
|
17
|
+
type ShutdownReason = 'last-window-close' | 'quit';
|
|
18
|
+
type ShutdownPhase = 'idle' | 'draining' | 'windows-destroyed' | 'cleaning-backend' | 'complete';
|
|
19
|
+
type ShutdownCoordinatorDependencies = Readonly<{
|
|
20
|
+
/** Live view of the tracked windows; iterated as a snapshot on each use. */
|
|
21
|
+
trackedWindows(): Iterable<readonly [string, ShutdownWindow]>;
|
|
22
|
+
/** Forget every tracked window after destruction. */
|
|
23
|
+
clearTrackedWindows(): void;
|
|
24
|
+
/** Bounded renderer drain (traces + persistence acknowledgements). */
|
|
25
|
+
drainRenderers(): Promise<void>;
|
|
26
|
+
/** Backend runtime/service teardown; runs only after windows are gone. */
|
|
27
|
+
cleanupBackend(): Promise<void>;
|
|
28
|
+
/** Ask Electron to quit; the coordinator calls this exactly once, at the end. */
|
|
29
|
+
requestQuit(): void;
|
|
30
|
+
logger: Pick<Logger, 'debug' | 'info' | 'warn'>;
|
|
31
|
+
}>;
|
|
32
|
+
declare function shouldDeferLastWindowCloseForCleanup(trackedWindowCount: number, cleanupComplete: boolean): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Owns the Desktop shutdown sequence so windows disappear first and the
|
|
35
|
+
* backend is torn down last:
|
|
36
|
+
*
|
|
37
|
+
* accepted last-window Close or Quit
|
|
38
|
+
* -> hide every tracked window immediately
|
|
39
|
+
* -> bounded renderer drain (trace close + persistence acknowledgements)
|
|
40
|
+
* -> destroy every tracked window
|
|
41
|
+
* -> backend runtime/service cleanup
|
|
42
|
+
* -> app.quit()
|
|
43
|
+
*
|
|
44
|
+
* The sequence runs exactly once. Every Electron quit attempt that arrives
|
|
45
|
+
* while it is in flight (repeated Cmd+Q, the `closed` -> `window-all-closed`
|
|
46
|
+
* cascade caused by our own window destruction, a second Close) is prevented
|
|
47
|
+
* so the process cannot exit before cleanup finishes. Once complete, the final
|
|
48
|
+
* `requestQuit()` is allowed through untouched.
|
|
49
|
+
*/
|
|
50
|
+
declare class ShutdownCoordinator {
|
|
51
|
+
private readonly deps;
|
|
52
|
+
private currentPhase;
|
|
53
|
+
private shutdown;
|
|
54
|
+
constructor(deps: ShutdownCoordinatorDependencies);
|
|
55
|
+
get phase(): ShutdownPhase;
|
|
56
|
+
get isActive(): boolean;
|
|
57
|
+
get isComplete(): boolean;
|
|
58
|
+
/** Resolves when the in-flight (or already finished) shutdown sequence settles. */
|
|
59
|
+
whenSettled(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* `close` handler for a tracked BrowserWindow. Returns true when the close
|
|
62
|
+
* was intercepted (last window, or a shutdown is already running); false
|
|
63
|
+
* means the window may close normally.
|
|
64
|
+
*/
|
|
65
|
+
handleTrackedWindowClose(event: ShutdownPreventableEvent, trackedWindowCount: number): boolean;
|
|
66
|
+
/** `before-quit` handler. Prevents every quit until the sequence completes. */
|
|
67
|
+
handleBeforeQuit(event: ShutdownPreventableEvent): void;
|
|
68
|
+
/**
|
|
69
|
+
* `window-all-closed` / last `closed` handler. While a shutdown is in flight
|
|
70
|
+
* the coordinator issues the final quit itself, so the cascade caused by
|
|
71
|
+
* destroying our own windows must not request another one.
|
|
72
|
+
*/
|
|
73
|
+
handleAllWindowsClosed(): void;
|
|
74
|
+
private begin;
|
|
75
|
+
private run;
|
|
76
|
+
private hideAllWindows;
|
|
77
|
+
private hideWindow;
|
|
78
|
+
private destroyAllWindows;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS, ShutdownCoordinator, type ShutdownCoordinatorDependencies, type ShutdownPhase, type ShutdownPreventableEvent, type ShutdownReason, type ShutdownWindow, shouldDeferLastWindowCloseForCleanup };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Logger } from '@noego/logger';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bounded time the last renderers get to acknowledge the shutdown drain
|
|
5
|
+
* (trace transport close + app-registered persistence hooks) before their
|
|
6
|
+
* windows are destroyed.
|
|
7
|
+
*/
|
|
8
|
+
declare const RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS = 1000;
|
|
9
|
+
type ShutdownWindow = Readonly<{
|
|
10
|
+
isDestroyed(): boolean;
|
|
11
|
+
hide(): void;
|
|
12
|
+
destroy(): void;
|
|
13
|
+
}>;
|
|
14
|
+
type ShutdownPreventableEvent = Readonly<{
|
|
15
|
+
preventDefault(): void;
|
|
16
|
+
}>;
|
|
17
|
+
type ShutdownReason = 'last-window-close' | 'quit';
|
|
18
|
+
type ShutdownPhase = 'idle' | 'draining' | 'windows-destroyed' | 'cleaning-backend' | 'complete';
|
|
19
|
+
type ShutdownCoordinatorDependencies = Readonly<{
|
|
20
|
+
/** Live view of the tracked windows; iterated as a snapshot on each use. */
|
|
21
|
+
trackedWindows(): Iterable<readonly [string, ShutdownWindow]>;
|
|
22
|
+
/** Forget every tracked window after destruction. */
|
|
23
|
+
clearTrackedWindows(): void;
|
|
24
|
+
/** Bounded renderer drain (traces + persistence acknowledgements). */
|
|
25
|
+
drainRenderers(): Promise<void>;
|
|
26
|
+
/** Backend runtime/service teardown; runs only after windows are gone. */
|
|
27
|
+
cleanupBackend(): Promise<void>;
|
|
28
|
+
/** Ask Electron to quit; the coordinator calls this exactly once, at the end. */
|
|
29
|
+
requestQuit(): void;
|
|
30
|
+
logger: Pick<Logger, 'debug' | 'info' | 'warn'>;
|
|
31
|
+
}>;
|
|
32
|
+
declare function shouldDeferLastWindowCloseForCleanup(trackedWindowCount: number, cleanupComplete: boolean): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Owns the Desktop shutdown sequence so windows disappear first and the
|
|
35
|
+
* backend is torn down last:
|
|
36
|
+
*
|
|
37
|
+
* accepted last-window Close or Quit
|
|
38
|
+
* -> hide every tracked window immediately
|
|
39
|
+
* -> bounded renderer drain (trace close + persistence acknowledgements)
|
|
40
|
+
* -> destroy every tracked window
|
|
41
|
+
* -> backend runtime/service cleanup
|
|
42
|
+
* -> app.quit()
|
|
43
|
+
*
|
|
44
|
+
* The sequence runs exactly once. Every Electron quit attempt that arrives
|
|
45
|
+
* while it is in flight (repeated Cmd+Q, the `closed` -> `window-all-closed`
|
|
46
|
+
* cascade caused by our own window destruction, a second Close) is prevented
|
|
47
|
+
* so the process cannot exit before cleanup finishes. Once complete, the final
|
|
48
|
+
* `requestQuit()` is allowed through untouched.
|
|
49
|
+
*/
|
|
50
|
+
declare class ShutdownCoordinator {
|
|
51
|
+
private readonly deps;
|
|
52
|
+
private currentPhase;
|
|
53
|
+
private shutdown;
|
|
54
|
+
constructor(deps: ShutdownCoordinatorDependencies);
|
|
55
|
+
get phase(): ShutdownPhase;
|
|
56
|
+
get isActive(): boolean;
|
|
57
|
+
get isComplete(): boolean;
|
|
58
|
+
/** Resolves when the in-flight (or already finished) shutdown sequence settles. */
|
|
59
|
+
whenSettled(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* `close` handler for a tracked BrowserWindow. Returns true when the close
|
|
62
|
+
* was intercepted (last window, or a shutdown is already running); false
|
|
63
|
+
* means the window may close normally.
|
|
64
|
+
*/
|
|
65
|
+
handleTrackedWindowClose(event: ShutdownPreventableEvent, trackedWindowCount: number): boolean;
|
|
66
|
+
/** `before-quit` handler. Prevents every quit until the sequence completes. */
|
|
67
|
+
handleBeforeQuit(event: ShutdownPreventableEvent): void;
|
|
68
|
+
/**
|
|
69
|
+
* `window-all-closed` / last `closed` handler. While a shutdown is in flight
|
|
70
|
+
* the coordinator issues the final quit itself, so the cascade caused by
|
|
71
|
+
* destroying our own windows must not request another one.
|
|
72
|
+
*/
|
|
73
|
+
handleAllWindowsClosed(): void;
|
|
74
|
+
private begin;
|
|
75
|
+
private run;
|
|
76
|
+
private hideAllWindows;
|
|
77
|
+
private hideWindow;
|
|
78
|
+
private destroyAllWindows;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { RENDERER_SHUTDOWN_DRAIN_TIMEOUT_MS, ShutdownCoordinator, type ShutdownCoordinatorDependencies, type ShutdownPhase, type ShutdownPreventableEvent, type ShutdownReason, type ShutdownWindow, shouldDeferLastWindowCloseForCleanup };
|