@dimina-kit/devtools 0.3.0 → 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 (116) 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 +1134 -300
  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/default-adapter.js +1 -3
  31. package/dist/main/services/mcp/server.d.ts +2 -1
  32. package/dist/main/services/mcp/server.js +5 -0
  33. package/dist/main/services/module.d.ts +18 -0
  34. package/dist/main/services/module.js +2 -0
  35. package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
  36. package/dist/main/services/projects/project-repository.d.ts +1 -0
  37. package/dist/main/services/projects/project-repository.js +3 -0
  38. package/dist/main/services/settings/index.d.ts +4 -0
  39. package/dist/main/services/settings/index.js +12 -1
  40. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  41. package/dist/main/services/simulator/custom-apis.js +26 -0
  42. package/dist/main/services/simulator/dir.d.ts +1 -1
  43. package/dist/main/services/simulator/dir.js +1 -4
  44. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  45. package/dist/main/services/simulator-storage/index.js +390 -84
  46. package/dist/main/services/update/update-manager.d.ts +16 -1
  47. package/dist/main/services/update/update-manager.js +18 -15
  48. package/dist/main/services/views/view-manager.d.ts +4 -0
  49. package/dist/main/services/views/view-manager.js +33 -2
  50. package/dist/main/services/window-service.d.ts +24 -0
  51. package/dist/main/services/window-service.js +26 -0
  52. package/dist/main/services/workbench-context.d.ts +15 -0
  53. package/dist/main/services/workbench-context.js +6 -0
  54. package/dist/main/services/workspace/workspace-service.d.ts +1 -0
  55. package/dist/main/services/workspace/workspace-service.js +7 -3
  56. package/dist/main/simulator-apis.d.ts +23 -0
  57. package/dist/main/simulator-apis.js +24 -0
  58. package/dist/main/utils/disposable.d.ts +13 -0
  59. package/dist/main/utils/disposable.js +60 -0
  60. package/dist/main/utils/ipc-registry.d.ts +39 -0
  61. package/dist/main/utils/ipc-registry.js +109 -0
  62. package/dist/main/utils/ipc-schema.d.ts +28 -0
  63. package/dist/main/utils/ipc-schema.js +38 -0
  64. package/dist/main/utils/paths.d.ts +8 -0
  65. package/dist/main/utils/paths.js +33 -4
  66. package/dist/main/utils/sender-policy.d.ts +23 -0
  67. package/dist/main/utils/sender-policy.js +42 -0
  68. package/dist/main/windows/main-window/create.d.ts +6 -0
  69. package/dist/main/windows/main-window/create.js +65 -9
  70. package/dist/main/windows/main-window/events.d.ts +2 -1
  71. package/dist/main/windows/main-window/events.js +25 -2
  72. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  73. package/dist/main/windows/navigation-hardening.js +55 -0
  74. package/dist/main/windows/settings-window/create.js +11 -1
  75. package/dist/preload/index.d.ts +17 -0
  76. package/dist/preload/index.js +17 -0
  77. package/dist/preload/instrumentation/app-data.js +178 -19
  78. package/dist/preload/instrumentation/wxml-extract.js +134 -3
  79. package/dist/preload/runtime/bridge.d.ts +3 -1
  80. package/dist/preload/runtime/bridge.js +55 -4
  81. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  82. package/dist/preload/runtime/custom-apis.js +18 -0
  83. package/dist/preload/runtime/host.d.ts +1 -0
  84. package/dist/preload/runtime/host.js +13 -0
  85. package/dist/preload/shared/types.d.ts +2 -1
  86. package/dist/preload/windows/main.cjs +47 -0
  87. package/dist/preload/windows/main.cjs.map +7 -0
  88. package/dist/preload/windows/simulator.js +274 -25
  89. package/dist/renderer/assets/index-DW_0thr-.js +46 -0
  90. package/dist/renderer/assets/{input-VLotwer2.js → input-C5M5Wxn6.js} +2 -2
  91. package/dist/renderer/assets/{ipc-transport-COKIC6Ny.js → ipc-transport-CUsl-fS2.js} +6 -6
  92. package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
  93. package/dist/renderer/assets/{popover-Dn0RwMLI.js → popover-BSBPVO0o.js} +2 -2
  94. package/dist/renderer/assets/{select-DzIsLaKX.js → select-B1tF3UAc.js} +2 -2
  95. package/dist/renderer/assets/{settings-CD-dacWB.js → settings-7w54H6p-.js} +2 -2
  96. package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
  97. package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
  98. package/dist/renderer/entries/main/index.html +12 -5
  99. package/dist/renderer/entries/popover/index.html +7 -5
  100. package/dist/renderer/entries/settings/index.html +7 -5
  101. package/dist/renderer/entries/workbench-settings/index.html +6 -4
  102. package/dist/shared/ipc-channels.d.ts +45 -0
  103. package/dist/shared/ipc-channels.js +28 -0
  104. package/dist/shared/ipc-schemas.d.ts +113 -0
  105. package/dist/shared/ipc-schemas.js +128 -0
  106. package/dist/shared/types.d.ts +2 -0
  107. package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
  108. package/dist/simulator/simulator.html +1 -1
  109. package/package.json +11 -27
  110. package/dist/preload/windows/main.d.ts +0 -6
  111. package/dist/preload/windows/main.js +0 -6
  112. package/dist/renderer/assets/index-D_HFRqZA.js +0 -46
  113. package/dist/renderer/assets/ipc-transport-6Gy3FXIL.css +0 -1
  114. package/dist/renderer/assets/settings-api-BV9MPckG.js +0 -2
  115. package/dist/renderer/assets/workbenchSettings-ClwgsvJL.js +0 -8
  116. 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) {
@@ -63,15 +74,32 @@ function setupCdpPort() {
63
74
 
64
75
  // src/main/app/app.ts
65
76
  import { app as app11, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
66
- import fs4 from "fs";
67
- import path6 from "path";
77
+ import fs5 from "fs";
78
+ import path8 from "path";
68
79
 
69
80
  // src/main/utils/paths.ts
81
+ import fs2 from "fs";
70
82
  import path2 from "path";
71
83
  import { fileURLToPath } from "url";
72
- var __dirname = path2.dirname(fileURLToPath(import.meta.url));
73
- var rendererDir = path2.join(__dirname, "../../../dist/renderer");
74
- var defaultPreloadPath = path2.join(__dirname, "../../preload/windows/simulator.js");
84
+ function resolveDevtoolsPackageRoot() {
85
+ let dir = path2.dirname(fileURLToPath(import.meta.url));
86
+ const root = path2.parse(dir).root;
87
+ while (dir !== root) {
88
+ const pkgPath = path2.join(dir, "package.json");
89
+ try {
90
+ const pkg2 = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
91
+ if (pkg2.name === "@dimina-kit/devtools") return dir;
92
+ } catch {
93
+ }
94
+ dir = path2.dirname(dir);
95
+ }
96
+ throw new Error("@dimina-kit/devtools package root not found from " + fileURLToPath(import.meta.url));
97
+ }
98
+ var devtoolsPackageRoot = resolveDevtoolsPackageRoot();
99
+ var rendererDir = path2.join(devtoolsPackageRoot, "dist/renderer");
100
+ var defaultPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/simulator.js");
101
+ var mainPreloadPath = path2.join(devtoolsPackageRoot, "dist/preload/windows/main.cjs");
102
+ var simulatorDir = path2.join(devtoolsPackageRoot, "dist/simulator");
75
103
 
76
104
  // src/main/app/lifecycle.ts
77
105
  import { app as app3, globalShortcut } from "electron";
@@ -86,7 +114,8 @@ function registerAppLifecycle() {
86
114
  }
87
115
 
88
116
  // src/main/windows/main-window/create.ts
89
- 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";
90
119
 
91
120
  // src/main/services/simulator/referer.ts
92
121
  var DEFAULT_VERSION = "develop";
@@ -113,12 +142,51 @@ function themeBg() {
113
142
  return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
114
143
  }
115
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
+
116
180
  // src/main/windows/main-window/create.ts
117
181
  var simulatorSessionConfigured = false;
118
- function configureSimulatorSession() {
182
+ function configureSimulatorSession(simulatorPreloadPath) {
119
183
  if (simulatorSessionConfigured) return;
120
184
  simulatorSessionConfigured = true;
121
185
  const simulatorSession = session.fromPartition("persist:simulator");
186
+ simulatorSession.registerPreloadScript({
187
+ type: "frame",
188
+ filePath: simulatorPreloadPath
189
+ });
122
190
  simulatorSession.webRequest.onBeforeSendHeaders((details, callback) => {
123
191
  const forcedReferer = getSimulatorServicewechatReferer();
124
192
  if (forcedReferer) {
@@ -137,7 +205,7 @@ function configureSimulatorSession() {
137
205
  });
138
206
  }
139
207
  function createMainWindow(opts) {
140
- configureSimulatorSession();
208
+ configureSimulatorSession(opts.simulatorPreloadPath);
141
209
  const mainWindow = new BrowserWindow({
142
210
  width: opts.width ?? 1280,
143
211
  height: opts.height ?? 980,
@@ -147,8 +215,13 @@ function createMainWindow(opts) {
147
215
  show: false,
148
216
  backgroundColor: themeBg(),
149
217
  webPreferences: {
150
- nodeIntegration: true,
151
- 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.
152
225
  webviewTag: true
153
226
  }
154
227
  });
@@ -162,22 +235,102 @@ function createMainWindow(opts) {
162
235
  }
163
236
  }
164
237
  });
165
- 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";
166
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
+ });
167
264
  });
168
265
  mainWindow.loadFile(opts.indexHtml);
169
266
  const container = new View();
170
267
  const mainWebView = mainWindow.contentView;
171
268
  container.addChildView(mainWebView);
172
269
  mainWindow.contentView = container;
173
- globalShortcut2.register("CommandOrControl+Shift+I", () => {
174
- mainWindow.webContents.openDevTools({ mode: "detach" });
175
- });
176
270
  return mainWindow;
177
271
  }
178
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
+
179
331
  // src/main/windows/main-window/events.ts
180
332
  function wireMainWindowEvents(win, state = {}) {
333
+ const registry = new DisposableRegistry();
181
334
  const mainWebView = win.contentView.children[0];
182
335
  const resizeMainWebView = () => {
183
336
  if (mainWebView) {
@@ -188,25 +341,53 @@ function wireMainWindowEvents(win, state = {}) {
188
341
  };
189
342
  resizeMainWebView();
190
343
  win.on("resize", resizeMainWebView);
344
+ registry.add(toDisposable(() => {
345
+ win.removeListener("resize", resizeMainWebView);
346
+ }));
191
347
  if (state.onClose) {
192
- win.on("close", (event) => {
348
+ const onCloseHandler = (event) => {
193
349
  void state.onClose?.(event);
194
- });
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
+ }));
195
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
+ };
196
380
  }
197
381
 
198
382
  // src/main/services/default-adapter.ts
199
- import path3 from "path";
200
- import { fileURLToPath as fileURLToPath2 } from "url";
383
+ import path5 from "path";
201
384
  import { app as app5 } from "electron";
202
- var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
203
- var simulatorDir = path3.resolve(__dirname2, "../../simulator");
204
385
  var defaultAdapter = {
205
386
  async openProject(opts) {
206
387
  const diminaKit = await import("@dimina-kit/devkit");
207
388
  const openProject = diminaKit.openProject;
208
389
  return openProject({
209
- outputDir: path3.join(app5.getPath("userData"), "dimina-fe-output"),
390
+ outputDir: path5.join(app5.getPath("userData"), "dimina-fe-output"),
210
391
  ...opts,
211
392
  simulatorDir
212
393
  });
@@ -224,10 +405,23 @@ var SimulatorChannel = {
224
405
  AppData: "simulator:appdata",
225
406
  AppDataAll: "simulator:appdata-all"
226
407
  };
408
+ var SimulatorCustomApiChannel = {
409
+ List: "simulator:custom-apis:list",
410
+ Invoke: "simulator:custom-apis:invoke"
411
+ };
227
412
  var SimulatorStorageChannel = {
228
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",
229
419
  Event: "simulator:storage:event"
230
420
  };
421
+ var SimulatorElementChannel = {
422
+ Inspect: "simulator:element:inspect",
423
+ Clear: "simulator:element:clear"
424
+ };
231
425
  var WorkbenchChannel = {
232
426
  GetPanelConfig: "workbench:getPanelConfig",
233
427
  GetApiNamespaces: "workbench:getApiNamespaces",
@@ -294,6 +488,13 @@ var SettingsChannel = {
294
488
  Closed: "settings:closed",
295
489
  Changed: "settings:changed"
296
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
+ };
297
498
 
298
499
  // src/main/services/notifications/renderer-notifier.ts
299
500
  function liveWebContents(wc) {
@@ -354,7 +555,7 @@ function createRendererNotifier(ctx) {
354
555
 
355
556
  // src/main/services/views/view-manager.ts
356
557
  import { WebContentsView as WebContentsView2, webContents } from "electron";
357
- import path4 from "path";
558
+ import path6 from "path";
358
559
 
359
560
  // src/main/services/layout/index.ts
360
561
  var HEADER_H = 40;
@@ -513,10 +714,16 @@ function createViewManager(ctx) {
513
714
  async function showSettings() {
514
715
  if (!settingsView) {
515
716
  settingsView = new WebContentsView2({
516
- webPreferences: { nodeIntegration: true, contextIsolation: false }
717
+ webPreferences: {
718
+ nodeIntegration: false,
719
+ contextIsolation: true,
720
+ sandbox: false,
721
+ preload: mainPreloadPath
722
+ }
517
723
  });
724
+ applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
518
725
  await settingsView.webContents.loadFile(
519
- path4.join(ctx.rendererDir, "entries/settings/index.html")
726
+ path6.join(ctx.rendererDir, "entries/settings/index.html")
520
727
  );
521
728
  }
522
729
  if (!settingsViewAdded) {
@@ -537,8 +744,14 @@ function createViewManager(ctx) {
537
744
  function showPopover(data) {
538
745
  hidePopover();
539
746
  const popover = new WebContentsView2({
540
- webPreferences: { nodeIntegration: true, contextIsolation: false }
747
+ webPreferences: {
748
+ nodeIntegration: false,
749
+ contextIsolation: true,
750
+ sandbox: false,
751
+ preload: mainPreloadPath
752
+ }
541
753
  });
754
+ applyNavigationHardening(popover.webContents, ctx.rendererDir);
542
755
  popoverView = popover;
543
756
  popover.setBackgroundColor("#00000000");
544
757
  ctx.mainWindow.contentView.addChildView(popover);
@@ -547,7 +760,7 @@ function createViewManager(ctx) {
547
760
  ctx.notify.popoverInit(popover, data);
548
761
  });
549
762
  popover.webContents.loadFile(
550
- path4.join(ctx.rendererDir, "entries/popover/index.html")
763
+ path6.join(ctx.rendererDir, "entries/popover/index.html")
551
764
  );
552
765
  }
553
766
  function hidePopover() {
@@ -613,15 +826,51 @@ function createViewManager(ctx) {
613
826
  if (settingsView.webContents.isDestroyed()) return null;
614
827
  return settingsView.webContents;
615
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
+ },
616
839
  resize,
617
840
  setVisible
618
841
  };
619
842
  }
620
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
+
621
870
  // src/main/services/projects/project-repository.ts
622
871
  import { app as app6 } from "electron";
623
- import path5 from "path";
624
- import fs2 from "fs";
872
+ import path7 from "path";
873
+ import fs3 from "fs";
625
874
 
626
875
  // src/shared/constants.ts
627
876
  var DEFAULT_SCENE = 1001;
@@ -667,17 +916,17 @@ function createLogger(tag) {
667
916
  // src/main/services/projects/project-repository.ts
668
917
  var log = createLogger("projects");
669
918
  function getProjectsFile() {
670
- return path5.join(app6.getPath("userData"), "dimina-projects.json");
919
+ return path7.join(app6.getPath("userData"), "dimina-projects.json");
671
920
  }
672
921
  function load() {
673
922
  try {
674
- return JSON.parse(fs2.readFileSync(getProjectsFile(), "utf-8"));
923
+ return JSON.parse(fs3.readFileSync(getProjectsFile(), "utf-8"));
675
924
  } catch {
676
925
  return [];
677
926
  }
678
927
  }
679
928
  function save(projects) {
680
- fs2.writeFileSync(getProjectsFile(), JSON.stringify(projects, null, 2));
929
+ fs3.writeFileSync(getProjectsFile(), JSON.stringify(projects, null, 2));
681
930
  }
682
931
  function listProjects() {
683
932
  return load();
@@ -686,16 +935,16 @@ function validateProjectDir(dirPath) {
686
935
  if (!dirPath) {
687
936
  return "\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u8DEF\u5F84\u4E3A\u7A7A\uFF0C\u8BF7\u9009\u62E9\u5305\u542B\u5C0F\u7A0B\u5E8F\u6E90\u7801\u7684\u76EE\u5F55";
688
937
  }
689
- if (!fs2.existsSync(dirPath)) {
938
+ if (!fs3.existsSync(dirPath)) {
690
939
  return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
691
940
  }
692
- if (!fs2.existsSync(path5.join(dirPath, "app.json"))) {
693
- const configPath = path5.join(dirPath, "project.config.json");
694
- if (fs2.existsSync(configPath)) {
941
+ if (!fs3.existsSync(path7.join(dirPath, "app.json"))) {
942
+ const configPath = path7.join(dirPath, "project.config.json");
943
+ if (fs3.existsSync(configPath)) {
695
944
  try {
696
- const cfg = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
945
+ const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
697
946
  if (cfg.miniprogramRoot) {
698
- const resolvedRoot = path5.resolve(dirPath, cfg.miniprogramRoot);
947
+ const resolvedRoot = path7.resolve(dirPath, cfg.miniprogramRoot);
699
948
  return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
700
949
  }
701
950
  } catch (err) {
@@ -706,13 +955,16 @@ function validateProjectDir(dirPath) {
706
955
  }
707
956
  return null;
708
957
  }
958
+ function hasProject(dirPath) {
959
+ return load().some((p) => p.path === dirPath);
960
+ }
709
961
  function addProject(dirPath) {
710
962
  const projects = load();
711
- let name = path5.basename(dirPath);
963
+ let name = path7.basename(dirPath);
712
964
  try {
713
- const configPath = path5.join(dirPath, "project.config.json");
714
- if (fs2.existsSync(configPath)) {
715
- const cfg = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
965
+ const configPath = path7.join(dirPath, "project.config.json");
966
+ if (fs3.existsSync(configPath)) {
967
+ const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
716
968
  if (cfg.projectname) name = cfg.projectname;
717
969
  }
718
970
  } catch (err) {
@@ -749,10 +1001,10 @@ function getCompileConfig(dirPath) {
749
1001
  };
750
1002
  }
751
1003
  function getProjectPages(dirPath) {
752
- const appJsonPath = path5.join(dirPath, "app.json");
1004
+ const appJsonPath = path7.join(dirPath, "app.json");
753
1005
  try {
754
1006
  const appJson = JSON.parse(
755
- fs2.readFileSync(appJsonPath, "utf-8")
1007
+ fs3.readFileSync(appJsonPath, "utf-8")
756
1008
  );
757
1009
  return {
758
1010
  pages: appJson.pages || [],
@@ -775,8 +1027,8 @@ function getProjectSettings(projectPath) {
775
1027
  return { uploadWithSourceMap: false };
776
1028
  }
777
1029
  try {
778
- const configPath = path5.join(projectPath, "project.config.json");
779
- const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
1030
+ const configPath = path7.join(projectPath, "project.config.json");
1031
+ const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
780
1032
  return {
781
1033
  uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
782
1034
  };
@@ -786,17 +1038,17 @@ function getProjectSettings(projectPath) {
786
1038
  }
787
1039
  function updateProjectSettings(projectPath, patch) {
788
1040
  if (!projectPath) return;
789
- const configPath = path5.join(projectPath, "project.config.json");
1041
+ const configPath = path7.join(projectPath, "project.config.json");
790
1042
  let config = {};
791
1043
  try {
792
- config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
1044
+ config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
793
1045
  } catch {
794
1046
  }
795
1047
  const nextSetting = {
796
1048
  ...config.setting ?? {},
797
1049
  ...patch.uploadWithSourceMap === void 0 ? {} : { uploadWithSourceMap: patch.uploadWithSourceMap }
798
1050
  };
799
- fs2.writeFileSync(
1051
+ fs3.writeFileSync(
800
1052
  configPath,
801
1053
  JSON.stringify({ ...config, setting: nextSetting }, null, 2)
802
1054
  );
@@ -806,8 +1058,8 @@ function updateProjectSettings(projectPath, patch) {
806
1058
  function createWorkspaceService(ctx) {
807
1059
  let currentSession = null;
808
1060
  let currentProjectPath = "";
809
- function sendStatus(status, message) {
810
- ctx.notify.projectStatus({ status, message });
1061
+ function sendStatus(status, message, hotReload) {
1062
+ ctx.notify.projectStatus(hotReload ? { status, message, hotReload: true } : { status, message });
811
1063
  }
812
1064
  function bestEffort(label, fn) {
813
1065
  try {
@@ -844,6 +1096,7 @@ function createWorkspaceService(ctx) {
844
1096
  listProjects: () => listProjects(),
845
1097
  addProject: (dirPath) => addProject(dirPath),
846
1098
  removeProject: (dirPath) => removeProject(dirPath),
1099
+ hasProject: (dirPath) => hasProject(dirPath),
847
1100
  validateProjectDir: (dirPath) => validateProjectDir(dirPath),
848
1101
  async openProject(projectPath) {
849
1102
  clearSimulatorServicewechatReferer();
@@ -855,12 +1108,14 @@ function createWorkspaceService(ctx) {
855
1108
  return { success: false, error: dirError };
856
1109
  }
857
1110
  sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
1111
+ const { compile } = loadWorkbenchSettings();
858
1112
  let session2;
859
1113
  try {
860
1114
  session2 = await ctx.adapter.openProject({
861
1115
  projectPath,
862
1116
  sourcemap: true,
863
- 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),
864
1119
  onBuildError: (err) => sendStatus("error", String(err))
865
1120
  });
866
1121
  } catch (err) {
@@ -918,9 +1173,12 @@ function createWorkbenchContext(opts) {
918
1173
  brandingProvider: opts.brandingProvider,
919
1174
  workbenchSettingsWindow: null
920
1175
  };
1176
+ ctx.registry = new DisposableRegistry();
1177
+ ctx.windows = createWindowService(ctx.mainWindow);
921
1178
  ctx.views = createViewManager(ctx);
922
1179
  ctx.notify = createRendererNotifier(ctx);
923
1180
  ctx.workspace = createWorkspaceService(ctx);
1181
+ ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
924
1182
  return ctx;
925
1183
  }
926
1184
 
@@ -991,56 +1249,246 @@ function installAppMenu(ctx) {
991
1249
  }
992
1250
 
993
1251
  // src/main/ipc/app.ts
1252
+ import fs4 from "fs";
1253
+
1254
+ // src/main/utils/ipc-registry.ts
994
1255
  import { ipcMain } from "electron";
995
- import fs3 from "fs";
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
996
1356
  function registerAppIpc(ctx) {
997
- ipcMain.handle(AppChannel.GetPreloadPath, () => {
998
- return `file://${fs3.realpathSync(ctx.preloadPath)}`;
999
- });
1000
- ipcMain.handle(AppChannel.GetBranding, async () => {
1357
+ return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
1358
+ return `file://${fs4.realpathSync(ctx.preloadPath)}`;
1359
+ }).handle(AppChannel.GetBranding, async () => {
1001
1360
  if (ctx.brandingProvider) return ctx.brandingProvider();
1002
1361
  return { appName: ctx.appName };
1003
1362
  });
1004
1363
  }
1005
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
+
1006
1453
  // src/main/ipc/simulator.ts
1007
- import { ipcMain as ipcMain2 } from "electron";
1008
1454
  function registerSimulatorIpc(ctx) {
1009
- 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);
1010
1457
  ctx.views.attachSimulator(simWcId, simWidth);
1011
- });
1012
- ipcMain2.handle(SimulatorChannel.Detach, () => {
1458
+ }).handle(SimulatorChannel.Detach, () => {
1013
1459
  ctx.views.detachSimulator();
1014
- });
1015
- ipcMain2.handle(SimulatorChannel.Resize, (_, simWidth) => {
1460
+ }).handle(SimulatorChannel.Resize, (_, ...args) => {
1461
+ const [simWidth] = validate(SimulatorChannel.Resize, SimulatorResizeSchema, args);
1016
1462
  ctx.views.resize(simWidth);
1017
- });
1018
- ipcMain2.handle(SimulatorChannel.SetVisible, (_, visible, simWidth) => {
1463
+ }).handle(SimulatorChannel.SetVisible, (_, ...args) => {
1464
+ const [visible, simWidth] = validate(SimulatorChannel.SetVisible, SimulatorSetVisibleSchema, args);
1019
1465
  ctx.views.setVisible(visible, simWidth);
1020
- });
1021
- 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, () => {
1022
1472
  return ctx.panels;
1023
- });
1024
- ipcMain2.handle(WorkbenchChannel.GetApiNamespaces, () => {
1473
+ }).handle(WorkbenchChannel.GetApiNamespaces, () => {
1025
1474
  return ctx.apiNamespaces;
1026
- });
1027
- ipcMain2.on(WorkbenchChannel.Reset, () => {
1475
+ }).on(WorkbenchChannel.Reset, () => {
1028
1476
  ctx.notify.workbenchReset();
1029
1477
  });
1030
1478
  }
1031
1479
 
1032
1480
  // src/main/ipc/panels.ts
1033
- import { ipcMain as ipcMain3, webContents as webContents2 } from "electron";
1481
+ import { webContents as webContents2 } from "electron";
1034
1482
  var BUILTIN_PANELS = [
1035
1483
  { id: "wxml", label: "WXML" },
1036
1484
  { id: "appdata", label: "AppData" },
1037
1485
  { id: "storage", label: "Storage" }
1038
1486
  ];
1039
1487
  function registerPanelsIpc(ctx) {
1040
- ipcMain3.handle(PanelChannel.List, () => {
1488
+ return new IpcRegistry(ctx.senderPolicy).handle(PanelChannel.List, () => {
1041
1489
  return BUILTIN_PANELS.filter((p) => ctx.panels.includes(p.id));
1042
- });
1043
- ipcMain3.handle(PanelChannel.Eval, async (_, expression) => {
1490
+ }).handle(PanelChannel.Eval, async (_event, ...args) => {
1491
+ const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
1044
1492
  const simWcId = ctx.views.getSimulatorWebContentsId();
1045
1493
  if (!ctx.workspace.hasActiveSession() || simWcId == null) return void 0;
1046
1494
  const sim = webContents2.fromId(simWcId);
@@ -1050,13 +1498,12 @@ function registerPanelsIpc(ctx) {
1050
1498
  } catch {
1051
1499
  return void 0;
1052
1500
  }
1053
- });
1054
- ipcMain3.handle(PanelChannel.Select, (_event, _panelId) => {
1501
+ }).handle(PanelChannel.Select, (_event, ...args) => {
1502
+ validate(PanelChannel.Select, PanelSelectSchema, args);
1055
1503
  if (ctx.views.hasSimulatorView() && ctx.views.isSimulatorAdded()) {
1056
1504
  ctx.views.hideSimulator();
1057
1505
  }
1058
- });
1059
- ipcMain3.handle(PanelChannel.SelectSimulator, () => {
1506
+ }).handle(PanelChannel.SelectSimulator, () => {
1060
1507
  if (ctx.views.hasSimulatorView() && !ctx.views.isSimulatorAdded()) {
1061
1508
  ctx.views.showSimulator(ctx.views.getLastSimWidth());
1062
1509
  }
@@ -1064,76 +1511,119 @@ function registerPanelsIpc(ctx) {
1064
1511
  }
1065
1512
 
1066
1513
  // src/main/ipc/popover.ts
1067
- import { ipcMain as ipcMain4 } from "electron";
1068
1514
  function registerPopoverIpc(ctx) {
1069
- 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);
1070
1517
  ctx.views.showPopover(data);
1071
- });
1072
- ipcMain4.handle(PopoverChannel.Hide, () => ctx.views.hidePopover());
1073
- 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);
1074
1522
  ctx.views.hidePopover();
1075
1523
  ctx.notify.popoverRelaunch(newConfig);
1076
1524
  });
1077
1525
  }
1526
+ var popoverModule = {
1527
+ setup: (ctx) => registerPopoverIpc(ctx)
1528
+ };
1078
1529
 
1079
1530
  // src/main/ipc/projects.ts
1080
- import { ipcMain as ipcMain5, dialog } from "electron";
1531
+ import { dialog } from "electron";
1081
1532
  function registerProjectsIpc(ctx) {
1082
- ipcMain5.handle(ProjectsChannel.List, () => ctx.workspace.listProjects());
1083
- ipcMain5.handle(DialogChannel.OpenDirectory, async () => {
1533
+ return new IpcRegistry(ctx.senderPolicy).handle(ProjectsChannel.List, () => {
1534
+ return ctx.workspace.listProjects();
1535
+ }).handle(DialogChannel.OpenDirectory, async () => {
1084
1536
  const result = await dialog.showOpenDialog(ctx.mainWindow, {
1085
1537
  properties: ["openDirectory"],
1086
1538
  title: "\u9009\u62E9\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55"
1087
1539
  });
1088
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);
1089
1569
  });
1090
- ipcMain5.handle(
1091
- ProjectsChannel.Add,
1092
- (_, dirPath) => ctx.workspace.addProject(dirPath)
1093
- );
1094
- ipcMain5.handle(
1095
- ProjectsChannel.Remove,
1096
- (_, dirPath) => ctx.workspace.removeProject(dirPath)
1097
- );
1098
1570
  }
1571
+ var projectsModule = {
1572
+ setup: (ctx) => registerProjectsIpc(ctx)
1573
+ };
1099
1574
 
1100
1575
  // src/main/ipc/session.ts
1101
- import { ipcMain as ipcMain6 } from "electron";
1102
1576
  function registerSessionIpc(ctx) {
1103
- ipcMain6.handle(
1104
- ProjectChannel.Open,
1105
- (_, projectPath) => ctx.workspace.openProject(projectPath)
1106
- );
1107
- ipcMain6.handle(
1108
- ProjectChannel.GetPages,
1109
- (_, projectPath) => ctx.workspace.getProjectPages(projectPath)
1110
- );
1111
- ipcMain6.handle(
1112
- ProjectChannel.GetCompileConfig,
1113
- (_, projectPath) => ctx.workspace.getCompileConfig(projectPath)
1114
- );
1115
- ipcMain6.handle(
1116
- ProjectChannel.SaveCompileConfig,
1117
- (_, projectPath, config) => ctx.workspace.saveCompileConfig(projectPath, config)
1118
- );
1119
- 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
+ });
1120
1600
  }
1601
+ var sessionModule = {
1602
+ setup: (ctx) => registerSessionIpc(ctx)
1603
+ };
1121
1604
 
1122
1605
  // src/main/ipc/settings.ts
1123
- import { app as app7, ipcMain as ipcMain7 } from "electron";
1606
+ import { app as app7 } from "electron";
1124
1607
  function registerSettingsIpc(ctx) {
1125
- ipcMain7.handle(WorkbenchSettingsChannel.Get, () => {
1608
+ return new IpcRegistry(ctx.senderPolicy).handle(WorkbenchSettingsChannel.Get, () => {
1126
1609
  return loadWorkbenchSettings();
1127
- });
1128
- ipcMain7.handle(WorkbenchSettingsChannel.Save, (_, settings) => {
1610
+ }).handle(WorkbenchSettingsChannel.Save, (_, ...args) => {
1611
+ const [settings] = validate(
1612
+ WorkbenchSettingsChannel.Save,
1613
+ WorkbenchSettingsSaveSchema,
1614
+ args
1615
+ );
1129
1616
  saveWorkbenchSettings(settings);
1130
1617
  applyTheme(settings.theme ?? "system");
1131
1618
  return { success: true };
1132
- });
1133
- ipcMain7.handle(WorkbenchSettingsChannel.SetTheme, (_, theme) => {
1619
+ }).handle(WorkbenchSettingsChannel.SetTheme, (_, ...args) => {
1620
+ const [theme] = validate(
1621
+ WorkbenchSettingsChannel.SetTheme,
1622
+ WorkbenchSettingsSetThemeSchema,
1623
+ args
1624
+ );
1134
1625
  applyTheme(theme);
1135
- });
1136
- ipcMain7.handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1626
+ }).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1137
1627
  const settings = loadWorkbenchSettings();
1138
1628
  const switchValue = app7.commandLine.getSwitchValue("remote-debugging-port");
1139
1629
  const implicitDevDefault = !app7.isPackaged && !settings.cdp.enabled && switchValue === "9222";
@@ -1144,15 +1634,19 @@ function registerSettingsIpc(ctx) {
1144
1634
  activePort: switchValue ? parseInt(switchValue, 10) : null,
1145
1635
  implicitDevDefault
1146
1636
  };
1147
- });
1148
- ipcMain7.handle(WorkbenchSettingsChannel.SetVisible, async (_, visible) => {
1637
+ }).handle(WorkbenchSettingsChannel.SetVisible, async (_, ...args) => {
1638
+ const [visible] = validate(
1639
+ WorkbenchSettingsChannel.SetVisible,
1640
+ WorkbenchSettingsSetVisibleSchema,
1641
+ args
1642
+ );
1149
1643
  if (visible) {
1150
1644
  await openSettingsWindow(ctx);
1151
1645
  } else {
1152
- ctx.workbenchSettingsWindow?.close();
1646
+ ctx.windows.closeSettingsWindow();
1153
1647
  }
1154
- });
1155
- ipcMain7.handle(SettingsChannel.SetVisible, async (_, visible) => {
1648
+ }).handle(SettingsChannel.SetVisible, async (_, ...args) => {
1649
+ const [visible] = validate(SettingsChannel.SetVisible, SettingsSetVisibleSchema, args);
1156
1650
  if (visible) {
1157
1651
  await ctx.views.showSettings();
1158
1652
  ctx.notify.settingsInit({
@@ -1164,32 +1658,55 @@ function registerSettingsIpc(ctx) {
1164
1658
  ctx.views.hideSettings();
1165
1659
  ctx.notify.settingsClosed();
1166
1660
  }
1167
- });
1168
- ipcMain7.on(SettingsChannel.ConfigChanged, (_, config) => {
1661
+ }).on(SettingsChannel.ConfigChanged, (_, ...args) => {
1662
+ const [config] = validate(
1663
+ SettingsChannel.ConfigChanged,
1664
+ SettingsConfigChangedSchema,
1665
+ args
1666
+ );
1169
1667
  if (ctx.workspace.getProjectPath()) {
1170
- ctx.workspace.saveCompileConfig(ctx.workspace.getProjectPath(), config);
1668
+ ctx.workspace.saveCompileConfig(
1669
+ ctx.workspace.getProjectPath(),
1670
+ config
1671
+ );
1171
1672
  }
1172
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
+ );
1173
1684
  });
1174
- ipcMain7.on(
1175
- SettingsChannel.ProjectSettingsChanged,
1176
- (_, patch) => {
1177
- ctx.workspace.updateProjectSettings(ctx.workspace.getProjectPath(), patch);
1178
- }
1179
- );
1180
1685
  }
1686
+ var settingsModule = {
1687
+ setup: (ctx) => registerSettingsIpc(ctx)
1688
+ };
1181
1689
 
1182
1690
  // src/main/ipc/toolbar.ts
1183
- import { ipcMain as ipcMain8 } from "electron";
1184
1691
  function registerToolbarIpc(ctx) {
1185
- ipcMain8.handle(ToolbarChannel.GetActions, async () => {
1692
+ return new IpcRegistry(ctx.senderPolicy).handle(ToolbarChannel.GetActions, async () => {
1186
1693
  if (ctx.toolbarActions) return ctx.toolbarActions();
1187
1694
  return [];
1188
1695
  });
1189
1696
  }
1190
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
+
1191
1709
  // src/main/services/automation/index.ts
1192
- import { ipcMain as ipcMain9 } from "electron";
1193
1710
  import { WebSocketServer } from "ws";
1194
1711
 
1195
1712
  // src/main/services/automation/exec.ts
@@ -1578,7 +2095,6 @@ var handlers = {
1578
2095
  ...elementHandlers
1579
2096
  };
1580
2097
  var currentPort = null;
1581
- var portIpcRegistered = false;
1582
2098
  async function startAutomationServer(ctx, port = 0) {
1583
2099
  const wss = new WebSocketServer({ port });
1584
2100
  const clients = /* @__PURE__ */ new Set();
@@ -1589,10 +2105,10 @@ async function startAutomationServer(ctx, port = 0) {
1589
2105
  const addr = wss.address();
1590
2106
  const resolvedPort = typeof addr === "object" && addr ? addr.port : port;
1591
2107
  currentPort = resolvedPort;
1592
- if (!portIpcRegistered) {
1593
- portIpcRegistered = true;
1594
- ipcMain9.handle(AutomationChannel.GetPort, () => currentPort);
1595
- }
2108
+ const portIpc = new IpcRegistry(ctx.senderPolicy);
2109
+ portIpc.handle(AutomationChannel.GetPort, () => {
2110
+ return currentPort;
2111
+ });
1596
2112
  function broadcast(event) {
1597
2113
  const msg = JSON.stringify(event);
1598
2114
  for (const ws of clients) {
@@ -1600,14 +2116,46 @@ async function startAutomationServer(ctx, port = 0) {
1600
2116
  }
1601
2117
  }
1602
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
+ }
1603
2148
  function setupConsoleForwarding() {
1604
2149
  if (consoleForwardingSetup) return;
1605
2150
  consoleForwardingSetup = true;
1606
- const check = setInterval(() => {
2151
+ pollInterval = setInterval(() => {
1607
2152
  const sim = getSimulator(ctx);
1608
2153
  if (!sim) return;
1609
- clearInterval(check);
1610
- sim.on("ipc-message-host", (_event, channel, data) => {
2154
+ if (pollInterval) {
2155
+ clearInterval(pollInterval);
2156
+ pollInterval = null;
2157
+ }
2158
+ const onIpcMessageHost = (_event, channel, data) => {
1611
2159
  if (channel === SimulatorChannel.Console) {
1612
2160
  const logData = data;
1613
2161
  broadcast({
@@ -1615,9 +2163,26 @@ async function startAutomationServer(ctx, port = 0) {
1615
2163
  params: { type: logData.level || "log", args: logData.args || [] }
1616
2164
  });
1617
2165
  }
1618
- });
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);
1619
2178
  }, 1e3);
1620
- setTimeout(() => clearInterval(check), 3e4);
2179
+ pollStopTimer = setTimeout(() => {
2180
+ if (pollInterval) {
2181
+ clearInterval(pollInterval);
2182
+ pollInterval = null;
2183
+ }
2184
+ pollStopTimer = null;
2185
+ }, 3e4);
1621
2186
  }
1622
2187
  wss.on("connection", (ws) => {
1623
2188
  clients.add(ws);
@@ -1648,7 +2213,24 @@ async function startAutomationServer(ctx, port = 0) {
1648
2213
  });
1649
2214
  return {
1650
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();
1651
2232
  if (currentPort === resolvedPort) currentPort = null;
2233
+ void portIpc.dispose();
1652
2234
  wss.close();
1653
2235
  },
1654
2236
  port: resolvedPort
@@ -1807,7 +2389,7 @@ function getClient(kind) {
1807
2389
  }
1808
2390
 
1809
2391
  // src/main/services/mcp/tool-registry.ts
1810
- import { z } from "zod";
2392
+ import { z as z2 } from "zod";
1811
2393
  function registerCommonTargetTools(server, kind) {
1812
2394
  const descriptions = kind === "simulator" ? {
1813
2395
  screenshot: "Take a screenshot of the simulator webview",
@@ -1828,9 +2410,9 @@ function registerCommonTargetTools(server, kind) {
1828
2410
  return { content: [{ type: "image", data, mimeType: "image/png" }] };
1829
2411
  });
1830
2412
  server.tool(`${kind}_console_logs`, descriptions.consoleLogs, {
1831
- limit: z.number().optional().default(50).describe("Maximum number of log entries"),
1832
- level: z.enum(["error", "warning", "warn", "info", "log", "debug"]).optional().describe("Only return entries with this level"),
1833
- 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")
1834
2416
  }, async ({ limit, level, sinceTimestamp }) => {
1835
2417
  getClient(kind);
1836
2418
  let entries = getTargetState(kind).consoleLogs;
@@ -1845,7 +2427,7 @@ function registerCommonTargetTools(server, kind) {
1845
2427
  });
1846
2428
  if (kind !== "workbench") {
1847
2429
  server.tool(`${kind}_evaluate`, descriptions.evaluate, {
1848
- expression: z.string().describe("JavaScript expression to evaluate")
2430
+ expression: z2.string().describe("JavaScript expression to evaluate")
1849
2431
  }, async ({ expression }) => {
1850
2432
  const c = getClient(kind);
1851
2433
  const result = await c.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true });
@@ -1858,15 +2440,15 @@ ${JSON.stringify(result.exceptionDetails, null, 2)}` }], isError: true };
1858
2440
  });
1859
2441
  }
1860
2442
  server.tool(`${kind}_get_dom`, descriptions.getDom, {
1861
- depth: z.number().optional().default(3).describe("Maximum depth")
2443
+ depth: z2.number().optional().default(3).describe("Maximum depth")
1862
2444
  }, async ({ depth }) => {
1863
2445
  const c = getClient(kind);
1864
2446
  const { root } = await c.DOM.getDocument({ depth });
1865
2447
  return { content: [{ type: "text", text: JSON.stringify(root, null, 2) }] };
1866
2448
  });
1867
2449
  server.tool(`${kind}_network_log`, descriptions.networkLog, {
1868
- limit: z.number().optional().default(20).describe("Maximum number of entries"),
1869
- 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)")
1870
2452
  }, async ({ limit, minStatus }) => {
1871
2453
  getClient(kind);
1872
2454
  let entries = getTargetState(kind).networkRequests;
@@ -2109,11 +2691,11 @@ function registerContextTools(server) {
2109
2691
  }
2110
2692
 
2111
2693
  // src/main/services/mcp/tools/simulator-tools.ts
2112
- import { z as z2 } from "zod";
2694
+ import { z as z3 } from "zod";
2113
2695
  function registerSimulatorTools(server) {
2114
2696
  server.tool("simulator_navigate", "Navigate the simulator to a URL, or reload the current page", {
2115
- url: z2.string().optional().describe("URL to navigate to"),
2116
- 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")
2117
2699
  }, async ({ url, reload }) => {
2118
2700
  const c = getClient("simulator");
2119
2701
  if (reload) {
@@ -2128,15 +2710,15 @@ function registerSimulatorTools(server) {
2128
2710
  return { content: [{ type: "text", text: "Error: either url or reload is required" }], isError: true };
2129
2711
  });
2130
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.", {
2131
- action: z2.enum(["tap_coord", "tap_selector", "type", "scroll", "key"]).describe("Input action to dispatch"),
2132
- x: z2.number().optional().describe("Viewport x coordinate for `tap_coord` or `scroll`"),
2133
- y: z2.number().optional().describe("Viewport y coordinate for `tap_coord` or `scroll`"),
2134
- selector: z2.string().optional().describe("CSS selector for `tap_selector` or `type`"),
2135
- nth: z2.number().int().optional().describe("Zero-based index when selector matches multiple elements; defaults to 0"),
2136
- text: z2.string().optional().describe("Text to insert for `type`"),
2137
- key: z2.string().optional().describe("Best-effort keyboard key name for `key`; common keys such as Enter, Escape, Tab, Arrow* and Space work best"),
2138
- deltaX: z2.number().optional().describe("Horizontal wheel delta for `scroll`"),
2139
- 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`")
2140
2722
  }, async ({ action, x, y, selector, nth, text, key, deltaX, deltaY }) => {
2141
2723
  const c = getClient("simulator");
2142
2724
  const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
@@ -2291,110 +2873,327 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
2291
2873
  httpServer.listen(mcpPort, "127.0.0.1", () => {
2292
2874
  console.log(`[MCP] SSE server listening on http://127.0.0.1:${mcpPort}/sse`);
2293
2875
  });
2876
+ return toDisposable(() => {
2877
+ transports.clear();
2878
+ httpServer.close();
2879
+ });
2294
2880
  }
2295
2881
 
2296
2882
  // src/main/services/simulator-storage/index.ts
2297
- import { app as app8, ipcMain as ipcMain10, webContents as wcStatic } from "electron";
2298
- var attachedWc = null;
2299
- var hostWc = null;
2883
+ import { app as app8, webContents as wcStatic } from "electron";
2300
2884
  function isSimulatorWebview(wc) {
2301
2885
  if (wc.isDestroyed()) return false;
2302
2886
  if (wc.getType() !== "webview") return false;
2303
2887
  return wc.getURL().includes("simulator.html");
2304
2888
  }
2305
- function detach() {
2306
- if (!attachedWc) return;
2307
- const wc = attachedWc;
2308
- attachedWc = null;
2889
+ function safeOff(target, event, fn) {
2309
2890
  try {
2310
- if (!wc.isDestroyed() && wc.debugger.isAttached()) {
2311
- wc.debugger.detach();
2312
- }
2891
+ if (target.isDestroyed?.()) return;
2892
+ target.removeListener(event, fn);
2313
2893
  } catch {
2314
2894
  }
2315
2895
  }
2316
- async function attachToSim(wc) {
2317
- if (attachedWc === wc) return;
2318
- if (wc.isDestroyed()) return;
2319
- detach();
2320
- try {
2321
- if (!wc.debugger.isAttached()) {
2322
- wc.debugger.attach("1.3");
2323
- }
2324
- await wc.debugger.sendCommand("DOMStorage.enable");
2325
- wc.debugger.on("message", onCdpMessage);
2326
- wc.debugger.on("detach", () => {
2327
- 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(() => {
2328
2914
  });
2329
- wc.once("destroyed", () => {
2330
- if (attachedWc === wc) attachedWc = null;
2331
- });
2332
- attachedWc = wc;
2333
- } catch (e) {
2334
- 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
+ }
2335
2921
  }
2336
- }
2337
- function onCdpMessage(_event, method, params) {
2338
- if (!hostWc || hostWc.isDestroyed()) return;
2339
- const p = params;
2340
- let evt;
2341
- switch (method) {
2342
- case "DOMStorage.domStorageItemAdded":
2343
- evt = { type: "added", key: String(p.key), newValue: String(p.newValue ?? "") };
2344
- break;
2345
- case "DOMStorage.domStorageItemUpdated":
2346
- evt = {
2347
- type: "updated",
2348
- key: String(p.key),
2349
- oldValue: String(p.oldValue ?? ""),
2350
- 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;
2351
2938
  };
2352
- break;
2353
- case "DOMStorage.domStorageItemRemoved":
2354
- evt = { type: "removed", key: String(p.key) };
2355
- break;
2356
- case "DOMStorage.domStorageItemsCleared":
2357
- evt = { type: "cleared" };
2358
- break;
2359
- default:
2360
- return;
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);
2361
2993
  }
2362
- hostWc.send(SimulatorStorageChannel.Event, evt);
2363
- }
2364
- async function getSnapshot() {
2365
- if (!attachedWc || attachedWc.isDestroyed()) return [];
2366
- try {
2367
- const origin = await attachedWc.executeJavaScript("location.origin");
2368
- const result = await attachedWc.debugger.sendCommand("DOMStorage.getDOMStorageItems", {
2369
- storageId: { securityOrigin: origin, isLocalStorage: true }
2370
- });
2371
- return result.entries.map(([key, value]) => ({ key, value }));
2372
- } catch {
2373
- return [];
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
+ }
2374
3111
  }
2375
- }
2376
- function setupSimulatorStorage(host) {
2377
- hostWc = host;
2378
3112
  for (const wc of wcStatic.getAllWebContents()) {
2379
3113
  if (isSimulatorWebview(wc)) {
2380
3114
  void attachToSim(wc);
2381
3115
  break;
2382
3116
  }
2383
3117
  }
2384
- app8.on("web-contents-created", (_event, wc) => {
2385
- wc.on("did-finish-load", () => {
2386
- if (isSimulatorWebview(wc)) {
2387
- void attachToSim(wc);
2388
- }
2389
- });
2390
- wc.on("did-navigate-in-page", () => {
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);
2391
3131
  });
3132
+ };
3133
+ app8.on("web-contents-created", onWcCreated);
3134
+ registry.add(() => {
3135
+ app8.removeListener("web-contents-created", onWcCreated);
2392
3136
  });
2393
- ipcMain10.handle(SimulatorStorageChannel.GetSnapshot, getSnapshot);
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;
3163
+ }
3164
+ function findSimulatorWebContents() {
3165
+ for (const wc of wcStatic.getAllWebContents()) {
3166
+ if (isSimulatorWebview(wc)) return wc;
3167
+ }
3168
+ return null;
3169
+ }
3170
+ async function inspectElement(sid) {
3171
+ if (!sid) return null;
3172
+ const sim = findSimulatorWebContents();
3173
+ if (!sim) return null;
3174
+ try {
3175
+ const result = await sim.executeJavaScript(
3176
+ `window.__simulatorData && window.__simulatorData.highlightElement ? window.__simulatorData.highlightElement(${JSON.stringify(sid)}) : null`
3177
+ );
3178
+ return result ?? null;
3179
+ } catch (e) {
3180
+ console.warn("[simulator-element] inspect failed:", e.message);
3181
+ return null;
3182
+ }
3183
+ }
3184
+ async function clearElementInspection() {
3185
+ const sim = findSimulatorWebContents();
3186
+ if (!sim) return;
3187
+ try {
3188
+ await sim.executeJavaScript(
3189
+ "window.__simulatorData && window.__simulatorData.unhighlightElement && window.__simulatorData.unhighlightElement()"
3190
+ );
3191
+ } catch {
3192
+ }
2394
3193
  }
2395
3194
 
2396
3195
  // src/main/services/update/update-manager.ts
2397
- import { app as app9, ipcMain as ipcMain11, shell } from "electron";
3196
+ import { app as app9, shell as shell3 } from "electron";
2398
3197
  var UpdateManager = class {
2399
3198
  checker;
2400
3199
  mainWindow;
@@ -2403,23 +3202,17 @@ var UpdateManager = class {
2403
3202
  downloadedPath = null;
2404
3203
  timer = null;
2405
3204
  initialTimer = null;
3205
+ ipc;
2406
3206
  constructor(opts) {
2407
3207
  this.checker = opts.checker;
2408
3208
  this.mainWindow = opts.mainWindow;
2409
3209
  this.getCurrentVersion = opts.getCurrentVersion ?? (() => app9.getVersion());
3210
+ this.ipc = new IpcRegistry(opts.senderPolicy);
2410
3211
  this.registerIpc();
2411
3212
  this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
2412
3213
  }
2413
3214
  registerIpc() {
2414
- ipcMain11.handle("updates:check", async () => {
2415
- return this.check();
2416
- });
2417
- ipcMain11.handle("updates:download", async () => {
2418
- return this.download();
2419
- });
2420
- ipcMain11.handle("updates:install", async () => {
2421
- this.install();
2422
- });
3215
+ this.ipc.handle(UpdateChannel.Check, async () => this.check()).handle(UpdateChannel.Download, async () => this.download()).handle(UpdateChannel.Install, async () => this.install());
2423
3216
  }
2424
3217
  async check() {
2425
3218
  try {
@@ -2442,7 +3235,7 @@ var UpdateManager = class {
2442
3235
  }
2443
3236
  try {
2444
3237
  const filePath = await this.checker.downloadUpdate(this.latestUpdate, (percent) => {
2445
- this.mainWindow.webContents.send("updates:downloadProgress", { percent });
3238
+ this.mainWindow.webContents.send(UpdateChannel.DownloadProgress, { percent });
2446
3239
  });
2447
3240
  this.downloadedPath = filePath;
2448
3241
  return { success: true, filePath };
@@ -2453,10 +3246,16 @@ var UpdateManager = class {
2453
3246
  }
2454
3247
  install() {
2455
3248
  if (this.downloadedPath) {
2456
- shell.openPath(this.downloadedPath);
3249
+ shell3.openPath(this.downloadedPath);
2457
3250
  app9.quit();
2458
3251
  }
2459
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
+ */
2460
3259
  dispose() {
2461
3260
  if (this.timer) {
2462
3261
  clearInterval(this.timer);
@@ -2466,9 +3265,7 @@ var UpdateManager = class {
2466
3265
  clearTimeout(this.initialTimer);
2467
3266
  this.initialTimer = null;
2468
3267
  }
2469
- ipcMain11.removeHandler("updates:check");
2470
- ipcMain11.removeHandler("updates:download");
2471
- ipcMain11.removeHandler("updates:install");
3268
+ return this.ipc.dispose();
2472
3269
  }
2473
3270
  startPeriodicCheck(interval, initialDelay) {
2474
3271
  this.initialTimer = setTimeout(() => void this.checkAndNotify(), initialDelay);
@@ -2477,7 +3274,7 @@ var UpdateManager = class {
2477
3274
  async checkAndNotify() {
2478
3275
  const result = await this.check();
2479
3276
  if (result.hasUpdate && result.info) {
2480
- this.mainWindow.webContents.send("updates:available", result.info);
3277
+ this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
2481
3278
  }
2482
3279
  }
2483
3280
  };
@@ -2493,16 +3290,12 @@ var DEFAULT_MODULES = {
2493
3290
  popover: true,
2494
3291
  settings: true
2495
3292
  };
2496
- var MODULE_REGISTRARS = {
2497
- projects: registerProjectsIpc,
2498
- session: registerSessionIpc,
2499
- simulator: (ctx) => {
2500
- registerSimulatorIpc(ctx);
2501
- registerPanelsIpc(ctx);
2502
- registerToolbarIpc(ctx);
2503
- },
2504
- popover: registerPopoverIpc,
2505
- settings: registerSettingsIpc
3293
+ var BUILTIN_MODULES = {
3294
+ projects: projectsModule,
3295
+ session: sessionModule,
3296
+ simulator: simulatorModule,
3297
+ popover: popoverModule,
3298
+ settings: settingsModule
2506
3299
  };
2507
3300
  function parseAutoArgs() {
2508
3301
  const argv = process.argv;
@@ -2527,18 +3320,25 @@ function resolveModules(config) {
2527
3320
  ...config.modules
2528
3321
  };
2529
3322
  }
2530
- async function disposeContext(ctx, updateManager) {
2531
- updateManager?.dispose();
3323
+ async function disposeContext(ctx) {
2532
3324
  await ctx.workspace.closeProject();
3325
+ await ctx.registry.dispose().catch((err) => {
3326
+ console.warn("[workbench] dispose registry encountered errors:", err);
3327
+ });
2533
3328
  }
2534
3329
  function createConfiguredMainWindow(config, rendererDir2) {
2535
3330
  const mainWindow = createMainWindow({
2536
3331
  title: config.appName ?? "Dimina DevTools",
2537
- indexHtml: path6.join(rendererDir2, "entries/main/index.html"),
3332
+ indexHtml: path8.join(rendererDir2, "entries/main/index.html"),
2538
3333
  width: config.window?.width,
2539
3334
  height: config.window?.height,
2540
3335
  minWidth: config.window?.minWidth,
2541
- 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
2542
3342
  });
2543
3343
  if (config.icon) {
2544
3344
  const icon = nativeImage.createFromPath(config.icon);
@@ -2562,7 +3362,7 @@ function createContext(config, mainWindow, rendererDir2) {
2562
3362
  function registerBuiltinModules(config, context) {
2563
3363
  const modules = resolveModules(config);
2564
3364
  Object.keys(modules).forEach((moduleId) => {
2565
- if (modules[moduleId]) MODULE_REGISTRARS[moduleId](context);
3365
+ if (modules[moduleId]) context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
2566
3366
  });
2567
3367
  }
2568
3368
  function installMenu(config, mainWindow, context) {
@@ -2577,19 +3377,20 @@ async function setupAutomation(instance) {
2577
3377
  if (autoArgs.auto) {
2578
3378
  const server = await startAutomationServer(instance.context, autoArgs.autoPort);
2579
3379
  instance.automationServer = server;
3380
+ instance.context.registry.add(() => server.close());
2580
3381
  console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
2581
3382
  }
2582
3383
  }
2583
3384
  function setupMcp() {
2584
3385
  const settings = loadWorkbenchSettings();
2585
- if (!settings.mcp.enabled) return;
3386
+ if (!settings.mcp.enabled) return null;
2586
3387
  const cdpPortSwitch = app11.commandLine.getSwitchValue("remote-debugging-port");
2587
3388
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
2588
- startMcpServer(cdpPort2, settings.mcp.port);
3389
+ return startMcpServer(cdpPort2, settings.mcp.port);
2589
3390
  }
2590
3391
  function wireAppWindowEvents(config, instance) {
2591
3392
  const { mainWindow, context } = instance;
2592
- wireMainWindowEvents(mainWindow, {
3393
+ return wireMainWindowEvents(mainWindow, {
2593
3394
  context,
2594
3395
  onResize: () => context.views.repositionAll(),
2595
3396
  onClose: async (e) => {
@@ -2598,23 +3399,32 @@ function wireAppWindowEvents(config, instance) {
2598
3399
  if (config.onBeforeClose) {
2599
3400
  await config.onBeforeClose(instance);
2600
3401
  }
2601
- await disposeContext(context);
3402
+ await context.workspace.closeProject();
2602
3403
  context.notify.windowNavigateBack();
2603
3404
  }
2604
3405
  });
2605
3406
  }
2606
3407
  function enableDevRendererAutoReload(rendererDir2) {
2607
- if (!app11.isPackaged) {
2608
- let reloadTimer = null;
2609
- fs4.watch(rendererDir2, { recursive: true }, () => {
2610
- if (reloadTimer) clearTimeout(reloadTimer);
2611
- reloadTimer = setTimeout(() => {
2612
- for (const win of BrowserWindow3.getAllWindows()) {
2613
- win.webContents.reload();
2614
- }
2615
- }, 300);
3408
+ if (app11.isPackaged) {
3409
+ return toDisposable(() => {
2616
3410
  });
2617
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
+ });
2618
3428
  }
2619
3429
  function createWorkbenchApp(config = {}) {
2620
3430
  let setupPromise = null;
@@ -2627,13 +3437,13 @@ function createWorkbenchApp(config = {}) {
2627
3437
  const rendererDir2 = config.rendererDir ?? rendererDir;
2628
3438
  const mainWindow = createConfiguredMainWindow(config, rendererDir2);
2629
3439
  const context = createContext(config, mainWindow, rendererDir2);
2630
- registerAppIpc(context);
3440
+ context.registry.add(registerAppIpc(context));
2631
3441
  registerBuiltinModules(config, context);
2632
3442
  installMenu(config, mainWindow, context);
2633
3443
  const instance = {
2634
3444
  mainWindow,
2635
3445
  context,
2636
- dispose: () => disposeContext(context, instance.updateManager)
3446
+ dispose: () => disposeContext(context)
2637
3447
  };
2638
3448
  if (config.onSetup) {
2639
3449
  await config.onSetup(instance);
@@ -2646,22 +3456,37 @@ function createWorkbenchApp(config = {}) {
2646
3456
  initialDelay: config.updateOptions?.initialDelay,
2647
3457
  getCurrentVersion: config.updateOptions?.getCurrentVersion
2648
3458
  });
3459
+ context.registry.add(() => instance.updateManager.dispose());
2649
3460
  }
2650
3461
  await setupAutomation(instance);
2651
- setupMcp();
2652
- setupSimulatorStorage(mainWindow.webContents);
2653
- wireAppWindowEvents(config, instance);
2654
- 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));
2655
3480
  return instance;
2656
3481
  });
2657
3482
  return setupPromise;
2658
3483
  }
2659
3484
  function start() {
2660
- void setup();
2661
3485
  if (!appEventsRegistered) {
2662
3486
  appEventsRegistered = true;
2663
3487
  registerAppLifecycle();
2664
3488
  }
3489
+ return setup().then(() => void 0);
2665
3490
  }
2666
3491
  return {
2667
3492
  setup,
@@ -2671,7 +3496,7 @@ function createWorkbenchApp(config = {}) {
2671
3496
 
2672
3497
  // src/main/windows/settings-window/create.ts
2673
3498
  import { BrowserWindow as BrowserWindow4 } from "electron";
2674
- import path7 from "path";
3499
+ import path9 from "path";
2675
3500
  async function createSettingsWindow(parent, rendererDir2) {
2676
3501
  const win = new BrowserWindow4({
2677
3502
  width: 420,
@@ -2681,9 +3506,15 @@ async function createSettingsWindow(parent, rendererDir2) {
2681
3506
  parent: parent ?? void 0,
2682
3507
  title: "\u5F00\u53D1\u5DE5\u5177\u8BBE\u7F6E",
2683
3508
  backgroundColor: themeBg(),
2684
- webPreferences: { nodeIntegration: true, contextIsolation: false }
3509
+ webPreferences: {
3510
+ nodeIntegration: false,
3511
+ contextIsolation: true,
3512
+ sandbox: false,
3513
+ preload: mainPreloadPath
3514
+ }
2685
3515
  });
2686
- 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"));
2687
3518
  return win;
2688
3519
  }
2689
3520
 
@@ -2696,13 +3527,16 @@ function wireSettingsWindowEvents(win, onClosed) {
2696
3527
 
2697
3528
  // src/main/app/launch.ts
2698
3529
  function launch(config = {}) {
2699
- createWorkbenchApp(config).start();
3530
+ return createWorkbenchApp(config).start();
2700
3531
  }
2701
3532
  async function openSettingsWindow(ctx) {
2702
3533
  if (!ctx.workbenchSettingsWindow || ctx.workbenchSettingsWindow.isDestroyed()) {
2703
- ctx.workbenchSettingsWindow = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
2704
- wireSettingsWindowEvents(ctx.workbenchSettingsWindow, () => {
3534
+ const win = await createSettingsWindow(ctx.mainWindow, ctx.rendererDir);
3535
+ ctx.workbenchSettingsWindow = win;
3536
+ ctx.windows.setSettingsWindow(win);
3537
+ wireSettingsWindowEvents(win, () => {
2705
3538
  ctx.workbenchSettingsWindow = null;
3539
+ ctx.windows.setSettingsWindow(null);
2706
3540
  });
2707
3541
  }
2708
3542
  ctx.workbenchSettingsWindow.show();