@easy-electron/core 1.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,787 @@
1
+ import { BrowserWindow, ipcMain, app } from 'electron';
2
+ import { Logger } from '@easy-electron/logger';
3
+ import path from 'node:path';
4
+ import { EEUtil, ConfigUtil, ipcRegistry } from '@easy-electron/utils';
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ class EasyElectronError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "EasyElectronError";
11
+ }
12
+ }
13
+
14
+ class IpcError extends EasyElectronError {
15
+ /** 错误码 */
16
+ code;
17
+ constructor(code, message) {
18
+ super(message);
19
+ this.name = "IpcError";
20
+ this.code = code;
21
+ }
22
+ /**
23
+ * 将任意错误包装为 IpcError
24
+ * @param error 原始错误
25
+ * @returns IpcError 实例
26
+ */
27
+ static from(error) {
28
+ if (error instanceof IpcError) return error;
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ return new IpcError("IPC_UNKNOWN" /* UNKNOWN */, message);
31
+ }
32
+ }
33
+
34
+ const WindowEventTypes = ["created", "closed", "focus", "blur", "ready"];
35
+ const DEFAULT_MAIN_WINDOW_ID = "main";
36
+ const DEFAULT_APP_CONFIG = {
37
+ app: {
38
+ name: "Electron App",
39
+ singleInstance: true
40
+ },
41
+ window: {
42
+ width: 800,
43
+ height: 600,
44
+ webPreferences: {
45
+ preload: EEUtil.getDefaultPreloadPath()
46
+ }
47
+ },
48
+ dev: {
49
+ openDevTools: "right"
50
+ },
51
+ prod: {
52
+ htmlPath: "dist/index.html"
53
+ },
54
+ plugins: []
55
+ };
56
+
57
+ class WindowManager {
58
+ windows;
59
+ defaultConfig;
60
+ eventListeners;
61
+ logger = Logger.scope("WindowManager");
62
+ /** 开发模式下的页面 URL(可选,替代默认的 VITE_DEV_SERVER_URL 检测) */
63
+ devUrl;
64
+ /** 生产模式下的 HTML 路径(可选) */
65
+ prodHtmlPath;
66
+ constructor(defaultConfig, devUrl, prodHtmlPath) {
67
+ this.windows = /* @__PURE__ */ new Map();
68
+ this.defaultConfig = defaultConfig || {};
69
+ this.eventListeners = /* @__PURE__ */ new Map();
70
+ this.devUrl = devUrl;
71
+ this.prodHtmlPath = prodHtmlPath;
72
+ WindowEventTypes.forEach((type) => {
73
+ this.eventListeners.set(type, /* @__PURE__ */ new Set());
74
+ });
75
+ }
76
+ /**
77
+ * 创建窗口
78
+ * @param config 窗口配置
79
+ * @returns 窗口实例
80
+ */
81
+ async create(config) {
82
+ const mergedConfig = ConfigUtil.deepMergeConfig(this.defaultConfig, config || {});
83
+ const windowId = this.windows.size === 0 ? DEFAULT_MAIN_WINDOW_ID : mergedConfig.id || randomUUID();
84
+ if (this.windows.has(windowId)) {
85
+ const error = new EasyElectronError(`Window with id '${windowId}' already exists`);
86
+ this.logger.error(error.message);
87
+ throw error;
88
+ }
89
+ this.logger.info(`Creating window with id '${windowId}'`);
90
+ const { id: _id, url: _url, htmlPath: _htmlPath, raw, ...nativeOptions } = mergedConfig;
91
+ const windowOptions = ConfigUtil.deepMergeConfig(
92
+ nativeOptions,
93
+ raw || {}
94
+ );
95
+ const browserWindow = new BrowserWindow(windowOptions);
96
+ const instance = {
97
+ id: windowId,
98
+ window: browserWindow,
99
+ config: mergedConfig
100
+ };
101
+ this.windows.set(windowId, instance);
102
+ this.setupWindowListeners(instance);
103
+ try {
104
+ await this.loadContent(browserWindow, mergedConfig);
105
+ } catch (error) {
106
+ this.logger.error(`\u7A97\u53E3\u52A0\u8F7D\u5931\u8D25 [${windowId}]:`, error instanceof Error ? error.message : "");
107
+ const errMsg = error instanceof Error ? error.message : String(error);
108
+ browserWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(this.buildErrorPage(windowId, errMsg))}`);
109
+ }
110
+ this.emit("created", instance);
111
+ return instance;
112
+ }
113
+ /**
114
+ * 加载窗口内容
115
+ */
116
+ async loadContent(window, config) {
117
+ if (config.url) {
118
+ await window.loadURL(config.url);
119
+ } else if (config.htmlPath) {
120
+ await window.loadFile(config.htmlPath);
121
+ } else if (this.devUrl) {
122
+ await window.loadURL(this.devUrl);
123
+ } else {
124
+ const htmlPath = this.prodHtmlPath || path.join(EEUtil.getAppRoot(), "dist", "index.html");
125
+ await window.loadFile(htmlPath);
126
+ }
127
+ }
128
+ /**
129
+ * 设置窗口事件监听器
130
+ */
131
+ setupWindowListeners(instance) {
132
+ const { window } = instance;
133
+ window.once("ready-to-show", () => {
134
+ this.emit("ready", instance);
135
+ });
136
+ window.on("focus", () => {
137
+ this.emit("focus", instance);
138
+ });
139
+ window.on("blur", () => {
140
+ this.emit("blur", instance);
141
+ });
142
+ window.on("close", () => {
143
+ this.emit("closed", instance);
144
+ this.windows.delete(instance.id);
145
+ });
146
+ }
147
+ /**
148
+ * 获取窗口实例
149
+ * @param id 窗口 ID
150
+ * @returns 窗口实例或 undefined
151
+ */
152
+ get(id) {
153
+ return this.windows.get(id);
154
+ }
155
+ /**
156
+ * 获取所有窗口实例
157
+ * @returns 窗口实例数组
158
+ */
159
+ getAll() {
160
+ return Array.from(this.windows.values());
161
+ }
162
+ /**
163
+ * 检查窗口是否存在
164
+ * @param id 窗口 ID
165
+ * @returns 是否存在
166
+ */
167
+ has(id) {
168
+ return this.windows.has(id);
169
+ }
170
+ /**
171
+ * 获取窗口数量
172
+ * @returns 窗口数量
173
+ */
174
+ count() {
175
+ return this.windows.size;
176
+ }
177
+ /**
178
+ * 关闭窗口
179
+ * @param id 窗口 ID
180
+ */
181
+ close(id) {
182
+ const instance = this.windows.get(id);
183
+ if (!instance) {
184
+ const error = new EasyElectronError(`Window with id '${id}' not found`);
185
+ this.logger.error(error.message);
186
+ throw error;
187
+ }
188
+ this.logger.info(`Closing window with id '${id}'`);
189
+ if (!instance.window.isDestroyed()) {
190
+ instance.window.close();
191
+ }
192
+ }
193
+ /**
194
+ * 关闭所有窗口
195
+ */
196
+ closeAll() {
197
+ const windowIds = Array.from(this.windows.keys());
198
+ windowIds.forEach((id) => {
199
+ try {
200
+ this.close(id);
201
+ } catch (error) {
202
+ console.error(`Error closing window '${id}':`, error);
203
+ }
204
+ });
205
+ }
206
+ /**
207
+ * 注册窗口事件监听器
208
+ * @param event 事件类型
209
+ * @param listener 监听器函数
210
+ */
211
+ on(event, listener) {
212
+ const listeners = this.eventListeners.get(event);
213
+ if (listeners) {
214
+ listeners.add(listener);
215
+ }
216
+ }
217
+ /**
218
+ * 移除窗口事件监听器
219
+ * @param event 事件类型
220
+ * @param listener 监听器函数
221
+ */
222
+ off(event, listener) {
223
+ const listeners = this.eventListeners.get(event);
224
+ if (listeners) {
225
+ listeners.delete(listener);
226
+ }
227
+ }
228
+ /**
229
+ * 触发窗口事件
230
+ * @param event 事件类型
231
+ * @param instance 窗口实例
232
+ */
233
+ emit(event, instance) {
234
+ const listeners = this.eventListeners.get(event);
235
+ if (listeners) {
236
+ listeners.forEach((listener) => {
237
+ try {
238
+ listener(instance);
239
+ } catch (error) {
240
+ console.error(`Error in window event listener for '${event}':`, error);
241
+ }
242
+ });
243
+ }
244
+ }
245
+ /**
246
+ * 获取默认配置
247
+ */
248
+ getDefaultConfig() {
249
+ return ConfigUtil.deepMergeConfig({}, this.defaultConfig);
250
+ }
251
+ /**
252
+ * 构建降级错误页面(当窗口内容加载失败时显示)
253
+ * @param windowId 窗口 ID
254
+ * @param errorMessage 错误信息
255
+ * @returns HTML 字符串
256
+ */
257
+ buildErrorPage(windowId, errorMessage) {
258
+ return `<!DOCTYPE html>
259
+ <html lang="zh-CN">
260
+ <head>
261
+ <meta charset="UTF-8">
262
+ <style>
263
+ * { margin: 0; padding: 0; box-sizing: border-box; }
264
+ body {
265
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
266
+ display: flex; justify-content: center; align-items: center;
267
+ min-height: 100vh; background: #1a1a2e; color: #e0e0e0;
268
+ }
269
+ .card {
270
+ background: #16213e; border-radius: 12px; padding: 40px;
271
+ max-width: 520px; text-align: center;
272
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
273
+ }
274
+ h1 { font-size: 20px; color: #e94560; margin-bottom: 16px; }
275
+ .detail {
276
+ background: #0f3460; border-radius: 8px; padding: 16px;
277
+ font-size: 13px; color: #a0a0b0; text-align: left;
278
+ word-break: break-all; margin: 16px 0;
279
+ }
280
+ .hint { font-size: 12px; color: #666; }
281
+ .hint code { background: #0f3460; padding: 2px 6px; border-radius: 4px; }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <div class="card">
286
+ <h1>\u26A0 \u7A97\u53E3\u52A0\u8F7D\u5931\u8D25</h1>
287
+ <p>\u7A97\u53E3 <strong>${windowId}</strong> \u7684\u5185\u5BB9\u672A\u80FD\u6210\u529F\u52A0\u8F7D\u3002</p>
288
+ <div class="detail">${errorMessage}</div>
289
+ <p class="hint">
290
+ \u8BF7\u68C0\u67E5 <code>htmlPath</code> \u6216 <code>url</code> \u914D\u7F6E\uFF0C\u6216\u786E\u4FDD\u5F00\u53D1\u670D\u52A1\u5668\u5DF2\u542F\u52A8\u3002
291
+ </p>
292
+ </div>
293
+ </body>
294
+ </html>`;
295
+ }
296
+ clear() {
297
+ this.windows.clear();
298
+ this.eventListeners.clear();
299
+ }
300
+ }
301
+
302
+ const IPC_MODULE_KEY = Symbol.for("easy-electron:ipc-module");
303
+ class IpcManager {
304
+ handlers;
305
+ windowManager;
306
+ logger = Logger.scope("IpcManager");
307
+ constructor(windowManager) {
308
+ this.handlers = /* @__PURE__ */ new Map();
309
+ this.windowManager = windowManager;
310
+ }
311
+ /**
312
+ * 注册 IPC 处理器
313
+ * @param channel IPC 通道名称
314
+ * @param handler 处理器函数
315
+ * @param options 处理器选项
316
+ */
317
+ handle(channel, handler, options) {
318
+ if (this.handlers.has(channel)) {
319
+ const error = new EasyElectronError(`IPC handler for channel '${channel}' already exists`);
320
+ this.logger.error(error.message);
321
+ throw error;
322
+ }
323
+ this.logger.info(`Registering IPC handler for channel '${channel}'`);
324
+ this.handlers.set(channel, handler);
325
+ if (options?.sync) {
326
+ ipcMain.on(channel, (event, ...args) => {
327
+ try {
328
+ const result = handler(event, ...args);
329
+ event.returnValue = result;
330
+ } catch (error) {
331
+ console.error(`Error in sync IPC handler for '${channel}':`, error);
332
+ event.returnValue = { error: error instanceof Error ? error.message : "Unknown error" };
333
+ }
334
+ });
335
+ } else {
336
+ ipcMain.handle(channel, async (event, ...args) => {
337
+ try {
338
+ return await handler(event, ...args);
339
+ } catch (error) {
340
+ console.error(`Error in async IPC handler for '${channel}':`, error);
341
+ throw error;
342
+ }
343
+ });
344
+ }
345
+ }
346
+ /**
347
+ * 批量注册 defineIpc 返回的处理器映射
348
+ *
349
+ * 自动识别函数形式(handle)和对象形式({type, handler}),
350
+ * 根据 moduleName 前缀拼接 IPC 通道名。
351
+ *
352
+ * @param handlers defineIpc 返回的处理器映射对象
353
+ * @param moduleName 可选的模块名称(用于拼接通道前缀,如 `module:method`)
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * import ipcHandlers from './electron/ipc/handlers'
358
+ * app.ipc.registerHandlers(ipcHandlers)
359
+ * ```
360
+ */
361
+ registerHandlers(handlers, moduleName) {
362
+ if (!moduleName) {
363
+ moduleName = handlers[IPC_MODULE_KEY];
364
+ }
365
+ for (const [name, handler] of Object.entries(handlers)) {
366
+ if (typeof name === "string" && name.startsWith("__")) continue;
367
+ const channel = moduleName ? `${moduleName}:${name}` : name;
368
+ if (typeof handler === "object" && handler !== null && "handler" in handler) {
369
+ const config = handler;
370
+ const type = config.type ?? "handle";
371
+ const handlerFn = config.handler;
372
+ if (type === "on" || type === "once") {
373
+ this.logger.info(`\u6CE8\u518C IPC \u5904\u7406\u5668(${type}): ${channel}`);
374
+ const method = type === "once" ? "once" : "on";
375
+ ipcMain[method](channel, (event, ...args) => {
376
+ try {
377
+ handlerFn(event, ...args);
378
+ } catch (error) {
379
+ console.error(`Error in IPC ${type} handler for '${channel}':`, error);
380
+ }
381
+ });
382
+ } else {
383
+ this.logger.info(`\u6CE8\u518C IPC \u5904\u7406\u5668(${type}): ${channel}`);
384
+ const method = type === "handleOnce" ? "handleOnce" : "handle";
385
+ ipcMain[method](channel, async (event, ...args) => {
386
+ try {
387
+ return await handlerFn(event, ...args);
388
+ } catch (error) {
389
+ console.error(`Error in IPC ${type} handler for '${channel}':`, error);
390
+ throw IpcError.from(error);
391
+ }
392
+ });
393
+ }
394
+ if (!this.handlers.has(channel)) {
395
+ this.handlers.set(channel, handlerFn);
396
+ }
397
+ } else if (typeof handler === "function") {
398
+ this.logger.info(`\u6CE8\u518C IPC \u5904\u7406\u5668(handle): ${channel}`);
399
+ ipcMain.handle(channel, async (event, ...args) => {
400
+ try {
401
+ return await handler(event, ...args);
402
+ } catch (error) {
403
+ console.error(`Error in IPC handler for '${channel}':`, error);
404
+ throw IpcError.from(error);
405
+ }
406
+ });
407
+ if (!this.handlers.has(channel)) {
408
+ this.handlers.set(channel, handler);
409
+ }
410
+ } else {
411
+ this.logger.warn(`\u8DF3\u8FC7\u65E0\u6CD5\u8BC6\u522B\u7684\u5904\u7406\u5668: ${channel}`);
412
+ }
413
+ }
414
+ }
415
+ /**
416
+ * 移除 IPC 处理器
417
+ * @param channel IPC 通道名称
418
+ */
419
+ removeHandler(channel) {
420
+ if (!this.handlers.has(channel)) {
421
+ return;
422
+ }
423
+ this.handlers.delete(channel);
424
+ ipcMain.removeHandler(channel);
425
+ ipcMain.removeAllListeners(channel);
426
+ }
427
+ /**
428
+ * 移除所有 IPC 处理器
429
+ */
430
+ removeAllHandlers() {
431
+ const channels = Array.from(this.handlers.keys());
432
+ channels.forEach((channel) => {
433
+ this.removeHandler(channel);
434
+ });
435
+ }
436
+ /**
437
+ * 获取已注册的处理器数量
438
+ */
439
+ count() {
440
+ return this.handlers.size;
441
+ }
442
+ /**
443
+ * 检查处理器是否已注册
444
+ */
445
+ has(channel) {
446
+ return this.handlers.has(channel);
447
+ }
448
+ /**
449
+ * 向主窗口发送消息
450
+ * @param channel IPC 通道名称
451
+ * @param args 消息参数
452
+ */
453
+ send(channel, ...args) {
454
+ const instance = this.windowManager.get(DEFAULT_MAIN_WINDOW_ID);
455
+ if (!instance) {
456
+ throw new EasyElectronError("Main window not found");
457
+ }
458
+ if (!instance.window.isDestroyed()) {
459
+ instance.window.webContents.send(channel, ...args);
460
+ }
461
+ }
462
+ /**
463
+ * 向指定窗口发送消息
464
+ * @param windowId 窗口 ID
465
+ * @param channel IPC 通道名称
466
+ * @param args 消息参数
467
+ */
468
+ sendTo(windowId, channel, ...args) {
469
+ const instance = this.windowManager.get(windowId);
470
+ if (!instance) {
471
+ throw new EasyElectronError(`Window with id '${windowId}' not found`);
472
+ }
473
+ if (!instance.window.isDestroyed()) {
474
+ instance.window.webContents.send(channel, ...args);
475
+ }
476
+ }
477
+ /**
478
+ * 向所有窗口广播消息
479
+ * @param channel IPC 通道名称
480
+ * @param args 消息参数
481
+ */
482
+ broadcast(channel, ...args) {
483
+ const instances = this.windowManager.getAll();
484
+ instances.forEach((instance) => {
485
+ if (!instance.window.isDestroyed()) {
486
+ try {
487
+ instance.window.webContents.send(channel, ...args);
488
+ } catch (error) {
489
+ console.error(`Error broadcasting to window '${instance.id}':`, error);
490
+ }
491
+ }
492
+ });
493
+ }
494
+ }
495
+
496
+ class LifecycleManager {
497
+ app;
498
+ windowManager;
499
+ readyHooks;
500
+ activateHooks;
501
+ beforeQuitHooks;
502
+ constructor(app, windowManager) {
503
+ this.app = app;
504
+ this.windowManager = windowManager;
505
+ this.readyHooks = /* @__PURE__ */ new Set();
506
+ this.activateHooks = /* @__PURE__ */ new Set();
507
+ this.beforeQuitHooks = /* @__PURE__ */ new Set();
508
+ }
509
+ /**
510
+ * 注册 ready 钩子
511
+ * @param hook 钩子函数
512
+ */
513
+ onReady(hook) {
514
+ this.readyHooks.add(hook);
515
+ }
516
+ /**
517
+ * 注册 activate 钩子(macOS)
518
+ * @param hook 钩子函数
519
+ */
520
+ onActivate(hook) {
521
+ this.activateHooks.add(hook);
522
+ }
523
+ /**
524
+ * 注册退出前的清理钩子
525
+ * @param hook 钩子函数
526
+ */
527
+ onBeforeQuit(hook) {
528
+ this.beforeQuitHooks.add(hook);
529
+ }
530
+ /**
531
+ * 初始化生命周期监听
532
+ */
533
+ init() {
534
+ this.app.whenReady().then(async () => {
535
+ await this.executeHooks(this.readyHooks);
536
+ });
537
+ this.app.on("activate", async () => {
538
+ await this.executeHooks(this.activateHooks);
539
+ });
540
+ this.app.on("will-quit", async (event) => {
541
+ if (this.beforeQuitHooks.size > 0) {
542
+ event.preventDefault();
543
+ try {
544
+ await this.executeHooks(this.beforeQuitHooks);
545
+ } catch (error) {
546
+ console.error("\u9000\u51FA\u524D\u6E05\u7406\u5931\u8D25:", error);
547
+ }
548
+ this.app.exit(0);
549
+ }
550
+ });
551
+ this.app.on("window-all-closed", () => {
552
+ if (process.platform !== "darwin") {
553
+ this.app.quit();
554
+ this.windowManager.clear();
555
+ }
556
+ });
557
+ }
558
+ /**
559
+ * 执行钩子集合
560
+ * @param hooks 钩子函数集合
561
+ */
562
+ async executeHooks(hooks) {
563
+ for (const hook of hooks) {
564
+ try {
565
+ await hook();
566
+ } catch (error) {
567
+ console.error("\u751F\u547D\u5468\u671F\u94A9\u5B50\u6267\u884C\u5931\u8D25:", error);
568
+ }
569
+ }
570
+ }
571
+ }
572
+
573
+ class PluginManager {
574
+ plugins;
575
+ context;
576
+ constructor(context) {
577
+ this.plugins = /* @__PURE__ */ new Map();
578
+ this.context = context;
579
+ }
580
+ /**
581
+ * 注册插件
582
+ * @param plugin 插件对象
583
+ */
584
+ register(plugin) {
585
+ if (!plugin.name) {
586
+ throw new EasyElectronError("Plugin must have a name");
587
+ }
588
+ if (this.plugins.has(plugin.name)) {
589
+ throw new EasyElectronError(`Plugin '${plugin.name}' is already registered`);
590
+ }
591
+ this.plugins.set(plugin.name, plugin);
592
+ }
593
+ /**
594
+ * 获取插件
595
+ * @param name 插件名称
596
+ * @returns 插件对象或 undefined
597
+ */
598
+ get(name) {
599
+ return this.plugins.get(name);
600
+ }
601
+ /**
602
+ * 检查插件是否已注册
603
+ * @param name 插件名称
604
+ * @returns 是否已注册
605
+ */
606
+ has(name) {
607
+ return this.plugins.has(name);
608
+ }
609
+ /**
610
+ * 调用插件钩子
611
+ * @param hookName 钩子名称
612
+ */
613
+ async callHook(hookName) {
614
+ for (const plugin of this.plugins.values()) {
615
+ const hook = plugin[hookName];
616
+ if (typeof hook === "function") {
617
+ try {
618
+ await hook.call(plugin, this.context);
619
+ } catch (error) {
620
+ console.error(`Error in plugin '${plugin.name}' hook '${String(hookName)}':`, error);
621
+ }
622
+ }
623
+ }
624
+ }
625
+ /**
626
+ * 获取已注册的插件数量
627
+ */
628
+ count() {
629
+ return this.plugins.size;
630
+ }
631
+ /**
632
+ * 获取所有插件
633
+ */
634
+ getAll() {
635
+ return Array.from(this.plugins.values());
636
+ }
637
+ }
638
+
639
+ class EasyElectron {
640
+ windowManager;
641
+ ipcManager;
642
+ lifecycleManager;
643
+ pluginManager;
644
+ config;
645
+ constructor(config) {
646
+ this.config = ConfigUtil.deepMergeConfig(DEFAULT_APP_CONFIG, config || {});
647
+ this.windowManager = new WindowManager(
648
+ this.config.window,
649
+ this.config.dev?.serverUrl,
650
+ this.config.prod?.htmlPath
651
+ );
652
+ this.ipcManager = new IpcManager(this.windowManager);
653
+ this.lifecycleManager = new LifecycleManager(app, this.windowManager);
654
+ const pluginContext = {
655
+ windows: this.windowManager,
656
+ ipc: this.ipcManager,
657
+ app,
658
+ config: this.config
659
+ };
660
+ this.pluginManager = new PluginManager(pluginContext);
661
+ if (this.config.plugins) {
662
+ this.config.plugins.forEach((plugin) => {
663
+ this.pluginManager.register(plugin);
664
+ });
665
+ }
666
+ }
667
+ /**
668
+ * 获取窗口管理器
669
+ */
670
+ get windows() {
671
+ return this.windowManager;
672
+ }
673
+ /**
674
+ * 获取 IPC 管理器
675
+ */
676
+ get ipc() {
677
+ return this.ipcManager;
678
+ }
679
+ /**
680
+ * 初始化应用程序
681
+ */
682
+ async init() {
683
+ if (this.config.app?.singleInstance) {
684
+ const gotLock = app.requestSingleInstanceLock();
685
+ if (!gotLock) {
686
+ app.quit();
687
+ return;
688
+ }
689
+ app.on("second-instance", () => {
690
+ const mainWindow = this.windowManager.get("main");
691
+ if (mainWindow && !mainWindow.window.isDestroyed()) {
692
+ if (mainWindow.window.isMinimized()) mainWindow.window.restore();
693
+ mainWindow.window.focus();
694
+ }
695
+ });
696
+ }
697
+ await this.pluginManager.callHook("install");
698
+ const entries = ipcRegistry.drain();
699
+ for (const entry of entries) {
700
+ this.ipcManager.registerHandlers(entry.handlers, entry.moduleName);
701
+ }
702
+ this.lifecycleManager.onActivate(async () => {
703
+ if (this.windowManager.count() === 0) {
704
+ await this.windowManager.create();
705
+ }
706
+ });
707
+ this.lifecycleManager.onReady(async () => {
708
+ await this.pluginManager.callHook("onReady");
709
+ const instance = await this.windowManager.create();
710
+ if (EEUtil.isDev && this.config.dev?.openDevTools) {
711
+ instance.window.webContents.openDevTools({ mode: this.config.dev?.openDevTools || "right" });
712
+ }
713
+ });
714
+ this.lifecycleManager.onBeforeQuit(async () => {
715
+ await this.pluginManager.callHook("onBeforeQuit");
716
+ this.ipcManager.removeAllHandlers();
717
+ });
718
+ this.lifecycleManager.init();
719
+ }
720
+ /**
721
+ * 注册插件
722
+ */
723
+ use(plugin) {
724
+ this.pluginManager.register(plugin);
725
+ return this;
726
+ }
727
+ /**
728
+ * 注册 ready 钩子
729
+ */
730
+ onReady(callback) {
731
+ this.lifecycleManager.onReady(callback);
732
+ return this;
733
+ }
734
+ /**
735
+ * 注册 activate 钩子
736
+ */
737
+ onActivate(callback) {
738
+ this.lifecycleManager.onActivate(callback);
739
+ return this;
740
+ }
741
+ /**
742
+ * 注册 beforeQuit 钩子
743
+ */
744
+ onBeforeQuit(callback) {
745
+ this.lifecycleManager.onBeforeQuit(callback);
746
+ return this;
747
+ }
748
+ /**
749
+ * 设置 IPC 处理器
750
+ * @param setup IPC 处理器设置函数
751
+ */
752
+ setupIpc(setup) {
753
+ setup(this.ipcManager);
754
+ return this;
755
+ }
756
+ /**
757
+ * 批量注册 IPC 处理器
758
+ *
759
+ * 接收 defineIpc 返回的定义对象,自动识别模块名并注册所有处理器。
760
+ *
761
+ * @param handlers defineIpc 返回的处理器映射
762
+ *
763
+ * @example
764
+ * ```ts
765
+ * import ipcHandlers from './electron/ipc/handlers'
766
+ * app.registerIpc(ipcHandlers)
767
+ * ```
768
+ */
769
+ registerIpc(handlers) {
770
+ this.ipcManager.registerHandlers(handlers);
771
+ return this;
772
+ }
773
+ /**
774
+ * 修改日志实现
775
+ * @param logger 日志实现
776
+ */
777
+ setLogger(logger) {
778
+ Logger.setImpl(logger);
779
+ return this;
780
+ }
781
+ }
782
+
783
+ function createElectronApp(config) {
784
+ return new EasyElectron(config);
785
+ }
786
+
787
+ export { EasyElectron, EasyElectronError, IpcManager, LifecycleManager, PluginManager, WindowManager, createElectronApp };