@dimina-kit/devtools 0.3.1-dev.20260513123738 → 0.3.1-dev.20260515105504

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +103 -41
  2. package/dist/main/api.d.ts +6 -2
  3. package/dist/main/api.js +5 -2
  4. package/dist/main/app/app.d.ts +1 -1
  5. package/dist/main/app/app.js +71 -36
  6. package/dist/main/app/launch.d.ts +2 -2
  7. package/dist/main/app/launch.js +6 -3
  8. package/dist/main/index.bundle.js +1074 -289
  9. package/dist/main/ipc/app.d.ts +2 -1
  10. package/dist/main/ipc/app.js +5 -4
  11. package/dist/main/ipc/index.d.ts +5 -4
  12. package/dist/main/ipc/index.js +5 -4
  13. package/dist/main/ipc/panels.d.ts +2 -1
  14. package/dist/main/ipc/panels.js +14 -9
  15. package/dist/main/ipc/popover.d.ts +4 -1
  16. package/dist/main/ipc/popover.js +15 -5
  17. package/dist/main/ipc/projects.d.ts +4 -1
  18. package/dist/main/ipc/projects.js +42 -5
  19. package/dist/main/ipc/session.d.ts +4 -1
  20. package/dist/main/ipc/session.js +26 -6
  21. package/dist/main/ipc/settings.d.ts +4 -1
  22. package/dist/main/ipc/settings.js +30 -17
  23. package/dist/main/ipc/simulator-module.d.ts +8 -0
  24. package/dist/main/ipc/simulator-module.js +19 -0
  25. package/dist/main/ipc/simulator.d.ts +2 -1
  26. package/dist/main/ipc/simulator.js +29 -15
  27. package/dist/main/ipc/toolbar.d.ts +2 -1
  28. package/dist/main/ipc/toolbar.js +3 -2
  29. package/dist/main/services/automation/index.js +88 -13
  30. package/dist/main/services/mcp/server.d.ts +2 -1
  31. package/dist/main/services/mcp/server.js +5 -0
  32. package/dist/main/services/module.d.ts +18 -0
  33. package/dist/main/services/module.js +2 -0
  34. package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
  35. package/dist/main/services/projects/project-repository.d.ts +1 -0
  36. package/dist/main/services/projects/project-repository.js +3 -0
  37. package/dist/main/services/settings/index.d.ts +4 -0
  38. package/dist/main/services/settings/index.js +12 -1
  39. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  40. package/dist/main/services/simulator/custom-apis.js +26 -0
  41. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  42. package/dist/main/services/simulator-storage/index.js +365 -98
  43. package/dist/main/services/update/update-manager.d.ts +16 -1
  44. package/dist/main/services/update/update-manager.js +18 -15
  45. package/dist/main/services/views/view-manager.d.ts +4 -0
  46. package/dist/main/services/views/view-manager.js +33 -2
  47. package/dist/main/services/window-service.d.ts +24 -0
  48. package/dist/main/services/window-service.js +26 -0
  49. package/dist/main/services/workbench-context.d.ts +15 -0
  50. package/dist/main/services/workbench-context.js +6 -0
  51. package/dist/main/services/workspace/workspace-service.d.ts +1 -0
  52. package/dist/main/services/workspace/workspace-service.js +7 -3
  53. package/dist/main/simulator-apis.d.ts +23 -0
  54. package/dist/main/simulator-apis.js +24 -0
  55. package/dist/main/utils/disposable.d.ts +13 -0
  56. package/dist/main/utils/disposable.js +60 -0
  57. package/dist/main/utils/ipc-registry.d.ts +39 -0
  58. package/dist/main/utils/ipc-registry.js +109 -0
  59. package/dist/main/utils/ipc-schema.d.ts +28 -0
  60. package/dist/main/utils/ipc-schema.js +38 -0
  61. package/dist/main/utils/paths.d.ts +6 -0
  62. package/dist/main/utils/paths.js +6 -0
  63. package/dist/main/utils/sender-policy.d.ts +23 -0
  64. package/dist/main/utils/sender-policy.js +42 -0
  65. package/dist/main/windows/main-window/create.d.ts +6 -0
  66. package/dist/main/windows/main-window/create.js +65 -9
  67. package/dist/main/windows/main-window/events.d.ts +2 -1
  68. package/dist/main/windows/main-window/events.js +25 -2
  69. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  70. package/dist/main/windows/navigation-hardening.js +55 -0
  71. package/dist/main/windows/settings-window/create.js +11 -1
  72. package/dist/preload/index.d.ts +17 -0
  73. package/dist/preload/index.js +17 -0
  74. package/dist/preload/instrumentation/app-data.js +178 -19
  75. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  76. package/dist/preload/runtime/custom-apis.js +18 -0
  77. package/dist/preload/runtime/host.d.ts +1 -0
  78. package/dist/preload/runtime/host.js +13 -0
  79. package/dist/preload/windows/main.cjs +47 -0
  80. package/dist/preload/windows/main.cjs.map +7 -0
  81. package/dist/preload/windows/simulator.js +174 -18
  82. package/dist/renderer/assets/index-DW_0thr-.js +46 -0
  83. package/dist/renderer/assets/{input-DxfYnzX_.js → input-C5M5Wxn6.js} +2 -2
  84. package/dist/renderer/assets/{ipc-transport-ClIvDcYY.js → ipc-transport-CUsl-fS2.js} +2 -2
  85. package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
  86. package/dist/renderer/assets/{popover-CtXcBJtM.js → popover-BSBPVO0o.js} +2 -2
  87. package/dist/renderer/assets/{select-DMfp49f1.js → select-B1tF3UAc.js} +2 -2
  88. package/dist/renderer/assets/{settings-DteGraEU.js → settings-7w54H6p-.js} +2 -2
  89. package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
  90. package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
  91. package/dist/renderer/entries/main/index.html +12 -5
  92. package/dist/renderer/entries/popover/index.html +7 -5
  93. package/dist/renderer/entries/settings/index.html +7 -5
  94. package/dist/renderer/entries/workbench-settings/index.html +6 -4
  95. package/dist/shared/ipc-channels.d.ts +22 -0
  96. package/dist/shared/ipc-channels.js +23 -0
  97. package/dist/shared/ipc-schemas.d.ts +113 -0
  98. package/dist/shared/ipc-schemas.js +128 -0
  99. package/dist/shared/types.d.ts +2 -0
  100. package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
  101. package/dist/simulator/simulator.html +1 -1
  102. package/package.json +11 -27
  103. package/dist/preload/windows/main.d.ts +0 -6
  104. package/dist/preload/windows/main.js +0 -6
  105. package/dist/renderer/assets/index-D1p6crtf.js +0 -46
  106. package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
  107. package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
  108. package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
  109. package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
@@ -14,6 +14,9 @@ var DEFAULTS = {
14
14
  enabled: false,
15
15
  port: 7789
16
16
  },
17
+ compile: {
18
+ watch: true
19
+ },
17
20
  theme: "system"
18
21
  };
19
22
  function getSettingsFile() {
@@ -32,10 +35,18 @@ function loadWorkbenchSettings() {
32
35
  enabled: data.mcp?.enabled ?? DEFAULTS.mcp.enabled,
33
36
  port: data.mcp?.port ?? DEFAULTS.mcp.port
34
37
  },
38
+ compile: {
39
+ watch: data.compile?.watch ?? DEFAULTS.compile.watch
40
+ },
35
41
  theme
36
42
  };
37
43
  } catch {
38
- return { ...DEFAULTS, cdp: { ...DEFAULTS.cdp }, mcp: { ...DEFAULTS.mcp } };
44
+ return {
45
+ ...DEFAULTS,
46
+ cdp: { ...DEFAULTS.cdp },
47
+ mcp: { ...DEFAULTS.mcp },
48
+ compile: { ...DEFAULTS.compile }
49
+ };
39
50
  }
40
51
  }
41
52
  function saveWorkbenchSettings(settings) {
@@ -64,7 +75,7 @@ function setupCdpPort() {
64
75
  // src/main/app/app.ts
65
76
  import { app as app11, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
66
77
  import fs5 from "fs";
67
- import path6 from "path";
78
+ import path8 from "path";
68
79
 
69
80
  // src/main/utils/paths.ts
70
81
  import fs2 from "fs";
@@ -87,6 +98,7 @@ function resolveDevtoolsPackageRoot() {
87
98
  var devtoolsPackageRoot = resolveDevtoolsPackageRoot();
88
99
  var rendererDir = path2.join(devtoolsPackageRoot, "dist/renderer");
89
100
  var defaultPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
101
+ var mainPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/main.cjs");
90
102
  var simulatorDir = path2.join(devtoolsPackageRoot, "dist/simulator");
91
103
 
92
104
  // src/main/app/lifecycle.ts
@@ -102,7 +114,8 @@ function registerAppLifecycle() {
102
114
  }
103
115
 
104
116
  // src/main/windows/main-window/create.ts
105
- import { app as app4, BrowserWindow, View, globalShortcut as globalShortcut2, session } from "electron";
117
+ import { app as app4, BrowserWindow, View, session, shell as shell2 } from "electron";
118
+ import path4 from "path";
106
119
 
107
120
  // src/main/services/simulator/referer.ts
108
121
  var DEFAULT_VERSION = "develop";
@@ -129,12 +142,51 @@ function themeBg() {
129
142
  return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
130
143
  }
131
144
 
145
+ // src/main/windows/navigation-hardening.ts
146
+ import { shell } from "electron";
147
+ import path3 from "path";
148
+ import { pathToFileURL } from "url";
149
+ function handleWindowOpenExternal(url) {
150
+ try {
151
+ const u = new URL(url);
152
+ if (u.protocol === "http:" || u.protocol === "https:") {
153
+ void shell.openExternal(url);
154
+ }
155
+ } catch {
156
+ }
157
+ return { action: "deny" };
158
+ }
159
+ function applyNavigationHardening(wc, rendererDir2) {
160
+ const rendererPrefix = pathToFileURL(rendererDir2 + path3.sep).href;
161
+ wc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
162
+ wc.on("will-navigate", (event, url) => {
163
+ let parsed;
164
+ try {
165
+ parsed = new URL(url);
166
+ } catch {
167
+ event.preventDefault();
168
+ return;
169
+ }
170
+ if (parsed.protocol === "file:" && parsed.href.startsWith(rendererPrefix)) {
171
+ return;
172
+ }
173
+ event.preventDefault();
174
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
175
+ void shell.openExternal(url);
176
+ }
177
+ });
178
+ }
179
+
132
180
  // src/main/windows/main-window/create.ts
133
181
  var simulatorSessionConfigured = false;
134
- function configureSimulatorSession() {
182
+ function configureSimulatorSession(simulatorPreloadPath) {
135
183
  if (simulatorSessionConfigured) return;
136
184
  simulatorSessionConfigured = true;
137
185
  const simulatorSession = session.fromPartition("persist:simulator");
186
+ simulatorSession.registerPreloadScript({
187
+ type: "frame",
188
+ filePath: simulatorPreloadPath
189
+ });
138
190
  simulatorSession.webRequest.onBeforeSendHeaders((details, callback) => {
139
191
  const forcedReferer = getSimulatorServicewechatReferer();
140
192
  if (forcedReferer) {
@@ -153,7 +205,7 @@ function configureSimulatorSession() {
153
205
  });
154
206
  }
155
207
  function createMainWindow(opts) {
156
- configureSimulatorSession();
208
+ configureSimulatorSession(opts.simulatorPreloadPath);
157
209
  const mainWindow = new BrowserWindow({
158
210
  width: opts.width ?? 1280,
159
211
  height: opts.height ?? 980,
@@ -163,8 +215,13 @@ function createMainWindow(opts) {
163
215
  show: false,
164
216
  backgroundColor: themeBg(),
165
217
  webPreferences: {
166
- nodeIntegration: true,
167
- contextIsolation: false,
218
+ nodeIntegration: false,
219
+ contextIsolation: true,
220
+ // `sandbox: false` is required so the preload bundle can `require('electron')`
221
+ // to access contextBridge/ipcRenderer. The renderer itself runs isolated.
222
+ sandbox: false,
223
+ preload: mainPreloadPath,
224
+ // The simulator <webview> still relies on the webview tag; keep it enabled.
168
225
  webviewTag: true
169
226
  }
170
227
  });
@@ -178,22 +235,102 @@ function createMainWindow(opts) {
178
235
  }
179
236
  }
180
237
  });
181
- mainWindow.webContents.on("will-attach-webview", (_event, webPreferences) => {
238
+ const rendererDir2 = path4.dirname(opts.indexHtml);
239
+ applyNavigationHardening(mainWindow.webContents, rendererDir2);
240
+ mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {
241
+ ;
242
+ webPreferences.partition = "persist:simulator";
243
+ params.partition = "persist:simulator";
182
244
  webPreferences.contextIsolation = false;
245
+ void event;
246
+ });
247
+ mainWindow.webContents.on("did-attach-webview", (_event, webviewWebContents) => {
248
+ webviewWebContents.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
249
+ webviewWebContents.on("will-navigate", (e, url) => {
250
+ try {
251
+ const u = new URL(url);
252
+ if (u.protocol === "about:") return;
253
+ if ((u.protocol === "http:" || u.protocol === "https:") && (u.hostname === "localhost" || u.hostname === "127.0.0.1")) {
254
+ return;
255
+ }
256
+ e.preventDefault();
257
+ if (u.protocol === "http:" || u.protocol === "https:") {
258
+ void shell2.openExternal(url);
259
+ }
260
+ } catch {
261
+ e.preventDefault();
262
+ }
263
+ });
183
264
  });
184
265
  mainWindow.loadFile(opts.indexHtml);
185
266
  const container = new View();
186
267
  const mainWebView = mainWindow.contentView;
187
268
  container.addChildView(mainWebView);
188
269
  mainWindow.contentView = container;
189
- globalShortcut2.register("CommandOrControl+Shift+I", () => {
190
- mainWindow.webContents.openDevTools({ mode: "detach" });
191
- });
192
270
  return mainWindow;
193
271
  }
194
272
 
273
+ // src/main/windows/main-window/events.ts
274
+ import { globalShortcut as globalShortcut2 } from "electron";
275
+
276
+ // src/main/utils/disposable.ts
277
+ function toDisposable(fn) {
278
+ let done = false;
279
+ return {
280
+ dispose() {
281
+ if (done) return;
282
+ done = true;
283
+ return fn();
284
+ }
285
+ };
286
+ }
287
+ var DisposableRegistry = class {
288
+ entries = [];
289
+ _disposed = false;
290
+ add(d) {
291
+ if (this._disposed) {
292
+ throw new Error("cannot add to disposed registry");
293
+ }
294
+ const fn = typeof d === "function" ? d : () => d.dispose();
295
+ const entry = { fn, released: false };
296
+ this.entries.push(entry);
297
+ return {
298
+ dispose: () => {
299
+ if (entry.released) return;
300
+ entry.released = true;
301
+ const i = this.entries.indexOf(entry);
302
+ if (i >= 0) this.entries.splice(i, 1);
303
+ return fn();
304
+ }
305
+ };
306
+ }
307
+ async disposeAll() {
308
+ if (this._disposed) return;
309
+ this._disposed = true;
310
+ const items = this.entries.slice().reverse();
311
+ this.entries = [];
312
+ const errors = [];
313
+ for (const entry of items) {
314
+ if (entry.released) continue;
315
+ entry.released = true;
316
+ try {
317
+ await entry.fn();
318
+ } catch (e) {
319
+ errors.push(e);
320
+ }
321
+ }
322
+ if (errors.length > 0) {
323
+ throw new AggregateError(errors, "DisposableRegistry encountered errors during disposeAll");
324
+ }
325
+ }
326
+ dispose() {
327
+ return this.disposeAll();
328
+ }
329
+ };
330
+
195
331
  // src/main/windows/main-window/events.ts
196
332
  function wireMainWindowEvents(win, state = {}) {
333
+ const registry = new DisposableRegistry();
197
334
  const mainWebView = win.contentView.children[0];
198
335
  const resizeMainWebView = () => {
199
336
  if (mainWebView) {
@@ -204,22 +341,53 @@ function wireMainWindowEvents(win, state = {}) {
204
341
  };
205
342
  resizeMainWebView();
206
343
  win.on("resize", resizeMainWebView);
344
+ registry.add(toDisposable(() => {
345
+ win.removeListener("resize", resizeMainWebView);
346
+ }));
207
347
  if (state.onClose) {
208
- win.on("close", (event) => {
348
+ const onCloseHandler = (event) => {
209
349
  void state.onClose?.(event);
210
- });
350
+ };
351
+ win.on("close", onCloseHandler);
352
+ registry.add(toDisposable(() => {
353
+ win.removeListener("close", onCloseHandler);
354
+ }));
355
+ }
356
+ const devToolsAccelerator = "CommandOrControl+Shift+I";
357
+ const registered = globalShortcut2.register(devToolsAccelerator, () => {
358
+ win.webContents.openDevTools({ mode: "detach" });
359
+ });
360
+ if (registered) {
361
+ registry.add(toDisposable(() => {
362
+ globalShortcut2.unregister(devToolsAccelerator);
363
+ }));
211
364
  }
365
+ return registry;
366
+ }
367
+
368
+ // src/main/utils/sender-policy.ts
369
+ function createWorkbenchSenderPolicy(ctx) {
370
+ return (sender) => {
371
+ if (sender.isDestroyed()) return false;
372
+ if (ctx.windows.isMainSender(sender.id)) return true;
373
+ if (ctx.windows.isSettingsWindowSender(sender.id)) return true;
374
+ const settingsViewId = ctx.views.getSettingsWebContentsId();
375
+ if (settingsViewId != null && sender.id === settingsViewId) return true;
376
+ const popoverViewId = ctx.views.getPopoverWebContentsId();
377
+ if (popoverViewId != null && sender.id === popoverViewId) return true;
378
+ return false;
379
+ };
212
380
  }
213
381
 
214
382
  // src/main/services/default-adapter.ts
215
- import path3 from "path";
383
+ import path5 from "path";
216
384
  import { app as app5 } from "electron";
217
385
  var defaultAdapter = {
218
386
  async openProject(opts) {
219
387
  const diminaKit = await import("@dimina-kit/devkit");
220
388
  const openProject = diminaKit.openProject;
221
389
  return openProject({
222
- outputDir: path3.join(app5.getPath("userData"), "dimina-fe-output"),
390
+ outputDir: path5.join(app5.getPath("userData"), "dimina-fe-output"),
223
391
  ...opts,
224
392
  simulatorDir
225
393
  });
@@ -237,8 +405,17 @@ var SimulatorChannel = {
237
405
  AppData: "simulator:appdata",
238
406
  AppDataAll: "simulator:appdata-all"
239
407
  };
408
+ var SimulatorCustomApiChannel = {
409
+ List: "simulator:custom-apis:list",
410
+ Invoke: "simulator:custom-apis:invoke"
411
+ };
240
412
  var SimulatorStorageChannel = {
241
413
  GetSnapshot: "simulator:storage:snapshot",
414
+ GetActivePrefix: "simulator:storage:activePrefix",
415
+ Set: "simulator:storage:set",
416
+ Remove: "simulator:storage:remove",
417
+ Clear: "simulator:storage:clear",
418
+ ClearAll: "simulator:storage:clearAll",
242
419
  Event: "simulator:storage:event"
243
420
  };
244
421
  var SimulatorElementChannel = {
@@ -311,6 +488,13 @@ var SettingsChannel = {
311
488
  Closed: "settings:closed",
312
489
  Changed: "settings:changed"
313
490
  };
491
+ var UpdateChannel = {
492
+ Check: "updates:check",
493
+ Download: "updates:download",
494
+ Install: "updates:install",
495
+ DownloadProgress: "updates:downloadProgress",
496
+ Available: "updates:available"
497
+ };
314
498
 
315
499
  // src/main/services/notifications/renderer-notifier.ts
316
500
  function liveWebContents(wc) {
@@ -371,7 +555,7 @@ function createRendererNotifier(ctx) {
371
555
 
372
556
  // src/main/services/views/view-manager.ts
373
557
  import { WebContentsView as WebContentsView2, webContents } from "electron";
374
- import path4 from "path";
558
+ import path6 from "path";
375
559
 
376
560
  // src/main/services/layout/index.ts
377
561
  var HEADER_H = 40;
@@ -530,10 +714,16 @@ function createViewManager(ctx) {
530
714
  async function showSettings() {
531
715
  if (!settingsView) {
532
716
  settingsView = new WebContentsView2({
533
- webPreferences: { nodeIntegration: true, contextIsolation: false }
717
+ webPreferences: {
718
+ nodeIntegration: false,
719
+ contextIsolation: true,
720
+ sandbox: false,
721
+ preload: mainPreloadPath
722
+ }
534
723
  });
724
+ applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
535
725
  await settingsView.webContents.loadFile(
536
- path4.join(ctx.rendererDir, "entries/settings/index.html")
726
+ path6.join(ctx.rendererDir, "entries/settings/index.html")
537
727
  );
538
728
  }
539
729
  if (!settingsViewAdded) {
@@ -554,8 +744,14 @@ function createViewManager(ctx) {
554
744
  function showPopover(data) {
555
745
  hidePopover();
556
746
  const popover = new WebContentsView2({
557
- webPreferences: { nodeIntegration: true, contextIsolation: false }
747
+ webPreferences: {
748
+ nodeIntegration: false,
749
+ contextIsolation: true,
750
+ sandbox: false,
751
+ preload: mainPreloadPath
752
+ }
558
753
  });
754
+ applyNavigationHardening(popover.webContents, ctx.rendererDir);
559
755
  popoverView = popover;
560
756
  popover.setBackgroundColor("#00000000");
561
757
  ctx.mainWindow.contentView.addChildView(popover);
@@ -564,7 +760,7 @@ function createViewManager(ctx) {
564
760
  ctx.notify.popoverInit(popover, data);
565
761
  });
566
762
  popover.webContents.loadFile(
567
- path4.join(ctx.rendererDir, "entries/popover/index.html")
763
+ path6.join(ctx.rendererDir, "entries/popover/index.html")
568
764
  );
569
765
  }
570
766
  function hidePopover() {
@@ -630,14 +826,50 @@ function createViewManager(ctx) {
630
826
  if (settingsView.webContents.isDestroyed()) return null;
631
827
  return settingsView.webContents;
632
828
  },
829
+ getSettingsWebContentsId: () => {
830
+ if (!settingsView) return null;
831
+ if (settingsView.webContents.isDestroyed()) return null;
832
+ return settingsView.webContents.id;
833
+ },
834
+ getPopoverWebContentsId: () => {
835
+ if (!popoverView) return null;
836
+ if (popoverView.webContents.isDestroyed()) return null;
837
+ return popoverView.webContents.id;
838
+ },
633
839
  resize,
634
840
  setVisible
635
841
  };
636
842
  }
637
843
 
844
+ // src/main/services/window-service.ts
845
+ function createWindowService(mainWindow) {
846
+ let settingsWin = null;
847
+ return {
848
+ get mainWindow() {
849
+ return mainWindow;
850
+ },
851
+ get settingsWindow() {
852
+ return settingsWin;
853
+ },
854
+ setSettingsWindow(win) {
855
+ settingsWin = win;
856
+ },
857
+ closeSettingsWindow() {
858
+ if (settingsWin && !settingsWin.isDestroyed()) settingsWin.close();
859
+ settingsWin = null;
860
+ },
861
+ isMainSender(id) {
862
+ return !mainWindow.isDestroyed() && mainWindow.webContents.id === id;
863
+ },
864
+ isSettingsWindowSender(id) {
865
+ return !!settingsWin && !settingsWin.isDestroyed() && settingsWin.webContents.id === id;
866
+ }
867
+ };
868
+ }
869
+
638
870
  // src/main/services/projects/project-repository.ts
639
871
  import { app as app6 } from "electron";
640
- import path5 from "path";
872
+ import path7 from "path";
641
873
  import fs3 from "fs";
642
874
 
643
875
  // src/shared/constants.ts
@@ -684,7 +916,7 @@ function createLogger(tag) {
684
916
  // src/main/services/projects/project-repository.ts
685
917
  var log = createLogger("projects");
686
918
  function getProjectsFile() {
687
- return path5.join(app6.getPath("userData"), "dimina-projects.json");
919
+ return path7.join(app6.getPath("userData"), "dimina-projects.json");
688
920
  }
689
921
  function load() {
690
922
  try {
@@ -706,13 +938,13 @@ function validateProjectDir(dirPath) {
706
938
  if (!fs3.existsSync(dirPath)) {
707
939
  return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
708
940
  }
709
- if (!fs3.existsSync(path5.join(dirPath, "app.json"))) {
710
- const configPath = path5.join(dirPath, "project.config.json");
941
+ if (!fs3.existsSync(path7.join(dirPath, "app.json"))) {
942
+ const configPath = path7.join(dirPath, "project.config.json");
711
943
  if (fs3.existsSync(configPath)) {
712
944
  try {
713
945
  const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
714
946
  if (cfg.miniprogramRoot) {
715
- const resolvedRoot = path5.resolve(dirPath, cfg.miniprogramRoot);
947
+ const resolvedRoot = path7.resolve(dirPath, cfg.miniprogramRoot);
716
948
  return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
717
949
  }
718
950
  } catch (err) {
@@ -723,11 +955,14 @@ function validateProjectDir(dirPath) {
723
955
  }
724
956
  return null;
725
957
  }
958
+ function hasProject(dirPath) {
959
+ return load().some((p) => p.path === dirPath);
960
+ }
726
961
  function addProject(dirPath) {
727
962
  const projects = load();
728
- let name = path5.basename(dirPath);
963
+ let name = path7.basename(dirPath);
729
964
  try {
730
- const configPath = path5.join(dirPath, "project.config.json");
965
+ const configPath = path7.join(dirPath, "project.config.json");
731
966
  if (fs3.existsSync(configPath)) {
732
967
  const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
733
968
  if (cfg.projectname) name = cfg.projectname;
@@ -766,7 +1001,7 @@ function getCompileConfig(dirPath) {
766
1001
  };
767
1002
  }
768
1003
  function getProjectPages(dirPath) {
769
- const appJsonPath = path5.join(dirPath, "app.json");
1004
+ const appJsonPath = path7.join(dirPath, "app.json");
770
1005
  try {
771
1006
  const appJson = JSON.parse(
772
1007
  fs3.readFileSync(appJsonPath, "utf-8")
@@ -792,7 +1027,7 @@ function getProjectSettings(projectPath) {
792
1027
  return { uploadWithSourceMap: false };
793
1028
  }
794
1029
  try {
795
- const configPath = path5.join(projectPath, "project.config.json");
1030
+ const configPath = path7.join(projectPath, "project.config.json");
796
1031
  const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
797
1032
  return {
798
1033
  uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
@@ -803,7 +1038,7 @@ function getProjectSettings(projectPath) {
803
1038
  }
804
1039
  function updateProjectSettings(projectPath, patch) {
805
1040
  if (!projectPath) return;
806
- const configPath = path5.join(projectPath, "project.config.json");
1041
+ const configPath = path7.join(projectPath, "project.config.json");
807
1042
  let config = {};
808
1043
  try {
809
1044
  config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
@@ -823,8 +1058,8 @@ function updateProjectSettings(projectPath, patch) {
823
1058
  function createWorkspaceService(ctx) {
824
1059
  let currentSession = null;
825
1060
  let currentProjectPath = "";
826
- function sendStatus(status, message) {
827
- ctx.notify.projectStatus({ status, message });
1061
+ function sendStatus(status, message, hotReload) {
1062
+ ctx.notify.projectStatus(hotReload ? { status, message, hotReload: true } : { status, message });
828
1063
  }
829
1064
  function bestEffort(label, fn) {
830
1065
  try {
@@ -861,6 +1096,7 @@ function createWorkspaceService(ctx) {
861
1096
  listProjects: () => listProjects(),
862
1097
  addProject: (dirPath) => addProject(dirPath),
863
1098
  removeProject: (dirPath) => removeProject(dirPath),
1099
+ hasProject: (dirPath) => hasProject(dirPath),
864
1100
  validateProjectDir: (dirPath) => validateProjectDir(dirPath),
865
1101
  async openProject(projectPath) {
866
1102
  clearSimulatorServicewechatReferer();
@@ -872,12 +1108,14 @@ function createWorkspaceService(ctx) {
872
1108
  return { success: false, error: dirError };
873
1109
  }
874
1110
  sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
1111
+ const { compile } = loadWorkbenchSettings();
875
1112
  let session2;
876
1113
  try {
877
1114
  session2 = await ctx.adapter.openProject({
878
1115
  projectPath,
879
1116
  sourcemap: true,
880
- onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u70ED\u66F4\u65B0"),
1117
+ watch: compile.watch,
1118
+ onRebuild: () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u70ED\u66F4\u65B0", true),
881
1119
  onBuildError: (err) => sendStatus("error", String(err))
882
1120
  });
883
1121
  } catch (err) {
@@ -935,9 +1173,12 @@ function createWorkbenchContext(opts) {
935
1173
  brandingProvider: opts.brandingProvider,
936
1174
  workbenchSettingsWindow: null
937
1175
  };
1176
+ ctx.registry = new DisposableRegistry();
1177
+ ctx.windows = createWindowService(ctx.mainWindow);
938
1178
  ctx.views = createViewManager(ctx);
939
1179
  ctx.notify = createRendererNotifier(ctx);
940
1180
  ctx.workspace = createWorkspaceService(ctx);
1181
+ ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
941
1182
  return ctx;
942
1183
  }
943
1184
 
@@ -1008,56 +1249,246 @@ function installAppMenu(ctx) {
1008
1249
  }
1009
1250
 
1010
1251
  // src/main/ipc/app.ts
1011
- import { ipcMain } from "electron";
1012
1252
  import fs4 from "fs";
1253
+
1254
+ // src/main/utils/ipc-registry.ts
1255
+ import { ipcMain } from "electron";
1256
+
1257
+ // src/main/utils/ipc-schema.ts
1258
+ var IpcValidationError = class extends Error {
1259
+ channel;
1260
+ paths;
1261
+ constructor(channel, issues) {
1262
+ const paths = issues.map((i) => i.path.map((p) => String(p)).join(".") || "<root>");
1263
+ super(`Invalid IPC arguments for ${channel}`);
1264
+ this.name = "IpcValidationError";
1265
+ this.channel = channel;
1266
+ this.paths = paths;
1267
+ }
1268
+ };
1269
+ function validate(channel, schema, args) {
1270
+ const r = schema.safeParse(args);
1271
+ if (!r.success) {
1272
+ console.warn("[ipc] schema reject", channel, r.error.issues);
1273
+ throw new IpcValidationError(channel, r.error.issues);
1274
+ }
1275
+ return r.data;
1276
+ }
1277
+
1278
+ // src/main/utils/ipc-registry.ts
1279
+ var log2 = createLogger("ipc");
1280
+ function reportListenerError(channel, err) {
1281
+ if (err instanceof IpcValidationError) {
1282
+ log2.warn(`schema reject on '${channel}' at [${err.paths.join(", ")}]`);
1283
+ return;
1284
+ }
1285
+ if (err instanceof Error) {
1286
+ log2.error(`listener threw on '${channel}': ${err.message}`, err.stack);
1287
+ return;
1288
+ }
1289
+ log2.error(`listener threw on '${channel}'`, err);
1290
+ }
1291
+ function isThenable(v) {
1292
+ return v != null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
1293
+ }
1294
+ function summarizeSender(sender) {
1295
+ if (sender.isDestroyed()) return "<destroyed>";
1296
+ const url = sender.getURL();
1297
+ return `id=${sender.id} ${url.slice(0, 120)}`;
1298
+ }
1299
+ var IpcRegistry = class {
1300
+ constructor(policy) {
1301
+ this.policy = policy;
1302
+ }
1303
+ policy;
1304
+ registry = new DisposableRegistry();
1305
+ handle(channel, fn) {
1306
+ const policy = this.policy;
1307
+ const guarded = policy ? (event, ...args) => {
1308
+ const sender = event.sender;
1309
+ if (!policy(sender)) {
1310
+ console.warn(
1311
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
1312
+ );
1313
+ throw new Error(`IPC sender rejected for channel ${channel}`);
1314
+ }
1315
+ return fn(event, ...args);
1316
+ } : fn;
1317
+ ipcMain.handle(channel, guarded);
1318
+ this.registry.add(() => ipcMain.removeHandler(channel));
1319
+ return this;
1320
+ }
1321
+ on(channel, fn) {
1322
+ const policy = this.policy;
1323
+ const raw = fn;
1324
+ const safeInvoke = (event, args) => {
1325
+ try {
1326
+ const ret = raw(event, ...args);
1327
+ if (isThenable(ret)) {
1328
+ Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
1329
+ }
1330
+ } catch (err) {
1331
+ reportListenerError(channel, err);
1332
+ }
1333
+ };
1334
+ const guarded = policy ? (event, ...args) => {
1335
+ const sender = event.sender;
1336
+ if (!policy(sender)) {
1337
+ console.warn(
1338
+ `[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`
1339
+ );
1340
+ return;
1341
+ }
1342
+ safeInvoke(event, args);
1343
+ } : (event, ...args) => safeInvoke(event, args);
1344
+ ipcMain.on(channel, guarded);
1345
+ this.registry.add(() => {
1346
+ ipcMain.removeListener(channel, guarded);
1347
+ });
1348
+ return this;
1349
+ }
1350
+ dispose() {
1351
+ return this.registry.dispose();
1352
+ }
1353
+ };
1354
+
1355
+ // src/main/ipc/app.ts
1013
1356
  function registerAppIpc(ctx) {
1014
- ipcMain.handle(AppChannel.GetPreloadPath, () => {
1357
+ return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
1015
1358
  return `file://${fs4.realpathSync(ctx.preloadPath)}`;
1016
- });
1017
- ipcMain.handle(AppChannel.GetBranding, async () => {
1359
+ }).handle(AppChannel.GetBranding, async () => {
1018
1360
  if (ctx.brandingProvider) return ctx.brandingProvider();
1019
1361
  return { appName: ctx.appName };
1020
1362
  });
1021
1363
  }
1022
1364
 
1365
+ // src/shared/ipc-schemas.ts
1366
+ import { z } from "zod";
1367
+ var AbsolutePath = z.string().min(1).refine(
1368
+ (p) => p.startsWith("/") || /^[a-zA-Z]:\\/.test(p) || /^[a-zA-Z]:\//.test(p),
1369
+ "must be an absolute path"
1370
+ );
1371
+ var PanelEvalSchema = z.tuple([z.string().min(1).max(5e4)]);
1372
+ var ProjectsAddSchema = z.tuple([AbsolutePath]);
1373
+ var ProjectOpenSchema = z.tuple([AbsolutePath]);
1374
+ var CompileConfigShape = z.looseObject({
1375
+ startPage: z.string().optional(),
1376
+ scene: z.number().optional(),
1377
+ queryParams: z.array(z.looseObject({ key: z.string(), value: z.string() })).optional()
1378
+ });
1379
+ var ProjectSaveCompileConfigSchema = z.tuple([
1380
+ AbsolutePath,
1381
+ CompileConfigShape
1382
+ ]);
1383
+ var PopoverShowSchema = z.tuple([z.looseObject({})]);
1384
+ var SimWidth = z.number().int().min(100).max(2e3);
1385
+ var SimulatorAttachSchema = z.tuple([
1386
+ z.number().int().positive(),
1387
+ SimWidth
1388
+ ]);
1389
+ var SimulatorResizeSchema = z.tuple([SimWidth]);
1390
+ var SimulatorSetVisibleSchema = z.tuple([z.boolean(), SimWidth]);
1391
+ var SimulatorCustomApiInvokeSchema = z.tuple([
1392
+ z.string().min(1).max(256),
1393
+ z.unknown()
1394
+ ]);
1395
+ var ProjectGetPagesSchema = z.tuple([AbsolutePath]);
1396
+ var ProjectGetCompileConfigSchema = z.tuple([AbsolutePath]);
1397
+ var ProjectsRemoveSchema = z.tuple([AbsolutePath]);
1398
+ var WorkbenchSettingsSaveSchema = z.tuple([
1399
+ z.looseObject({
1400
+ cdp: z.looseObject({
1401
+ enabled: z.boolean(),
1402
+ port: z.number().int().min(0).max(65535)
1403
+ }),
1404
+ mcp: z.looseObject({
1405
+ enabled: z.boolean(),
1406
+ port: z.number().int().min(0).max(65535)
1407
+ }),
1408
+ compile: z.looseObject({
1409
+ watch: z.boolean()
1410
+ }),
1411
+ theme: z.enum(["system", "light", "dark"])
1412
+ })
1413
+ ]);
1414
+ var WorkbenchSettingsSetThemeSchema = z.tuple([
1415
+ z.enum(["system", "light", "dark"])
1416
+ ]);
1417
+ var WorkbenchSettingsSetVisibleSchema = z.tuple([z.boolean()]);
1418
+ var SettingsSetVisibleSchema = z.tuple([z.boolean()]);
1419
+ var SettingsConfigChangedSchema = z.tuple([CompileConfigShape]);
1420
+ var PopoverRelaunchSchema = z.tuple([CompileConfigShape]);
1421
+ var SettingsProjectSettingsChangedSchema = z.tuple([
1422
+ z.looseObject({
1423
+ uploadWithSourceMap: z.boolean().optional()
1424
+ })
1425
+ ]);
1426
+ var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
1427
+
1428
+ // src/main/services/simulator/custom-apis.ts
1429
+ function createSimulatorApiRegistry() {
1430
+ const handlers2 = /* @__PURE__ */ new Map();
1431
+ return {
1432
+ register(name, handler) {
1433
+ handlers2.set(name, handler);
1434
+ return () => {
1435
+ if (handlers2.get(name) === handler) handlers2.delete(name);
1436
+ };
1437
+ },
1438
+ list() {
1439
+ return Array.from(handlers2.keys());
1440
+ },
1441
+ async invoke(name, params) {
1442
+ const handler = handlers2.get(name);
1443
+ if (!handler) throw new Error(`Simulator API "${name}" is not registered`);
1444
+ return await handler(params);
1445
+ },
1446
+ clear() {
1447
+ handlers2.clear();
1448
+ }
1449
+ };
1450
+ }
1451
+ var simulatorApiRegistry = createSimulatorApiRegistry();
1452
+
1023
1453
  // src/main/ipc/simulator.ts
1024
- import { ipcMain as ipcMain2 } from "electron";
1025
1454
  function registerSimulatorIpc(ctx) {
1026
- ipcMain2.handle(SimulatorChannel.Attach, (_, simWcId, simWidth) => {
1455
+ return new IpcRegistry(ctx.senderPolicy).handle(SimulatorChannel.Attach, (_, ...args) => {
1456
+ const [simWcId, simWidth] = validate(SimulatorChannel.Attach, SimulatorAttachSchema, args);
1027
1457
  ctx.views.attachSimulator(simWcId, simWidth);
1028
- });
1029
- ipcMain2.handle(SimulatorChannel.Detach, () => {
1458
+ }).handle(SimulatorChannel.Detach, () => {
1030
1459
  ctx.views.detachSimulator();
1031
- });
1032
- ipcMain2.handle(SimulatorChannel.Resize, (_, simWidth) => {
1460
+ }).handle(SimulatorChannel.Resize, (_, ...args) => {
1461
+ const [simWidth] = validate(SimulatorChannel.Resize, SimulatorResizeSchema, args);
1033
1462
  ctx.views.resize(simWidth);
1034
- });
1035
- ipcMain2.handle(SimulatorChannel.SetVisible, (_, visible, simWidth) => {
1463
+ }).handle(SimulatorChannel.SetVisible, (_, ...args) => {
1464
+ const [visible, simWidth] = validate(SimulatorChannel.SetVisible, SimulatorSetVisibleSchema, args);
1036
1465
  ctx.views.setVisible(visible, simWidth);
1037
- });
1038
- ipcMain2.handle(WorkbenchChannel.GetPanelConfig, () => {
1466
+ }).handle(SimulatorCustomApiChannel.List, () => {
1467
+ return simulatorApiRegistry.list();
1468
+ }).handle(SimulatorCustomApiChannel.Invoke, (_, ...args) => {
1469
+ const [name, params] = validate(SimulatorCustomApiChannel.Invoke, SimulatorCustomApiInvokeSchema, args);
1470
+ return simulatorApiRegistry.invoke(name, params);
1471
+ }).handle(WorkbenchChannel.GetPanelConfig, () => {
1039
1472
  return ctx.panels;
1040
- });
1041
- ipcMain2.handle(WorkbenchChannel.GetApiNamespaces, () => {
1473
+ }).handle(WorkbenchChannel.GetApiNamespaces, () => {
1042
1474
  return ctx.apiNamespaces;
1043
- });
1044
- ipcMain2.on(WorkbenchChannel.Reset, () => {
1475
+ }).on(WorkbenchChannel.Reset, () => {
1045
1476
  ctx.notify.workbenchReset();
1046
1477
  });
1047
1478
  }
1048
1479
 
1049
1480
  // src/main/ipc/panels.ts
1050
- import { ipcMain as ipcMain3, webContents as webContents2 } from "electron";
1481
+ import { webContents as webContents2 } from "electron";
1051
1482
  var BUILTIN_PANELS = [
1052
1483
  { id: "wxml", label: "WXML" },
1053
1484
  { id: "appdata", label: "AppData" },
1054
1485
  { id: "storage", label: "Storage" }
1055
1486
  ];
1056
1487
  function registerPanelsIpc(ctx) {
1057
- ipcMain3.handle(PanelChannel.List, () => {
1488
+ return new IpcRegistry(ctx.senderPolicy).handle(PanelChannel.List, () => {
1058
1489
  return BUILTIN_PANELS.filter((p) => ctx.panels.includes(p.id));
1059
- });
1060
- ipcMain3.handle(PanelChannel.Eval, async (_, expression) => {
1490
+ }).handle(PanelChannel.Eval, async (_event, ...args) => {
1491
+ const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
1061
1492
  const simWcId = ctx.views.getSimulatorWebContentsId();
1062
1493
  if (!ctx.workspace.hasActiveSession() || simWcId == null) return void 0;
1063
1494
  const sim = webContents2.fromId(simWcId);
@@ -1067,13 +1498,12 @@ function registerPanelsIpc(ctx) {
1067
1498
  } catch {
1068
1499
  return void 0;
1069
1500
  }
1070
- });
1071
- ipcMain3.handle(PanelChannel.Select, (_event, _panelId) => {
1501
+ }).handle(PanelChannel.Select, (_event, ...args) => {
1502
+ validate(PanelChannel.Select, PanelSelectSchema, args);
1072
1503
  if (ctx.views.hasSimulatorView() && ctx.views.isSimulatorAdded()) {
1073
1504
  ctx.views.hideSimulator();
1074
1505
  }
1075
- });
1076
- ipcMain3.handle(PanelChannel.SelectSimulator, () => {
1506
+ }).handle(PanelChannel.SelectSimulator, () => {
1077
1507
  if (ctx.views.hasSimulatorView() && !ctx.views.isSimulatorAdded()) {
1078
1508
  ctx.views.showSimulator(ctx.views.getLastSimWidth());
1079
1509
  }
@@ -1081,76 +1511,119 @@ function registerPanelsIpc(ctx) {
1081
1511
  }
1082
1512
 
1083
1513
  // src/main/ipc/popover.ts
1084
- import { ipcMain as ipcMain4 } from "electron";
1085
1514
  function registerPopoverIpc(ctx) {
1086
- ipcMain4.handle(PopoverChannel.Show, (_event, data) => {
1515
+ return new IpcRegistry(ctx.senderPolicy).handle(PopoverChannel.Show, (_event, ...args) => {
1516
+ const [data] = validate(PopoverChannel.Show, PopoverShowSchema, args);
1087
1517
  ctx.views.showPopover(data);
1088
- });
1089
- ipcMain4.handle(PopoverChannel.Hide, () => ctx.views.hidePopover());
1090
- ipcMain4.on(PopoverChannel.Relaunch, (_, newConfig) => {
1518
+ }).handle(PopoverChannel.Hide, () => {
1519
+ ctx.views.hidePopover();
1520
+ }).on(PopoverChannel.Relaunch, (_event, ...args) => {
1521
+ const [newConfig] = validate(PopoverChannel.Relaunch, PopoverRelaunchSchema, args);
1091
1522
  ctx.views.hidePopover();
1092
1523
  ctx.notify.popoverRelaunch(newConfig);
1093
1524
  });
1094
1525
  }
1526
+ var popoverModule = {
1527
+ setup: (ctx) => registerPopoverIpc(ctx)
1528
+ };
1095
1529
 
1096
1530
  // src/main/ipc/projects.ts
1097
- import { ipcMain as ipcMain5, dialog } from "electron";
1531
+ import { dialog } from "electron";
1098
1532
  function registerProjectsIpc(ctx) {
1099
- ipcMain5.handle(ProjectsChannel.List, () => ctx.workspace.listProjects());
1100
- ipcMain5.handle(DialogChannel.OpenDirectory, async () => {
1533
+ return new IpcRegistry(ctx.senderPolicy).handle(ProjectsChannel.List, () => {
1534
+ return ctx.workspace.listProjects();
1535
+ }).handle(DialogChannel.OpenDirectory, async () => {
1101
1536
  const result = await dialog.showOpenDialog(ctx.mainWindow, {
1102
1537
  properties: ["openDirectory"],
1103
1538
  title: "\u9009\u62E9\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55"
1104
1539
  });
1105
1540
  return result.canceled ? null : result.filePaths[0];
1541
+ }).handle(ProjectsChannel.Add, async (_event, ...args) => {
1542
+ const [dirPath] = validate(ProjectsChannel.Add, ProjectsAddSchema, args);
1543
+ const dirError = ctx.workspace.validateProjectDir(dirPath);
1544
+ if (dirError) {
1545
+ await dialog.showMessageBox(ctx.mainWindow, {
1546
+ type: "error",
1547
+ title: "\u65E0\u6CD5\u5BFC\u5165\u9879\u76EE",
1548
+ message: "\u8BE5\u76EE\u5F55\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u9879\u76EE",
1549
+ detail: dirError,
1550
+ buttons: ["\u786E\u5B9A"]
1551
+ });
1552
+ throw new Error(dirError);
1553
+ }
1554
+ const duplicate = ctx.workspace.hasProject(dirPath);
1555
+ const project = ctx.workspace.addProject(dirPath);
1556
+ if (duplicate) {
1557
+ await dialog.showMessageBox(ctx.mainWindow, {
1558
+ type: "info",
1559
+ title: "\u9879\u76EE\u5DF2\u5B58\u5728",
1560
+ message: "\u8BE5\u9879\u76EE\u5DF2\u5728\u5217\u8868\u4E2D",
1561
+ detail: dirPath,
1562
+ buttons: ["\u786E\u5B9A"]
1563
+ });
1564
+ }
1565
+ return project;
1566
+ }).handle(ProjectsChannel.Remove, (_event, ...args) => {
1567
+ const [dirPath] = validate(ProjectsChannel.Remove, ProjectsRemoveSchema, args);
1568
+ return ctx.workspace.removeProject(dirPath);
1106
1569
  });
1107
- ipcMain5.handle(
1108
- ProjectsChannel.Add,
1109
- (_, dirPath) => ctx.workspace.addProject(dirPath)
1110
- );
1111
- ipcMain5.handle(
1112
- ProjectsChannel.Remove,
1113
- (_, dirPath) => ctx.workspace.removeProject(dirPath)
1114
- );
1115
1570
  }
1571
+ var projectsModule = {
1572
+ setup: (ctx) => registerProjectsIpc(ctx)
1573
+ };
1116
1574
 
1117
1575
  // src/main/ipc/session.ts
1118
- import { ipcMain as ipcMain6 } from "electron";
1119
1576
  function registerSessionIpc(ctx) {
1120
- ipcMain6.handle(
1121
- ProjectChannel.Open,
1122
- (_, projectPath) => ctx.workspace.openProject(projectPath)
1123
- );
1124
- ipcMain6.handle(
1125
- ProjectChannel.GetPages,
1126
- (_, projectPath) => ctx.workspace.getProjectPages(projectPath)
1127
- );
1128
- ipcMain6.handle(
1129
- ProjectChannel.GetCompileConfig,
1130
- (_, projectPath) => ctx.workspace.getCompileConfig(projectPath)
1131
- );
1132
- ipcMain6.handle(
1133
- ProjectChannel.SaveCompileConfig,
1134
- (_, projectPath, config) => ctx.workspace.saveCompileConfig(projectPath, config)
1135
- );
1136
- ipcMain6.handle(ProjectChannel.Close, () => ctx.workspace.closeProject());
1577
+ return new IpcRegistry(ctx.senderPolicy).handle(ProjectChannel.Open, (_, ...args) => {
1578
+ const [projectPath] = validate(ProjectChannel.Open, ProjectOpenSchema, args);
1579
+ return ctx.workspace.openProject(projectPath);
1580
+ }).handle(ProjectChannel.GetPages, (_, ...args) => {
1581
+ const [projectPath] = validate(ProjectChannel.GetPages, ProjectGetPagesSchema, args);
1582
+ return ctx.workspace.getProjectPages(projectPath);
1583
+ }).handle(ProjectChannel.GetCompileConfig, (_, ...args) => {
1584
+ const [projectPath] = validate(
1585
+ ProjectChannel.GetCompileConfig,
1586
+ ProjectGetCompileConfigSchema,
1587
+ args
1588
+ );
1589
+ return ctx.workspace.getCompileConfig(projectPath);
1590
+ }).handle(ProjectChannel.SaveCompileConfig, (_, ...args) => {
1591
+ const [projectPath, config] = validate(
1592
+ ProjectChannel.SaveCompileConfig,
1593
+ ProjectSaveCompileConfigSchema,
1594
+ args
1595
+ );
1596
+ return ctx.workspace.saveCompileConfig(projectPath, config);
1597
+ }).handle(ProjectChannel.Close, () => {
1598
+ return ctx.workspace.closeProject();
1599
+ });
1137
1600
  }
1601
+ var sessionModule = {
1602
+ setup: (ctx) => registerSessionIpc(ctx)
1603
+ };
1138
1604
 
1139
1605
  // src/main/ipc/settings.ts
1140
- import { app as app7, ipcMain as ipcMain7 } from "electron";
1606
+ import { app as app7 } from "electron";
1141
1607
  function registerSettingsIpc(ctx) {
1142
- ipcMain7.handle(WorkbenchSettingsChannel.Get, () => {
1608
+ return new IpcRegistry(ctx.senderPolicy).handle(WorkbenchSettingsChannel.Get, () => {
1143
1609
  return loadWorkbenchSettings();
1144
- });
1145
- ipcMain7.handle(WorkbenchSettingsChannel.Save, (_, settings) => {
1610
+ }).handle(WorkbenchSettingsChannel.Save, (_, ...args) => {
1611
+ const [settings] = validate(
1612
+ WorkbenchSettingsChannel.Save,
1613
+ WorkbenchSettingsSaveSchema,
1614
+ args
1615
+ );
1146
1616
  saveWorkbenchSettings(settings);
1147
1617
  applyTheme(settings.theme ?? "system");
1148
1618
  return { success: true };
1149
- });
1150
- ipcMain7.handle(WorkbenchSettingsChannel.SetTheme, (_, theme) => {
1619
+ }).handle(WorkbenchSettingsChannel.SetTheme, (_, ...args) => {
1620
+ const [theme] = validate(
1621
+ WorkbenchSettingsChannel.SetTheme,
1622
+ WorkbenchSettingsSetThemeSchema,
1623
+ args
1624
+ );
1151
1625
  applyTheme(theme);
1152
- });
1153
- ipcMain7.handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1626
+ }).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1154
1627
  const settings = loadWorkbenchSettings();
1155
1628
  const switchValue = app7.commandLine.getSwitchValue("remote-debugging-port");
1156
1629
  const implicitDevDefault = !app7.isPackaged && !settings.cdp.enabled && switchValue === "9222";
@@ -1161,15 +1634,19 @@ function registerSettingsIpc(ctx) {
1161
1634
  activePort: switchValue ? parseInt(switchValue, 10) : null,
1162
1635
  implicitDevDefault
1163
1636
  };
1164
- });
1165
- ipcMain7.handle(WorkbenchSettingsChannel.SetVisible, async (_, visible) => {
1637
+ }).handle(WorkbenchSettingsChannel.SetVisible, async (_, ...args) => {
1638
+ const [visible] = validate(
1639
+ WorkbenchSettingsChannel.SetVisible,
1640
+ WorkbenchSettingsSetVisibleSchema,
1641
+ args
1642
+ );
1166
1643
  if (visible) {
1167
1644
  await openSettingsWindow(ctx);
1168
1645
  } else {
1169
- ctx.workbenchSettingsWindow?.close();
1646
+ ctx.windows.closeSettingsWindow();
1170
1647
  }
1171
- });
1172
- ipcMain7.handle(SettingsChannel.SetVisible, async (_, visible) => {
1648
+ }).handle(SettingsChannel.SetVisible, async (_, ...args) => {
1649
+ const [visible] = validate(SettingsChannel.SetVisible, SettingsSetVisibleSchema, args);
1173
1650
  if (visible) {
1174
1651
  await ctx.views.showSettings();
1175
1652
  ctx.notify.settingsInit({
@@ -1181,32 +1658,55 @@ function registerSettingsIpc(ctx) {
1181
1658
  ctx.views.hideSettings();
1182
1659
  ctx.notify.settingsClosed();
1183
1660
  }
1184
- });
1185
- ipcMain7.on(SettingsChannel.ConfigChanged, (_, config) => {
1661
+ }).on(SettingsChannel.ConfigChanged, (_, ...args) => {
1662
+ const [config] = validate(
1663
+ SettingsChannel.ConfigChanged,
1664
+ SettingsConfigChangedSchema,
1665
+ args
1666
+ );
1186
1667
  if (ctx.workspace.getProjectPath()) {
1187
- ctx.workspace.saveCompileConfig(ctx.workspace.getProjectPath(), config);
1668
+ ctx.workspace.saveCompileConfig(
1669
+ ctx.workspace.getProjectPath(),
1670
+ config
1671
+ );
1188
1672
  }
1189
1673
  ctx.notify.settingsChanged(config);
1674
+ }).on(SettingsChannel.ProjectSettingsChanged, (_, ...args) => {
1675
+ const [patch] = validate(
1676
+ SettingsChannel.ProjectSettingsChanged,
1677
+ SettingsProjectSettingsChangedSchema,
1678
+ args
1679
+ );
1680
+ ctx.workspace.updateProjectSettings(
1681
+ ctx.workspace.getProjectPath(),
1682
+ patch
1683
+ );
1190
1684
  });
1191
- ipcMain7.on(
1192
- SettingsChannel.ProjectSettingsChanged,
1193
- (_, patch) => {
1194
- ctx.workspace.updateProjectSettings(ctx.workspace.getProjectPath(), patch);
1195
- }
1196
- );
1197
1685
  }
1686
+ var settingsModule = {
1687
+ setup: (ctx) => registerSettingsIpc(ctx)
1688
+ };
1198
1689
 
1199
1690
  // src/main/ipc/toolbar.ts
1200
- import { ipcMain as ipcMain8 } from "electron";
1201
1691
  function registerToolbarIpc(ctx) {
1202
- ipcMain8.handle(ToolbarChannel.GetActions, async () => {
1692
+ return new IpcRegistry(ctx.senderPolicy).handle(ToolbarChannel.GetActions, async () => {
1203
1693
  if (ctx.toolbarActions) return ctx.toolbarActions();
1204
1694
  return [];
1205
1695
  });
1206
1696
  }
1207
1697
 
1698
+ // src/main/ipc/simulator-module.ts
1699
+ var simulatorModule = {
1700
+ setup: (ctx) => {
1701
+ const reg = new DisposableRegistry();
1702
+ reg.add(registerSimulatorIpc(ctx));
1703
+ reg.add(registerPanelsIpc(ctx));
1704
+ reg.add(registerToolbarIpc(ctx));
1705
+ return reg;
1706
+ }
1707
+ };
1708
+
1208
1709
  // src/main/services/automation/index.ts
1209
- import { ipcMain as ipcMain9 } from "electron";
1210
1710
  import { WebSocketServer } from "ws";
1211
1711
 
1212
1712
  // src/main/services/automation/exec.ts
@@ -1595,7 +2095,6 @@ var handlers = {
1595
2095
  ...elementHandlers
1596
2096
  };
1597
2097
  var currentPort = null;
1598
- var portIpcRegistered = false;
1599
2098
  async function startAutomationServer(ctx, port = 0) {
1600
2099
  const wss = new WebSocketServer({ port });
1601
2100
  const clients = /* @__PURE__ */ new Set();
@@ -1606,10 +2105,10 @@ async function startAutomationServer(ctx, port = 0) {
1606
2105
  const addr = wss.address();
1607
2106
  const resolvedPort = typeof addr === "object" && addr ? addr.port : port;
1608
2107
  currentPort = resolvedPort;
1609
- if (!portIpcRegistered) {
1610
- portIpcRegistered = true;
1611
- ipcMain9.handle(AutomationChannel.GetPort, () => currentPort);
1612
- }
2108
+ const portIpc = new IpcRegistry(ctx.senderPolicy);
2109
+ portIpc.handle(AutomationChannel.GetPort, () => {
2110
+ return currentPort;
2111
+ });
1613
2112
  function broadcast(event) {
1614
2113
  const msg = JSON.stringify(event);
1615
2114
  for (const ws of clients) {
@@ -1617,14 +2116,46 @@ async function startAutomationServer(ctx, port = 0) {
1617
2116
  }
1618
2117
  }
1619
2118
  let consoleForwardingSetup = false;
2119
+ let pollInterval = null;
2120
+ let pollStopTimer = null;
2121
+ let attachedSim = null;
2122
+ let ipcMessageHostHandler = null;
2123
+ let simDestroyedHandler = null;
2124
+ function detachConsoleForwarding() {
2125
+ if (attachedSim) {
2126
+ if (ipcMessageHostHandler) {
2127
+ try {
2128
+ if (!attachedSim.isDestroyed()) {
2129
+ ;
2130
+ attachedSim.removeListener("ipc-message-host", ipcMessageHostHandler);
2131
+ }
2132
+ } catch {
2133
+ }
2134
+ }
2135
+ if (simDestroyedHandler) {
2136
+ try {
2137
+ if (!attachedSim.isDestroyed()) {
2138
+ attachedSim.removeListener("destroyed", simDestroyedHandler);
2139
+ }
2140
+ } catch {
2141
+ }
2142
+ }
2143
+ }
2144
+ attachedSim = null;
2145
+ ipcMessageHostHandler = null;
2146
+ simDestroyedHandler = null;
2147
+ }
1620
2148
  function setupConsoleForwarding() {
1621
2149
  if (consoleForwardingSetup) return;
1622
2150
  consoleForwardingSetup = true;
1623
- const check = setInterval(() => {
2151
+ pollInterval = setInterval(() => {
1624
2152
  const sim = getSimulator(ctx);
1625
2153
  if (!sim) return;
1626
- clearInterval(check);
1627
- sim.on("ipc-message-host", (_event, channel, data) => {
2154
+ if (pollInterval) {
2155
+ clearInterval(pollInterval);
2156
+ pollInterval = null;
2157
+ }
2158
+ const onIpcMessageHost = (_event, channel, data) => {
1628
2159
  if (channel === SimulatorChannel.Console) {
1629
2160
  const logData = data;
1630
2161
  broadcast({
@@ -1632,9 +2163,26 @@ async function startAutomationServer(ctx, port = 0) {
1632
2163
  params: { type: logData.level || "log", args: logData.args || [] }
1633
2164
  });
1634
2165
  }
1635
- });
2166
+ };
2167
+ const onSimDestroyed = () => {
2168
+ attachedSim = null;
2169
+ ipcMessageHostHandler = null;
2170
+ simDestroyedHandler = null;
2171
+ consoleForwardingSetup = false;
2172
+ };
2173
+ attachedSim = sim;
2174
+ ipcMessageHostHandler = onIpcMessageHost;
2175
+ simDestroyedHandler = onSimDestroyed;
2176
+ sim.on("ipc-message-host", onIpcMessageHost);
2177
+ sim.once("destroyed", onSimDestroyed);
1636
2178
  }, 1e3);
1637
- setTimeout(() => clearInterval(check), 3e4);
2179
+ pollStopTimer = setTimeout(() => {
2180
+ if (pollInterval) {
2181
+ clearInterval(pollInterval);
2182
+ pollInterval = null;
2183
+ }
2184
+ pollStopTimer = null;
2185
+ }, 3e4);
1638
2186
  }
1639
2187
  wss.on("connection", (ws) => {
1640
2188
  clients.add(ws);
@@ -1665,7 +2213,24 @@ async function startAutomationServer(ctx, port = 0) {
1665
2213
  });
1666
2214
  return {
1667
2215
  close: () => {
2216
+ if (pollInterval) {
2217
+ clearInterval(pollInterval);
2218
+ pollInterval = null;
2219
+ }
2220
+ if (pollStopTimer) {
2221
+ clearTimeout(pollStopTimer);
2222
+ pollStopTimer = null;
2223
+ }
2224
+ detachConsoleForwarding();
2225
+ for (const ws of clients) {
2226
+ try {
2227
+ ws.close();
2228
+ } catch {
2229
+ }
2230
+ }
2231
+ clients.clear();
1668
2232
  if (currentPort === resolvedPort) currentPort = null;
2233
+ void portIpc.dispose();
1669
2234
  wss.close();
1670
2235
  },
1671
2236
  port: resolvedPort
@@ -1824,7 +2389,7 @@ function getClient(kind) {
1824
2389
  }
1825
2390
 
1826
2391
  // src/main/services/mcp/tool-registry.ts
1827
- import { z } from "zod";
2392
+ import { z as z2 } from "zod";
1828
2393
  function registerCommonTargetTools(server, kind) {
1829
2394
  const descriptions = kind === "simulator" ? {
1830
2395
  screenshot: "Take a screenshot of the simulator webview",
@@ -1845,9 +2410,9 @@ function registerCommonTargetTools(server, kind) {
1845
2410
  return { content: [{ type: "image", data, mimeType: "image/png" }] };
1846
2411
  });
1847
2412
  server.tool(`${kind}_console_logs`, descriptions.consoleLogs, {
1848
- limit: z.number().optional().default(50).describe("Maximum number of log entries"),
1849
- level: z.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
1850
- sinceTimestamp: z.string().optional().describe("ISO timestamp; only return entries at or after this time")
2413
+ limit: z2.number().optional().default(50).describe("Maximum number of log entries"),
2414
+ level: z2.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
2415
+ sinceTimestamp: z2.string().optional().describe("ISO timestamp; only return entries at or after this time")
1851
2416
  }, async ({ limit, level, sinceTimestamp }) => {
1852
2417
  getClient(kind);
1853
2418
  let entries = getTargetState(kind).consoleLogs;
@@ -1862,7 +2427,7 @@ function registerCommonTargetTools(server, kind) {
1862
2427
  });
1863
2428
  if (kind !== "workbench") {
1864
2429
  server.tool(`${kind}_evaluate`, descriptions.evaluate, {
1865
- expression: z.string().describe("JavaScript expression to evaluate")
2430
+ expression: z2.string().describe("JavaScript expression to evaluate")
1866
2431
  }, async ({ expression }) => {
1867
2432
  const c = getClient(kind);
1868
2433
  const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
@@ -1875,15 +2440,15 @@ ${JSON.stringify(result.exceptionDetails, null, 2)}` }], isError: true };
1875
2440
  });
1876
2441
  }
1877
2442
  server.tool(`${kind}_get_dom`, descriptions.getDom, {
1878
- depth: z.number().optional().default(3).describe("Maximum depth")
2443
+ depth: z2.number().optional().default(3).describe("Maximum depth")
1879
2444
  }, async ({ depth }) => {
1880
2445
  const c = getClient(kind);
1881
2446
  const { root } = await c.DOM.getDocument({ depth });
1882
2447
  return { content: [{ type: "text", text: JSON.stringify(root, null, 2) }] };
1883
2448
  });
1884
2449
  server.tool(`${kind}_network_log`, descriptions.networkLog, {
1885
- limit: z.number().optional().default(20).describe("Maximum number of entries"),
1886
- minStatus: z.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
2450
+ limit: z2.number().optional().default(20).describe("Maximum number of entries"),
2451
+ minStatus: z2.number().optional().describe("Only return entries with status >= minStatus (status 0 = failed before response is included when minStatus >= 400)")
1887
2452
  }, async ({ limit, minStatus }) => {
1888
2453
  getClient(kind);
1889
2454
  let entries = getTargetState(kind).networkRequests;
@@ -2126,11 +2691,11 @@ function registerContextTools(server) {
2126
2691
  }
2127
2692
 
2128
2693
  // src/main/services/mcp/tools/simulator-tools.ts
2129
- import { z as z2 } from "zod";
2694
+ import { z as z3 } from "zod";
2130
2695
  function registerSimulatorTools(server) {
2131
2696
  server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
2132
- url: z2.string().optional().describe("URL to navigate to"),
2133
- reload: z2.boolean().optional().describe("If true, reload current page instead of navigating")
2697
+ url: z3.string().optional().describe("URL to navigate to"),
2698
+ reload: z3.boolean().optional().describe("If true, reload current page instead of navigating")
2134
2699
  }, async ({ url, reload }) => {
2135
2700
  const c = getClient("simulator");
2136
2701
  if (reload) {
@@ -2145,15 +2710,15 @@ function registerSimulatorTools(server) {
2145
2710
  return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
2146
2711
  });
2147
2712
  server.tool("simulator_input", "Dispatch input to the simulator. `tap_coord` requires x and y. `tap_selector` requires selector and optional nth. `type` requires selector and text. `scroll` requires x, y, deltaX, and deltaY. `key` is best-effort for common keys such as Enter, Escape, Tab, Arrow* and Space.", {
2148
- action: z2.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
2149
- x: z2.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
2150
- y: z2.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
2151
- selector: z2.string().optional().describe("CSS selector for `tap_selector` or `type`"),
2152
- nth: z2.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
2153
- text: z2.string().optional().describe("Text to insert for `type`"),
2154
- key: z2.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
2155
- deltaX: z2.number().optional().describe("Horizontal wheel delta for `scroll`"),
2156
- deltaY: z2.number().optional().describe("Vertical wheel delta for `scroll`")
2713
+ action: z3.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
2714
+ x: z3.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
2715
+ y: z3.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
2716
+ selector: z3.string().optional().describe("CSS selector for `tap_selector` or `type`"),
2717
+ nth: z3.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
2718
+ text: z3.string().optional().describe("Text to insert for `type`"),
2719
+ key: z3.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
2720
+ deltaX: z3.number().optional().describe("Horizontal wheel delta for `scroll`"),
2721
+ deltaY: z3.number().optional().describe("Vertical wheel delta for `scroll`")
2157
2722
  }, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
2158
2723
  const c = getClient("simulator");
2159
2724
  const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
@@ -2308,87 +2873,293 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
2308
2873
  httpServer.listen(mcpPort, "127.0.0.1", () => {
2309
2874
  console.log(`[MCP] SSE server listening on http://127.0.0.1:${mcpPort}/sse`);
2310
2875
  });
2876
+ return toDisposable(() => {
2877
+ transports.clear();
2878
+ httpServer.close();
2879
+ });
2311
2880
  }
2312
2881
 
2313
2882
  // src/main/services/simulator-storage/index.ts
2314
- import { app as app8, ipcMain as ipcMain10, webContents as wcStatic } from "electron";
2315
- var attachedWc = null;
2316
- var hostWc = null;
2883
+ import { app as app8, webContents as wcStatic } from "electron";
2317
2884
  function isSimulatorWebview(wc) {
2318
2885
  if (wc.isDestroyed()) return false;
2319
2886
  if (wc.getType() !== "webview") return false;
2320
2887
  return wc.getURL().includes("simulator.html");
2321
2888
  }
2322
- function detach() {
2323
- if (!attachedWc) return;
2324
- const wc = attachedWc;
2325
- attachedWc = null;
2889
+ function safeOff(target, event, fn) {
2326
2890
  try {
2327
- if (!wc.isDestroyed() && wc.debugger.isAttached()) {
2328
- wc.debugger.detach();
2329
- }
2891
+ if (target.isDestroyed?.()) return;
2892
+ target.removeListener(event, fn);
2330
2893
  } catch {
2331
2894
  }
2332
2895
  }
2333
- async function attachToSim(wc) {
2334
- if (attachedWc === wc) return;
2335
- if (wc.isDestroyed()) return;
2336
- detach();
2337
- try {
2338
- if (!wc.debugger.isAttached()) {
2339
- wc.debugger.attach("1.3");
2340
- }
2341
- await wc.debugger.sendCommand("DOMStorage.enable");
2342
- wc.debugger.on("message", onCdpMessage);
2343
- wc.debugger.on("detach", () => {
2344
- if (attachedWc === wc) attachedWc = null;
2896
+ function setupSimulatorStorage(host, options) {
2897
+ const registry = new DisposableRegistry();
2898
+ const getActiveAppId = options.getActiveAppId;
2899
+ let attachedWc = null;
2900
+ let attachDisposables = null;
2901
+ let cachedOrigin = null;
2902
+ function activePrefix() {
2903
+ const appId = getActiveAppId();
2904
+ return appId ? `${appId}_` : null;
2905
+ }
2906
+ function detachFromSim() {
2907
+ if (!attachedWc) return;
2908
+ const wc = attachedWc;
2909
+ attachedWc = null;
2910
+ cachedOrigin = null;
2911
+ const ad = attachDisposables;
2912
+ attachDisposables = null;
2913
+ if (ad) void ad.disposeAll().catch(() => {
2345
2914
  });
2346
- wc.once("destroyed", () => {
2347
- if (attachedWc === wc) attachedWc = null;
2348
- });
2349
- attachedWc = wc;
2350
- } catch (e) {
2351
- console.warn("[storage-watcher] attach failed:", e.message);
2915
+ try {
2916
+ if (!wc.isDestroyed() && wc.debugger.isAttached()) {
2917
+ wc.debugger.detach();
2918
+ }
2919
+ } catch {
2920
+ }
2352
2921
  }
2353
- }
2354
- function onCdpMessage(_event, method, params) {
2355
- if (!hostWc || hostWc.isDestroyed()) return;
2356
- const p = params;
2357
- let evt;
2358
- switch (method) {
2359
- case "DOMStorage.domStorageItemAdded":
2360
- evt = { type: "added", key: String(p.key), newValue: String(p.newValue ?? "") };
2361
- break;
2362
- case "DOMStorage.domStorageItemUpdated":
2363
- evt = {
2364
- type: "updated",
2365
- key: String(p.key),
2366
- oldValue: String(p.oldValue ?? ""),
2367
- newValue: String(p.newValue ?? "")
2922
+ async function attachToSim(wc) {
2923
+ if (attachedWc === wc) return;
2924
+ if (wc.isDestroyed()) return;
2925
+ detachFromSim();
2926
+ try {
2927
+ if (!wc.debugger.isAttached()) {
2928
+ wc.debugger.attach("1.3");
2929
+ }
2930
+ await wc.debugger.sendCommand("DOMStorage.enable");
2931
+ const attach = new DisposableRegistry();
2932
+ attachDisposables = attach;
2933
+ const onMessage = (_event, method, params) => forwardCdpMessage(method, params);
2934
+ wc.debugger.on("message", onMessage);
2935
+ attach.add(() => safeOff(wc.debugger, "message", onMessage));
2936
+ const onDetach = () => {
2937
+ if (attachedWc === wc) attachedWc = null;
2368
2938
  };
2939
+ wc.debugger.on("detach", onDetach);
2940
+ attach.add(() => safeOff(wc.debugger, "detach", onDetach));
2941
+ const onDestroyed = () => {
2942
+ if (attachedWc === wc) {
2943
+ attachedWc = null;
2944
+ const ad = attachDisposables;
2945
+ attachDisposables = null;
2946
+ if (ad) void ad.disposeAll().catch(() => {
2947
+ });
2948
+ }
2949
+ };
2950
+ wc.once("destroyed", onDestroyed);
2951
+ attach.add(() => safeOff(wc, "destroyed", onDestroyed));
2952
+ attachedWc = wc;
2953
+ } catch (e) {
2954
+ console.warn("[storage-watcher] attach failed:", e.message);
2955
+ }
2956
+ }
2957
+ function forwardCdpMessage(method, params) {
2958
+ if (host.isDestroyed()) return;
2959
+ const p = params;
2960
+ const prefix = activePrefix();
2961
+ let evt;
2962
+ switch (method) {
2963
+ case "DOMStorage.domStorageItemAdded": {
2964
+ const key = String(p.key);
2965
+ if (prefix && !key.startsWith(prefix)) return;
2966
+ evt = { type: "added", key, newValue: String(p.newValue ?? "") };
2967
+ break;
2968
+ }
2969
+ case "DOMStorage.domStorageItemUpdated": {
2970
+ const key = String(p.key);
2971
+ if (prefix && !key.startsWith(prefix)) return;
2972
+ evt = {
2973
+ type: "updated",
2974
+ key,
2975
+ oldValue: String(p.oldValue ?? ""),
2976
+ newValue: String(p.newValue ?? "")
2977
+ };
2978
+ break;
2979
+ }
2980
+ case "DOMStorage.domStorageItemRemoved": {
2981
+ const key = String(p.key);
2982
+ if (prefix && !key.startsWith(prefix)) return;
2983
+ evt = { type: "removed", key };
2984
+ break;
2985
+ }
2986
+ case "DOMStorage.domStorageItemsCleared":
2987
+ evt = { type: "cleared" };
2988
+ break;
2989
+ default:
2990
+ return;
2991
+ }
2992
+ host.send(SimulatorStorageChannel.Event, evt);
2993
+ }
2994
+ async function ensureAttached() {
2995
+ if (attachedWc && !attachedWc.isDestroyed()) return;
2996
+ const sim = findSimulatorWebContents();
2997
+ if (!sim) return;
2998
+ await attachToSim(sim);
2999
+ }
3000
+ async function getStorageId() {
3001
+ if (!attachedWc || attachedWc.isDestroyed()) return null;
3002
+ if (!cachedOrigin) {
3003
+ try {
3004
+ cachedOrigin = String(await attachedWc.executeJavaScript("location.origin"));
3005
+ } catch {
3006
+ return null;
3007
+ }
3008
+ }
3009
+ return { securityOrigin: cachedOrigin, isLocalStorage: true };
3010
+ }
3011
+ function pushSyntheticEvent(evt) {
3012
+ if (host.isDestroyed()) return;
3013
+ const prefix = activePrefix();
3014
+ if (prefix && !evt.key.startsWith(prefix)) return;
3015
+ host.send(SimulatorStorageChannel.Event, evt);
3016
+ }
3017
+ async function setItem(key, value) {
3018
+ await ensureAttached();
3019
+ if (!attachedWc || attachedWc.isDestroyed()) {
3020
+ return { ok: false, error: "simulator not attached" };
3021
+ }
3022
+ const storageId = await getStorageId();
3023
+ if (!storageId) return { ok: false, error: "failed to resolve simulator origin" };
3024
+ try {
3025
+ await attachedWc.debugger.sendCommand("DOMStorage.setDOMStorageItem", {
3026
+ storageId,
3027
+ key,
3028
+ value
3029
+ });
3030
+ pushSyntheticEvent({ type: "added", key, newValue: value });
3031
+ return { ok: true };
3032
+ } catch (e) {
3033
+ return { ok: false, error: e.message };
3034
+ }
3035
+ }
3036
+ async function removeItem(key) {
3037
+ await ensureAttached();
3038
+ if (!attachedWc || attachedWc.isDestroyed()) {
3039
+ return { ok: false, error: "simulator not attached" };
3040
+ }
3041
+ const storageId = await getStorageId();
3042
+ if (!storageId) return { ok: false, error: "failed to resolve simulator origin" };
3043
+ try {
3044
+ await attachedWc.debugger.sendCommand("DOMStorage.removeDOMStorageItem", {
3045
+ storageId,
3046
+ key
3047
+ });
3048
+ pushSyntheticEvent({ type: "removed", key });
3049
+ return { ok: true };
3050
+ } catch (e) {
3051
+ return { ok: false, error: e.message };
3052
+ }
3053
+ }
3054
+ async function clearScoped() {
3055
+ await ensureAttached();
3056
+ if (!attachedWc || attachedWc.isDestroyed()) {
3057
+ return { ok: false, error: "simulator not attached" };
3058
+ }
3059
+ const storageId = await getStorageId();
3060
+ if (!storageId) return { ok: false, error: "failed to resolve simulator origin" };
3061
+ const prefix = activePrefix();
3062
+ try {
3063
+ if (!prefix) {
3064
+ await attachedWc.debugger.sendCommand("DOMStorage.clear", { storageId });
3065
+ return { ok: true };
3066
+ }
3067
+ const result = await attachedWc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
3068
+ storageId
3069
+ });
3070
+ for (const [key] of result.entries) {
3071
+ if (!key.startsWith(prefix)) continue;
3072
+ await attachedWc.debugger.sendCommand("DOMStorage.removeDOMStorageItem", {
3073
+ storageId,
3074
+ key
3075
+ });
3076
+ }
3077
+ return { ok: true };
3078
+ } catch (e) {
3079
+ return { ok: false, error: e.message };
3080
+ }
3081
+ }
3082
+ async function clearAll() {
3083
+ await ensureAttached();
3084
+ if (!attachedWc || attachedWc.isDestroyed()) {
3085
+ return { ok: false, error: "simulator not attached" };
3086
+ }
3087
+ const storageId = await getStorageId();
3088
+ if (!storageId) return { ok: false, error: "failed to resolve simulator origin" };
3089
+ try {
3090
+ await attachedWc.debugger.sendCommand("DOMStorage.clear", { storageId });
3091
+ return { ok: true };
3092
+ } catch (e) {
3093
+ return { ok: false, error: e.message };
3094
+ }
3095
+ }
3096
+ async function getSnapshot() {
3097
+ await ensureAttached();
3098
+ if (!attachedWc || attachedWc.isDestroyed()) return [];
3099
+ try {
3100
+ const storageId = await getStorageId();
3101
+ if (!storageId) return [];
3102
+ const result = await attachedWc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
3103
+ storageId
3104
+ });
3105
+ const prefix = activePrefix();
3106
+ const filtered = prefix ? result.entries.filter(([key]) => key.startsWith(prefix)) : result.entries;
3107
+ return filtered.map(([key, value]) => ({ key, value }));
3108
+ } catch {
3109
+ return [];
3110
+ }
3111
+ }
3112
+ for (const wc of wcStatic.getAllWebContents()) {
3113
+ if (isSimulatorWebview(wc)) {
3114
+ void attachToSim(wc);
2369
3115
  break;
2370
- case "DOMStorage.domStorageItemRemoved":
2371
- evt = { type: "removed", key: String(p.key) };
2372
- break;
2373
- case "DOMStorage.domStorageItemsCleared":
2374
- evt = { type: "cleared" };
2375
- break;
2376
- default:
2377
- return;
3116
+ }
2378
3117
  }
2379
- hostWc.send(SimulatorStorageChannel.Event, evt);
2380
- }
2381
- async function getSnapshot() {
2382
- if (!attachedWc || attachedWc.isDestroyed()) return [];
2383
- try {
2384
- const origin = await attachedWc.executeJavaScript("location.origin");
2385
- const result = await attachedWc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
2386
- storageId: { securityOrigin: origin, isLocalStorage: true }
3118
+ const wcSubs = /* @__PURE__ */ new Map();
3119
+ const onWcCreated = (_event, wc) => {
3120
+ const sub = new DisposableRegistry();
3121
+ wcSubs.set(wc, sub);
3122
+ const onFinishLoad = () => {
3123
+ if (isSimulatorWebview(wc)) void attachToSim(wc);
3124
+ };
3125
+ wc.on("did-finish-load", onFinishLoad);
3126
+ sub.add(() => safeOff(wc, "did-finish-load", onFinishLoad));
3127
+ wc.once("destroyed", () => {
3128
+ void sub.disposeAll().catch(() => {
3129
+ });
3130
+ wcSubs.delete(wc);
2387
3131
  });
2388
- return result.entries.map(([key, value]) => ({ key, value }));
2389
- } catch {
2390
- return [];
2391
- }
3132
+ };
3133
+ app8.on("web-contents-created", onWcCreated);
3134
+ registry.add(() => {
3135
+ app8.removeListener("web-contents-created", onWcCreated);
3136
+ });
3137
+ registry.add(async () => {
3138
+ const subs = Array.from(wcSubs.values());
3139
+ wcSubs.clear();
3140
+ for (const sub of subs) {
3141
+ await sub.disposeAll().catch(() => {
3142
+ });
3143
+ }
3144
+ });
3145
+ const ipc = new IpcRegistry(options.senderPolicy);
3146
+ ipc.handle(SimulatorStorageChannel.GetSnapshot, () => getSnapshot());
3147
+ ipc.handle(SimulatorStorageChannel.GetActivePrefix, () => activePrefix() ?? "");
3148
+ ipc.handle(
3149
+ SimulatorStorageChannel.Set,
3150
+ (_event, payload) => setItem(String(payload?.key ?? ""), String(payload?.value ?? ""))
3151
+ );
3152
+ ipc.handle(
3153
+ SimulatorStorageChannel.Remove,
3154
+ (_event, payload) => removeItem(String(payload?.key ?? ""))
3155
+ );
3156
+ ipc.handle(SimulatorStorageChannel.Clear, () => clearScoped());
3157
+ ipc.handle(SimulatorStorageChannel.ClearAll, () => clearAll());
3158
+ ipc.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
3159
+ ipc.handle(SimulatorElementChannel.Clear, () => clearElementInspection());
3160
+ registry.add(ipc);
3161
+ registry.add(() => detachFromSim());
3162
+ return registry;
2392
3163
  }
2393
3164
  function findSimulatorWebContents() {
2394
3165
  for (const wc of wcStatic.getAllWebContents()) {
@@ -2420,30 +3191,9 @@ async function clearElementInspection() {
2420
3191
  } catch {
2421
3192
  }
2422
3193
  }
2423
- function setupSimulatorStorage(host) {
2424
- hostWc = host;
2425
- for (const wc of wcStatic.getAllWebContents()) {
2426
- if (isSimulatorWebview(wc)) {
2427
- void attachToSim(wc);
2428
- break;
2429
- }
2430
- }
2431
- app8.on("web-contents-created", (_event, wc) => {
2432
- wc.on("did-finish-load", () => {
2433
- if (isSimulatorWebview(wc)) {
2434
- void attachToSim(wc);
2435
- }
2436
- });
2437
- wc.on("did-navigate-in-page", () => {
2438
- });
2439
- });
2440
- ipcMain10.handle(SimulatorStorageChannel.GetSnapshot, getSnapshot);
2441
- ipcMain10.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
2442
- ipcMain10.handle(SimulatorElementChannel.Clear, clearElementInspection);
2443
- }
2444
3194
 
2445
3195
  // src/main/services/update/update-manager.ts
2446
- import { app as app9, ipcMain as ipcMain11, shell } from "electron";
3196
+ import { app as app9, shell as shell3 } from "electron";
2447
3197
  var UpdateManager = class {
2448
3198
  checker;
2449
3199
  mainWindow;
@@ -2452,23 +3202,17 @@ var UpdateManager = class {
2452
3202
  downloadedPath = null;
2453
3203
  timer = null;
2454
3204
  initialTimer = null;
3205
+ ipc;
2455
3206
  constructor(opts) {
2456
3207
  this.checker = opts.checker;
2457
3208
  this.mainWindow = opts.mainWindow;
2458
3209
  this.getCurrentVersion = opts.getCurrentVersion ?? (() => app9.getVersion());
3210
+ this.ipc = new IpcRegistry(opts.senderPolicy);
2459
3211
  this.registerIpc();
2460
3212
  this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
2461
3213
  }
2462
3214
  registerIpc() {
2463
- ipcMain11.handle("updates:check", async () => {
2464
- return this.check();
2465
- });
2466
- ipcMain11.handle("updates:download", async () => {
2467
- return this.download();
2468
- });
2469
- ipcMain11.handle("updates:install", async () => {
2470
- this.install();
2471
- });
3215
+ this.ipc.handle(UpdateChannel.Check, async () => this.check()).handle(UpdateChannel.Download, async () => this.download()).handle(UpdateChannel.Install, async () => this.install());
2472
3216
  }
2473
3217
  async check() {
2474
3218
  try {
@@ -2491,7 +3235,7 @@ var UpdateManager = class {
2491
3235
  }
2492
3236
  try {
2493
3237
  const filePath = await this.checker.downloadUpdate(this.latestUpdate, (percent) => {
2494
- this.mainWindow.webContents.send("updates:downloadProgress", { percent });
3238
+ this.mainWindow.webContents.send(UpdateChannel.DownloadProgress, { percent });
2495
3239
  });
2496
3240
  this.downloadedPath = filePath;
2497
3241
  return { success: true, filePath };
@@ -2502,10 +3246,16 @@ var UpdateManager = class {
2502
3246
  }
2503
3247
  install() {
2504
3248
  if (this.downloadedPath) {
2505
- shell.openPath(this.downloadedPath);
3249
+ shell3.openPath(this.downloadedPath);
2506
3250
  app9.quit();
2507
3251
  }
2508
3252
  }
3253
+ /**
3254
+ * Tear down timers + IPC handlers. Returns a Promise so callers that need
3255
+ * a settled cleanup (e.g. workbench registry shutdown, the unit-test
3256
+ * lifecycle suite) can await it; calling without await is also safe since
3257
+ * the timer clears + handler removals are scheduled synchronously.
3258
+ */
2509
3259
  dispose() {
2510
3260
  if (this.timer) {
2511
3261
  clearInterval(this.timer);
@@ -2515,9 +3265,7 @@ var UpdateManager = class {
2515
3265
  clearTimeout(this.initialTimer);
2516
3266
  this.initialTimer = null;
2517
3267
  }
2518
- ipcMain11.removeHandler("updates:check");
2519
- ipcMain11.removeHandler("updates:download");
2520
- ipcMain11.removeHandler("updates:install");
3268
+ return this.ipc.dispose();
2521
3269
  }
2522
3270
  startPeriodicCheck(interval, initialDelay) {
2523
3271
  this.initialTimer = setTimeout(() => void this.checkAndNotify(), initialDelay);
@@ -2526,7 +3274,7 @@ var UpdateManager = class {
2526
3274
  async checkAndNotify() {
2527
3275
  const result = await this.check();
2528
3276
  if (result.hasUpdate && result.info) {
2529
- this.mainWindow.webContents.send("updates:available", result.info);
3277
+ this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
2530
3278
  }
2531
3279
  }
2532
3280
  };
@@ -2542,16 +3290,12 @@ var DEFAULT_MODULES = {
2542
3290
  popover: true,
2543
3291
  settings: true
2544
3292
  };
2545
- var MODULE_REGISTRARS = {
2546
- projects: registerProjectsIpc,
2547
- session: registerSessionIpc,
2548
- simulator: (ctx) => {
2549
- registerSimulatorIpc(ctx);
2550
- registerPanelsIpc(ctx);
2551
- registerToolbarIpc(ctx);
2552
- },
2553
- popover: registerPopoverIpc,
2554
- settings: registerSettingsIpc
3293
+ var BUILTIN_MODULES = {
3294
+ projects: projectsModule,
3295
+ session: sessionModule,
3296
+ simulator: simulatorModule,
3297
+ popover: popoverModule,
3298
+ settings: settingsModule
2555
3299
  };
2556
3300
  function parseAutoArgs() {
2557
3301
  const argv = process.argv;
@@ -2576,18 +3320,25 @@ function resolveModules(config) {
2576
3320
  ...config.modules
2577
3321
  };
2578
3322
  }
2579
- async function disposeContext(ctx, updateManager) {
2580
- updateManager?.dispose();
3323
+ async function disposeContext(ctx) {
2581
3324
  await ctx.workspace.closeProject();
3325
+ await ctx.registry.dispose().catch((err) => {
3326
+ console.warn("[workbench] dispose registry encountered errors:", err);
3327
+ });
2582
3328
  }
2583
3329
  function createConfiguredMainWindow(config, rendererDir2) {
2584
3330
  const mainWindow = createMainWindow({
2585
3331
  title: config.appName ?? "Dimina DevTools",
2586
- indexHtml: path6.join(rendererDir2, "entries/main/index.html"),
3332
+ indexHtml: path8.join(rendererDir2, "entries/main/index.html"),
2587
3333
  width: config.window?.width,
2588
3334
  height: config.window?.height,
2589
3335
  minWidth: config.window?.minWidth,
2590
- minHeight: config.window?.minHeight
3336
+ minHeight: config.window?.minHeight,
3337
+ // P1: pass the same preload path the workbench context stores on
3338
+ // ctx.preloadPath so hosts that ship a custom simulator preload are the
3339
+ // ones registered on the persist:simulator session — otherwise the
3340
+ // backstop wouldn't be the host's script.
3341
+ simulatorPreloadPath: config.preloadPath ?? defaultPreloadPath
2591
3342
  });
2592
3343
  if (config.icon) {
2593
3344
  const icon = nativeImage.createFromPath(config.icon);
@@ -2611,7 +3362,7 @@ function createContext(config, mainWindow, rendererDir2) {
2611
3362
  function registerBuiltinModules(config, context) {
2612
3363
  const modules = resolveModules(config);
2613
3364
  Object.keys(modules).forEach((moduleId) => {
2614
- if (modules[moduleId]) MODULE_REGISTRARS[moduleId](context);
3365
+ if (modules[moduleId]) context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
2615
3366
  });
2616
3367
  }
2617
3368
  function installMenu(config, mainWindow, context) {
@@ -2626,19 +3377,20 @@ async function setupAutomation(instance) {
2626
3377
  if (autoArgs.auto) {
2627
3378
  const server = await startAutomationServer(instance.context, autoArgs.autoPort);
2628
3379
  instance.automationServer = server;
3380
+ instance.context.registry.add(() => server.close());
2629
3381
  console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
2630
3382
  }
2631
3383
  }
2632
3384
  function setupMcp() {
2633
3385
  const settings = loadWorkbenchSettings();
2634
- if (!settings.mcp.enabled) return;
3386
+ if (!settings.mcp.enabled) return null;
2635
3387
  const cdpPortSwitch = app11.commandLine.getSwitchValue("remote-debugging-port");
2636
3388
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
2637
- startMcpServer(cdpPort2, settings.mcp.port);
3389
+ return startMcpServer(cdpPort2, settings.mcp.port);
2638
3390
  }
2639
3391
  function wireAppWindowEvents(config, instance) {
2640
3392
  const { mainWindow, context } = instance;
2641
- wireMainWindowEvents(mainWindow, {
3393
+ return wireMainWindowEvents(mainWindow, {
2642
3394
  context,
2643
3395
  onResize: () => context.views.repositionAll(),
2644
3396
  onClose: async (e) => {
@@ -2647,23 +3399,32 @@ function wireAppWindowEvents(config, instance) {
2647
3399
  if (config.onBeforeClose) {
2648
3400
  await config.onBeforeClose(instance);
2649
3401
  }
2650
- await disposeContext(context);
3402
+ await context.workspace.closeProject();
2651
3403
  context.notify.windowNavigateBack();
2652
3404
  }
2653
3405
  });
2654
3406
  }
2655
3407
  function enableDevRendererAutoReload(rendererDir2) {
2656
- if (!app11.isPackaged) {
2657
- let reloadTimer = null;
2658
- fs5.watch(rendererDir2, { recursive: true }, () => {
2659
- if (reloadTimer) clearTimeout(reloadTimer);
2660
- reloadTimer = setTimeout(() => {
2661
- for (const win of BrowserWindow3.getAllWindows()) {
2662
- win.webContents.reload();
2663
- }
2664
- }, 300);
3408
+ if (app11.isPackaged) {
3409
+ return toDisposable(() => {
2665
3410
  });
2666
3411
  }
3412
+ let reloadTimer = null;
3413
+ const watcher = fs5.watch(rendererDir2, { recursive: true }, () => {
3414
+ if (reloadTimer) clearTimeout(reloadTimer);
3415
+ reloadTimer = setTimeout(() => {
3416
+ for (const win of BrowserWindow3.getAllWindows()) {
3417
+ win.webContents.reload();
3418
+ }
3419
+ }, 300);
3420
+ });
3421
+ return toDisposable(() => {
3422
+ if (reloadTimer) {
3423
+ clearTimeout(reloadTimer);
3424
+ reloadTimer = null;
3425
+ }
3426
+ watcher.close();
3427
+ });
2667
3428
  }
2668
3429
  function createWorkbenchApp(config = {}) {
2669
3430
  let setupPromise = null;
@@ -2676,13 +3437,13 @@ function createWorkbenchApp(config = {}) {
2676
3437
  const rendererDir2 = config.rendererDir ?? rendererDir;
2677
3438
  const mainWindow = createConfiguredMainWindow(config, rendererDir2);
2678
3439
  const context = createContext(config, mainWindow, rendererDir2);
2679
- registerAppIpc(context);
3440
+ context.registry.add(registerAppIpc(context));
2680
3441
  registerBuiltinModules(config, context);
2681
3442
  installMenu(config, mainWindow, context);
2682
3443
  const instance = {
2683
3444
  mainWindow,
2684
3445
  context,
2685
- dispose: () => disposeContext(context, instance.updateManager)
3446
+ dispose: () => disposeContext(context)
2686
3447
  };
2687
3448
  if (config.onSetup) {
2688
3449
  await config.onSetup(instance);
@@ -2695,22 +3456,37 @@ function createWorkbenchApp(config = {}) {
2695
3456
  initialDelay: config.updateOptions?.initialDelay,
2696
3457
  getCurrentVersion: config.updateOptions?.getCurrentVersion
2697
3458
  });
3459
+ context.registry.add(() => instance.updateManager.dispose());
2698
3460
  }
2699
3461
  await setupAutomation(instance);
2700
- setupMcp();
2701
- setupSimulatorStorage(mainWindow.webContents);
2702
- wireAppWindowEvents(config, instance);
2703
- enableDevRendererAutoReload(rendererDir2);
3462
+ const mcp = setupMcp();
3463
+ if (mcp) context.registry.add(mcp);
3464
+ context.registry.add(setupSimulatorStorage(mainWindow.webContents, {
3465
+ senderPolicy: context.senderPolicy,
3466
+ // Per-project filter for the simulator-storage panel: the simulator
3467
+ // uses a fixed `persist:simulator` partition + a fixed simulator.html
3468
+ // origin, so localStorage is shared across every project that has
3469
+ // ever opened. The dimina runtime isolates writes with `${appId}_`
3470
+ // prefixes; this callback lets the storage panel filter the CDP
3471
+ // snapshot/event stream to the active appId.
3472
+ getActiveAppId: () => {
3473
+ const session2 = context.workspace.getSession();
3474
+ const appInfo = session2?.appInfo;
3475
+ return appInfo?.appId ?? null;
3476
+ }
3477
+ }));
3478
+ context.registry.add(wireAppWindowEvents(config, instance));
3479
+ context.registry.add(enableDevRendererAutoReload(rendererDir2));
2704
3480
  return instance;
2705
3481
  });
2706
3482
  return setupPromise;
2707
3483
  }
2708
3484
  function start() {
2709
- void setup();
2710
3485
  if (!appEventsRegistered) {
2711
3486
  appEventsRegistered = true;
2712
3487
  registerAppLifecycle();
2713
3488
  }
3489
+ return setup().then(() => void 0);
2714
3490
  }
2715
3491
  return {
2716
3492
  setup,
@@ -2720,7 +3496,7 @@ function createWorkbenchApp(config = {}) {
2720
3496
 
2721
3497
  // src/main/windows/settings-window/create.ts
2722
3498
  import { BrowserWindow as BrowserWindow4 } from "electron";
2723
- import path7 from "path";
3499
+ import path9 from "path";
2724
3500
  async function createSettingsWindow(parent, rendererDir2) {
2725
3501
  const win = new BrowserWindow4({
2726
3502
  width: 420,
@@ -2730,9 +3506,15 @@ async function createSettingsWindow(parent, rendererDir2) {
2730
3506
  parent: parent ?? void 0,
2731
3507
  title: "\u5F00\u53D1\u5DE5\u5177\u8BBE\u7F6E",
2732
3508
  backgroundColor: themeBg(),
2733
- webPreferences: { nodeIntegration: true, contextIsolation: false }
3509
+ webPreferences: {
3510
+ nodeIntegration: false,
3511
+ contextIsolation: true,
3512
+ sandbox: false,
3513
+ preload: mainPreloadPath
3514
+ }
2734
3515
  });
2735
- await win.loadFile(path7.join(rendererDir2, "entries/workbench-settings/index.html"));
3516
+ applyNavigationHardening(win.webContents, rendererDir2);
3517
+ await win.loadFile(path9.join(rendererDir2, "entries/workbench-settings/index.html"));
2736
3518
  return win;
2737
3519
  }
2738
3520
 
@@ -2745,13 +3527,16 @@ function wireSettingsWindowEvents(win, onClosed) {
2745
3527
 
2746
3528
  // src/main/app/launch.ts
2747
3529
  function launch(config = {}) {
2748
- createWorkbenchApp(config).start();
3530
+ return createWorkbenchApp(config).start();
2749
3531
  }
2750
3532
  async function openSettingsWindow(ctx) {
2751
3533
  if (!ctx.workbenchSettingsWindow || ctx.workbenchSettingsWindow.isDestroyed()) {
2752
- ctx.workbenchSettingsWindow = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
2753
- wireSettingsWindowEvents(ctx.workbenchSettingsWindow, () => {
3534
+ const win = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
3535
+ ctx.workbenchSettingsWindow = win;
3536
+ ctx.windows.setSettingsWindow(win);
3537
+ wireSettingsWindowEvents(win, () => {
2754
3538
  ctx.workbenchSettingsWindow = null;
3539
+ ctx.windows.setSettingsWindow(null);
2755
3540
  });
2756
3541
  }
2757
3542
  ctx.workbenchSettingsWindow.show();