@noego/wood 0.0.4 → 0.1.1

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.
Files changed (81) hide show
  1. package/README.md +9 -0
  2. package/bin/wood.js +2 -1
  3. package/dist/client/controller_contract.cjs.map +1 -1
  4. package/dist/client/controller_contract.d.cts +4 -2
  5. package/dist/client/controller_contract.d.ts +4 -2
  6. package/dist/client/index.cjs +15 -2
  7. package/dist/client/index.cjs.map +1 -1
  8. package/dist/client/index.d.cts +2 -1
  9. package/dist/client/index.d.ts +2 -1
  10. package/dist/client/index.js +16 -3
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/codegen/preload_generator.cjs +26 -0
  13. package/dist/codegen/preload_generator.cjs.map +1 -1
  14. package/dist/codegen/preload_generator.js +26 -0
  15. package/dist/codegen/preload_generator.js.map +1 -1
  16. package/dist/controller/controller_lifecycle.cjs +41 -0
  17. package/dist/controller/controller_lifecycle.cjs.map +1 -0
  18. package/dist/controller/controller_lifecycle.d.cts +12 -0
  19. package/dist/controller/controller_lifecycle.d.ts +12 -0
  20. package/dist/controller/controller_lifecycle.js +17 -0
  21. package/dist/controller/controller_lifecycle.js.map +1 -0
  22. package/dist/controller/index.cjs +5 -2
  23. package/dist/controller/index.cjs.map +1 -1
  24. package/dist/controller/index.d.cts +1 -0
  25. package/dist/controller/index.d.ts +1 -0
  26. package/dist/controller/index.js +3 -1
  27. package/dist/controller/index.js.map +1 -1
  28. package/dist/index.cjs +6 -0
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +2 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +7 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/main/index.cjs +61 -16
  35. package/dist/main/index.cjs.map +1 -1
  36. package/dist/main/index.d.cts +3 -0
  37. package/dist/main/index.d.ts +3 -0
  38. package/dist/main/index.js +62 -16
  39. package/dist/main/index.js.map +1 -1
  40. package/dist/navigation/navigation_identity.cjs +43 -0
  41. package/dist/navigation/navigation_identity.cjs.map +1 -0
  42. package/dist/navigation/navigation_identity.d.cts +5 -0
  43. package/dist/navigation/navigation_identity.d.ts +5 -0
  44. package/dist/navigation/navigation_identity.js +18 -0
  45. package/dist/navigation/navigation_identity.js.map +1 -0
  46. package/dist/router/wood_router.cjs +36 -11
  47. package/dist/router/wood_router.cjs.map +1 -1
  48. package/dist/router/wood_router.js +36 -11
  49. package/dist/router/wood_router.js.map +1 -1
  50. package/dist/runtime/runtime.cjs +11 -4
  51. package/dist/runtime/runtime.cjs.map +1 -1
  52. package/dist/runtime/runtime.d.cts +2 -0
  53. package/dist/runtime/runtime.d.ts +2 -0
  54. package/dist/runtime/runtime.js +12 -5
  55. package/dist/runtime/runtime.js.map +1 -1
  56. package/dist/testing/create_controller_harness.cjs +4 -4
  57. package/dist/testing/create_controller_harness.cjs.map +1 -1
  58. package/dist/testing/create_controller_harness.d.cts +1 -1
  59. package/dist/testing/create_controller_harness.d.ts +1 -1
  60. package/dist/testing/create_controller_harness.js +4 -4
  61. package/dist/testing/create_controller_harness.js.map +1 -1
  62. package/dist/tracing/index.cjs +6 -2
  63. package/dist/tracing/index.cjs.map +1 -1
  64. package/dist/tracing/index.d.cts +2 -2
  65. package/dist/tracing/index.d.ts +2 -2
  66. package/dist/tracing/index.js +5 -1
  67. package/dist/tracing/index.js.map +1 -1
  68. package/dist/tracing/trace_hub.cjs +38 -11
  69. package/dist/tracing/trace_hub.cjs.map +1 -1
  70. package/dist/tracing/trace_hub.d.cts +3 -1
  71. package/dist/tracing/trace_hub.d.ts +3 -1
  72. package/dist/tracing/trace_hub.js +35 -10
  73. package/dist/tracing/trace_hub.js.map +1 -1
  74. package/dist/tracing/trace_provider.cjs +63 -6
  75. package/dist/tracing/trace_provider.cjs.map +1 -1
  76. package/dist/tracing/trace_provider.d.cts +10 -2
  77. package/dist/tracing/trace_provider.d.ts +10 -2
  78. package/dist/tracing/trace_provider.js +64 -6
  79. package/dist/tracing/trace_provider.js.map +1 -1
  80. package/package.json +17 -17
  81. package/src/components/NavigationShell.svelte +18 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/main/index.ts"],"sourcesContent":["import path from 'path';\nimport {\n app as electronApp,\n BrowserWindow,\n ipcMain,\n protocol,\n net,\n shell,\n screen,\n} from 'electron';\nimport type {\n BrowserWindowConstructorOptions,\n BrowserWindow as ElectronBrowserWindow,\n IpcMainInvokeEvent,\n Privileges,\n} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { getContainer } from '../runtime/get_container.js';\nimport { runtime as bootRuntime } from '../runtime/runtime.js';\nimport type { TraceLevel } from '../tracing/index.js';\nimport { setApplicationRoot } from '../runtime/application_root.js';\nimport type { RegisterRuntimeRouter } from '../runtime/runtime.js';\nimport {\n getRendererLogDispatcher,\n type RendererLogCallback,\n} from '../runtime/renderer_log_dispatcher.js';\nimport { Ipc, getIpc } from '../ipc/index.js';\nimport { setIpcPushSender, clearIpcPushSender, type IpcPushOptions } from '../ipc/shared.js';\n\ntype CleanupHandler = () => void | Promise<void>;\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};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\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 };\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 setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\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 onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;\n onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;\n traceMinLevel?: TraceLevel;\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\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 debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\n let mainWindow: ElectronBrowserWindow | null = null;\n let runtimeContext: WoodMainRuntimeContext | null = null;\n let mainBootCleanup: CleanupHandler | undefined;\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 },\n logger,\n };\n\n void (async () => {\n const appBootResult = options.appBoot\n ? await options.appBoot(bootContext)\n : undefined;\n\n const bootOverrides = (appBootResult ?? {}) as WoodMainAppBootResult;\n const scope = bootOverrides.scope ?? options.scope ?? process.cwd();\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 container = getContainer({\n scope,\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, () => getIpc());\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 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 });\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 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 show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(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 win.on('closed', () => {\n windows.delete(windowId);\n if (windowId === 'main') {\n mainWindow = null;\n }\n });\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n win.webContents.send('__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 && !target.isDestroyed()) {\n target.webContents.send(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 win.webContents.send(eventChannel, data);\n }\n };\n\n 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 win.webContents.send(channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n setRendererLogCallback: (callback) => {\n getRendererLogDispatcher().setCallback(callback);\n },\n getRendererLogCallback: () => getRendererLogDispatcher().getCallback(),\n getDebugMetadata,\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 await bootRuntime({\n container,\n ipcMain,\n registerRuntime: options.registerRuntime,\n controllersDir: resolveRuntimePath(options.controllersDir),\n middlewareDir: resolveRuntimePath(options.middlewareDir),\n ...(options.traceMinLevel ? { traceMinLevel: options.traceMinLevel } : {}),\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 });\n\n createWindow('main');\n logger.info('bootstrap complete');\n };\n\n void electronApp.whenReady().then(bootstrap).catch((error) => {\n logger.error('bootstrap failed', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n });\n\n electronApp.on('before-quit', () => {\n if (!runtimeContext) {\n return;\n }\n\n void (async () => {\n try {\n clearIpcPushSender();\n if (mainBootCleanup) {\n await mainBootCleanup();\n }\n if (options.onBeforeQuit) {\n await options.onBeforeQuit(runtimeContext!);\n }\n } catch (error) {\n logger.warn('before-quit handler failed', { error: toErrorMessage(error) });\n }\n })();\n\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 electronApp.on('window-all-closed', () => {\n electronApp.quit();\n });\n\n electronApp.on('activate', () => {\n if (BrowserWindow.getAllWindows().length === 0) {\n createWindow('main');\n }\n });\n })().catch((error) => {\n logger.error('startup failed before ready', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAiB;AACjB,sBAQO;AAOP,oBAA0B;AAC1B,gCAAiC;AACjC,2BAA6B;AAC7B,qBAAuC;AAEvC,8BAAmC;AAEnC,qCAGO;AACP,iBAA4B;AAC5B,oBAA0E;AAqG1E,MAAM,qCAAqC;AAEpC,SAAS,sCAAsC,MAAyB,QAAQ,KAIrF;AACA,QAAM,gBAAgB,IAAI,cAAc;AACxC,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,UAAU,IAAI;AACpB,QAAM,aAAa,UAAU,OAAO,SAAS,SAAS,EAAE,IAAI;AAC5D,QAAM,OAAO,OAAO,SAAS,UAAU,KAAK,aAAa,IACrD,aACA;AAEJ,SAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAEA,SAAS,iBACP,YACA,WAAW,QACO;AAClB,QAAM,MAAM,WAAW,QAAQ;AAC/B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,WAAW,QAAQ,yCAAyC;AAAA,EAC9E;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,wBAAwC;AAClE,MAAI,YAAAA,QAAK,WAAW,sBAAsB,GAAG;AAC3C,WAAO;AAAA,EACT;AACA,SAAO,YAAAA,QAAK,KAAK,gBAAAC,IAAY,WAAW,GAAG,sBAAsB;AACnE;AAEA,SAAS,eAAe,OAAwB;AAC9C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEO,SAAS,cAAc,SAAqC;AACjE,QAAM,aAAS,yBAAU,WAAW,EAAE,MAAM,WAAW;AACvD,QAAM,oBAAgB,yBAAU,WAAW,EAAE,MAAM,SAAS;AAC5D,QAAM,cAAc,sCAAsC;AAC1D,QAAM,cAAc,IAAI,2CAAiB;AACzC,QAAM,UAAU,oBAAI,IAAmC;AACvD,MAAI,aAA2C;AAC/C,MAAI,iBAAgD;AACpD,MAAI;AAEJ,aAAW,UAAU,CAAC,QAAQ,QAAQ,QAAQ,MAAM,GAAG;AACrD,WAAO,GAAG,SAAS,CAAC,UAAiC;AACnD,UAAI,MAAM,SAAS,SAAS;AAC1B;AAAA,MACF;AACA,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,MAAI,YAAY,WAAW,YAAY,MAAM;AAC3C,oBAAAA,IAAY,YAAY,aAAa,yBAAyB,OAAO,YAAY,IAAI,CAAC;AACtF,oBAAAA,IAAY,YAAY,aAAa,4BAA4B,WAAW;AAC5E,WAAO,KAAK,qCAAqC;AAAA,MAC/C,MAAM,YAAY;AAAA,MAClB,MAAM,YAAY;AAAA,IACpB,CAAC;AAAA,EACH;AAEA,QAAM,cAAmC;AAAA,IACvC,UAAU;AAAA,MACR,KAAK,gBAAAA;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AAEA,QAAM,YAAY;AAChB,UAAM,gBAAgB,QAAQ,UAC1B,MAAM,QAAQ,QAAQ,WAAW,IACjC;AAEJ,UAAM,gBAAiB,iBAAiB,CAAC;AACzC,UAAM,QAAQ,cAAc,SAAS,QAAQ,SAAS,QAAQ,IAAI;AAClE,UAAM,qBAAqB,cAAc,sBAAsB,QAAQ;AACvE,UAAM,oBAAoB,MAAM,QAAQ,cAAc,2BAA2B,IAC7E,cAAc,8BACd,CAAC;AAEL,QAAI,kBAAkB,SAAS,GAAG;AAChC,+BAAS,4BAA4B,iBAAiB;AACtD,aAAO,KAAK,iCAAiC;AAAA,QAC3C,OAAO,kBAAkB;AAAA,QACzB,SAAS,kBAAkB,IAAI,CAAC,UAAU,MAAM,MAAM;AAAA,MACxD,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,cAAc,YAAAD,QAAK,KAAK,gBAAAC,IAAY,WAAW,GAAG,QAAQ,WAAW,UAAU;AACrF,UAAM,eAAe,YAAAD,QAAK,KAAK,gBAAAC,IAAY,WAAW,GAAG,QAAQ,YAAY,YAAY;AAEzF,UAAM,gBAAY,mCAAa;AAAA,MAC7B;AAAA,MACA,GAAI,qBAAqB,EAAE,WAAW,mBAAmB,IAAI,CAAC;AAAA,IAChE,CAAC;AAED,UAAM,wBAAwB;AAQ9B,QAAI,OAAO,sBAAsB,qBAAqB,YAAY;AAChE,4BAAsB,iBAAiB,gBAAK,UAAM,mBAAO,CAAC;AAAA,IAC5D;AAEA,UAAM,YAAY,CAAC,aAAmD;AACpE,YAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,UAAI,CAAC,UAAU;AACb,eAAO;AAAA,MACT;AACA,UAAI,SAAS,YAAY,GAAG;AAC1B,gBAAQ,OAAO,QAAQ;AACvB,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAEA,UAAM,4BAA4B,CAAC,kBAAyC;AAC1E,iBAAW,CAAC,UAAU,GAAG,KAAK,QAAQ,QAAQ,GAAG;AAC/C,YAAI,IAAI,YAAY,GAAG;AACrB,kBAAQ,OAAO,QAAQ;AACvB;AAAA,QACF;AACA,YAAI,IAAI,YAAY,OAAO,eAAe;AACxC,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,UAAM,qBAAqB,OAAO,QAA8C;AAC9E,YAAM,cAAc,IAAI;AAGxB,YAAM,YAAY,OAAO,YAAY,uBAAuB,aACxD,YAAY,mBAAmB,IAC/B,YAAY,UAAU;AAC1B,UAAI,CAAC,WAAW;AACd;AAAA,MACF;AAEA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,oBAAY,KAAK,mBAAmB,MAAM,QAAQ,CAAC;AAAA,MACrD,CAAC;AAAA,IACH;AAEA,UAAM,mBAAmB,MAAyB;AAChD,YAAM,eAA0C,CAAC;AAEjD,iBAAW,CAAC,UAAU,GAAG,KAAK,QAAQ,QAAQ,GAAG;AAC/C,YAAI,IAAI,YAAY,GAAG;AACrB,kBAAQ,OAAO,QAAQ;AACvB;AAAA,QACF;AAEA,qBAAa,KAAK;AAAA,UAChB;AAAA,UACA,OAAO,IAAI,SAAS;AAAA,UACpB,eAAe,IAAI,YAAY;AAAA,UAC/B,KAAK,IAAI,YAAY,OAAO;AAAA,UAC5B,SAAS,IAAI,UAAU;AAAA,QACzB,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS,YAAY;AAAA,QACrB,MAAM,YAAY;AAAA,QAClB,GAAI,YAAY,OAAO,EAAE,MAAM,YAAY,KAAK,IAAI,CAAC;AAAA,QACrD,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,eAAe,CAAC,WAAW,WAAkC;AACjE,YAAM,WAAW,UAAU,QAAQ;AACnC,UAAI,UAAU;AACZ,iBAAS,KAAK;AACd,iBAAS,MAAM;AACf,eAAO;AAAA,MACT;AAEA,YAAM,YAAY,iBAAiB,QAAQ,YAAY,QAAQ;AAC/D,YAAM,cAAc,UAAU,QAAQ;AACtC,YAAM,eAAe,UAAU,SAAS,UAAU,UAAU,MAAM,IAAI;AAEtE,kBAAY,UAAU,sBAAM;AAC5B,YAAM,cAAc,UAAU,iBAAiB,YAAY,UAAU,QAAQ,IAAI;AAEjF,YAAM,gBAAiD;AAAA,QACrD,OAAO,aAAa,SAAS,UAAU;AAAA,QACvC,QAAQ,aAAa,UAAU,UAAU;AAAA,QACzC,GAAI,cAAc,EAAE,GAAG,YAAY,GAAG,GAAG,YAAY,EAAE,IAAI,CAAC;AAAA,QAC5D,GAAI,UAAU,YAAY,OAAO,EAAE,UAAU,UAAU,SAAS,IAAI,CAAC;AAAA,QACrE,GAAI,UAAU,aAAa,OAAO,EAAE,WAAW,UAAU,UAAU,IAAI,CAAC;AAAA,QACxE,GAAI,UAAU,YAAY,OAAO,EAAE,UAAU,UAAU,SAAS,IAAI,CAAC;AAAA,QACrE,GAAI,UAAU,aAAa,OAAO,EAAE,WAAW,UAAU,UAAU,IAAI,CAAC;AAAA,QACxE,GAAI,OAAO,UAAU,UAAU,YAAY,EAAE,OAAO,UAAU,MAAM,IAAI,CAAC;AAAA,QACzE,GAAI,eAAe,EAAE,QAAQ,aAAa,IAAI,CAAC;AAAA,QAC/C,OAAO,UAAU;AAAA,QACjB,OAAO,UAAU,SAAS;AAAA,QAC1B,aAAa,UAAU,eAAe;AAAA,QACtC,WAAW,UAAU,aAAa;AAAA,QAClC,aAAa,UAAU,eAAe;AAAA,QACtC,GAAI,OAAO,UAAU,oBAAoB,WAAW,EAAE,iBAAiB,UAAU,gBAAgB,IAAI,CAAC;AAAA,QACtG,GAAI,QAAQ,aAAa,YAAY,OAAO,UAAU,aAAa,WAC/D;AAAA,UACE,UAAU,UAAU;AAAA,UACpB,mBAAmB;AAAA,QACrB,IACA,CAAC;AAAA,QACL,GAAI,QAAQ,aAAa,WAAW,OAAO,UAAU,uBAAuB,WACxE;AAAA,UACE,oBAAoB,UAAU;AAAA,QAChC,IACA,CAAC;AAAA,QACL,GAAI,OAAO,UAAU,qBAAqB,YAAY,EAAE,kBAAkB,UAAU,iBAAiB,IAAI,CAAC;AAAA,QAC1G,MAAM,cAAc,QAAQ;AAAA,QAC5B,gBAAgB;AAAA,UACd,SAAS;AAAA,UACT,kBAAkB;AAAA,UAClB,iBAAiB;AAAA,QACnB;AAAA,MACF;AAEA,YAAM,MAAM,IAAI,8BAAc,aAAa;AAC3C,cAAQ,IAAI,UAAU,GAAG;AACzB,UAAI,aAAa,QAAQ;AACvB,qBAAa;AAAA,MACf;AAEA,UAAI,aAAa,aAAa;AAC5B,YAAI,SAAS;AAAA,MACf,WAAW,aAAa,cAAc;AACpC,YAAI,cAAc,IAAI;AAAA,MACxB;AAEA,UAAI,eAAe,aAAa;AAC9B,YAAI,KAAK;AAAA,MACX;AAEA,UAAI,UAAU,gBAAgB;AAC5B,cAAM,sBAAsB,MAAM;AAChC,cAAI,CAAC,IAAI,YAAY,KAAK,CAAC,IAAI,aAAa,GAAG;AAC7C,kBAAM,OAAO,IAAI,UAAU;AAC3B,wBAAY,sBAAsB,UAAU;AAAA,cAC1C,GAAG;AAAA,cACH,aAAa;AAAA,cACb,cAAc;AAAA,YAChB,CAAC;AAAA,UACH;AAAA,QACF;AACA,YAAI,GAAG,QAAQ,mBAAmB;AAClC,YAAI,GAAG,UAAU,mBAAmB;AACpC,YAAI,GAAG,SAAS,MAAM;AACpB,gBAAM,OAAO,IAAI,UAAU;AAC3B,sBAAY,eAAe,UAAU;AAAA,YACnC,GAAG;AAAA,YACH,aAAa,IAAI,YAAY;AAAA,YAC7B,cAAc,IAAI,aAAa;AAAA,UACjC,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAEA,UAAI,YAAY,qBAAqB,CAAC,EAAE,IAAI,MAAM;AAChD,YAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;AAC3D,eAAK,sBAAM,aAAa,GAAG;AAAA,QAC7B;AACA,eAAO,EAAE,QAAQ,OAAO;AAAA,MAC1B,CAAC;AAED,UAAI,YAAY,GAAG,iBAAiB,CAAC,OAAO,QAAQ;AAClD,YAAI,QAAQ,IAAI,yBAAyB,IAAI,WAAW,QAAQ,IAAI,qBAAqB,GAAG;AAC1F;AAAA,QACF;AACA,YAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;AAC3D,gBAAM,eAAe;AACrB,eAAK,sBAAM,aAAa,GAAG;AAAA,QAC7B;AAAA,MACF,CAAC;AAED,YAAM,qBAAiB,yBAAU,WAAW,EAAE,MAAM,YAAY,QAAQ,EAAE;AAC1E,YAAM,WAAgE;AAAA,QACpE,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AAEA,UAAI,YAAY,GAAG,mBAAmB,CAAC,QAAQ,OAAO,SAAS,MAAM,aAAa;AAChF,cAAM,SAAS,SAAS,KAAK,KAAK;AAClC,uBAAe,MAAM,EAAE,SAAS,EAAE,QAAQ,UAAU,MAAM,SAAS,CAAC;AAAA,MACtE,CAAC;AAED,UAAI,QAAQ,IAAI,uBAAuB;AACrC,aAAK,IAAI,QAAQ,QAAQ,IAAI,qBAAqB;AAAA,MACpD,OAAO;AACL,aAAK,IAAI,SAAS,YAAY;AAAA,MAChC;AAEA,UAAI,GAAG,UAAU,MAAM;AACrB,gBAAQ,OAAO,QAAQ;AACvB,YAAI,aAAa,QAAQ;AACvB,uBAAa;AAAA,QACf;AAAA,MACF,CAAC;AAED,aAAO,KAAK,kBAAkB,EAAE,SAAS,CAAC;AAC1C,aAAO;AAAA,IACT;AAEA,UAAM,iBAAiB,OAAO,UAAkB,UAAiC;AAC/E,YAAM,MAAM,aAAa,QAAQ;AACjC,YAAM,mBAAmB,GAAG;AAC5B,UAAI,YAAY,KAAK,sBAAsB,EAAE,MAAM,CAAC;AACpD,UAAI,KAAK;AACT,UAAI,MAAM;AAAA,IACZ;AAEA,UAAM,oBAAoB,CACxB,SACA,MACAC,aACS;AACT,YAAM,eAAe,QAAQ,WAAW,UAAU,IAC9C,UACA,WAAW,OAAO;AAEtB,UAAIA,UAAS,UAAU;AACrB,cAAM,SAAS,UAAUA,SAAQ,QAAQ;AACzC,YAAI,UAAU,CAAC,OAAO,YAAY,GAAG;AACnC,iBAAO,YAAY,KAAK,cAAc,IAAI;AAAA,QAC5C;AACA;AAAA,MACF;AAEA,iBAAW,CAAC,UAAU,GAAG,KAAK,QAAQ,QAAQ,GAAG;AAC/C,YAAI,IAAI,YAAY,GAAG;AACrB,kBAAQ,OAAO,QAAQ;AACvB;AAAA,QACF;AACA,YAAI,YAAY,KAAK,cAAc,IAAI;AAAA,MACzC;AAAA,IACF;AAEA,wCAAiB,iBAAiB;AAElC,UAAM,YAAY,CAAC,SAAiB,SAAwB;AAC1D,iBAAW,CAAC,UAAU,GAAG,KAAK,QAAQ,QAAQ,GAAG;AAC/C,YAAI,IAAI,YAAY,GAAG;AACrB,kBAAQ,OAAO,QAAQ;AACvB;AAAA,QACF;AACA,YAAI,YAAY,KAAK,SAAS,IAAI;AAAA,MACpC;AAAA,IACF;AAEA,qBAAiB;AAAA,MACf,GAAG;AAAA,MACH;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,wBAAwB,CAAC,aAAa;AACpC,qEAAyB,EAAE,YAAY,QAAQ;AAAA,MACjD;AAAA,MACA,wBAAwB,UAAM,yDAAyB,EAAE,YAAY;AAAA,MACrE;AAAA,IACF;AAEA,UAAM,YAAY,YAAY;AAC5B,aAAO,KAAK,oBAAoB;AAChC,YAAM,sBAAkB,4CAAmB,YAAAF,QAAK,KAAK,gBAAAC,IAAY,QAAQ,UAAU,GAAG,MAAM,CAAC;AAC7F,qBAAgB,kBAAkB;AAClC,aAAO,KAAK,+BAA+B,EAAE,gBAAgB,CAAC;AAE9D,UAAI,QAAQ,UAAU;AACpB,cAAM,aAAa,MAAM,QAAQ,SAAS,eAAe,cAAe;AACxE,YAAI,OAAO,eAAe,YAAY;AACpC,4BAAkB;AAAA,QACpB;AAAA,MACF;AAEA,gBAAM,eAAAE,SAAY;AAAA,QAChB;AAAA,QACA;AAAA,QACA,iBAAiB,QAAQ;AAAA,QACzB,gBAAgB,mBAAmB,QAAQ,cAAc;AAAA,QACzD,eAAe,mBAAmB,QAAQ,aAAa;AAAA,QACvD,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,QACxE,QAAQ,QAAQ,WAAW,YAAY;AACrC,gBAAM,IAAI,MAAM,2DAA2D;AAAA,QAC7E;AAAA,QACA,YAAY,OAAO,YAAY;AAC7B,gBAAM,OAAO;AACb,cAAI,CAAC,KAAK,UAAU;AAClB,kBAAM,IAAI,MAAM,8BAA8B;AAAA,UAChD;AACA,cAAI,CAAC,KAAK,OAAO;AACf,kBAAM,IAAI,MAAM,2BAA2B;AAAA,UAC7C;AACA,gBAAM,eAAe,KAAK,UAAU,KAAK,KAAK;AAC9C,iBAAO,EAAE,IAAI,KAAK;AAAA,QACpB;AAAA,QACA,UAAU,OAAO,SAAS,UAAU;AAClC,gBAAM,OAAO;AACb,gBAAM,SAAS,KAAK,UAAU;AAC9B,gBAAM,WAAW,KAAK;AAEtB,cAAI,WAAW,WAAW;AACxB,kBAAM,WAAW;AACjB,kBAAM,iBAAiB,UAAU,SAAS,0BAA0B,SAAS,OAAO,EAAE,IAAI;AAC1F,gBAAI,CAAC,gBAAgB;AACnB,qBAAO,EAAE,IAAI,MAAM;AAAA,YACrB;AACA,kBAAM,kBAAkB,iBAAiB,QAAQ,YAAY,cAAc;AAC3E,mBAAO;AAAA,cACL,IAAI;AAAA,cACJ,UAAU;AAAA,cACV,cAAc,gBAAgB;AAAA,YAChC;AAAA,UACF;AAEA,cAAI,WAAW,QAAQ;AACrB,gBAAI,CAAC,UAAU;AACb,oBAAM,IAAI,MAAM,iCAAiC;AAAA,YACnD;AACA,yBAAa,QAAQ;AACrB,mBAAO,EAAE,IAAI,KAAK;AAAA,UACpB;AAEA,gBAAM,MAAM,WAAW,UAAU,QAAQ,IAAI;AAC7C,cAAI,CAAC,KAAK;AACR,mBAAO,EAAE,IAAI,MAAM;AAAA,UACrB;AAEA,kBAAQ,QAAQ;AAAA,YACd,KAAK;AACH,kBAAI,MAAM;AACV,qBAAO,EAAE,IAAI,KAAK;AAAA,YACpB,KAAK;AACH,kBAAI,KAAK;AACT,kBAAI,MAAM;AACV,qBAAO,EAAE,IAAI,KAAK;AAAA,YACpB,KAAK;AACH,kBAAI,SAAS;AACb,qBAAO,EAAE,IAAI,KAAK;AAAA,YACpB,KAAK;AACH,kBAAI,YAAY,IAAI,IAAI,WAAW,IAAI,IAAI,SAAS;AACpD,qBAAO,EAAE,IAAI,KAAK;AAAA,YACpB;AACE,qBAAO,EAAE,IAAI,MAAM;AAAA,UACvB;AAAA,QACF;AAAA,QACA,SAAS,OAAO,YAAY;AAC1B,gBAAM,OAAO;AACb,gBAAM,SAAS,MAAM,UAAU;AAE/B,cAAI,WAAW,WAAW;AACxB,mBAAO,iBAAiB;AAAA,UAC1B;AAEA,iBAAO,EAAE,IAAI,MAAM;AAAA,QACrB;AAAA,QACA,eAAe,QAAQ,kBAAkB,OAAO,UAAmB,UAAoB;AACrF,gBAAM,WAAW;AACjB,gBAAM,WAAW,UAAU,QAAQ;AACnC,wBAAc,MAAM,0BAA0B,EAAE,SAAS,CAAC;AAC1D,iBAAO,EAAE,IAAI,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AAED,mBAAa,MAAM;AACnB,aAAO,KAAK,oBAAoB;AAAA,IAClC;AAEA,SAAK,gBAAAF,IAAY,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,CAAC,UAAU;AAC5D,aAAO,MAAM,oBAAoB;AAAA,QAC/B,SAAS,eAAe,KAAK;AAAA,QAC7B,OAAO,iBAAiB,QAAQ,MAAM,QAAQ;AAAA,MAChD,CAAC;AAAA,IACH,CAAC;AAED,oBAAAA,IAAY,GAAG,eAAe,MAAM;AAClC,UAAI,CAAC,gBAAgB;AACnB;AAAA,MACF;AAEA,YAAM,YAAY;AAChB,YAAI;AACF,gDAAmB;AACnB,cAAI,iBAAiB;AACnB,kBAAM,gBAAgB;AAAA,UACxB;AACA,cAAI,QAAQ,cAAc;AACxB,kBAAM,QAAQ,aAAa,cAAe;AAAA,UAC5C;AAAA,QACF,SAAS,OAAO;AACd,iBAAO,KAAK,8BAA8B,EAAE,OAAO,eAAe,KAAK,EAAE,CAAC;AAAA,QAC5E;AAAA,MACF,GAAG;AAEH,iBAAW,CAAC,IAAI,GAAG,KAAK,SAAS;AAC/B,YAAI,CAAC,IAAI,YAAY,GAAG;AACtB,cAAI,QAAQ;AACZ,iBAAO,MAAM,4BAA4B,EAAE,UAAU,GAAG,CAAC;AAAA,QAC3D;AAAA,MACF;AACA,cAAQ,MAAM;AAAA,IAChB,CAAC;AAED,oBAAAA,IAAY,GAAG,qBAAqB,MAAM;AACxC,sBAAAA,IAAY,KAAK;AAAA,IACnB,CAAC;AAED,oBAAAA,IAAY,GAAG,YAAY,MAAM;AAC/B,UAAI,8BAAc,cAAc,EAAE,WAAW,GAAG;AAC9C,qBAAa,MAAM;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,EAAE,MAAM,CAAC,UAAU;AACpB,WAAO,MAAM,+BAA+B;AAAA,MAC1C,SAAS,eAAe,KAAK;AAAA,MAC7B,OAAO,iBAAiB,QAAQ,MAAM,QAAQ;AAAA,IAChD,CAAC;AAAA,EACH,CAAC;AACH;","names":["path","electronApp","options","bootRuntime"]}
1
+ {"version":3,"sources":["../../src/main/index.ts"],"sourcesContent":["import path from 'path';\nimport {\n app as electronApp,\n BrowserWindow,\n ipcMain,\n MessageChannelMain,\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} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { getContainer } from '../runtime/get_container.js';\nimport { runtime as bootRuntime } from '../runtime/runtime.js';\nimport { dispatchRendererTracePayload, type TraceLevel } from '../tracing/index.js';\nimport { setApplicationRoot } from '../runtime/application_root.js';\nimport type { RegisterRuntimeRouter } from '../runtime/runtime.js';\nimport {\n getRendererLogDispatcher,\n type RendererLogCallback,\n} from '../runtime/renderer_log_dispatcher.js';\nimport { Ipc, getIpc } from '../ipc/index.js';\nimport { setIpcPushSender, clearIpcPushSender, type IpcPushOptions } from '../ipc/shared.js';\n\ntype CleanupHandler = () => void | Promise<void>;\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};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\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 };\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 setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\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 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 traceTransport?: 'ipc' | 'messageport';\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\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 debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\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\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 },\n logger,\n };\n\n void (async () => {\n const appBootResult = options.appBoot\n ? await options.appBoot(bootContext)\n : undefined;\n\n const bootOverrides = (appBootResult ?? {}) as WoodMainAppBootResult;\n const scope = bootOverrides.scope ?? options.scope ?? process.cwd();\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 container = getContainer({\n scope,\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, () => getIpc());\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 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 });\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 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 show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(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 };\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 if (windowId === 'main') {\n mainWindow = null;\n }\n if (windows.size === 0) {\n electronApp.quit();\n }\n });\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n win.webContents.send('__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 && !target.isDestroyed()) {\n target.webContents.send(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 win.webContents.send(eventChannel, data);\n }\n };\n\n 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 win.webContents.send(channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n setRendererLogCallback: (callback) => {\n getRendererLogDispatcher().setCallback(callback);\n },\n getRendererLogCallback: () => getRendererLogDispatcher().getCallback(),\n getDebugMetadata,\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 await bootRuntime({\n container,\n ipcMain,\n registerRuntime: options.registerRuntime,\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 });\n\n createWindow('main');\n logger.info('bootstrap complete');\n };\n\n void electronApp.whenReady().then(bootstrap).catch((error) => {\n logger.error('bootstrap failed', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\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 clearIpcPushSender();\n if (mainBootCleanup) {\n await mainBootCleanup();\n }\n if (options.onBeforeQuit) {\n await options.onBeforeQuit(runtimeContext!);\n }\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 if (BrowserWindow.getAllWindows().length === 0) {\n createWindow('main');\n }\n });\n })().catch((error) => {\n logger.error('startup failed before ready', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n });\n}\n"],"mappings":"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","names":["path","electronApp","options","bootRuntime"]}
@@ -77,6 +77,9 @@ interface StartWoodMainOptions {
77
77
  onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;
78
78
  onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;
79
79
  traceMinLevel?: TraceLevel;
80
+ rendererTraceEnabled?: boolean;
81
+ rendererTraceMinLevel?: TraceLevel;
82
+ traceTransport?: 'ipc' | 'messageport';
80
83
  }
81
84
  interface WoodDebugWindowMetadata {
82
85
  windowId: string;
@@ -77,6 +77,9 @@ interface StartWoodMainOptions {
77
77
  onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;
78
78
  onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;
79
79
  traceMinLevel?: TraceLevel;
80
+ rendererTraceEnabled?: boolean;
81
+ rendererTraceMinLevel?: TraceLevel;
82
+ traceTransport?: 'ipc' | 'messageport';
80
83
  }
81
84
  interface WoodDebugWindowMetadata {
82
85
  windowId: string;
@@ -3,6 +3,7 @@ import {
3
3
  app as electronApp,
4
4
  BrowserWindow,
5
5
  ipcMain,
6
+ MessageChannelMain,
6
7
  protocol,
7
8
  net,
8
9
  shell,
@@ -12,6 +13,7 @@ import { getLogger } from "@noego/logger";
12
13
  import { WindowStateStore } from "../window/window_state_store.js";
13
14
  import { getContainer } from "../runtime/get_container.js";
14
15
  import { runtime as bootRuntime } from "../runtime/runtime.js";
16
+ import { dispatchRendererTracePayload } from "../tracing/index.js";
15
17
  import { setApplicationRoot } from "../runtime/application_root.js";
16
18
  import {
17
19
  getRendererLogDispatcher
@@ -59,6 +61,8 @@ function startWoodMain(options) {
59
61
  const windowState = new WindowStateStore();
60
62
  const windows = /* @__PURE__ */ new Map();
61
63
  let mainWindow = null;
64
+ let beforeQuitCleanupComplete = false;
65
+ let beforeQuitCleanupPromise = null;
62
66
  let runtimeContext = null;
63
67
  let mainBootCleanup;
64
68
  for (const stream of [process.stdout, process.stderr]) {
@@ -278,11 +282,32 @@ function startWoodMain(options) {
278
282
  } else {
279
283
  void win.loadFile(rendererFile);
280
284
  }
285
+ if (options.traceTransport === "messageport" && options.rendererTraceEnabled === true) {
286
+ const { port1, port2 } = new MessageChannelMain();
287
+ const portHandler = (event) => {
288
+ dispatchRendererTracePayload(event.data);
289
+ };
290
+ port1.on("message", portHandler);
291
+ port1.start();
292
+ win.webContents.once("did-finish-load", () => {
293
+ if (!win.isDestroyed()) {
294
+ win.webContents.postMessage("__trace-port", null, [port2]);
295
+ logger.info("trace messageport sent to renderer", { windowId });
296
+ }
297
+ });
298
+ win.on("closed", () => {
299
+ port1.off("message", portHandler);
300
+ port1.close();
301
+ });
302
+ }
281
303
  win.on("closed", () => {
282
304
  windows.delete(windowId);
283
305
  if (windowId === "main") {
284
306
  mainWindow = null;
285
307
  }
308
+ if (windows.size === 0) {
309
+ electronApp.quit();
310
+ }
286
311
  });
287
312
  logger.info("window created", { windowId });
288
313
  return win;
@@ -353,6 +378,9 @@ function startWoodMain(options) {
353
378
  controllersDir: resolveRuntimePath(options.controllersDir),
354
379
  middlewareDir: resolveRuntimePath(options.middlewareDir),
355
380
  ...options.traceMinLevel ? { traceMinLevel: options.traceMinLevel } : {},
381
+ ...typeof options.rendererTraceEnabled === "boolean" ? { rendererTraceEnabled: options.rendererTraceEnabled } : {},
382
+ ...options.rendererTraceMinLevel ? { rendererTraceMinLevel: options.rendererTraceMinLevel } : {},
383
+ ...options.traceTransport === "messageport" ? { enableRendererTraceBridge: false } : {},
356
384
  onLoad: options.onLoad ?? (async () => {
357
385
  throw new Error('Reserved channel "__load" is not configured for this app.');
358
386
  }),
@@ -437,30 +465,48 @@ function startWoodMain(options) {
437
465
  stack: error instanceof Error ? error.stack : void 0
438
466
  });
439
467
  });
440
- electronApp.on("before-quit", () => {
468
+ const destroyWindowsForQuit = () => {
469
+ for (const [id, win] of windows) {
470
+ if (!win.isDestroyed()) {
471
+ win.destroy();
472
+ logger.debug("destroyed window on quit", { windowId: id });
473
+ }
474
+ }
475
+ windows.clear();
476
+ };
477
+ const runBeforeQuitCleanup = async () => {
478
+ clearIpcPushSender();
479
+ if (mainBootCleanup) {
480
+ await mainBootCleanup();
481
+ }
482
+ if (options.onBeforeQuit) {
483
+ await options.onBeforeQuit(runtimeContext);
484
+ }
485
+ };
486
+ electronApp.on("before-quit", (event) => {
441
487
  if (!runtimeContext) {
442
488
  return;
443
489
  }
444
- void (async () => {
490
+ if (beforeQuitCleanupComplete) {
491
+ destroyWindowsForQuit();
492
+ return;
493
+ }
494
+ event.preventDefault();
495
+ if (beforeQuitCleanupPromise) {
496
+ return;
497
+ }
498
+ beforeQuitCleanupPromise = (async () => {
445
499
  try {
446
- clearIpcPushSender();
447
- if (mainBootCleanup) {
448
- await mainBootCleanup();
449
- }
450
- if (options.onBeforeQuit) {
451
- await options.onBeforeQuit(runtimeContext);
452
- }
500
+ await runBeforeQuitCleanup();
453
501
  } catch (error) {
454
502
  logger.warn("before-quit handler failed", { error: toErrorMessage(error) });
503
+ } finally {
504
+ beforeQuitCleanupComplete = true;
505
+ beforeQuitCleanupPromise = null;
455
506
  }
507
+ destroyWindowsForQuit();
508
+ electronApp.quit();
456
509
  })();
457
- for (const [id, win] of windows) {
458
- if (!win.isDestroyed()) {
459
- win.destroy();
460
- logger.debug("destroyed window on quit", { windowId: id });
461
- }
462
- }
463
- windows.clear();
464
510
  });
465
511
  electronApp.on("window-all-closed", () => {
466
512
  electronApp.quit();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/main/index.ts"],"sourcesContent":["import path from 'path';\nimport {\n app as electronApp,\n BrowserWindow,\n ipcMain,\n protocol,\n net,\n shell,\n screen,\n} from 'electron';\nimport type {\n BrowserWindowConstructorOptions,\n BrowserWindow as ElectronBrowserWindow,\n IpcMainInvokeEvent,\n Privileges,\n} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { getContainer } from '../runtime/get_container.js';\nimport { runtime as bootRuntime } from '../runtime/runtime.js';\nimport type { TraceLevel } from '../tracing/index.js';\nimport { setApplicationRoot } from '../runtime/application_root.js';\nimport type { RegisterRuntimeRouter } from '../runtime/runtime.js';\nimport {\n getRendererLogDispatcher,\n type RendererLogCallback,\n} from '../runtime/renderer_log_dispatcher.js';\nimport { Ipc, getIpc } from '../ipc/index.js';\nimport { setIpcPushSender, clearIpcPushSender, type IpcPushOptions } from '../ipc/shared.js';\n\ntype CleanupHandler = () => void | Promise<void>;\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};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\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 };\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 setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\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 onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;\n onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;\n traceMinLevel?: TraceLevel;\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\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 debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\n let mainWindow: ElectronBrowserWindow | null = null;\n let runtimeContext: WoodMainRuntimeContext | null = null;\n let mainBootCleanup: CleanupHandler | undefined;\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 },\n logger,\n };\n\n void (async () => {\n const appBootResult = options.appBoot\n ? await options.appBoot(bootContext)\n : undefined;\n\n const bootOverrides = (appBootResult ?? {}) as WoodMainAppBootResult;\n const scope = bootOverrides.scope ?? options.scope ?? process.cwd();\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 container = getContainer({\n scope,\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, () => getIpc());\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 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 });\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 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 show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(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 win.on('closed', () => {\n windows.delete(windowId);\n if (windowId === 'main') {\n mainWindow = null;\n }\n });\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n win.webContents.send('__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 && !target.isDestroyed()) {\n target.webContents.send(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 win.webContents.send(eventChannel, data);\n }\n };\n\n 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 win.webContents.send(channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n setRendererLogCallback: (callback) => {\n getRendererLogDispatcher().setCallback(callback);\n },\n getRendererLogCallback: () => getRendererLogDispatcher().getCallback(),\n getDebugMetadata,\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 await bootRuntime({\n container,\n ipcMain,\n registerRuntime: options.registerRuntime,\n controllersDir: resolveRuntimePath(options.controllersDir),\n middlewareDir: resolveRuntimePath(options.middlewareDir),\n ...(options.traceMinLevel ? { traceMinLevel: options.traceMinLevel } : {}),\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 });\n\n createWindow('main');\n logger.info('bootstrap complete');\n };\n\n void electronApp.whenReady().then(bootstrap).catch((error) => {\n logger.error('bootstrap failed', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n });\n\n electronApp.on('before-quit', () => {\n if (!runtimeContext) {\n return;\n }\n\n void (async () => {\n try {\n clearIpcPushSender();\n if (mainBootCleanup) {\n await mainBootCleanup();\n }\n if (options.onBeforeQuit) {\n await options.onBeforeQuit(runtimeContext!);\n }\n } catch (error) {\n logger.warn('before-quit handler failed', { error: toErrorMessage(error) });\n }\n })();\n\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 electronApp.on('window-all-closed', () => {\n electronApp.quit();\n });\n\n electronApp.on('activate', () => {\n if (BrowserWindow.getAllWindows().length === 0) {\n createWindow('main');\n }\n });\n })().catch((error) => {\n logger.error('startup failed before ready', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n });\n}\n"],"mappings":"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","names":["options"]}
1
+ {"version":3,"sources":["../../src/main/index.ts"],"sourcesContent":["import path from 'path';\nimport {\n app as electronApp,\n BrowserWindow,\n ipcMain,\n MessageChannelMain,\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} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { getContainer } from '../runtime/get_container.js';\nimport { runtime as bootRuntime } from '../runtime/runtime.js';\nimport { dispatchRendererTracePayload, type TraceLevel } from '../tracing/index.js';\nimport { setApplicationRoot } from '../runtime/application_root.js';\nimport type { RegisterRuntimeRouter } from '../runtime/runtime.js';\nimport {\n getRendererLogDispatcher,\n type RendererLogCallback,\n} from '../runtime/renderer_log_dispatcher.js';\nimport { Ipc, getIpc } from '../ipc/index.js';\nimport { setIpcPushSender, clearIpcPushSender, type IpcPushOptions } from '../ipc/shared.js';\n\ntype CleanupHandler = () => void | Promise<void>;\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};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\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 };\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 setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\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 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 traceTransport?: 'ipc' | 'messageport';\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\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 debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\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\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 },\n logger,\n };\n\n void (async () => {\n const appBootResult = options.appBoot\n ? await options.appBoot(bootContext)\n : undefined;\n\n const bootOverrides = (appBootResult ?? {}) as WoodMainAppBootResult;\n const scope = bootOverrides.scope ?? options.scope ?? process.cwd();\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 container = getContainer({\n scope,\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, () => getIpc());\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 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 });\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 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 show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(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 };\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 if (windowId === 'main') {\n mainWindow = null;\n }\n if (windows.size === 0) {\n electronApp.quit();\n }\n });\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n win.webContents.send('__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 && !target.isDestroyed()) {\n target.webContents.send(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 win.webContents.send(eventChannel, data);\n }\n };\n\n 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 win.webContents.send(channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n setRendererLogCallback: (callback) => {\n getRendererLogDispatcher().setCallback(callback);\n },\n getRendererLogCallback: () => getRendererLogDispatcher().getCallback(),\n getDebugMetadata,\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 await bootRuntime({\n container,\n ipcMain,\n registerRuntime: options.registerRuntime,\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 });\n\n createWindow('main');\n logger.info('bootstrap complete');\n };\n\n void electronApp.whenReady().then(bootstrap).catch((error) => {\n logger.error('bootstrap failed', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\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 clearIpcPushSender();\n if (mainBootCleanup) {\n await mainBootCleanup();\n }\n if (options.onBeforeQuit) {\n await options.onBeforeQuit(runtimeContext!);\n }\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 if (BrowserWindow.getAllWindows().length === 0) {\n createWindow('main');\n }\n });\n })().catch((error) => {\n logger.error('startup failed before ready', {\n message: toErrorMessage(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n });\n}\n"],"mappings":"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","names":["options"]}
@@ -0,0 +1,43 @@
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 navigation_identity_exports = {};
20
+ __export(navigation_identity_exports, {
21
+ createNavigationMountKey: () => createNavigationMountKey,
22
+ hashNavigationParams: () => hashNavigationParams
23
+ });
24
+ module.exports = __toCommonJS(navigation_identity_exports);
25
+ function normalizeParamValue(value) {
26
+ if (value === null || value === void 0) {
27
+ return "";
28
+ }
29
+ return String(value);
30
+ }
31
+ function hashNavigationParams(params = {}) {
32
+ const normalizedEntries = Object.entries(params).map(([key, value]) => [String(key), normalizeParamValue(value)]).sort(([left], [right]) => left.localeCompare(right));
33
+ return JSON.stringify(normalizedEntries);
34
+ }
35
+ function createNavigationMountKey(page, params = {}) {
36
+ return `${page}:${hashNavigationParams(params)}`;
37
+ }
38
+ // Annotate the CommonJS export names for ESM import in node:
39
+ 0 && (module.exports = {
40
+ createNavigationMountKey,
41
+ hashNavigationParams
42
+ });
43
+ //# sourceMappingURL=navigation_identity.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/navigation/navigation_identity.ts"],"sourcesContent":["export type NavigationParams = Record<string, unknown>;\n\nfunction normalizeParamValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '';\n }\n return String(value);\n}\n\nexport function hashNavigationParams(params: NavigationParams = {}): string {\n const normalizedEntries = Object.entries(params)\n .map(([key, value]) => [String(key), normalizeParamValue(value)] as const)\n .sort(([left], [right]) => left.localeCompare(right));\n\n return JSON.stringify(normalizedEntries);\n}\n\nexport function createNavigationMountKey(\n page: string,\n params: NavigationParams = {},\n): string {\n return `${page}:${hashNavigationParams(params)}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,SAAS,oBAAoB,OAAwB;AACnD,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AACA,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,qBAAqB,SAA2B,CAAC,GAAW;AAC1E,QAAM,oBAAoB,OAAO,QAAQ,MAAM,EAC5C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,GAAG,GAAG,oBAAoB,KAAK,CAAC,CAAU,EACxE,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,MAAM,KAAK,cAAc,KAAK,CAAC;AAEtD,SAAO,KAAK,UAAU,iBAAiB;AACzC;AAEO,SAAS,yBACd,MACA,SAA2B,CAAC,GACpB;AACR,SAAO,GAAG,IAAI,IAAI,qBAAqB,MAAM,CAAC;AAChD;","names":[]}
@@ -0,0 +1,5 @@
1
+ type NavigationParams = Record<string, unknown>;
2
+ declare function hashNavigationParams(params?: NavigationParams): string;
3
+ declare function createNavigationMountKey(page: string, params?: NavigationParams): string;
4
+
5
+ export { type NavigationParams, createNavigationMountKey, hashNavigationParams };
@@ -0,0 +1,5 @@
1
+ type NavigationParams = Record<string, unknown>;
2
+ declare function hashNavigationParams(params?: NavigationParams): string;
3
+ declare function createNavigationMountKey(page: string, params?: NavigationParams): string;
4
+
5
+ export { type NavigationParams, createNavigationMountKey, hashNavigationParams };
@@ -0,0 +1,18 @@
1
+ function normalizeParamValue(value) {
2
+ if (value === null || value === void 0) {
3
+ return "";
4
+ }
5
+ return String(value);
6
+ }
7
+ function hashNavigationParams(params = {}) {
8
+ const normalizedEntries = Object.entries(params).map(([key, value]) => [String(key), normalizeParamValue(value)]).sort(([left], [right]) => left.localeCompare(right));
9
+ return JSON.stringify(normalizedEntries);
10
+ }
11
+ function createNavigationMountKey(page, params = {}) {
12
+ return `${page}:${hashNavigationParams(params)}`;
13
+ }
14
+ export {
15
+ createNavigationMountKey,
16
+ hashNavigationParams
17
+ };
18
+ //# sourceMappingURL=navigation_identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/navigation/navigation_identity.ts"],"sourcesContent":["export type NavigationParams = Record<string, unknown>;\n\nfunction normalizeParamValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '';\n }\n return String(value);\n}\n\nexport function hashNavigationParams(params: NavigationParams = {}): string {\n const normalizedEntries = Object.entries(params)\n .map(([key, value]) => [String(key), normalizeParamValue(value)] as const)\n .sort(([left], [right]) => left.localeCompare(right));\n\n return JSON.stringify(normalizedEntries);\n}\n\nexport function createNavigationMountKey(\n page: string,\n params: NavigationParams = {},\n): string {\n return `${page}:${hashNavigationParams(params)}`;\n}\n"],"mappings":"AAEA,SAAS,oBAAoB,OAAwB;AACnD,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AACA,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,qBAAqB,SAA2B,CAAC,GAAW;AAC1E,QAAM,oBAAoB,OAAO,QAAQ,MAAM,EAC5C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,GAAG,GAAG,oBAAoB,KAAK,CAAC,CAAU,EACxE,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,MAAM,KAAK,cAAc,KAAK,CAAC;AAEtD,SAAO,KAAK,UAAU,iBAAiB;AACzC;AAEO,SAAS,yBACd,MACA,SAA2B,CAAC,GACpB;AACR,SAAO,GAAG,IAAI,IAAI,qBAAqB,MAAM,CAAC;AAChD;","names":[]}