@dimina-kit/devtools 0.4.0-dev.20260716163758 → 0.4.0-dev.20260717120050

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 (28) hide show
  1. package/dist/main/app/app.js +3 -0
  2. package/dist/main/index.bundle.js +79 -34
  3. package/dist/main/services/simulator-appdata/index.d.ts +6 -0
  4. package/dist/main/services/simulator-appdata/index.js +20 -1
  5. package/dist/main/utils/ipc-registry.d.ts +2 -1
  6. package/dist/main/utils/ipc-registry.js +30 -10
  7. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  8. package/dist/preload/windows/simulator.cjs.map +2 -2
  9. package/dist/renderer/assets/index-U9F8MrtV.js +49 -0
  10. package/dist/renderer/assets/{input-7P0WbXyg.js → input-c2OcrrZy.js} +2 -2
  11. package/dist/renderer/assets/ipc-transport-Blac3sOj.css +1 -0
  12. package/dist/renderer/assets/{ipc-transport-DTEmLpxx.js → ipc-transport-CuDJ07aE.js} +2 -2
  13. package/dist/renderer/assets/{popover-DsOcyIS6.js → popover-kv2qspWz.js} +2 -2
  14. package/dist/renderer/assets/{select-Bzbhbg9Z.js → select-DKqzMtTh.js} +2 -2
  15. package/dist/renderer/assets/{settings-api-B1wVFAPB.js → settings-api-qpXKDRXt.js} +2 -2
  16. package/dist/renderer/assets/{settings-CR1TyBWu.js → settings-dJNKTGr8.js} +2 -2
  17. package/dist/renderer/assets/{workbenchSettings-CFbToT4Q.js → workbenchSettings-DQ_VP5-W.js} +2 -2
  18. package/dist/renderer/entries/main/index.html +6 -6
  19. package/dist/renderer/entries/popover/index.html +5 -5
  20. package/dist/renderer/entries/settings/index.html +5 -5
  21. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  22. package/dist/service-host/appdata-set-data.cjs +39 -0
  23. package/dist/service-host/preload.cjs +21 -0
  24. package/dist/shared/ipc-channels.d.ts +9 -0
  25. package/dist/shared/ipc-channels.js +11 -0
  26. package/package.json +7 -7
  27. package/dist/renderer/assets/index-DQnIsRhp.js +0 -49
  28. package/dist/renderer/assets/ipc-transport-BGtDnv6p.css +0 -1
@@ -537,6 +537,9 @@ export async function createDevtoolsRuntime(config = {}) {
537
537
  const appDataService = setupSimulatorAppData(mainWindow.webContents, {
538
538
  senderPolicy: context.senderPolicy,
539
539
  getActiveAppId,
540
+ // AppData-panel edit write-back target: the service-host window owning
541
+ // the edited page bridge.
542
+ bridge: context.bridge,
540
543
  });
541
544
  // bridge-router feeds this via ctx.appData (service→render tap + evict).
542
545
  context.appData = appDataService;
@@ -171,7 +171,6 @@ import { z } from "zod";
171
171
 
172
172
  // src/main/utils/ipc-registry.ts
173
173
  import { ipcMain } from "electron";
174
- import { DisposableRegistry } from "@dimina-kit/electron-deck/main";
175
174
 
176
175
  // src/main/utils/ipc-schema.ts
177
176
  var IpcValidationError = class extends Error {
@@ -230,7 +229,13 @@ var IpcRegistry = class {
230
229
  this.policy = policy;
231
230
  }
232
231
  policy;
233
- registry = new DisposableRegistry();
232
+ // Every cleanup is a synchronous ipcMain.removeHandler/removeListener call,
233
+ // so dispose() can (and must) run them all before returning: callers
234
+ // `dispose()` without awaiting and rely on every channel being unregistered
235
+ // synchronously — an async registry would leave all but the first handler
236
+ // live until a later microtask.
237
+ cleanups = [];
238
+ _disposed = false;
234
239
  handle(channel, fn) {
235
240
  const policy = this.policy;
236
241
  const guarded = policy ? async (event, ...args) => {
@@ -244,7 +249,7 @@ var IpcRegistry = class {
244
249
  return fn(event, ...args);
245
250
  } : fn;
246
251
  ipcMain.handle(channel, guarded);
247
- this.registry.add(() => ipcMain.removeHandler(channel));
252
+ this.cleanups.push(() => ipcMain.removeHandler(channel));
248
253
  return this;
249
254
  }
250
255
  /**
@@ -282,9 +287,7 @@ var IpcRegistry = class {
282
287
  }
283
288
  };
284
289
  ipcMain.on(channel, listener);
285
- this.registry.add(() => {
286
- ipcMain.removeListener(channel, listener);
287
- });
290
+ this.cleanups.push(() => ipcMain.removeListener(channel, listener));
288
291
  return this;
289
292
  }
290
293
  on(channel, fn) {
@@ -311,13 +314,28 @@ var IpcRegistry = class {
311
314
  safeInvoke(event, args);
312
315
  } : (event, ...args) => safeInvoke(event, args);
313
316
  ipcMain.on(channel, guarded);
314
- this.registry.add(() => {
315
- ipcMain.removeListener(channel, guarded);
316
- });
317
+ this.cleanups.push(() => ipcMain.removeListener(channel, guarded));
317
318
  return this;
318
319
  }
319
320
  dispose() {
320
- return this.registry.dispose();
321
+ if (this._disposed) return Promise.resolve();
322
+ this._disposed = true;
323
+ const items = this.cleanups.slice().reverse();
324
+ this.cleanups = [];
325
+ const errors = [];
326
+ for (const cleanup of items) {
327
+ try {
328
+ cleanup();
329
+ } catch (e) {
330
+ errors.push(e);
331
+ }
332
+ }
333
+ if (errors.length > 0) {
334
+ return Promise.reject(
335
+ new AggregateError(errors, "IpcRegistry encountered errors during dispose")
336
+ );
337
+ }
338
+ return Promise.resolve();
321
339
  }
322
340
  };
323
341
 
@@ -352,7 +370,15 @@ var ServiceHostChannel = {
352
370
  * change without a relaunch. The service-host preload mutates
353
371
  * `__diminaSpawnContext.hostEnvSnapshot` in place (see `service-host/preload.cjs`).
354
372
  */
355
- HostEnvUpdate: "service-host:host-env:update"
373
+ HostEnvUpdate: "service-host:host-env:update",
374
+ /**
375
+ * NATIVE-HOST ONLY. Deliver an AppData-panel edit (`{bridgeId, data}`) into
376
+ * the service-host window. The preload resolves the page instance via
377
+ * `getCurrentPages()` and calls `page.setData(data)`, so the resulting `ub`
378
+ * publish flows back through the normal service→render tap — the panel
379
+ * refreshes from the runtime's own state, not an optimistic local echo.
380
+ */
381
+ AppDataSetData: "service-host:appdata:set-data"
356
382
  };
357
383
  var SimulatorCustomApiChannel = {
358
384
  Invoke: "simulator:custom-apis:invoke"
@@ -384,7 +410,10 @@ var SimulatorWxmlChannel = {
384
410
  };
385
411
  var SimulatorAppDataChannel = {
386
412
  GetSnapshot: "simulator:appdata:snapshot",
387
- Event: "simulator:appdata:event"
413
+ Event: "simulator:appdata:event",
414
+ // renderer → main invoke: write an AppData-panel edit back into the running
415
+ // page (forwarded to the service host via ServiceHostChannel.AppDataSetData).
416
+ SetData: "simulator:appdata:set-data"
388
417
  };
389
418
  var WorkbenchSettingsChannel = {
390
419
  Get: "workbenchSettings:get",
@@ -1008,9 +1037,9 @@ function createMainWindow(opts) {
1008
1037
 
1009
1038
  // src/main/windows/main-window/events.ts
1010
1039
  import { globalShortcut } from "electron";
1011
- import { DisposableRegistry as DisposableRegistry2, toDisposable as toDisposable2 } from "@dimina-kit/electron-deck/main";
1040
+ import { DisposableRegistry, toDisposable as toDisposable2 } from "@dimina-kit/electron-deck/main";
1012
1041
  function wireMainWindowEvents(win, state = {}) {
1013
- const registry = new DisposableRegistry2();
1042
+ const registry = new DisposableRegistry();
1014
1043
  const mainWebView = win.contentView.children[0];
1015
1044
  const resizeMainWebView = () => {
1016
1045
  if (mainWebView) {
@@ -1096,7 +1125,7 @@ function resolveNativeAppDataKeys(context, appId) {
1096
1125
  // src/main/services/workbench-context.ts
1097
1126
  import {
1098
1127
  createConnectionRegistry,
1099
- DisposableRegistry as DisposableRegistry3
1128
+ DisposableRegistry as DisposableRegistry2
1100
1129
  } from "@dimina-kit/electron-deck/main";
1101
1130
 
1102
1131
  // src/main/utils/sender-policy.ts
@@ -4539,7 +4568,7 @@ function createWorkbenchContext(opts) {
4539
4568
  appName: opts.appName ?? "Dimina DevTools",
4540
4569
  brandingProvider: opts.brandingProvider
4541
4570
  };
4542
- ctx.registry = new DisposableRegistry3();
4571
+ ctx.registry = new DisposableRegistry2();
4543
4572
  ctx.connections = createConnectionRegistry();
4544
4573
  ctx.trustedWindowSenderIds = /* @__PURE__ */ new Map();
4545
4574
  ctx.simulatorApis = createSimulatorApiRegistry();
@@ -5184,7 +5213,7 @@ var settingsModule = {
5184
5213
  };
5185
5214
 
5186
5215
  // src/main/ipc/simulator-module.ts
5187
- import { DisposableRegistry as DisposableRegistry6 } from "@dimina-kit/electron-deck/main";
5216
+ import { DisposableRegistry as DisposableRegistry5 } from "@dimina-kit/electron-deck/main";
5188
5217
 
5189
5218
  // src/main/ipc/bridge-router.ts
5190
5219
  import { app as app12, ipcMain as ipcMain3, protocol as protocol2, session as electronSession, webContents as webContents3 } from "electron";
@@ -5735,7 +5764,7 @@ var ServiceHostPool = class {
5735
5764
  };
5736
5765
 
5737
5766
  // src/main/services/console-forward/index.ts
5738
- import { DisposableRegistry as DisposableRegistry4, toDisposable as toDisposable3 } from "@dimina-kit/electron-deck/main";
5767
+ import { DisposableRegistry as DisposableRegistry3, toDisposable as toDisposable3 } from "@dimina-kit/electron-deck/main";
5739
5768
  var FORWARDABLE_LEVELS = /* @__PURE__ */ new Set(["log", "warn", "error", "info", "debug"]);
5740
5769
  function buildForwardScript(level, args) {
5741
5770
  const method = FORWARDABLE_LEVELS.has(level) ? level : "log";
@@ -5756,7 +5785,7 @@ function buildDiagnosticScript(severity, message) {
5756
5785
  }
5757
5786
  function createConsoleForwarder(bridge, diagnostics) {
5758
5787
  const sinks = /* @__PURE__ */ new Set();
5759
- const registry = new DisposableRegistry4();
5788
+ const registry = new DisposableRegistry3();
5760
5789
  const pendingBySession = /* @__PURE__ */ new Map();
5761
5790
  const pendingGlobal = [];
5762
5791
  const readySessions = /* @__PURE__ */ new Set();
@@ -5902,7 +5931,7 @@ function createDiagnosticsBus(opts) {
5902
5931
 
5903
5932
  // src/main/services/simulator-storage/index.ts
5904
5933
  import { app as app11, webContents as wcStatic } from "electron";
5905
- import { DisposableRegistry as DisposableRegistry5 } from "@dimina-kit/electron-deck/main";
5934
+ import { DisposableRegistry as DisposableRegistry4 } from "@dimina-kit/electron-deck/main";
5906
5935
 
5907
5936
  // src/main/services/simulator-storage/service-storage-ops.ts
5908
5937
  function encodeStorageValue(data) {
@@ -5989,7 +6018,7 @@ function safeOff(target, event, fn) {
5989
6018
  }
5990
6019
  }
5991
6020
  function setupSimulatorStorage(host, options) {
5992
- const registry = new DisposableRegistry5();
6021
+ const registry = new DisposableRegistry4();
5993
6022
  const getActiveAppId = options.getActiveAppId;
5994
6023
  function nativeServiceWc() {
5995
6024
  if (!options.bridge?.isNativeHost()) return null;
@@ -6027,7 +6056,7 @@ function setupSimulatorStorage(host, options) {
6027
6056
  wc.debugger.attach("1.3");
6028
6057
  }
6029
6058
  await wc.debugger.sendCommand("DOMStorage.enable");
6030
- const attach = new DisposableRegistry5();
6059
+ const attach = new DisposableRegistry4();
6031
6060
  attachDisposables = attach;
6032
6061
  const onMessage = (_event, method, params) => forwardCdpMessage(method, params);
6033
6062
  wc.debugger.on("message", onMessage);
@@ -6250,7 +6279,7 @@ function setupSimulatorStorage(host, options) {
6250
6279
  }
6251
6280
  const wcSubs = /* @__PURE__ */ new Map();
6252
6281
  const onWcCreated = (_event, wc) => {
6253
- const sub = new DisposableRegistry5();
6282
+ const sub = new DisposableRegistry4();
6254
6283
  wcSubs.set(wc, sub);
6255
6284
  const onFinishLoad = () => {
6256
6285
  if (isSimulatorWebview(wc)) void attachToSim(wc);
@@ -7971,7 +8000,7 @@ function registerViewsIpc(ctx) {
7971
8000
  // src/main/ipc/simulator-module.ts
7972
8001
  var simulatorModule = {
7973
8002
  setup: (ctx) => {
7974
- const reg = new DisposableRegistry6();
8003
+ const reg = new DisposableRegistry5();
7975
8004
  reg.add(registerSimulatorIpc(ctx));
7976
8005
  reg.add(registerViewsIpc(ctx));
7977
8006
  installBridgeRouter(ctx);
@@ -9361,7 +9390,7 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
9361
9390
  }
9362
9391
 
9363
9392
  // src/main/services/network-forward/index.ts
9364
- import { DisposableRegistry as DisposableRegistry7, toDisposable as toDisposable5 } from "@dimina-kit/electron-deck/main";
9393
+ import { DisposableRegistry as DisposableRegistry6, toDisposable as toDisposable5 } from "@dimina-kit/electron-deck/main";
9365
9394
 
9366
9395
  // src/main/services/network-forward/dispatch-batch.ts
9367
9396
  function packDispatchBatch(queue, maxSingleChars, maxBatchChars) {
@@ -9508,7 +9537,7 @@ var MAX_DISPATCH_QUEUE = 2e3;
9508
9537
  var DEVTOOLS_READY_TIMEOUT_MS = 5e3;
9509
9538
  var READY_RETRY_MS = 100;
9510
9539
  function createNetworkForwarder(bridge) {
9511
- const registry = new DisposableRegistry7();
9540
+ const registry = new DisposableRegistry6();
9512
9541
  let simWc = null;
9513
9542
  let attachDisposables = null;
9514
9543
  let devtoolsWc = null;
@@ -9731,7 +9760,7 @@ function createNetworkForwarder(bridge) {
9731
9760
  return;
9732
9761
  }
9733
9762
  simWc = wc;
9734
- const attach = new DisposableRegistry7();
9763
+ const attach = new DisposableRegistry6();
9735
9764
  attachDisposables = attach;
9736
9765
  const ns = new RequestIdNamespace(String(Date.now()));
9737
9766
  const pending = /* @__PURE__ */ new Map();
@@ -9907,10 +9936,10 @@ function createNetworkForwarder(bridge) {
9907
9936
  }
9908
9937
 
9909
9938
  // src/main/services/simulator-wxml/index.ts
9910
- import { DisposableRegistry as DisposableRegistry8 } from "@dimina-kit/electron-deck/main";
9939
+ import { DisposableRegistry as DisposableRegistry7 } from "@dimina-kit/electron-deck/main";
9911
9940
  function setupSimulatorWxml(host, options) {
9912
9941
  const { bridge, inspector, getActiveAppId } = options;
9913
- const registry = new DisposableRegistry8();
9942
+ const registry = new DisposableRegistry7();
9914
9943
  let active = false;
9915
9944
  let seq = 0;
9916
9945
  let pulling = false;
@@ -10001,7 +10030,7 @@ import {
10001
10030
  decodeWorkerMessage,
10002
10031
  decodedToInput
10003
10032
  } from "@dimina-kit/inspect";
10004
- import { DisposableRegistry as DisposableRegistry9 } from "@dimina-kit/electron-deck/main";
10033
+ import { DisposableRegistry as DisposableRegistry8 } from "@dimina-kit/electron-deck/main";
10005
10034
  var EMPTY_SNAPSHOT = { bridges: [], entries: {} };
10006
10035
  function setupSimulatorAppData(host, options) {
10007
10036
  const { getActiveAppId } = options;
@@ -10044,7 +10073,20 @@ function setupSimulatorAppData(host, options) {
10044
10073
  if (!appId) return EMPTY_SNAPSHOT;
10045
10074
  return accumulators.get(appId)?.snapshot() ?? EMPTY_SNAPSHOT;
10046
10075
  });
10047
- const registry = new DisposableRegistry9();
10076
+ function isValidSetDataPatch(data) {
10077
+ return !!data && typeof data === "object" && !Array.isArray(data) && Object.keys(data).length > 0;
10078
+ }
10079
+ ipc.handle(SimulatorAppDataChannel.SetData, (_event, payload) => {
10080
+ const p = payload;
10081
+ if (!p || typeof p !== "object") return false;
10082
+ if (typeof p.bridgeId !== "string" || p.bridgeId.length === 0) return false;
10083
+ if (!isValidSetDataPatch(p.data)) return false;
10084
+ const wc = options.bridge?.getServiceWcForBridge(p.bridgeId);
10085
+ if (!wc || wc.isDestroyed()) return false;
10086
+ wc.send(ServiceHostChannel.AppDataSetData, { bridgeId: p.bridgeId, data: p.data });
10087
+ return true;
10088
+ });
10089
+ const registry = new DisposableRegistry8();
10048
10090
  registry.add(() => accumulators.clear());
10049
10091
  registry.add(ipc);
10050
10092
  return {
@@ -10057,9 +10099,9 @@ function setupSimulatorAppData(host, options) {
10057
10099
  }
10058
10100
 
10059
10101
  // src/main/services/simulator-current-page/index.ts
10060
- import { DisposableRegistry as DisposableRegistry10 } from "@dimina-kit/electron-deck/main";
10102
+ import { DisposableRegistry as DisposableRegistry9 } from "@dimina-kit/electron-deck/main";
10061
10103
  function setupSimulatorCurrentPage(host, options) {
10062
- const registry = new DisposableRegistry10();
10104
+ const registry = new DisposableRegistry9();
10063
10105
  const off = options.bridge.onRenderEvent((ev) => {
10064
10106
  if (ev.kind !== "activePage" || !ev.pagePath) return;
10065
10107
  if (host.isDestroyed()) return;
@@ -11673,7 +11715,10 @@ async function createDevtoolsRuntime(config = {}) {
11673
11715
  }));
11674
11716
  const appDataService = setupSimulatorAppData(mainWindow.webContents, {
11675
11717
  senderPolicy: context.senderPolicy,
11676
- getActiveAppId
11718
+ getActiveAppId,
11719
+ // AppData-panel edit write-back target: the service-host window owning
11720
+ // the edited page bridge.
11721
+ bridge: context.bridge
11677
11722
  });
11678
11723
  context.appData = appDataService;
11679
11724
  context.registry.add(appDataService);
@@ -30,6 +30,12 @@ export interface SimulatorAppDataService extends AppDataTap, Disposable {
30
30
  export interface SimulatorAppDataOptions {
31
31
  getActiveAppId: () => string | null;
32
32
  senderPolicy?: SenderPolicy;
33
+ /** Resolves the service-host WebContents owning a page bridge — the SetData
34
+ * write-back target. Absent (host without a service bridge) → edits are
35
+ * rejected with `false`. */
36
+ bridge?: {
37
+ getServiceWcForBridge: (bridgeId: string) => WebContents | null;
38
+ };
33
39
  }
34
40
  export declare function setupSimulatorAppData(host: WebContents, options: SimulatorAppDataOptions): SimulatorAppDataService;
35
41
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- import { SimulatorAppDataChannel } from '../../../shared/ipc-channels.js';
1
+ import { ServiceHostChannel, SimulatorAppDataChannel } from '../../../shared/ipc-channels.js';
2
2
  import { AppDataAccumulator, decodeWorkerMessage, decodedToInput, } from '@dimina-kit/inspect';
3
3
  import { DisposableRegistry } from '@dimina-kit/electron-deck/main';
4
4
  import { IpcRegistry } from '../../utils/ipc-registry.js';
@@ -54,6 +54,25 @@ export function setupSimulatorAppData(host, options) {
54
54
  return EMPTY_SNAPSHOT;
55
55
  return accumulators.get(appId)?.snapshot() ?? EMPTY_SNAPSHOT;
56
56
  });
57
+ /** A plain, non-empty `{key: value}` patch — the only shape page.setData accepts. */
58
+ function isValidSetDataPatch(data) {
59
+ return !!data && typeof data === 'object' && !Array.isArray(data)
60
+ && Object.keys(data).length > 0;
61
+ }
62
+ ipc.handle(SimulatorAppDataChannel.SetData, (_event, payload) => {
63
+ const p = payload;
64
+ if (!p || typeof p !== 'object')
65
+ return false;
66
+ if (typeof p.bridgeId !== 'string' || p.bridgeId.length === 0)
67
+ return false;
68
+ if (!isValidSetDataPatch(p.data))
69
+ return false;
70
+ const wc = options.bridge?.getServiceWcForBridge(p.bridgeId);
71
+ if (!wc || wc.isDestroyed())
72
+ return false;
73
+ wc.send(ServiceHostChannel.AppDataSetData, { bridgeId: p.bridgeId, data: p.data });
74
+ return true;
75
+ });
57
76
  // disposeAll runs LIFO; add the IPC registry LAST so it is torn down first —
58
77
  // its removeHandler then runs synchronously (before the first `await` yields),
59
78
  // which callers that `dispose()` without awaiting rely on.
@@ -29,7 +29,8 @@ export type SenderPolicy = (sender: WebContents) => boolean;
29
29
  */
30
30
  export declare class IpcRegistry implements Disposable {
31
31
  private policy?;
32
- private registry;
32
+ private cleanups;
33
+ private _disposed;
33
34
  constructor(policy?: SenderPolicy | undefined);
34
35
  handle(channel: string, fn: HandleFn): this;
35
36
  /**
@@ -1,5 +1,4 @@
1
1
  import { ipcMain } from 'electron';
2
- import { DisposableRegistry } from '@dimina-kit/electron-deck/main';
3
2
  import { IpcValidationError } from './ipc-schema.js';
4
3
  import { createLogger } from './logger.js';
5
4
  const log = createLogger('ipc');
@@ -66,7 +65,13 @@ function isMainFrameSender(event) {
66
65
  */
67
66
  export class IpcRegistry {
68
67
  policy;
69
- registry = new DisposableRegistry();
68
+ // Every cleanup is a synchronous ipcMain.removeHandler/removeListener call,
69
+ // so dispose() can (and must) run them all before returning: callers
70
+ // `dispose()` without awaiting and rely on every channel being unregistered
71
+ // synchronously — an async registry would leave all but the first handler
72
+ // live until a later microtask.
73
+ cleanups = [];
74
+ _disposed = false;
70
75
  constructor(policy) {
71
76
  this.policy = policy;
72
77
  }
@@ -87,7 +92,7 @@ export class IpcRegistry {
87
92
  }
88
93
  : fn;
89
94
  ipcMain.handle(channel, guarded);
90
- this.registry.add(() => ipcMain.removeHandler(channel));
95
+ this.cleanups.push(() => ipcMain.removeHandler(channel));
91
96
  return this;
92
97
  }
93
98
  /**
@@ -128,9 +133,7 @@ export class IpcRegistry {
128
133
  }
129
134
  };
130
135
  ipcMain.on(channel, listener);
131
- this.registry.add(() => {
132
- ipcMain.removeListener(channel, listener);
133
- });
136
+ this.cleanups.push(() => ipcMain.removeListener(channel, listener));
134
137
  return this;
135
138
  }
136
139
  on(channel, fn) {
@@ -161,13 +164,30 @@ export class IpcRegistry {
161
164
  }
162
165
  : (event, ...args) => safeInvoke(event, args);
163
166
  ipcMain.on(channel, guarded);
164
- this.registry.add(() => {
165
- ipcMain.removeListener(channel, guarded);
166
- });
167
+ this.cleanups.push(() => ipcMain.removeListener(channel, guarded));
167
168
  return this;
168
169
  }
169
170
  dispose() {
170
- return this.registry.dispose();
171
+ // Synchronous drain (LIFO, idempotent): every channel is unregistered
172
+ // before this returns; the promise only carries aggregated errors.
173
+ if (this._disposed)
174
+ return Promise.resolve();
175
+ this._disposed = true;
176
+ const items = this.cleanups.slice().reverse();
177
+ this.cleanups = [];
178
+ const errors = [];
179
+ for (const cleanup of items) {
180
+ try {
181
+ cleanup();
182
+ }
183
+ catch (e) {
184
+ errors.push(e);
185
+ }
186
+ }
187
+ if (errors.length > 0) {
188
+ return Promise.reject(new AggregateError(errors, 'IpcRegistry encountered errors during dispose'));
189
+ }
190
+ return Promise.resolve();
171
191
  }
172
192
  }
173
193
  //# sourceMappingURL=ipc-registry.js.map