@noego/wood 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/bin/wood.js +12 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/type_generator.cjs +34 -9
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +34 -9
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/dev/wood_dev_server.cjs +2 -0
- package/dist/dev/wood_dev_server.cjs.map +1 -1
- package/dist/dev/wood_dev_server.js +2 -0
- package/dist/dev/wood_dev_server.js.map +1 -1
- package/dist/dev/yaml_watcher.cjs +8 -1
- package/dist/dev/yaml_watcher.cjs.map +1 -1
- package/dist/dev/yaml_watcher.d.cts +1 -0
- package/dist/dev/yaml_watcher.d.ts +1 -0
- package/dist/dev/yaml_watcher.js +8 -1
- package/dist/dev/yaml_watcher.js.map +1 -1
- package/dist/main/index.cjs +154 -7
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +28 -2
- package/dist/main/index.d.ts +28 -2
- package/dist/main/index.js +155 -7
- package/dist/main/index.js.map +1 -1
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +12 -0
- package/dist/types/config.d.ts +12 -0
- package/dist/window/window_manager.cjs +3 -0
- package/dist/window/window_manager.cjs.map +1 -1
- package/dist/window/window_manager.js +3 -0
- package/dist/window/window_manager.js.map +1 -1
- package/package.json +1 -1
package/dist/main/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BrowserWindow,
|
|
5
5
|
ipcMain,
|
|
6
6
|
MessageChannelMain,
|
|
7
|
+
Menu,
|
|
7
8
|
protocol,
|
|
8
9
|
net,
|
|
9
10
|
shell,
|
|
@@ -60,11 +61,13 @@ function startWoodMain(options) {
|
|
|
60
61
|
const debugConfig = resolveWoodDebugMetadataConfiguration();
|
|
61
62
|
const windowState = new WindowStateStore();
|
|
62
63
|
const windows = /* @__PURE__ */ new Map();
|
|
64
|
+
const rendererHealth = /* @__PURE__ */ new Map();
|
|
63
65
|
let mainWindow = null;
|
|
64
66
|
let beforeQuitCleanupComplete = false;
|
|
65
67
|
let beforeQuitCleanupPromise = null;
|
|
66
68
|
let runtimeContext = null;
|
|
67
69
|
let mainBootCleanup;
|
|
70
|
+
let nativeQuitFallbackInstalled = false;
|
|
68
71
|
for (const stream of [process.stdout, process.stderr]) {
|
|
69
72
|
stream.on("error", (error) => {
|
|
70
73
|
if (error.code === "EPIPE") {
|
|
@@ -89,7 +92,8 @@ function startWoodMain(options) {
|
|
|
89
92
|
protocol,
|
|
90
93
|
net,
|
|
91
94
|
shell,
|
|
92
|
-
screen
|
|
95
|
+
screen,
|
|
96
|
+
Menu
|
|
93
97
|
},
|
|
94
98
|
logger
|
|
95
99
|
};
|
|
@@ -140,6 +144,129 @@ function startWoodMain(options) {
|
|
|
140
144
|
}
|
|
141
145
|
return null;
|
|
142
146
|
};
|
|
147
|
+
const getRendererHealthState = (windowId) => {
|
|
148
|
+
return rendererHealth.get(windowId) ?? null;
|
|
149
|
+
};
|
|
150
|
+
const notifyRendererHealthChanged = (event) => {
|
|
151
|
+
if (!options.onRendererHealthChanged) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!runtimeContext) {
|
|
155
|
+
runtimeLogger.warn("renderer health hook skipped before runtime context", {
|
|
156
|
+
windowId: event.windowId,
|
|
157
|
+
state: event.state
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
Promise.resolve(options.onRendererHealthChanged(event, runtimeContext)).catch((error) => {
|
|
162
|
+
runtimeLogger.warn("renderer health hook failed", {
|
|
163
|
+
windowId: event.windowId,
|
|
164
|
+
state: event.state,
|
|
165
|
+
error: toErrorMessage(error)
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
const setRendererHealth = (windowId, state, details = {}) => {
|
|
170
|
+
const previousState = rendererHealth.get(windowId) ?? null;
|
|
171
|
+
rendererHealth.set(windowId, state);
|
|
172
|
+
notifyRendererHealthChanged({
|
|
173
|
+
windowId,
|
|
174
|
+
state,
|
|
175
|
+
previousState,
|
|
176
|
+
...details
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
const attachRendererHealth = (windowId, win) => {
|
|
180
|
+
const healthLogger = getLogger("wood:main").named(`renderer-health:${windowId}`);
|
|
181
|
+
const webContentsId = win.webContents.id;
|
|
182
|
+
setRendererHealth(windowId, "loading", { webContentsId });
|
|
183
|
+
win.webContents.on("did-start-loading", () => {
|
|
184
|
+
setRendererHealth(windowId, "loading", { webContentsId });
|
|
185
|
+
healthLogger.info("renderer loading", { windowId, webContentsId });
|
|
186
|
+
});
|
|
187
|
+
win.webContents.on("did-finish-load", () => {
|
|
188
|
+
setRendererHealth(windowId, "ready", { webContentsId });
|
|
189
|
+
healthLogger.info("renderer ready", { windowId, webContentsId });
|
|
190
|
+
});
|
|
191
|
+
win.webContents.on("unresponsive", () => {
|
|
192
|
+
setRendererHealth(windowId, "unresponsive", { webContentsId });
|
|
193
|
+
healthLogger.error("renderer unresponsive", { windowId, webContentsId });
|
|
194
|
+
});
|
|
195
|
+
win.webContents.on("responsive", () => {
|
|
196
|
+
setRendererHealth(windowId, "ready", { webContentsId });
|
|
197
|
+
healthLogger.info("renderer responsive", { windowId, webContentsId });
|
|
198
|
+
});
|
|
199
|
+
win.webContents.on("render-process-gone", (_event, details) => {
|
|
200
|
+
setRendererHealth(windowId, "gone", {
|
|
201
|
+
webContentsId,
|
|
202
|
+
reason: details.reason,
|
|
203
|
+
exitCode: details.exitCode
|
|
204
|
+
});
|
|
205
|
+
healthLogger.error("renderer process gone", {
|
|
206
|
+
windowId,
|
|
207
|
+
webContentsId,
|
|
208
|
+
reason: details.reason,
|
|
209
|
+
exitCode: details.exitCode
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
win.webContents.on("destroyed", () => {
|
|
213
|
+
setRendererHealth(windowId, "destroyed");
|
|
214
|
+
healthLogger.warn("renderer destroyed", { windowId, webContentsId });
|
|
215
|
+
});
|
|
216
|
+
win.on("closed", () => {
|
|
217
|
+
setRendererHealth(windowId, "destroyed");
|
|
218
|
+
rendererHealth.delete(windowId);
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
const canSendToWindow = (win) => {
|
|
222
|
+
if (win.isDestroyed()) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (win.webContents.isDestroyed()) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (typeof win.webContents.isCrashed === "function" && win.webContents.isCrashed()) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
};
|
|
233
|
+
const safeSend = (windowId, win, channel, data) => {
|
|
234
|
+
if (!canSendToWindow(win)) {
|
|
235
|
+
runtimeLogger.warn("skipping send to unhealthy renderer", {
|
|
236
|
+
windowId,
|
|
237
|
+
channel,
|
|
238
|
+
rendererHealth: getRendererHealthState(windowId)
|
|
239
|
+
});
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
win.webContents.send(channel, data);
|
|
244
|
+
return true;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
runtimeLogger.warn("send to renderer failed", {
|
|
247
|
+
windowId,
|
|
248
|
+
channel,
|
|
249
|
+
rendererHealth: getRendererHealthState(windowId),
|
|
250
|
+
error: toErrorMessage(error)
|
|
251
|
+
});
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const installNativeQuitFallback = () => {
|
|
256
|
+
if (nativeQuitFallbackInstalled) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
Menu.setApplicationMenu(Menu.buildFromTemplate([
|
|
260
|
+
{
|
|
261
|
+
label: electronApp.name,
|
|
262
|
+
submenu: [
|
|
263
|
+
{ role: "quit" }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
]));
|
|
267
|
+
nativeQuitFallbackInstalled = true;
|
|
268
|
+
logger.info("native quit fallback installed");
|
|
269
|
+
};
|
|
143
270
|
const waitForWindowReady = async (win) => {
|
|
144
271
|
const webContents = win.webContents;
|
|
145
272
|
const isLoading = typeof webContents.isLoadingMainFrame === "function" ? webContents.isLoadingMainFrame() : webContents.isLoading();
|
|
@@ -162,7 +289,8 @@ function startWoodMain(options) {
|
|
|
162
289
|
title: win.getTitle(),
|
|
163
290
|
webContentsId: win.webContents.id,
|
|
164
291
|
url: win.webContents.getURL(),
|
|
165
|
-
focused: win.isFocused()
|
|
292
|
+
focused: win.isFocused(),
|
|
293
|
+
rendererHealth: getRendererHealthState(windowId) ?? void 0
|
|
166
294
|
});
|
|
167
295
|
}
|
|
168
296
|
return {
|
|
@@ -183,6 +311,9 @@ function startWoodMain(options) {
|
|
|
183
311
|
const windowDef = resolveWindowDef(options.windowDefs, windowId);
|
|
184
312
|
const defaultShow = windowDef.show ?? true;
|
|
185
313
|
const parentWindow = windowDef.parent ? getWindow(windowDef.parent) : null;
|
|
314
|
+
if (windowDef.nativeQuitFallback === true) {
|
|
315
|
+
installNativeQuitFallback();
|
|
316
|
+
}
|
|
186
317
|
windowState.setScreen(screen);
|
|
187
318
|
const savedBounds = windowDef.rememberBounds ? windowState.getBounds(windowId) : null;
|
|
188
319
|
const windowOptions = {
|
|
@@ -209,6 +340,12 @@ function startWoodMain(options) {
|
|
|
209
340
|
backgroundMaterial: windowDef.backgroundMaterial
|
|
210
341
|
} : {},
|
|
211
342
|
...typeof windowDef.acceptFirstMouse === "boolean" ? { acceptFirstMouse: windowDef.acceptFirstMouse } : {},
|
|
343
|
+
// Hidden title bar with native window controls: macOS draws the
|
|
344
|
+
// traffic lights (positionable), Windows draws min/max/close via the
|
|
345
|
+
// Window Controls Overlay (always top-right; styled via titleBarOverlay).
|
|
346
|
+
...typeof windowDef.titleBarStyle === "string" ? { titleBarStyle: windowDef.titleBarStyle } : {},
|
|
347
|
+
...process.platform === "darwin" && windowDef.trafficLightPosition ? { trafficLightPosition: windowDef.trafficLightPosition } : {},
|
|
348
|
+
...process.platform === "win32" && windowDef.titleBarOverlay != null ? { titleBarOverlay: windowDef.titleBarOverlay } : {},
|
|
212
349
|
show: savedBounds ? false : defaultShow,
|
|
213
350
|
webPreferences: {
|
|
214
351
|
preload: preloadPath,
|
|
@@ -218,6 +355,7 @@ function startWoodMain(options) {
|
|
|
218
355
|
};
|
|
219
356
|
const win = new BrowserWindow(windowOptions);
|
|
220
357
|
windows.set(windowId, win);
|
|
358
|
+
attachRendererHealth(windowId, win);
|
|
221
359
|
if (windowId === "main") {
|
|
222
360
|
mainWindow = win;
|
|
223
361
|
}
|
|
@@ -305,6 +443,7 @@ function startWoodMain(options) {
|
|
|
305
443
|
}
|
|
306
444
|
win.on("closed", () => {
|
|
307
445
|
windows.delete(windowId);
|
|
446
|
+
rendererHealth.delete(windowId);
|
|
308
447
|
if (windowId === "main") {
|
|
309
448
|
mainWindow = null;
|
|
310
449
|
}
|
|
@@ -312,13 +451,21 @@ function startWoodMain(options) {
|
|
|
312
451
|
electronApp.quit();
|
|
313
452
|
}
|
|
314
453
|
});
|
|
454
|
+
if (options.onWindowCreated) {
|
|
455
|
+
Promise.resolve(options.onWindowCreated(windowId, win, runtimeContext)).catch((error) => {
|
|
456
|
+
runtimeLogger.warn("window created hook failed", {
|
|
457
|
+
windowId,
|
|
458
|
+
error: toErrorMessage(error)
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}
|
|
315
462
|
logger.info("window created", { windowId });
|
|
316
463
|
return win;
|
|
317
464
|
};
|
|
318
465
|
const navigateWindow = async (windowId, route) => {
|
|
319
466
|
const win = createWindow(windowId);
|
|
320
467
|
await waitForWindowReady(win);
|
|
321
|
-
win
|
|
468
|
+
safeSend(windowId, win, "__event:__navigate", { route });
|
|
322
469
|
win.show();
|
|
323
470
|
win.focus();
|
|
324
471
|
};
|
|
@@ -326,8 +473,8 @@ function startWoodMain(options) {
|
|
|
326
473
|
const eventChannel = channel.startsWith("__event:") ? channel : `__event:${channel}`;
|
|
327
474
|
if (options2?.windowId) {
|
|
328
475
|
const target = getWindow(options2.windowId);
|
|
329
|
-
if (target
|
|
330
|
-
target
|
|
476
|
+
if (target) {
|
|
477
|
+
safeSend(options2.windowId, target, eventChannel, data);
|
|
331
478
|
}
|
|
332
479
|
return;
|
|
333
480
|
}
|
|
@@ -336,7 +483,7 @@ function startWoodMain(options) {
|
|
|
336
483
|
windows.delete(windowId);
|
|
337
484
|
continue;
|
|
338
485
|
}
|
|
339
|
-
win
|
|
486
|
+
safeSend(windowId, win, eventChannel, data);
|
|
340
487
|
}
|
|
341
488
|
};
|
|
342
489
|
setIpcPushSender(dispatchPushEvent);
|
|
@@ -346,7 +493,7 @@ function startWoodMain(options) {
|
|
|
346
493
|
windows.delete(windowId);
|
|
347
494
|
continue;
|
|
348
495
|
}
|
|
349
|
-
win
|
|
496
|
+
safeSend(windowId, win, channel, data);
|
|
350
497
|
}
|
|
351
498
|
};
|
|
352
499
|
runtimeContext = {
|
|
@@ -357,6 +504,7 @@ function startWoodMain(options) {
|
|
|
357
504
|
createWindow,
|
|
358
505
|
navigateWindow,
|
|
359
506
|
broadcast,
|
|
507
|
+
getRendererHealthState,
|
|
360
508
|
setRendererLogCallback: (callback) => {
|
|
361
509
|
getRendererLogDispatcher().setCallback(callback);
|
|
362
510
|
},
|
package/dist/main/index.js.map
CHANGED
|
@@ -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 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 windowId,\n webContentsId: win.webContents.id,\n });\n };\n port1.on('message', portHandler);\n port1.start();\n\n win.webContents.once('did-finish-load', () => {\n if (!win.isDestroyed()) {\n win.webContents.postMessage('__trace-port', null, [port2]);\n logger.info('trace messageport sent to renderer', { windowId });\n }\n });\n\n win.on('closed', () => {\n port1.off('message', portHandler);\n port1.close();\n });\n }\n\n win.on('closed', () => {\n windows.delete(windowId);\n 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 resolveRendererTraceContext: (event) => {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const webContentsId = ipcEvent?.sender?.id;\n if (typeof webContentsId !== 'number') {\n return undefined;\n }\n return {\n webContentsId,\n windowId: findWindowIdByWebContents(webContentsId) ?? undefined,\n };\n },\n });\n\n 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"]}
|
|
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 Menu,\n protocol,\n net,\n shell,\n screen,\n} from 'electron';\nimport type {\n BrowserWindowConstructorOptions,\n BrowserWindow as ElectronBrowserWindow,\n Event as ElectronEvent,\n IpcMainInvokeEvent,\n Privileges,\n RenderProcessGoneDetails,\n} from 'electron';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from '../window/window_state_store.js';\nimport { 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 RendererHealthState =\n | 'ready'\n | 'loading'\n | 'unresponsive'\n | 'gone'\n | 'destroyed';\n\nexport type WoodWindowConfig = {\n title: string;\n width: number;\n height: number;\n defaultRoute?: string;\n frame?: boolean;\n transparent?: boolean;\n resizable?: boolean;\n alwaysOnTop?: boolean;\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n show?: boolean;\n parent?: string;\n modal?: boolean;\n vibrancy?: string;\n backgroundMaterial?: string;\n backgroundColor?: string;\n rememberBounds?: boolean;\n contextMenu?: 'native' | 'custom';\n acceptFirstMouse?: boolean;\n nativeQuitFallback?: boolean;\n titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';\n trafficLightPosition?: { x: number; y: number };\n titleBarOverlay?: boolean | { color?: string; symbolColor?: string; height?: number };\n};\n\nexport type PrivilegedSchemeRegistration = {\n scheme: string;\n privileges: Privileges;\n};\n\nexport interface WoodMainAppBootResult {\n scope?: string;\n configureContainer?: (container: any) => void;\n registerSchemesAsPrivileged?: PrivilegedSchemeRegistration[];\n}\n\nexport interface WoodMainBootContext {\n electron: {\n app: typeof electronApp;\n BrowserWindow: typeof BrowserWindow;\n ipcMain: typeof ipcMain;\n protocol: typeof protocol;\n net: typeof net;\n shell: typeof shell;\n screen: typeof screen;\n Menu: typeof Menu;\n };\n logger: ReturnType<typeof getLogger>;\n}\n\nexport interface WoodMainRuntimeContext extends WoodMainBootContext {\n container: any;\n applicationRoot: string;\n getWindow: (windowId: string) => ElectronBrowserWindow | null;\n createWindow: (windowId?: string) => ElectronBrowserWindow;\n navigateWindow: (windowId: string, route: string) => Promise<void>;\n broadcast: (channel: string, data: unknown) => void;\n getRendererHealthState: (windowId: string) => RendererHealthState | null;\n setRendererLogCallback: (callback: RendererLogCallback | null) => void;\n getRendererLogCallback: () => RendererLogCallback | null;\n getDebugMetadata: () => WoodDebugMetadata;\n}\n\nexport interface WoodRendererHealthEvent {\n windowId: string;\n state: RendererHealthState;\n previousState: RendererHealthState | null;\n webContentsId?: number;\n reason?: string;\n exitCode?: number;\n}\n\nexport type WoodWindowLifecycleContext = WoodMainRuntimeContext;\n\nexport interface StartWoodMainOptions {\n registerRuntime: (router: RegisterRuntimeRouter) => void;\n windowDefs: Record<string, WoodWindowConfig>;\n controllersDir: string;\n middlewareDir: string;\n outDir?: string;\n scope?: string;\n configureContainer?: (container: any) => void;\n appBoot?: (\n context: WoodMainBootContext,\n ) => void | WoodMainAppBootResult | Promise<void | WoodMainAppBootResult>;\n mainBoot?: (\n appBootResult: unknown,\n context: WoodMainRuntimeContext,\n ) => void | CleanupHandler | Promise<void | CleanupHandler>;\n onBeforeQuit?: (context: WoodMainRuntimeContext) => void | Promise<void>;\n onWindowCreated?: (\n windowId: string,\n win: ElectronBrowserWindow,\n context: WoodMainRuntimeContext,\n ) => void | Promise<void>;\n onRendererHealthChanged?: (\n event: WoodRendererHealthEvent,\n context: WoodMainRuntimeContext,\n ) => void | Promise<void>;\n onLoad?: (data: unknown, event?: unknown) => Promise<unknown>;\n onContextMenu?: (data: unknown, event?: unknown) => Promise<unknown>;\n traceMinLevel?: TraceLevel;\n rendererTraceEnabled?: boolean;\n rendererTraceMinLevel?: TraceLevel;\n traceTransport?: 'ipc' | 'messageport';\n}\n\nexport interface WoodDebugWindowMetadata {\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: RendererHealthState;\n}\n\nexport interface WoodDebugMetadata {\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: WoodDebugWindowMetadata[];\n}\n\nconst DEFAULT_WOOD_REMOTE_DEBUGGING_PORT = 9333;\n\nexport function resolveWoodDebugMetadataConfiguration(env: NodeJS.ProcessEnv = process.env): {\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n} {\n const isDevelopment = env.WOOD_MODE === 'development';\n if (!isDevelopment) {\n return {\n enabled: false,\n mode: 'production',\n };\n }\n\n const rawPort = env.WOOD_REMOTE_DEBUGGING_PORT;\n const parsedPort = rawPort ? Number.parseInt(rawPort, 10) : DEFAULT_WOOD_REMOTE_DEBUGGING_PORT;\n const port = Number.isFinite(parsedPort) && parsedPort > 0\n ? parsedPort\n : DEFAULT_WOOD_REMOTE_DEBUGGING_PORT;\n\n return {\n enabled: true,\n mode: 'development',\n port,\n };\n}\n\nfunction resolveWindowDef(\n windowDefs: Record<string, WoodWindowConfig>,\n windowId = 'main',\n): WoodWindowConfig {\n const def = windowDefs[windowId];\n if (!def) {\n throw new Error(`Window \"${windowId}\" not defined in generated window defs.`);\n }\n return def;\n}\n\nfunction resolveRuntimePath(relativeOrAbsolutePath: string): string {\n if (path.isAbsolute(relativeOrAbsolutePath)) {\n return relativeOrAbsolutePath;\n }\n return path.join(electronApp.getAppPath(), relativeOrAbsolutePath);\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function startWoodMain(options: StartWoodMainOptions): void {\n const logger = getLogger('wood:main').named('bootstrap');\n const runtimeLogger = getLogger('wood:main').named('runtime');\n const debugConfig = resolveWoodDebugMetadataConfiguration();\n const windowState = new WindowStateStore();\n const windows = new Map<string, ElectronBrowserWindow>();\n const rendererHealth = new Map<string, RendererHealthState>();\n let mainWindow: ElectronBrowserWindow | null = null;\n let beforeQuitCleanupComplete = false;\n let beforeQuitCleanupPromise: Promise<void> | null = null;\n let runtimeContext: WoodMainRuntimeContext | null = null;\n let mainBootCleanup: CleanupHandler | undefined;\n let nativeQuitFallbackInstalled = false;\n\n for (const stream of [process.stdout, process.stderr]) {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n return;\n }\n throw error;\n });\n }\n\n if (debugConfig.enabled && debugConfig.port) {\n electronApp.commandLine.appendSwitch('remote-debugging-port', String(debugConfig.port));\n electronApp.commandLine.appendSwitch('remote-debugging-address', '127.0.0.1');\n logger.info('devtools remote debugging enabled', {\n port: debugConfig.port,\n mode: debugConfig.mode,\n });\n }\n\n const bootContext: WoodMainBootContext = {\n electron: {\n app: electronApp,\n BrowserWindow,\n ipcMain,\n protocol,\n net,\n shell,\n screen,\n Menu,\n },\n logger,\n };\n\n 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 getRendererHealthState = (windowId: string): RendererHealthState | null => {\n return rendererHealth.get(windowId) ?? null;\n };\n\n const notifyRendererHealthChanged = (event: WoodRendererHealthEvent): void => {\n if (!options.onRendererHealthChanged) {\n return;\n }\n if (!runtimeContext) {\n runtimeLogger.warn('renderer health hook skipped before runtime context', {\n windowId: event.windowId,\n state: event.state,\n });\n return;\n }\n\n Promise.resolve(options.onRendererHealthChanged(event, runtimeContext)).catch((error) => {\n runtimeLogger.warn('renderer health hook failed', {\n windowId: event.windowId,\n state: event.state,\n error: toErrorMessage(error),\n });\n });\n };\n\n const setRendererHealth = (\n windowId: string,\n state: RendererHealthState,\n details: {\n webContentsId?: number;\n reason?: string;\n exitCode?: number;\n } = {},\n ): void => {\n const previousState = rendererHealth.get(windowId) ?? null;\n rendererHealth.set(windowId, state);\n notifyRendererHealthChanged({\n windowId,\n state,\n previousState,\n ...details,\n });\n };\n\n const attachRendererHealth = (windowId: string, win: ElectronBrowserWindow): void => {\n const healthLogger = getLogger('wood:main').named(`renderer-health:${windowId}`);\n const webContentsId = win.webContents.id;\n\n setRendererHealth(windowId, 'loading', { webContentsId });\n\n win.webContents.on('did-start-loading', () => {\n setRendererHealth(windowId, 'loading', { webContentsId });\n healthLogger.info('renderer loading', { windowId, webContentsId });\n });\n\n win.webContents.on('did-finish-load', () => {\n setRendererHealth(windowId, 'ready', { webContentsId });\n healthLogger.info('renderer ready', { windowId, webContentsId });\n });\n\n win.webContents.on('unresponsive', () => {\n setRendererHealth(windowId, 'unresponsive', { webContentsId });\n healthLogger.error('renderer unresponsive', { windowId, webContentsId });\n });\n\n win.webContents.on('responsive', () => {\n setRendererHealth(windowId, 'ready', { webContentsId });\n healthLogger.info('renderer responsive', { windowId, webContentsId });\n });\n\n win.webContents.on('render-process-gone', (_event, details: RenderProcessGoneDetails) => {\n setRendererHealth(windowId, 'gone', {\n webContentsId,\n reason: details.reason,\n exitCode: details.exitCode,\n });\n healthLogger.error('renderer process gone', {\n windowId,\n webContentsId,\n reason: details.reason,\n exitCode: details.exitCode,\n });\n });\n\n win.webContents.on('destroyed', () => {\n setRendererHealth(windowId, 'destroyed');\n healthLogger.warn('renderer destroyed', { windowId, webContentsId });\n });\n\n win.on('closed', () => {\n setRendererHealth(windowId, 'destroyed');\n rendererHealth.delete(windowId);\n });\n };\n\n const canSendToWindow = (win: ElectronBrowserWindow): boolean => {\n if (win.isDestroyed()) {\n return false;\n }\n if (win.webContents.isDestroyed()) {\n return false;\n }\n if (typeof win.webContents.isCrashed === 'function' && win.webContents.isCrashed()) {\n return false;\n }\n return true;\n };\n\n const safeSend = (\n windowId: string,\n win: ElectronBrowserWindow,\n channel: string,\n data: unknown,\n ): boolean => {\n if (!canSendToWindow(win)) {\n runtimeLogger.warn('skipping send to unhealthy renderer', {\n windowId,\n channel,\n rendererHealth: getRendererHealthState(windowId),\n });\n return false;\n }\n\n try {\n win.webContents.send(channel, data);\n return true;\n } catch (error) {\n runtimeLogger.warn('send to renderer failed', {\n windowId,\n channel,\n rendererHealth: getRendererHealthState(windowId),\n error: toErrorMessage(error),\n });\n return false;\n }\n };\n\n const installNativeQuitFallback = (): void => {\n if (nativeQuitFallbackInstalled) {\n return;\n }\n Menu.setApplicationMenu(Menu.buildFromTemplate([\n {\n label: electronApp.name,\n submenu: [\n { role: 'quit' },\n ],\n },\n ]));\n nativeQuitFallbackInstalled = true;\n logger.info('native quit fallback installed');\n };\n\n const waitForWindowReady = async (win: ElectronBrowserWindow): Promise<void> => {\n const webContents = win.webContents as ElectronBrowserWindow['webContents'] & {\n isLoadingMainFrame?: () => boolean;\n };\n const isLoading = typeof webContents.isLoadingMainFrame === 'function'\n ? webContents.isLoadingMainFrame()\n : webContents.isLoading();\n if (!isLoading) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n webContents.once('did-finish-load', () => resolve());\n });\n };\n\n const getDebugMetadata = (): WoodDebugMetadata => {\n const debugWindows: WoodDebugWindowMetadata[] = [];\n\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n\n debugWindows.push({\n windowId,\n title: win.getTitle(),\n webContentsId: win.webContents.id,\n url: win.webContents.getURL(),\n focused: win.isFocused(),\n rendererHealth: getRendererHealthState(windowId) ?? undefined,\n });\n }\n\n return {\n ok: true,\n enabled: debugConfig.enabled,\n mode: debugConfig.mode,\n ...(debugConfig.port ? { port: debugConfig.port } : {}),\n windows: debugWindows,\n };\n };\n\n const createWindow = (windowId = 'main'): ElectronBrowserWindow => {\n const existing = getWindow(windowId);\n if (existing) {\n existing.show();\n existing.focus();\n return existing;\n }\n\n const windowDef = resolveWindowDef(options.windowDefs, windowId);\n const defaultShow = windowDef.show ?? true;\n const parentWindow = windowDef.parent ? getWindow(windowDef.parent) : null;\n\n if (windowDef.nativeQuitFallback === true) {\n installNativeQuitFallback();\n }\n\n windowState.setScreen(screen);\n const savedBounds = windowDef.rememberBounds ? windowState.getBounds(windowId) : null;\n\n const windowOptions: BrowserWindowConstructorOptions = {\n width: savedBounds?.width ?? windowDef.width,\n height: savedBounds?.height ?? windowDef.height,\n ...(savedBounds ? { x: savedBounds.x, y: savedBounds.y } : {}),\n ...(windowDef.minWidth != null ? { minWidth: windowDef.minWidth } : {}),\n ...(windowDef.minHeight != null ? { minHeight: windowDef.minHeight } : {}),\n ...(windowDef.maxWidth != null ? { maxWidth: windowDef.maxWidth } : {}),\n ...(windowDef.maxHeight != null ? { maxHeight: windowDef.maxHeight } : {}),\n ...(typeof windowDef.modal === 'boolean' ? { modal: windowDef.modal } : {}),\n ...(parentWindow ? { parent: parentWindow } : {}),\n title: windowDef.title,\n frame: windowDef.frame ?? true,\n transparent: windowDef.transparent ?? false,\n resizable: windowDef.resizable ?? true,\n alwaysOnTop: windowDef.alwaysOnTop ?? false,\n ...(typeof windowDef.backgroundColor === 'string' ? { backgroundColor: windowDef.backgroundColor } : {}),\n ...(process.platform === 'darwin' && typeof windowDef.vibrancy === 'string'\n ? {\n vibrancy: windowDef.vibrancy as BrowserWindowConstructorOptions['vibrancy'],\n visualEffectState: 'active' as const,\n }\n : {}),\n ...(process.platform === 'win32' && typeof windowDef.backgroundMaterial === 'string'\n ? {\n backgroundMaterial: windowDef.backgroundMaterial as BrowserWindowConstructorOptions['backgroundMaterial'],\n }\n : {}),\n ...(typeof windowDef.acceptFirstMouse === 'boolean' ? { acceptFirstMouse: windowDef.acceptFirstMouse } : {}),\n // Hidden title bar with native window controls: macOS draws the\n // traffic lights (positionable), Windows draws min/max/close via the\n // Window Controls Overlay (always top-right; styled via titleBarOverlay).\n ...(typeof windowDef.titleBarStyle === 'string'\n ? { titleBarStyle: windowDef.titleBarStyle as BrowserWindowConstructorOptions['titleBarStyle'] }\n : {}),\n ...(process.platform === 'darwin' && windowDef.trafficLightPosition\n ? { trafficLightPosition: windowDef.trafficLightPosition }\n : {}),\n ...(process.platform === 'win32' && windowDef.titleBarOverlay != null\n ? { titleBarOverlay: windowDef.titleBarOverlay }\n : {}),\n show: savedBounds ? false : defaultShow,\n webPreferences: {\n preload: preloadPath,\n contextIsolation: true,\n nodeIntegration: false,\n },\n };\n\n const win = new BrowserWindow(windowOptions);\n windows.set(windowId, win);\n attachRendererHealth(windowId, win);\n if (windowId === 'main') {\n mainWindow = win;\n }\n\n if (savedBounds?.isMaximized) {\n win.maximize();\n } else if (savedBounds?.isFullScreen) {\n win.setFullScreen(true);\n }\n\n if (savedBounds && defaultShow) {\n win.show();\n }\n\n if (windowDef.rememberBounds) {\n const captureNormalBounds = () => {\n if (!win.isMaximized() && !win.isFullScreen()) {\n const rect = win.getBounds();\n windowState.updateBoundsDebounced(windowId, {\n ...rect,\n isMaximized: false,\n isFullScreen: false,\n });\n }\n };\n win.on('move', captureNormalBounds);\n win.on('resize', captureNormalBounds);\n win.on('close', () => {\n const rect = win.getBounds();\n windowState.saveBoundsSync(windowId, {\n ...rect,\n isMaximized: win.isMaximized(),\n isFullScreen: win.isFullScreen(),\n });\n });\n }\n\n win.webContents.setWindowOpenHandler(({ url }) => {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n void shell.openExternal(url);\n }\n return { action: 'deny' };\n });\n\n win.webContents.on('will-navigate', (event, url) => {\n if (process.env.ELECTRON_RENDERER_URL && url.startsWith(process.env.ELECTRON_RENDERER_URL)) {\n return;\n }\n if (url.startsWith('http://') || url.startsWith('https://')) {\n event.preventDefault();\n void shell.openExternal(url);\n }\n });\n\n const rendererLogger = getLogger('wood:main').named(`renderer:${windowId}`);\n const levelMap: Record<number, 'debug' | 'info' | 'warn' | 'error'> = {\n 0: 'debug',\n 1: 'info',\n 2: 'warn',\n 3: 'error',\n };\n\n win.webContents.on('console-message', (_event, level, message, line, sourceId) => {\n const method = levelMap[level] ?? 'info';\n rendererLogger[method](message, { source: sourceId, line, windowId });\n });\n\n if (process.env.ELECTRON_RENDERER_URL) {\n void win.loadURL(process.env.ELECTRON_RENDERER_URL);\n } else {\n void win.loadFile(rendererFile);\n }\n\n if (options.traceTransport === 'messageport' && options.rendererTraceEnabled === true) {\n const { port1, port2 } = new MessageChannelMain();\n\n const portHandler = (event: Electron.MessageEvent) => {\n dispatchRendererTracePayload(event.data, {\n windowId,\n webContentsId: win.webContents.id,\n });\n };\n port1.on('message', portHandler);\n port1.start();\n\n win.webContents.once('did-finish-load', () => {\n if (!win.isDestroyed()) {\n win.webContents.postMessage('__trace-port', null, [port2]);\n logger.info('trace messageport sent to renderer', { windowId });\n }\n });\n\n win.on('closed', () => {\n port1.off('message', portHandler);\n port1.close();\n });\n }\n\n win.on('closed', () => {\n windows.delete(windowId);\n rendererHealth.delete(windowId);\n if (windowId === 'main') {\n mainWindow = null;\n }\n if (windows.size === 0) {\n electronApp.quit();\n }\n });\n\n if (options.onWindowCreated) {\n Promise.resolve(options.onWindowCreated(windowId, win, runtimeContext!)).catch((error) => {\n runtimeLogger.warn('window created hook failed', {\n windowId,\n error: toErrorMessage(error),\n });\n });\n }\n\n logger.info('window created', { windowId });\n return win;\n };\n\n const navigateWindow = async (windowId: string, route: string): Promise<void> => {\n const win = createWindow(windowId);\n await waitForWindowReady(win);\n safeSend(windowId, win, '__event:__navigate', { route });\n win.show();\n win.focus();\n };\n\n const dispatchPushEvent = (\n channel: string,\n data: unknown,\n options?: IpcPushOptions,\n ): void => {\n const eventChannel = channel.startsWith('__event:')\n ? channel\n : `__event:${channel}`;\n\n if (options?.windowId) {\n const target = getWindow(options.windowId);\n if (target) {\n safeSend(options.windowId, target, eventChannel, data);\n }\n return;\n }\n\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n safeSend(windowId, win, eventChannel, data);\n }\n };\n\n setIpcPushSender(dispatchPushEvent);\n\n const broadcast = (channel: string, data: unknown): void => {\n for (const [windowId, win] of windows.entries()) {\n if (win.isDestroyed()) {\n windows.delete(windowId);\n continue;\n }\n safeSend(windowId, win, channel, data);\n }\n };\n\n runtimeContext = {\n ...bootContext,\n container,\n applicationRoot: '',\n getWindow,\n createWindow,\n navigateWindow,\n broadcast,\n getRendererHealthState,\n setRendererLogCallback: (callback) => {\n 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 resolveRendererTraceContext: (event) => {\n const ipcEvent = event as IpcMainInvokeEvent | undefined;\n const webContentsId = ipcEvent?.sender?.id;\n if (typeof webContentsId !== 'number') {\n return undefined;\n }\n return {\n webContentsId,\n windowId: findWindowIdByWebContents(webContentsId) ?? undefined,\n };\n },\n });\n\n 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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/config.ts"],"sourcesContent":["/** Configuration types for wood.config.yml */\n\n/** Top-level Wood configuration parsed from wood.config.yml */\nexport interface WoodConfig {\n root: string;\n outDir: string;\n app: {\n boot: string;\n watch: string[];\n };\n main: {\n entry: string;\n controllers: string;\n middleware: string;\n openapi: string;\n watch: string[];\n };\n renderer: {\n openapi: string;\n componentDir: string;\n controllers: string;\n entry?: string;\n watch?: string[];\n };\n windows: Record<string, WindowDef>;\n database?: {\n path: string;\n migrations?: string;\n };\n assets?: string[];\n dev?: DevConfig;\n build?: ElectronBuilderConfig;\n}\n\n/** BrowserWindow definition from the `windows` section of wood.config.yml */\nexport interface WindowDef {\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 rememberBounds?: boolean;\n contextMenu?: 'native' | 'custom';\n}\n\n/** Dev-mode settings from the `dev` section of wood.config.yml */\nexport interface DevConfig {\n port?: number;\n watch?: boolean;\n splitServe?: boolean;\n electronArgs?: string[];\n inspectPort?: number;\n backendInspectPort?: number;\n}\n\n/** Electron-builder packaging configuration from the `build` section */\nexport interface ElectronBuilderConfig {\n appId: string;\n productName: string;\n mac?: Record<string, unknown>;\n win?: Record<string, unknown>;\n linux?: Record<string, unknown>;\n [key: string]: unknown;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/types/config.ts"],"sourcesContent":["/** Configuration types for wood.config.yml */\n\n/** Top-level Wood configuration parsed from wood.config.yml */\nexport interface WoodConfig {\n root: string;\n outDir: string;\n app: {\n boot: string;\n watch: string[];\n };\n main: {\n entry: string;\n controllers: string;\n middleware: string;\n openapi: string;\n watch: string[];\n };\n renderer: {\n openapi: string;\n componentDir: string;\n controllers: string;\n entry?: string;\n watch?: string[];\n };\n windows: Record<string, WindowDef>;\n database?: {\n path: string;\n migrations?: string;\n };\n assets?: string[];\n dev?: DevConfig;\n build?: ElectronBuilderConfig;\n}\n\n/** BrowserWindow definition from the `windows` section of wood.config.yml */\nexport interface WindowDef {\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 rememberBounds?: boolean;\n contextMenu?: 'native' | 'custom';\n acceptFirstMouse?: boolean;\n nativeQuitFallback?: boolean;\n titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';\n trafficLightPosition?: { x: number; y: number };\n titleBarOverlay?: boolean | { color?: string; symbolColor?: string; height?: number };\n}\n\n/** Dev-mode settings from the `dev` section of wood.config.yml */\nexport interface DevConfig {\n port?: number;\n watch?: boolean;\n splitServe?: boolean;\n electronArgs?: string[];\n inspectPort?: number;\n backendInspectPort?: number;\n}\n\n/** Electron-builder packaging configuration from the `build` section */\nexport interface ElectronBuilderConfig {\n appId: string;\n productName: string;\n mac?: Record<string, unknown>;\n win?: Record<string, unknown>;\n linux?: Record<string, unknown>;\n [key: string]: unknown;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/types/config.d.cts
CHANGED
|
@@ -51,6 +51,18 @@ interface WindowDef {
|
|
|
51
51
|
backgroundMaterial?: string;
|
|
52
52
|
rememberBounds?: boolean;
|
|
53
53
|
contextMenu?: 'native' | 'custom';
|
|
54
|
+
acceptFirstMouse?: boolean;
|
|
55
|
+
nativeQuitFallback?: boolean;
|
|
56
|
+
titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';
|
|
57
|
+
trafficLightPosition?: {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
};
|
|
61
|
+
titleBarOverlay?: boolean | {
|
|
62
|
+
color?: string;
|
|
63
|
+
symbolColor?: string;
|
|
64
|
+
height?: number;
|
|
65
|
+
};
|
|
54
66
|
}
|
|
55
67
|
/** Dev-mode settings from the `dev` section of wood.config.yml */
|
|
56
68
|
interface DevConfig {
|
package/dist/types/config.d.ts
CHANGED
|
@@ -51,6 +51,18 @@ interface WindowDef {
|
|
|
51
51
|
backgroundMaterial?: string;
|
|
52
52
|
rememberBounds?: boolean;
|
|
53
53
|
contextMenu?: 'native' | 'custom';
|
|
54
|
+
acceptFirstMouse?: boolean;
|
|
55
|
+
nativeQuitFallback?: boolean;
|
|
56
|
+
titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';
|
|
57
|
+
trafficLightPosition?: {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
};
|
|
61
|
+
titleBarOverlay?: boolean | {
|
|
62
|
+
color?: string;
|
|
63
|
+
symbolColor?: string;
|
|
64
|
+
height?: number;
|
|
65
|
+
};
|
|
54
66
|
}
|
|
55
67
|
/** Dev-mode settings from the `dev` section of wood.config.yml */
|
|
56
68
|
interface DevConfig {
|
|
@@ -125,6 +125,9 @@ class WindowManager {
|
|
|
125
125
|
...def.maxHeight != null && { maxHeight: def.maxHeight },
|
|
126
126
|
...def.vibrancy && { vibrancy: def.vibrancy },
|
|
127
127
|
...def.backgroundMaterial && { backgroundMaterial: def.backgroundMaterial },
|
|
128
|
+
...def.titleBarStyle && { titleBarStyle: def.titleBarStyle },
|
|
129
|
+
...def.trafficLightPosition && { trafficLightPosition: def.trafficLightPosition },
|
|
130
|
+
...def.titleBarOverlay != null && { titleBarOverlay: def.titleBarOverlay },
|
|
128
131
|
...parentWindow && { parent: parentWindow },
|
|
129
132
|
webPreferences: {
|
|
130
133
|
preload: this.preloadPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/window/window_manager.ts"],"sourcesContent":["import { join } from 'path';\nimport type { WindowDef } from '../types/index.cjs';\nimport type { ContextMenuItem } from '../types/context_menu.cjs';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from './window_state_store.cjs';\nimport type { ScreenLike } from './window_state_store.cjs';\n\nconst logger = getLogger('wood:window');\n\n/** Subset of Electron BrowserWindow used by WindowManager (avoids compile-time electron dep). */\nexport interface BrowserWindowLike {\n loadURL(url: string): Promise<void>;\n loadFile(filePath: string): Promise<void>;\n close(): void;\n focus(): void;\n show(): void;\n destroy(): void;\n isDestroyed(): boolean;\n webContents: {\n send(channel: string, ...args: unknown[]): void;\n reload(): void;\n setWindowOpenHandler(handler: (details: { url: string }) => { action: 'deny' | 'allow' }): void;\n on(event: string, handler: (...args: any[]) => void): void;\n };\n on(event: string, handler: (...args: unknown[]) => void): void;\n getBounds(): { x: number; y: number; width: number; height: number };\n isMaximized(): boolean;\n isFullScreen(): boolean;\n maximize(): void;\n setFullScreen(flag: boolean): void;\n}\n\nexport interface BrowserWindowConstructor {\n new (options: Record<string, unknown>): BrowserWindowLike;\n getAllWindows(): BrowserWindowLike[];\n}\n\n/** Minimal shell interface for opening external URLs. */\nexport interface ShellLike {\n openExternal(url: string): Promise<unknown>;\n}\n\n/** Minimal clipboard interface for copying text. */\nexport interface ClipboardLike {\n writeText(text: string): void;\n}\n\n/** Minimal Menu interface for right-click context menus. */\nexport interface MenuLike {\n popup(options?: { window?: BrowserWindowLike }): void;\n}\n\nexport interface MenuConstructorLike {\n buildFromTemplate(template: Array<{ label: string; click: () => void; type?: 'separator'; enabled?: boolean }>): MenuLike;\n}\n\nexport class WindowManager {\n private windows = new Map<string, BrowserWindowLike>();\n private windowDefs: Record<string, WindowDef>;\n private BrowserWindowClass: BrowserWindowConstructor | null = null;\n private preloadPath: string;\n private rendererUrl?: string;\n private rendererDir?: string;\n private shell: ShellLike | null = null;\n private clipboard: ClipboardLike | null = null;\n private MenuClass: MenuConstructorLike | null = null;\n private stateStore = new WindowStateStore();\n\n constructor(options: {\n windowDefs: Record<string, WindowDef>;\n preloadPath: string;\n rendererUrl?: string;\n rendererDir?: string;\n applicationRoot?: string;\n }) {\n this.windowDefs = options.windowDefs;\n this.preloadPath = options.preloadPath;\n this.rendererUrl = options.rendererUrl;\n this.rendererDir = options.rendererDir;\n if (options.applicationRoot) {\n this.stateStore.setApplicationRoot(options.applicationRoot);\n }\n }\n\n /**\n * Set the BrowserWindow class (from electron).\n * Called at runtime so we don't need electron at compile time.\n */\n setBrowserWindow(BW: BrowserWindowConstructor): void {\n this.BrowserWindowClass = BW;\n }\n\n /**\n * Set the shell module (from electron) for opening external URLs.\n */\n setShell(shell: ShellLike): void {\n this.shell = shell;\n }\n\n /**\n * Set the clipboard module (from electron) for copying text.\n */\n setClipboard(clipboard: ClipboardLike): void {\n this.clipboard = clipboard;\n }\n\n /**\n * Set the Menu class (from electron) for context menus.\n */\n setMenu(menu: MenuConstructorLike): void {\n this.MenuClass = menu;\n }\n\n /**\n * Set the user data path for persisting window state.\n */\n setUserDataPath(path: string): void {\n this.stateStore.setUserDataPath(path);\n }\n\n /**\n * Set the application root directory for Wood JSON state persistence.\n */\n setApplicationRoot(path: string): void {\n this.stateStore.setApplicationRoot(path);\n }\n\n /**\n * Set the screen module (from electron) for display validation.\n */\n setScreen(screen: ScreenLike): void {\n this.stateStore.setScreen(screen);\n }\n\n /**\n * Create a window from its definition.\n */\n async createWindow(windowId: string, extraOptions?: Record<string, unknown>): Promise<BrowserWindowLike> {\n if (!this.BrowserWindowClass) {\n throw new Error('BrowserWindow class not set. Call setBrowserWindow() first.');\n }\n\n const def = this.windowDefs[windowId];\n if (!def) {\n throw new Error(`Window \"${windowId}\" not defined in config`);\n }\n\n // If the window already exists and isn't destroyed, just focus it\n const existing = this.windows.get(windowId);\n if (existing && !existing.isDestroyed()) {\n existing.focus();\n return existing;\n }\n\n // Resolve parent window\n let parentWindow: BrowserWindowLike | undefined;\n if (def.parent) {\n parentWindow = this.windows.get(def.parent);\n }\n\n // Resolve saved bounds for rememberBounds\n const useRememberBounds = def.rememberBounds === true;\n const savedBounds = useRememberBounds ? this.stateStore.getBounds(windowId) : null;\n\n // Determine if we should defer showing (anti-flicker for restored windows)\n const defShow = def.show ?? true;\n const deferShow = useRememberBounds && savedBounds != null && defShow && extraOptions?.show !== false;\n\n // Build BrowserWindow options from WindowDef\n const options: Record<string, unknown> = {\n title: def.title,\n width: savedBounds?.width ?? def.width,\n height: savedBounds?.height ?? def.height,\n ...(savedBounds && { x: savedBounds.x, y: savedBounds.y }),\n frame: def.frame ?? true,\n transparent: def.transparent ?? false,\n resizable: def.resizable ?? true,\n alwaysOnTop: def.alwaysOnTop ?? false,\n show: deferShow ? false : defShow,\n modal: def.modal ?? false,\n ...(def.minWidth != null && { minWidth: def.minWidth }),\n ...(def.minHeight != null && { minHeight: def.minHeight }),\n ...(def.maxWidth != null && { maxWidth: def.maxWidth }),\n ...(def.maxHeight != null && { maxHeight: def.maxHeight }),\n ...(def.vibrancy && { vibrancy: def.vibrancy }),\n ...(def.backgroundMaterial && { backgroundMaterial: def.backgroundMaterial }),\n ...(parentWindow && { parent: parentWindow }),\n webPreferences: {\n preload: this.preloadPath,\n contextIsolation: true,\n sandbox: true,\n nodeIntegration: false,\n },\n ...extraOptions,\n };\n\n const win = new this.BrowserWindowClass(options);\n\n // Restore maximized/fullscreen state\n if (savedBounds?.isMaximized) {\n win.maximize();\n } else if (savedBounds?.isFullScreen) {\n win.setFullScreen(true);\n }\n\n // Show window after restoring state (anti-flicker)\n if (deferShow) {\n win.show();\n }\n\n // Load content\n if (this.rendererUrl) {\n await win.loadURL(this.rendererUrl);\n } else if (this.rendererDir) {\n await win.loadFile(join(this.rendererDir, 'index.html'));\n }\n\n // Open http/https links in system browser instead of inside Electron\n if (this.shell) {\n const shellRef = this.shell;\n win.webContents.setWindowOpenHandler(({ url }) => {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n shellRef.openExternal(url);\n }\n return { action: 'deny' };\n });\n }\n\n // Track window\n this.windows.set(windowId, win);\n logger.debug(`Created window \"${windowId}\"`);\n\n // Persist bounds on move/resize/close\n if (useRememberBounds) {\n const captureNormalBounds = () => {\n if (!win.isMaximized() && !win.isFullScreen()) {\n const rect = win.getBounds();\n this.stateStore.updateBoundsDebounced(windowId, {\n ...rect,\n isMaximized: false,\n isFullScreen: false,\n });\n }\n };\n\n win.on('move', captureNormalBounds);\n win.on('resize', captureNormalBounds);\n\n win.on('close', () => {\n const rect = win.getBounds();\n this.stateStore.saveBoundsSync(windowId, {\n ...rect,\n isMaximized: win.isMaximized(),\n isFullScreen: win.isFullScreen(),\n });\n });\n }\n\n // Cleanup on close\n win.on('closed', () => {\n this.windows.delete(windowId);\n logger.debug(`Window \"${windowId}\" closed`);\n });\n\n return win;\n }\n\n /**\n * Get a window by ID.\n */\n getWindow(windowId: string): BrowserWindowLike | undefined {\n const win = this.windows.get(windowId);\n if (win && !win.isDestroyed()) return win;\n return undefined;\n }\n\n /**\n * Close a window by ID.\n */\n closeWindow(windowId: string): void {\n const win = this.windows.get(windowId);\n if (win && !win.isDestroyed()) {\n win.close();\n }\n }\n\n /**\n * Focus a window by ID.\n */\n focusWindow(windowId: string): void {\n const win = this.windows.get(windowId);\n if (win && !win.isDestroyed()) {\n win.show();\n win.focus();\n }\n }\n\n /**\n * Send an event to all open windows.\n */\n broadcastToAll(channel: string, ...data: unknown[]): void {\n for (const [, win] of this.windows) {\n if (!win.isDestroyed()) {\n win.webContents.send(channel, ...data);\n }\n }\n }\n\n /**\n * Get all open window IDs.\n */\n getOpenWindowIds(): string[] {\n return Array.from(this.windows.entries())\n .filter(([, win]) => !win.isDestroyed())\n .map(([id]) => id);\n }\n\n /**\n * Close all windows.\n */\n closeAll(): void {\n for (const [, win] of this.windows) {\n if (!win.isDestroyed()) {\n win.destroy();\n }\n }\n this.windows.clear();\n logger.debug('All windows closed');\n }\n\n /**\n * Handle the __window IPC channel.\n */\n async handleWindowOperation(data: {\n action: string;\n windowId?: string;\n options?: Record<string, unknown>;\n }): Promise<void> {\n switch (data.action) {\n case 'open':\n if (!data.windowId) throw new Error('windowId required for open');\n await this.createWindow(data.windowId, data.options);\n break;\n case 'close':\n if (data.windowId) {\n this.closeWindow(data.windowId);\n }\n break;\n case 'focus':\n if (!data.windowId) throw new Error('windowId required for focus');\n this.focusWindow(data.windowId);\n break;\n default:\n throw new Error(`Unknown window action: ${data.action}`);\n }\n }\n\n /**\n * Handle the __contextMenu IPC channel.\n *\n * Receives `{ action: 'show', items, position }` from the renderer's\n * `use:contextMenu` action. Checks the window's contextMenu config:\n * - **native**: builds Electron Menu from items, pops it. String actions\n * are handled on main. Callback items send `__contextMenu:callback`.\n * - **custom**: forwards items + position as `__contextMenu:show` push event.\n */\n handleContextMenu(\n windowId: string,\n data: { action: string; items: ContextMenuItem[]; position: { x: number; y: number } },\n ): void {\n const win = this.windows.get(windowId);\n if (!win || win.isDestroyed()) return;\n\n const def = this.windowDefs[windowId];\n if (!def) return;\n\n if (data.action !== 'show') return;\n\n if (def.contextMenu === 'native') {\n this.showNativeContextMenu(win, data.items);\n } else if (def.contextMenu === 'custom') {\n win.webContents.send('__event:__contextMenu:show', {\n items: data.items,\n position: data.position,\n });\n }\n }\n\n /**\n * Build and show a native Electron context menu from serialized items.\n */\n private showNativeContextMenu(win: BrowserWindowLike, items: ContextMenuItem[]): void {\n if (!this.MenuClass) return;\n\n const template = items.map(item => {\n if (item.type === 'separator') {\n return { label: '', click: () => {}, type: 'separator' as const, enabled: true };\n }\n return {\n label: item.label ?? '',\n enabled: item.disabled !== true,\n click: () => {\n if (item._callbackId) {\n win.webContents.send('__event:__contextMenu:callback', item._callbackId);\n } else if (item.action) {\n this.executeMenuAction(item.action, item.data, win);\n }\n },\n };\n });\n\n if (template.length === 0) return;\n\n const menu = this.MenuClass.buildFromTemplate(template);\n menu.popup({ window: win });\n }\n\n /**\n * Execute a built-in menu action on the main process.\n */\n private executeMenuAction(action: string, data: string | undefined, win: BrowserWindowLike): void {\n switch (action) {\n case 'openExternal':\n if (data && this.shell) {\n this.shell.openExternal(data);\n }\n break;\n case 'copyText':\n if (data && this.clipboard) {\n this.clipboard.writeText(data);\n }\n break;\n case 'copy':\n win.webContents.send('__contextMenu:exec', 'copy');\n break;\n case 'cut':\n win.webContents.send('__contextMenu:exec', 'cut');\n break;\n case 'paste':\n win.webContents.send('__contextMenu:exec', 'paste');\n break;\n case 'selectAll':\n win.webContents.send('__contextMenu:exec', 'selectAll');\n break;\n default:\n // Unknown action — dispatch to renderer\n win.webContents.send('__contextMenu:action', action, data);\n break;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/window/window_manager.ts"],"sourcesContent":["import { join } from 'path';\nimport type { WindowDef } from '../types/index.cjs';\nimport type { ContextMenuItem } from '../types/context_menu.cjs';\nimport { getLogger } from '@noego/logger';\nimport { WindowStateStore } from './window_state_store.cjs';\nimport type { ScreenLike } from './window_state_store.cjs';\n\nconst logger = getLogger('wood:window');\n\n/** Subset of Electron BrowserWindow used by WindowManager (avoids compile-time electron dep). */\nexport interface BrowserWindowLike {\n loadURL(url: string): Promise<void>;\n loadFile(filePath: string): Promise<void>;\n close(): void;\n focus(): void;\n show(): void;\n destroy(): void;\n isDestroyed(): boolean;\n webContents: {\n send(channel: string, ...args: unknown[]): void;\n reload(): void;\n setWindowOpenHandler(handler: (details: { url: string }) => { action: 'deny' | 'allow' }): void;\n on(event: string, handler: (...args: any[]) => void): void;\n };\n on(event: string, handler: (...args: unknown[]) => void): void;\n getBounds(): { x: number; y: number; width: number; height: number };\n isMaximized(): boolean;\n isFullScreen(): boolean;\n maximize(): void;\n setFullScreen(flag: boolean): void;\n}\n\nexport interface BrowserWindowConstructor {\n new (options: Record<string, unknown>): BrowserWindowLike;\n getAllWindows(): BrowserWindowLike[];\n}\n\n/** Minimal shell interface for opening external URLs. */\nexport interface ShellLike {\n openExternal(url: string): Promise<unknown>;\n}\n\n/** Minimal clipboard interface for copying text. */\nexport interface ClipboardLike {\n writeText(text: string): void;\n}\n\n/** Minimal Menu interface for right-click context menus. */\nexport interface MenuLike {\n popup(options?: { window?: BrowserWindowLike }): void;\n}\n\nexport interface MenuConstructorLike {\n buildFromTemplate(template: Array<{ label: string; click: () => void; type?: 'separator'; enabled?: boolean }>): MenuLike;\n}\n\nexport class WindowManager {\n private windows = new Map<string, BrowserWindowLike>();\n private windowDefs: Record<string, WindowDef>;\n private BrowserWindowClass: BrowserWindowConstructor | null = null;\n private preloadPath: string;\n private rendererUrl?: string;\n private rendererDir?: string;\n private shell: ShellLike | null = null;\n private clipboard: ClipboardLike | null = null;\n private MenuClass: MenuConstructorLike | null = null;\n private stateStore = new WindowStateStore();\n\n constructor(options: {\n windowDefs: Record<string, WindowDef>;\n preloadPath: string;\n rendererUrl?: string;\n rendererDir?: string;\n applicationRoot?: string;\n }) {\n this.windowDefs = options.windowDefs;\n this.preloadPath = options.preloadPath;\n this.rendererUrl = options.rendererUrl;\n this.rendererDir = options.rendererDir;\n if (options.applicationRoot) {\n this.stateStore.setApplicationRoot(options.applicationRoot);\n }\n }\n\n /**\n * Set the BrowserWindow class (from electron).\n * Called at runtime so we don't need electron at compile time.\n */\n setBrowserWindow(BW: BrowserWindowConstructor): void {\n this.BrowserWindowClass = BW;\n }\n\n /**\n * Set the shell module (from electron) for opening external URLs.\n */\n setShell(shell: ShellLike): void {\n this.shell = shell;\n }\n\n /**\n * Set the clipboard module (from electron) for copying text.\n */\n setClipboard(clipboard: ClipboardLike): void {\n this.clipboard = clipboard;\n }\n\n /**\n * Set the Menu class (from electron) for context menus.\n */\n setMenu(menu: MenuConstructorLike): void {\n this.MenuClass = menu;\n }\n\n /**\n * Set the user data path for persisting window state.\n */\n setUserDataPath(path: string): void {\n this.stateStore.setUserDataPath(path);\n }\n\n /**\n * Set the application root directory for Wood JSON state persistence.\n */\n setApplicationRoot(path: string): void {\n this.stateStore.setApplicationRoot(path);\n }\n\n /**\n * Set the screen module (from electron) for display validation.\n */\n setScreen(screen: ScreenLike): void {\n this.stateStore.setScreen(screen);\n }\n\n /**\n * Create a window from its definition.\n */\n async createWindow(windowId: string, extraOptions?: Record<string, unknown>): Promise<BrowserWindowLike> {\n if (!this.BrowserWindowClass) {\n throw new Error('BrowserWindow class not set. Call setBrowserWindow() first.');\n }\n\n const def = this.windowDefs[windowId];\n if (!def) {\n throw new Error(`Window \"${windowId}\" not defined in config`);\n }\n\n // If the window already exists and isn't destroyed, just focus it\n const existing = this.windows.get(windowId);\n if (existing && !existing.isDestroyed()) {\n existing.focus();\n return existing;\n }\n\n // Resolve parent window\n let parentWindow: BrowserWindowLike | undefined;\n if (def.parent) {\n parentWindow = this.windows.get(def.parent);\n }\n\n // Resolve saved bounds for rememberBounds\n const useRememberBounds = def.rememberBounds === true;\n const savedBounds = useRememberBounds ? this.stateStore.getBounds(windowId) : null;\n\n // Determine if we should defer showing (anti-flicker for restored windows)\n const defShow = def.show ?? true;\n const deferShow = useRememberBounds && savedBounds != null && defShow && extraOptions?.show !== false;\n\n // Build BrowserWindow options from WindowDef\n const options: Record<string, unknown> = {\n title: def.title,\n width: savedBounds?.width ?? def.width,\n height: savedBounds?.height ?? def.height,\n ...(savedBounds && { x: savedBounds.x, y: savedBounds.y }),\n frame: def.frame ?? true,\n transparent: def.transparent ?? false,\n resizable: def.resizable ?? true,\n alwaysOnTop: def.alwaysOnTop ?? false,\n show: deferShow ? false : defShow,\n modal: def.modal ?? false,\n ...(def.minWidth != null && { minWidth: def.minWidth }),\n ...(def.minHeight != null && { minHeight: def.minHeight }),\n ...(def.maxWidth != null && { maxWidth: def.maxWidth }),\n ...(def.maxHeight != null && { maxHeight: def.maxHeight }),\n ...(def.vibrancy && { vibrancy: def.vibrancy }),\n ...(def.backgroundMaterial && { backgroundMaterial: def.backgroundMaterial }),\n ...(def.titleBarStyle && { titleBarStyle: def.titleBarStyle }),\n ...(def.trafficLightPosition && { trafficLightPosition: def.trafficLightPosition }),\n ...(def.titleBarOverlay != null && { titleBarOverlay: def.titleBarOverlay }),\n ...(parentWindow && { parent: parentWindow }),\n webPreferences: {\n preload: this.preloadPath,\n contextIsolation: true,\n sandbox: true,\n nodeIntegration: false,\n },\n ...extraOptions,\n };\n\n const win = new this.BrowserWindowClass(options);\n\n // Restore maximized/fullscreen state\n if (savedBounds?.isMaximized) {\n win.maximize();\n } else if (savedBounds?.isFullScreen) {\n win.setFullScreen(true);\n }\n\n // Show window after restoring state (anti-flicker)\n if (deferShow) {\n win.show();\n }\n\n // Load content\n if (this.rendererUrl) {\n await win.loadURL(this.rendererUrl);\n } else if (this.rendererDir) {\n await win.loadFile(join(this.rendererDir, 'index.html'));\n }\n\n // Open http/https links in system browser instead of inside Electron\n if (this.shell) {\n const shellRef = this.shell;\n win.webContents.setWindowOpenHandler(({ url }) => {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n shellRef.openExternal(url);\n }\n return { action: 'deny' };\n });\n }\n\n // Track window\n this.windows.set(windowId, win);\n logger.debug(`Created window \"${windowId}\"`);\n\n // Persist bounds on move/resize/close\n if (useRememberBounds) {\n const captureNormalBounds = () => {\n if (!win.isMaximized() && !win.isFullScreen()) {\n const rect = win.getBounds();\n this.stateStore.updateBoundsDebounced(windowId, {\n ...rect,\n isMaximized: false,\n isFullScreen: false,\n });\n }\n };\n\n win.on('move', captureNormalBounds);\n win.on('resize', captureNormalBounds);\n\n win.on('close', () => {\n const rect = win.getBounds();\n this.stateStore.saveBoundsSync(windowId, {\n ...rect,\n isMaximized: win.isMaximized(),\n isFullScreen: win.isFullScreen(),\n });\n });\n }\n\n // Cleanup on close\n win.on('closed', () => {\n this.windows.delete(windowId);\n logger.debug(`Window \"${windowId}\" closed`);\n });\n\n return win;\n }\n\n /**\n * Get a window by ID.\n */\n getWindow(windowId: string): BrowserWindowLike | undefined {\n const win = this.windows.get(windowId);\n if (win && !win.isDestroyed()) return win;\n return undefined;\n }\n\n /**\n * Close a window by ID.\n */\n closeWindow(windowId: string): void {\n const win = this.windows.get(windowId);\n if (win && !win.isDestroyed()) {\n win.close();\n }\n }\n\n /**\n * Focus a window by ID.\n */\n focusWindow(windowId: string): void {\n const win = this.windows.get(windowId);\n if (win && !win.isDestroyed()) {\n win.show();\n win.focus();\n }\n }\n\n /**\n * Send an event to all open windows.\n */\n broadcastToAll(channel: string, ...data: unknown[]): void {\n for (const [, win] of this.windows) {\n if (!win.isDestroyed()) {\n win.webContents.send(channel, ...data);\n }\n }\n }\n\n /**\n * Get all open window IDs.\n */\n getOpenWindowIds(): string[] {\n return Array.from(this.windows.entries())\n .filter(([, win]) => !win.isDestroyed())\n .map(([id]) => id);\n }\n\n /**\n * Close all windows.\n */\n closeAll(): void {\n for (const [, win] of this.windows) {\n if (!win.isDestroyed()) {\n win.destroy();\n }\n }\n this.windows.clear();\n logger.debug('All windows closed');\n }\n\n /**\n * Handle the __window IPC channel.\n */\n async handleWindowOperation(data: {\n action: string;\n windowId?: string;\n options?: Record<string, unknown>;\n }): Promise<void> {\n switch (data.action) {\n case 'open':\n if (!data.windowId) throw new Error('windowId required for open');\n await this.createWindow(data.windowId, data.options);\n break;\n case 'close':\n if (data.windowId) {\n this.closeWindow(data.windowId);\n }\n break;\n case 'focus':\n if (!data.windowId) throw new Error('windowId required for focus');\n this.focusWindow(data.windowId);\n break;\n default:\n throw new Error(`Unknown window action: ${data.action}`);\n }\n }\n\n /**\n * Handle the __contextMenu IPC channel.\n *\n * Receives `{ action: 'show', items, position }` from the renderer's\n * `use:contextMenu` action. Checks the window's contextMenu config:\n * - **native**: builds Electron Menu from items, pops it. String actions\n * are handled on main. Callback items send `__contextMenu:callback`.\n * - **custom**: forwards items + position as `__contextMenu:show` push event.\n */\n handleContextMenu(\n windowId: string,\n data: { action: string; items: ContextMenuItem[]; position: { x: number; y: number } },\n ): void {\n const win = this.windows.get(windowId);\n if (!win || win.isDestroyed()) return;\n\n const def = this.windowDefs[windowId];\n if (!def) return;\n\n if (data.action !== 'show') return;\n\n if (def.contextMenu === 'native') {\n this.showNativeContextMenu(win, data.items);\n } else if (def.contextMenu === 'custom') {\n win.webContents.send('__event:__contextMenu:show', {\n items: data.items,\n position: data.position,\n });\n }\n }\n\n /**\n * Build and show a native Electron context menu from serialized items.\n */\n private showNativeContextMenu(win: BrowserWindowLike, items: ContextMenuItem[]): void {\n if (!this.MenuClass) return;\n\n const template = items.map(item => {\n if (item.type === 'separator') {\n return { label: '', click: () => {}, type: 'separator' as const, enabled: true };\n }\n return {\n label: item.label ?? '',\n enabled: item.disabled !== true,\n click: () => {\n if (item._callbackId) {\n win.webContents.send('__event:__contextMenu:callback', item._callbackId);\n } else if (item.action) {\n this.executeMenuAction(item.action, item.data, win);\n }\n },\n };\n });\n\n if (template.length === 0) return;\n\n const menu = this.MenuClass.buildFromTemplate(template);\n menu.popup({ window: win });\n }\n\n /**\n * Execute a built-in menu action on the main process.\n */\n private executeMenuAction(action: string, data: string | undefined, win: BrowserWindowLike): void {\n switch (action) {\n case 'openExternal':\n if (data && this.shell) {\n this.shell.openExternal(data);\n }\n break;\n case 'copyText':\n if (data && this.clipboard) {\n this.clipboard.writeText(data);\n }\n break;\n case 'copy':\n win.webContents.send('__contextMenu:exec', 'copy');\n break;\n case 'cut':\n win.webContents.send('__contextMenu:exec', 'cut');\n break;\n case 'paste':\n win.webContents.send('__contextMenu:exec', 'paste');\n break;\n case 'selectAll':\n win.webContents.send('__contextMenu:exec', 'selectAll');\n break;\n default:\n // Unknown action — dispatch to renderer\n win.webContents.send('__contextMenu:action', action, data);\n break;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAqB;AAGrB,oBAA0B;AAC1B,gCAAiC;AAGjC,MAAM,aAAS,yBAAU,aAAa;AAiD/B,MAAM,cAAc;AAAA,EAYzB,YAAY,SAMT;AAjBH,SAAQ,UAAU,oBAAI,IAA+B;AAErD,SAAQ,qBAAsD;AAI9D,SAAQ,QAA0B;AAClC,SAAQ,YAAkC;AAC1C,SAAQ,YAAwC;AAChD,SAAQ,aAAa,IAAI,2CAAiB;AASxC,SAAK,aAAa,QAAQ;AAC1B,SAAK,cAAc,QAAQ;AAC3B,SAAK,cAAc,QAAQ;AAC3B,SAAK,cAAc,QAAQ;AAC3B,QAAI,QAAQ,iBAAiB;AAC3B,WAAK,WAAW,mBAAmB,QAAQ,eAAe;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,IAAoC;AACnD,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAwB;AAC/B,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,WAAgC;AAC3C,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,MAAiC;AACvC,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAoB;AAClC,SAAK,WAAW,gBAAgB,IAAI;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,MAAoB;AACrC,SAAK,WAAW,mBAAmB,IAAI;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAA0B;AAClC,SAAK,WAAW,UAAU,MAAM;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,UAAkB,cAAoE;AACvG,QAAI,CAAC,KAAK,oBAAoB;AAC5B,YAAM,IAAI,MAAM,6DAA6D;AAAA,IAC/E;AAEA,UAAM,MAAM,KAAK,WAAW,QAAQ;AACpC,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,WAAW,QAAQ,yBAAyB;AAAA,IAC9D;AAGA,UAAM,WAAW,KAAK,QAAQ,IAAI,QAAQ;AAC1C,QAAI,YAAY,CAAC,SAAS,YAAY,GAAG;AACvC,eAAS,MAAM;AACf,aAAO;AAAA,IACT;AAGA,QAAI;AACJ,QAAI,IAAI,QAAQ;AACd,qBAAe,KAAK,QAAQ,IAAI,IAAI,MAAM;AAAA,IAC5C;AAGA,UAAM,oBAAoB,IAAI,mBAAmB;AACjD,UAAM,cAAc,oBAAoB,KAAK,WAAW,UAAU,QAAQ,IAAI;AAG9E,UAAM,UAAU,IAAI,QAAQ;AAC5B,UAAM,YAAY,qBAAqB,eAAe,QAAQ,WAAW,cAAc,SAAS;AAGhG,UAAM,UAAmC;AAAA,MACvC,OAAO,IAAI;AAAA,MACX,OAAO,aAAa,SAAS,IAAI;AAAA,MACjC,QAAQ,aAAa,UAAU,IAAI;AAAA,MACnC,GAAI,eAAe,EAAE,GAAG,YAAY,GAAG,GAAG,YAAY,EAAE;AAAA,MACxD,OAAO,IAAI,SAAS;AAAA,MACpB,aAAa,IAAI,eAAe;AAAA,MAChC,WAAW,IAAI,aAAa;AAAA,MAC5B,aAAa,IAAI,eAAe;AAAA,MAChC,MAAM,YAAY,QAAQ;AAAA,MAC1B,OAAO,IAAI,SAAS;AAAA,MACpB,GAAI,IAAI,YAAY,QAAQ,EAAE,UAAU,IAAI,SAAS;AAAA,MACrD,GAAI,IAAI,aAAa,QAAQ,EAAE,WAAW,IAAI,UAAU;AAAA,MACxD,GAAI,IAAI,YAAY,QAAQ,EAAE,UAAU,IAAI,SAAS;AAAA,MACrD,GAAI,IAAI,aAAa,QAAQ,EAAE,WAAW,IAAI,UAAU;AAAA,MACxD,GAAI,IAAI,YAAY,EAAE,UAAU,IAAI,SAAS;AAAA,MAC7C,GAAI,IAAI,sBAAsB,EAAE,oBAAoB,IAAI,mBAAmB;AAAA,MAC3E,GAAI,IAAI,iBAAiB,EAAE,eAAe,IAAI,cAAc;AAAA,MAC5D,GAAI,IAAI,wBAAwB,EAAE,sBAAsB,IAAI,qBAAqB;AAAA,MACjF,GAAI,IAAI,mBAAmB,QAAQ,EAAE,iBAAiB,IAAI,gBAAgB;AAAA,MAC1E,GAAI,gBAAgB,EAAE,QAAQ,aAAa;AAAA,MAC3C,gBAAgB;AAAA,QACd,SAAS,KAAK;AAAA,QACd,kBAAkB;AAAA,QAClB,SAAS;AAAA,QACT,iBAAiB;AAAA,MACnB;AAAA,MACA,GAAG;AAAA,IACL;AAEA,UAAM,MAAM,IAAI,KAAK,mBAAmB,OAAO;AAG/C,QAAI,aAAa,aAAa;AAC5B,UAAI,SAAS;AAAA,IACf,WAAW,aAAa,cAAc;AACpC,UAAI,cAAc,IAAI;AAAA,IACxB;AAGA,QAAI,WAAW;AACb,UAAI,KAAK;AAAA,IACX;AAGA,QAAI,KAAK,aAAa;AACpB,YAAM,IAAI,QAAQ,KAAK,WAAW;AAAA,IACpC,WAAW,KAAK,aAAa;AAC3B,YAAM,IAAI,aAAS,kBAAK,KAAK,aAAa,YAAY,CAAC;AAAA,IACzD;AAGA,QAAI,KAAK,OAAO;AACd,YAAM,WAAW,KAAK;AACtB,UAAI,YAAY,qBAAqB,CAAC,EAAE,IAAI,MAAM;AAChD,YAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;AAC3D,mBAAS,aAAa,GAAG;AAAA,QAC3B;AACA,eAAO,EAAE,QAAQ,OAAO;AAAA,MAC1B,CAAC;AAAA,IACH;AAGA,SAAK,QAAQ,IAAI,UAAU,GAAG;AAC9B,WAAO,MAAM,mBAAmB,QAAQ,GAAG;AAG3C,QAAI,mBAAmB;AACrB,YAAM,sBAAsB,MAAM;AAChC,YAAI,CAAC,IAAI,YAAY,KAAK,CAAC,IAAI,aAAa,GAAG;AAC7C,gBAAM,OAAO,IAAI,UAAU;AAC3B,eAAK,WAAW,sBAAsB,UAAU;AAAA,YAC9C,GAAG;AAAA,YACH,aAAa;AAAA,YACb,cAAc;AAAA,UAChB,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,GAAG,QAAQ,mBAAmB;AAClC,UAAI,GAAG,UAAU,mBAAmB;AAEpC,UAAI,GAAG,SAAS,MAAM;AACpB,cAAM,OAAO,IAAI,UAAU;AAC3B,aAAK,WAAW,eAAe,UAAU;AAAA,UACvC,GAAG;AAAA,UACH,aAAa,IAAI,YAAY;AAAA,UAC7B,cAAc,IAAI,aAAa;AAAA,QACjC,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAGA,QAAI,GAAG,UAAU,MAAM;AACrB,WAAK,QAAQ,OAAO,QAAQ;AAC5B,aAAO,MAAM,WAAW,QAAQ,UAAU;AAAA,IAC5C,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAiD;AACzD,UAAM,MAAM,KAAK,QAAQ,IAAI,QAAQ;AACrC,QAAI,OAAO,CAAC,IAAI,YAAY,EAAG,QAAO;AACtC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAwB;AAClC,UAAM,MAAM,KAAK,QAAQ,IAAI,QAAQ;AACrC,QAAI,OAAO,CAAC,IAAI,YAAY,GAAG;AAC7B,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAwB;AAClC,UAAM,MAAM,KAAK,QAAQ,IAAI,QAAQ;AACrC,QAAI,OAAO,CAAC,IAAI,YAAY,GAAG;AAC7B,UAAI,KAAK;AACT,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,YAAoB,MAAuB;AACxD,eAAW,CAAC,EAAE,GAAG,KAAK,KAAK,SAAS;AAClC,UAAI,CAAC,IAAI,YAAY,GAAG;AACtB,YAAI,YAAY,KAAK,SAAS,GAAG,IAAI;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA6B;AAC3B,WAAO,MAAM,KAAK,KAAK,QAAQ,QAAQ,CAAC,EACrC,OAAO,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,YAAY,CAAC,EACtC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,eAAW,CAAC,EAAE,GAAG,KAAK,KAAK,SAAS;AAClC,UAAI,CAAC,IAAI,YAAY,GAAG;AACtB,YAAI,QAAQ;AAAA,MACd;AAAA,IACF;AACA,SAAK,QAAQ,MAAM;AACnB,WAAO,MAAM,oBAAoB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,sBAAsB,MAIV;AAChB,YAAQ,KAAK,QAAQ;AAAA,MACnB,KAAK;AACH,YAAI,CAAC,KAAK,SAAU,OAAM,IAAI,MAAM,4BAA4B;AAChE,cAAM,KAAK,aAAa,KAAK,UAAU,KAAK,OAAO;AACnD;AAAA,MACF,KAAK;AACH,YAAI,KAAK,UAAU;AACjB,eAAK,YAAY,KAAK,QAAQ;AAAA,QAChC;AACA;AAAA,MACF,KAAK;AACH,YAAI,CAAC,KAAK,SAAU,OAAM,IAAI,MAAM,6BAA6B;AACjE,aAAK,YAAY,KAAK,QAAQ;AAC9B;AAAA,MACF;AACE,cAAM,IAAI,MAAM,0BAA0B,KAAK,MAAM,EAAE;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,kBACE,UACA,MACM;AACN,UAAM,MAAM,KAAK,QAAQ,IAAI,QAAQ;AACrC,QAAI,CAAC,OAAO,IAAI,YAAY,EAAG;AAE/B,UAAM,MAAM,KAAK,WAAW,QAAQ;AACpC,QAAI,CAAC,IAAK;AAEV,QAAI,KAAK,WAAW,OAAQ;AAE5B,QAAI,IAAI,gBAAgB,UAAU;AAChC,WAAK,sBAAsB,KAAK,KAAK,KAAK;AAAA,IAC5C,WAAW,IAAI,gBAAgB,UAAU;AACvC,UAAI,YAAY,KAAK,8BAA8B;AAAA,QACjD,OAAO,KAAK;AAAA,QACZ,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,KAAwB,OAAgC;AACpF,QAAI,CAAC,KAAK,UAAW;AAErB,UAAM,WAAW,MAAM,IAAI,UAAQ;AACjC,UAAI,KAAK,SAAS,aAAa;AAC7B,eAAO,EAAE,OAAO,IAAI,OAAO,MAAM;AAAA,QAAC,GAAG,MAAM,aAAsB,SAAS,KAAK;AAAA,MACjF;AACA,aAAO;AAAA,QACL,OAAO,KAAK,SAAS;AAAA,QACrB,SAAS,KAAK,aAAa;AAAA,QAC3B,OAAO,MAAM;AACX,cAAI,KAAK,aAAa;AACpB,gBAAI,YAAY,KAAK,kCAAkC,KAAK,WAAW;AAAA,UACzE,WAAW,KAAK,QAAQ;AACtB,iBAAK,kBAAkB,KAAK,QAAQ,KAAK,MAAM,GAAG;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI,SAAS,WAAW,EAAG;AAE3B,UAAM,OAAO,KAAK,UAAU,kBAAkB,QAAQ;AACtD,SAAK,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB,QAAgB,MAA0B,KAA8B;AAChG,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,YAAI,QAAQ,KAAK,OAAO;AACtB,eAAK,MAAM,aAAa,IAAI;AAAA,QAC9B;AACA;AAAA,MACF,KAAK;AACH,YAAI,QAAQ,KAAK,WAAW;AAC1B,eAAK,UAAU,UAAU,IAAI;AAAA,QAC/B;AACA;AAAA,MACF,KAAK;AACH,YAAI,YAAY,KAAK,sBAAsB,MAAM;AACjD;AAAA,MACF,KAAK;AACH,YAAI,YAAY,KAAK,sBAAsB,KAAK;AAChD;AAAA,MACF,KAAK;AACH,YAAI,YAAY,KAAK,sBAAsB,OAAO;AAClD;AAAA,MACF,KAAK;AACH,YAAI,YAAY,KAAK,sBAAsB,WAAW;AACtD;AAAA,MACF;AAEE,YAAI,YAAY,KAAK,wBAAwB,QAAQ,IAAI;AACzD;AAAA,IACJ;AAAA,EACF;AACF;","names":[]}
|