@dimina-kit/devtools 0.4.0-dev.20260625063832 → 0.4.0-dev.20260630033804
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/app/app.js +2 -0
- package/dist/main/index.bundle.js +241 -151
- package/dist/main/ipc/bridge-router.d.ts +21 -0
- package/dist/main/ipc/bridge-router.js +63 -3
- package/dist/main/services/automation/index.js +0 -1
- package/dist/main/services/elements-forward/index.js +81 -72
- package/dist/main/services/mcp/target-manager.d.ts +3 -3
- package/dist/main/services/views/miniapp-partition.d.ts +18 -8
- package/dist/main/services/views/miniapp-partition.js +34 -13
- package/dist/main/services/views/view-manager.js +76 -41
- package/dist/main/services/workbench-coi-server.d.ts +7 -0
- package/dist/main/services/workbench-coi-server.js +6 -0
- package/dist/main/services/workbench-context.d.ts +4 -2
- package/dist/main/services/workbench-context.js +1 -0
- package/dist/main/services/workspace/op-lock.d.ts +26 -0
- package/dist/main/services/workspace/op-lock.js +23 -0
- package/dist/main/services/workspace/workspace-service.d.ts +7 -0
- package/dist/main/services/workspace/workspace-service.js +197 -103
- package/dist/main/windows/service-host-window/create.d.ts +5 -1
- package/dist/main/windows/service-host-window/create.js +6 -3
- package/dist/native-host/render/render.js +485 -449
- package/dist/native-host/service/service.js +1 -1
- package/dist/renderer/assets/index-BMwmmaSp.js +46 -0
- package/dist/renderer/entries/main/index.html +1 -1
- package/dist/shared/types.d.ts +15 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-B0DTNerG.js +1 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-BuLjVMfN.js +1 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-thkx7I19.js +1 -0
- package/dist/vscode-workbench/assets/{dist-SFZ2HNEz.js → dist-CS7SQP3K.js} +4 -4
- package/dist/vscode-workbench/assets/dist-VSgdjUlW.js +8 -0
- package/dist/vscode-workbench/assets/editor.worker-DmMg51ee.js +41 -0
- package/dist/vscode-workbench/assets/extensionHost.worker-BMpl1cpB.js +207 -0
- package/dist/vscode-workbench/assets/{iconv-lite-umd-izd5l6fa.js → iconv-lite-umd-D3q-1-o6.js} +1 -1
- package/dist/vscode-workbench/assets/{iconv-lite-umd-COfjYshr.js → iconv-lite-umd-Jn9RTrsv.js} +2 -2
- package/dist/vscode-workbench/assets/index-zjigpZ25.js +2842 -0
- package/dist/vscode-workbench/assets/{jschardet-Bf-VMK4F.js → jschardet-CjCVc2Gw.js} +1 -1
- package/dist/vscode-workbench/assets/{jschardet-Dh5TyKsQ.js → jschardet-Cu4ufeHq.js} +1 -1
- package/dist/vscode-workbench/assets/{layout.contribution.darwin-cz2ttHTW.js → layout.contribution.darwin-CoH1BZtz.js} +1 -1
- package/dist/vscode-workbench/assets/{layout.contribution.linux-fEtdzbcY.js → layout.contribution.linux-Di2FF-CG.js} +1 -1
- package/dist/vscode-workbench/assets/{layout.contribution.win-BFyOKI8c.js → layout.contribution.win-moiNStil.js} +1 -1
- package/dist/vscode-workbench/assets/{main-Bh8TI5LP.js → main-DzHL45xo.js} +4 -4
- package/dist/vscode-workbench/assets/{main2-Bds9Fhfn.js → main2-CWVd-Hyp.js} +1 -1
- package/dist/vscode-workbench/assets/{worker-D9ti6A0r.js → worker-CRTb-Arc.js} +7 -7
- package/dist/vscode-workbench/assets/{worker-DQtXDXIZ.js → worker-CVB8WPuM.js} +2 -2
- package/dist/vscode-workbench/index.html +1 -4
- package/package.json +8 -38
- package/dist/renderer/assets/index-6XUSo6Su.js +0 -46
- package/dist/vscode-workbench/assets/__vite-browser-external-BUwiolNy.js +0 -1
- package/dist/vscode-workbench/assets/__vite-browser-external-CmG9GjjO.js +0 -1
- package/dist/vscode-workbench/assets/__vite-browser-external-DGJPTKLg.js +0 -1
- package/dist/vscode-workbench/assets/_commonjsHelpers-qcUHfnrg.js +0 -1
- package/dist/vscode-workbench/assets/chunk-B3K2TuZy.js +0 -1
- package/dist/vscode-workbench/assets/chunk-DECur_0Z.js +0 -1
- package/dist/vscode-workbench/assets/dist-tesJ1Kd2.js +0 -8
- package/dist/vscode-workbench/assets/editor.worker-BC1qOyXG.js +0 -41
- package/dist/vscode-workbench/assets/extension.api-BWA2aPAU.js +0 -1
- package/dist/vscode-workbench/assets/extensionHost.worker-Dvu35anN.js +0 -207
- package/dist/vscode-workbench/assets/index-CQ5xP0pp.js +0 -1656
- package/dist/vscode-workbench/assets/localExtensionHost-Ccz7QUdA.js +0 -1188
- package/dist/vscode-workbench/assets/main-BDgdQmvs.js +0 -4
- package/dist/vscode-workbench/assets/main2-DwfDVleQ.js +0 -1
- package/dist/vscode-workbench/assets/preload-helper-xBbMyY7u.js +0 -1
- /package/dist/vscode-workbench/assets/{_.contribution-C9aS_G-o.js → _.contribution-B46wcGKQ.js} +0 -0
- /package/dist/vscode-workbench/assets/{_commonjsHelpers-BPKu-ps5.js → _commonjsHelpers-DMmVv2OA.js} +0 -0
|
@@ -49,6 +49,27 @@ export interface BridgeRouterHandle {
|
|
|
49
49
|
getDevice(): NativeDeviceInfo | null;
|
|
50
50
|
/** Cache the selected device + push DEVICE_CHANGE to the live simulator WC(s). */
|
|
51
51
|
setDevice(device: NativeDeviceInfo): void;
|
|
52
|
+
/**
|
|
53
|
+
* Deterministically tear down every app session bound to the given simulator
|
|
54
|
+
* WebContentsView id (project close / DeviceShell respawn). The session
|
|
55
|
+
* mappings are cleared, the render-host guest webContents are closed, and the
|
|
56
|
+
* service-host window is closed/released — synchronously, instead of waiting
|
|
57
|
+
* on the WCV's async `'destroyed'` cascade — so the next project never
|
|
58
|
+
* re-resolves or screenshots the outgoing project's render guest. Idempotent
|
|
59
|
+
* with the `simulatorWc.once('destroyed')` hook (`disposeAppSession`
|
|
60
|
+
* early-returns once a session is gone). Optional so partial test mocks of the
|
|
61
|
+
* handle need not stub it.
|
|
62
|
+
*
|
|
63
|
+
* The synchronous prefix of the underlying disposeAppSession clears every map
|
|
64
|
+
* + closes the render guests + the service window before it awaits, so the
|
|
65
|
+
* bridge is clean the moment this is called. The returned promise resolves
|
|
66
|
+
* after the async tail (pool.release / resourceServer.close) so the lifecycle
|
|
67
|
+
* owner can sequence a reopen after full teardown. NOTE: disposeAppSession
|
|
68
|
+
* catches and logs pool/resource-release failures internally, so the promise
|
|
69
|
+
* resolves rather than rejecting on those — it is a completion signal, not an
|
|
70
|
+
* error channel.
|
|
71
|
+
*/
|
|
72
|
+
disposeSessionsForSimulator?(simulatorWcId: number): Promise<void>;
|
|
52
73
|
/**
|
|
53
74
|
* The flag-gated debugTap (§7) over the bridge message stream. Exposed so a
|
|
54
75
|
* hidden devtools panel / automation can read `.entries()` when
|
|
@@ -119,11 +119,27 @@ function resolveCurrentApp(state, ctx, appId) {
|
|
|
119
119
|
if (match)
|
|
120
120
|
return match;
|
|
121
121
|
}
|
|
122
|
-
//
|
|
122
|
+
// During a project close the workspace nulls its session BEFORE the bridge app
|
|
123
|
+
// session is torn down (disposeSession runs before disposeAll). Resolving here
|
|
124
|
+
// would hand a consumer the closing project's dying guest, so refuse to guess
|
|
125
|
+
// while a close is in flight.
|
|
126
|
+
if (ctx.workspace?.isClosing?.())
|
|
127
|
+
return undefined;
|
|
128
|
+
// No appId hint and no workspace session to disambiguate. Picking by appId is
|
|
129
|
+
// impossible, so fall back to the most-recent spawn — but ONLY when every live
|
|
130
|
+
// session belongs to the same app. Same-appId multiples are a respawn/reopen
|
|
131
|
+
// in progress where the newest is the live one (insertion order = spawn
|
|
132
|
+
// order). Multiple DISTINCT appIds mean a previous project is mid-teardown
|
|
133
|
+
// alongside the new one; any pick is a guess that can resolve the WRONG
|
|
134
|
+
// project's content (the screenshot/inspect-stale-app bug class), so prefer
|
|
135
|
+
// null over a wrong guess.
|
|
123
136
|
let last;
|
|
124
|
-
|
|
137
|
+
const distinctAppIds = new Set();
|
|
138
|
+
for (const ap of state.appSessions.values()) {
|
|
125
139
|
last = ap;
|
|
126
|
-
|
|
140
|
+
distinctAppIds.add(ap.appId);
|
|
141
|
+
}
|
|
142
|
+
return distinctAppIds.size <= 1 ? last : undefined;
|
|
127
143
|
}
|
|
128
144
|
export function installBridgeRouter(ctx) {
|
|
129
145
|
const state = {
|
|
@@ -262,6 +278,22 @@ export function installBridgeRouter(ctx) {
|
|
|
262
278
|
}
|
|
263
279
|
}
|
|
264
280
|
},
|
|
281
|
+
disposeSessionsForSimulator: (simulatorWcId) => {
|
|
282
|
+
// Snapshot ids first: disposeAppSession mutates state.appSessions.
|
|
283
|
+
const ids = [];
|
|
284
|
+
for (const [id, ap] of state.appSessions) {
|
|
285
|
+
if (ap.simulatorWc.id === simulatorWcId)
|
|
286
|
+
ids.push(id);
|
|
287
|
+
}
|
|
288
|
+
// disposeAppSession's synchronous prefix clears every map + closes the
|
|
289
|
+
// render guests + the service window, so the bridge is clean the moment
|
|
290
|
+
// this is called. The joined promise resolves after the async tail
|
|
291
|
+
// (pool.release / resourceServer.close) so a caller can sequence a reopen
|
|
292
|
+
// after full teardown. disposeAppSession logs those tail failures
|
|
293
|
+
// internally, so this resolves rather than rejecting on them — it is a
|
|
294
|
+
// completion signal, not an error channel.
|
|
295
|
+
return Promise.all(ids.map((id) => disposeAppSession(state, id))).then(() => { });
|
|
296
|
+
},
|
|
265
297
|
debugTap: state.debugTap,
|
|
266
298
|
};
|
|
267
299
|
ctx.bridge = bridgeHandle;
|
|
@@ -506,6 +538,13 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
506
538
|
serviceWindow = createServiceHostWindow({
|
|
507
539
|
bridgeId,
|
|
508
540
|
appId,
|
|
541
|
+
// Same (appId, projectPath) pair the simulator WCV uses, so this project's
|
|
542
|
+
// service host and render guests land on ONE partition while a different
|
|
543
|
+
// project with the same appId is isolated. Use the path captured at spawn
|
|
544
|
+
// ENTRY (workspaceProjectPath), not a re-read: a project switch during the
|
|
545
|
+
// awaits above would otherwise give the service host a different path than
|
|
546
|
+
// the simulator WCV was built with, splitting the partition.
|
|
547
|
+
projectPath: workspaceProjectPath || undefined,
|
|
509
548
|
pagePath,
|
|
510
549
|
pkgRoot,
|
|
511
550
|
root,
|
|
@@ -585,6 +624,17 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
585
624
|
void disposeAppSession(state, appSessionId);
|
|
586
625
|
};
|
|
587
626
|
simulatorWc.once('destroyed', onSimulatorDestroyed);
|
|
627
|
+
// The simulator WCV may have been torn down DURING this spawn's awaits
|
|
628
|
+
// (loadAppConfig / resource server / service-host creation) — e.g. a project
|
|
629
|
+
// close/switch that ran while we were mid-flight. In that case its 'destroyed'
|
|
630
|
+
// event already fired and the once() above will never run, so the freshly
|
|
631
|
+
// registered session would be a zombie owned by a dead simulator (the
|
|
632
|
+
// teardown's disposeSessionsForSimulator snapshot taken before appSessions.set
|
|
633
|
+
// could not have included it). Dispose now — idempotent with the graceful
|
|
634
|
+
// path — so a closed/superseded simulator can't leave a resurrected session.
|
|
635
|
+
if (simulatorWc.isDestroyed()) {
|
|
636
|
+
void disposeAppSession(state, appSessionId);
|
|
637
|
+
}
|
|
588
638
|
if (usedPool) {
|
|
589
639
|
// A pooled/fallback window passes through about:blank (warm load or the
|
|
590
640
|
// fallback's initial load). Boot only once the REAL service.html navigation
|
|
@@ -1232,6 +1282,16 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
|
|
|
1232
1282
|
for (const page of ap.pages.values()) {
|
|
1233
1283
|
if (page.renderWc && !page.renderWc.isDestroyed()) {
|
|
1234
1284
|
state.wcIdToBridgeId.delete(page.renderWc.id);
|
|
1285
|
+
// The render-host <webview> guest is otherwise reclaimed only when its
|
|
1286
|
+
// host simulator WCV is destroyed, and that cascade is async. Close it
|
|
1287
|
+
// here so a project-close / respawn tears the guest down deterministically
|
|
1288
|
+
// rather than leaving the outgoing project's guest alive to be
|
|
1289
|
+
// screenshotted or re-resolved by the next project. Idempotent with the
|
|
1290
|
+
// cascade (guarded on isDestroyed).
|
|
1291
|
+
try {
|
|
1292
|
+
page.renderWc.close();
|
|
1293
|
+
}
|
|
1294
|
+
catch { /* guest already gone */ }
|
|
1235
1295
|
}
|
|
1236
1296
|
state.pageSessions.delete(page.bridgeId);
|
|
1237
1297
|
}
|
|
@@ -91,7 +91,6 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
91
91
|
if (ipcMessageHostHandler) {
|
|
92
92
|
try {
|
|
93
93
|
if (!attachedSim.isDestroyed()) {
|
|
94
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
94
|
;
|
|
96
95
|
attachedSim.removeListener('ipc-message-host', ipcMessageHostHandler);
|
|
97
96
|
}
|
|
@@ -132,6 +132,24 @@ export function buildElementsHookScript() {
|
|
|
132
132
|
return 'partial';
|
|
133
133
|
}catch(e){ return 'error:' + (e && e.message); }})()`;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* One reconcile tick run in the front-end realm: idempotently (re)install the
|
|
137
|
+
* hook AND drain the outbound queue in a SINGLE round-trip. The front-end's
|
|
138
|
+
* `globalThis` is wiped on every devtools (re)load (service-host pool swap on a
|
|
139
|
+
* hot-reload respawn re-opens DevTools and re-bootstraps the front-end), so the
|
|
140
|
+
* hook must be re-asserted continuously — not once on `dom-ready`. Returns
|
|
141
|
+
* `{ status, batch }`: `status` is `'installed'` the tick we (re)wrap a freshly
|
|
142
|
+
* loaded front-end (the caller re-primes + re-pulls so Elements snaps back to the
|
|
143
|
+
* render guest), `'already'` once it is in place, `'partial'` while the embedder
|
|
144
|
+
* global has not appeared yet. `batch` is the drained command queue.
|
|
145
|
+
*/
|
|
146
|
+
function buildReconcileScript() {
|
|
147
|
+
return `(function(){`
|
|
148
|
+
+ `var status; try { status = ${buildElementsHookScript()}; } catch(e){ status = 'error:'+(e&&e.message); }`
|
|
149
|
+
+ `var batch = []; try { if (globalThis.__diminaElementsOutbound) batch = globalThis.__diminaElementsOutbound.splice(0); } catch(_){}`
|
|
150
|
+
+ `return { status: status, batch: batch };`
|
|
151
|
+
+ `})()`;
|
|
152
|
+
}
|
|
135
153
|
// ── main → front-end dispatch (mirrored from network-forward/index.ts) ────────
|
|
136
154
|
// network-forward keeps these file-private and the task forbids touching it, so
|
|
137
155
|
// they're re-implemented here. Keep the chunk contract in sync if that changes.
|
|
@@ -171,14 +189,10 @@ function buildDocumentUpdatedScript() {
|
|
|
171
189
|
const msg = JSON.stringify({ method: 'DOM.documentUpdated', params: {} });
|
|
172
190
|
return buildDispatchScript(msg);
|
|
173
191
|
}
|
|
192
|
+
// Reconcile cadence: each tick re-asserts the hook (idempotent) and drains the
|
|
193
|
+
// outbound queue. Fast enough that a freshly reloaded front-end is re-hooked
|
|
194
|
+
// within a tick, cheap because the install script short-circuits to 'already'.
|
|
174
195
|
const DRAIN_INTERVAL_MS = 150;
|
|
175
|
-
/** Bounded retry for the front-end hook install (front-end boots asynchronously). */
|
|
176
|
-
// Per-load install poll window. Must outlast the gap between a devtools
|
|
177
|
-
// front-end `dom-ready` and `InspectorFrontendHost` becoming available on a
|
|
178
|
-
// cold/slow open (pool cold-start). 200×50ms = 10s — generous, and each future
|
|
179
|
-
// front-end load re-arms a fresh window anyway (see the `dom-ready` listener).
|
|
180
|
-
const INSTALL_POLL_TRIES = 200;
|
|
181
|
-
const INSTALL_POLL_INTERVAL_MS = 50;
|
|
182
196
|
/**
|
|
183
197
|
* Install Elements forwarding on a DevTools front-end host wc. Returns a disposer
|
|
184
198
|
* (`stop`) that clears timers, unsubscribes render events, detaches ONLY the
|
|
@@ -192,8 +206,16 @@ const INSTALL_POLL_INTERVAL_MS = 50;
|
|
|
192
206
|
export function installElementsForward(deps) {
|
|
193
207
|
const { devtoolsWc, bridge } = deps;
|
|
194
208
|
let disposed = false;
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
// A single self-healing loop drives BOTH hook (re)install and outbound drain;
|
|
210
|
+
// it never self-terminates so a front-end reload (respawn) is re-hooked.
|
|
211
|
+
let reconcileTimer = null;
|
|
212
|
+
// Teardown that can only be wired AFTER the subscriptions below exist (render
|
|
213
|
+
// events, the dom-ready listener, the connection 'closed' sub). `stop()` runs it
|
|
214
|
+
// so EVERY teardown entry point — internal wc-destroy detection, the connection
|
|
215
|
+
// 'closed'/'destroyed' handler, and the returned disposer — does the SAME full,
|
|
216
|
+
// idempotent cleanup. Without this, a wc-destroy that reaches `stop()` directly
|
|
217
|
+
// would leave the render-event subscription (and dom-ready listener) dangling.
|
|
218
|
+
let lateCleanup = null;
|
|
197
219
|
// Staleness is keyed on the CURRENT active render guest, resolved fresh from the
|
|
198
220
|
// bridge per check — NOT a generation snapshot. An event/response is honoured
|
|
199
221
|
// only while its originating guest is still the active one, so switching away
|
|
@@ -212,13 +234,9 @@ export function installElementsForward(deps) {
|
|
|
212
234
|
const selfAttached = new Set();
|
|
213
235
|
const stop = () => {
|
|
214
236
|
disposed = true;
|
|
215
|
-
if (
|
|
216
|
-
clearInterval(
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
if (drainTimer) {
|
|
220
|
-
clearInterval(drainTimer);
|
|
221
|
-
drainTimer = null;
|
|
237
|
+
if (reconcileTimer) {
|
|
238
|
+
clearInterval(reconcileTimer);
|
|
239
|
+
reconcileTimer = null;
|
|
222
240
|
}
|
|
223
241
|
for (const cleanup of wiredGuests.values()) {
|
|
224
242
|
try {
|
|
@@ -228,6 +246,15 @@ export function installElementsForward(deps) {
|
|
|
228
246
|
}
|
|
229
247
|
wiredGuests.clear();
|
|
230
248
|
detachSelfAttached();
|
|
249
|
+
// Run the late-wired teardown once (render events, dom-ready, 'closed' sub).
|
|
250
|
+
if (lateCleanup) {
|
|
251
|
+
const c = lateCleanup;
|
|
252
|
+
lateCleanup = null;
|
|
253
|
+
try {
|
|
254
|
+
c();
|
|
255
|
+
}
|
|
256
|
+
catch { /* already gone */ }
|
|
257
|
+
}
|
|
231
258
|
};
|
|
232
259
|
/** Detach every session we own; leave safe-area's alone. */
|
|
233
260
|
function detachSelfAttached() {
|
|
@@ -538,91 +565,73 @@ export function installElementsForward(deps) {
|
|
|
538
565
|
pushDocumentUpdated();
|
|
539
566
|
};
|
|
540
567
|
const unsubscribeRenderEvents = bridge.onRenderEvent(onRenderEvent);
|
|
541
|
-
// ──
|
|
568
|
+
// ── self-healing reconcile loop: (re)install hook + drain, every tick ───────
|
|
542
569
|
const onReady = () => {
|
|
543
570
|
if (disposed)
|
|
544
571
|
return;
|
|
545
|
-
// Re-arm safe: a devtools front-end (re)load re-runs this, so clear any
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
if (
|
|
549
|
-
clearInterval(
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
572
|
+
// Re-arm safe: a devtools front-end (re)load re-runs this, so clear any prior
|
|
573
|
+
// loop before starting a fresh one — without this a reload would stack
|
|
574
|
+
// intervals (leak) and double-drain.
|
|
575
|
+
if (reconcileTimer) {
|
|
576
|
+
clearInterval(reconcileTimer);
|
|
577
|
+
reconcileTimer = null;
|
|
578
|
+
}
|
|
579
|
+
// ONE loop, never self-terminating: each tick idempotently (re)installs the
|
|
580
|
+
// hook AND drains the outbound queue in a single round-trip. The earlier
|
|
581
|
+
// design stopped its install poll on first success and re-armed ONLY via
|
|
582
|
+
// `dom-ready` — but a service-host pool swap on a hot-reload respawn re-points
|
|
583
|
+
// the front-end without a reliable `dom-ready`, so the hook (which lives in
|
|
584
|
+
// the front-end `globalThis` and is wiped on every reload) stayed uninstalled
|
|
585
|
+
// and Elements fell back to the natively-inspected service host. Re-asserting
|
|
586
|
+
// the hook every tick makes re-establishment independent of any single event.
|
|
587
|
+
// The tick we freshly (re)wrap a reloaded front-end (`status === 'installed'`)
|
|
588
|
+
// we re-prime the active guest and nudge a re-pull so the panel snaps back to
|
|
589
|
+
// the render guest. We deliberately do NOT force showView('elements') — the
|
|
590
|
+
// panel stays on the user-preferred Console default; the eager DOM pull (and
|
|
591
|
+
// any later Elements click) is already routed to render.
|
|
592
|
+
reconcileTimer = setInterval(() => {
|
|
559
593
|
if (disposed || devtoolsWc.isDestroyed()) {
|
|
560
594
|
stop();
|
|
561
595
|
return;
|
|
562
596
|
}
|
|
563
597
|
devtoolsWc
|
|
564
|
-
.executeJavaScript(
|
|
565
|
-
.then((
|
|
598
|
+
.executeJavaScript(buildReconcileScript())
|
|
599
|
+
.then((res) => {
|
|
566
600
|
if (disposed)
|
|
567
601
|
return;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
clearInterval(installTimer);
|
|
571
|
-
installTimer = null;
|
|
572
|
-
}
|
|
573
|
-
// Prime the current guest, then nudge the front-end to (re-)pull DOM
|
|
574
|
-
// so the bootstrap document.getDocument is routed at the render guest.
|
|
575
|
-
// We deliberately do NOT force showView('elements') — the panel stays
|
|
576
|
-
// on the user-preferred Console default; the eager DOM pull (and any
|
|
577
|
-
// later Elements click) is already routed to render.
|
|
602
|
+
const r = (res ?? {});
|
|
603
|
+
if (r.status === 'installed') {
|
|
578
604
|
const wc = activeRenderWc();
|
|
579
605
|
if (wc)
|
|
580
606
|
primeGuest(wc);
|
|
581
607
|
pushDocumentUpdated();
|
|
582
608
|
}
|
|
609
|
+
handleOutbound(r.batch);
|
|
583
610
|
})
|
|
584
611
|
.catch(() => { });
|
|
585
|
-
if (tries > INSTALL_POLL_TRIES && installTimer) {
|
|
586
|
-
clearInterval(installTimer);
|
|
587
|
-
installTimer = null;
|
|
588
|
-
}
|
|
589
|
-
}, INSTALL_POLL_INTERVAL_MS);
|
|
590
|
-
// Poll-drain the outbound command queue.
|
|
591
|
-
drainTimer = setInterval(() => {
|
|
592
|
-
if (disposed || devtoolsWc.isDestroyed()) {
|
|
593
|
-
stop();
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
devtoolsWc
|
|
597
|
-
.executeJavaScript('(globalThis.__diminaElementsOutbound ? __diminaElementsOutbound.splice(0) : [])')
|
|
598
|
-
.then(handleOutbound)
|
|
599
|
-
.catch(() => { });
|
|
600
612
|
}, DRAIN_INTERVAL_MS);
|
|
601
613
|
};
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
// DevTools (`pointNativeDevtoolsAtServiceWc`) and re-bootstraps the front-end.
|
|
606
|
-
// A single-shot `once('dom-ready')` left the hook uninstalled after such a
|
|
607
|
-
// reload, so Elements silently fell back to the native service-host DOM. A
|
|
608
|
-
// PERSISTENT listener re-runs the (idempotent — the sentinel returns 'already')
|
|
609
|
-
// install on each load; the immediate call covers an already-loaded wc and the
|
|
610
|
-
// first `openDevTools` whose `dom-ready` may have fired before this ran.
|
|
614
|
+
// `dom-ready` re-arms the loop promptly on a front-end (re)load; the loop itself
|
|
615
|
+
// is the durable self-healer (it re-asserts the hook every tick regardless), so
|
|
616
|
+
// a missed `dom-ready` no longer leaves Elements stuck on the service host.
|
|
611
617
|
devtoolsWc.on('dom-ready', onReady);
|
|
612
618
|
onReady();
|
|
613
619
|
let devtoolsClosedSub;
|
|
614
620
|
try {
|
|
615
621
|
// Devtools front-end wc is never pool-reset → use `'closed'` (fires only on
|
|
616
|
-
// real destroy). `on('closed')` dispose() removes WITHOUT firing, so
|
|
617
|
-
//
|
|
618
|
-
//
|
|
622
|
+
// real destroy). `on('closed')` dispose() removes WITHOUT firing, so `stop()`
|
|
623
|
+
// (which now disposes it) can be the sole teardown — no stale disposers
|
|
624
|
+
// accumulate across re-install on a surviving devtoolsWc.
|
|
619
625
|
if (deps.connections)
|
|
620
626
|
devtoolsClosedSub = deps.connections.acquire(devtoolsWc).on('closed', stop);
|
|
621
627
|
else
|
|
622
628
|
devtoolsWc.once('destroyed', stop);
|
|
623
629
|
}
|
|
624
630
|
catch { /* fake/minimal wc */ }
|
|
625
|
-
|
|
631
|
+
// Now that every subscription exists, route them through `stop()` so a teardown
|
|
632
|
+
// from ANY entry point (returned disposer, 'closed'/'destroyed' handler, or the
|
|
633
|
+
// reconcile tick noticing the wc is gone) does the identical full cleanup.
|
|
634
|
+
lateCleanup = () => {
|
|
626
635
|
try {
|
|
627
636
|
unsubscribeRenderEvents();
|
|
628
637
|
}
|
|
@@ -635,7 +644,7 @@ export function installElementsForward(deps) {
|
|
|
635
644
|
devtoolsWc.removeListener('dom-ready', onReady);
|
|
636
645
|
}
|
|
637
646
|
catch { /* already gone */ }
|
|
638
|
-
stop();
|
|
639
647
|
};
|
|
648
|
+
return stop;
|
|
640
649
|
}
|
|
641
650
|
//# sourceMappingURL=index.js.map
|
|
@@ -36,9 +36,9 @@ export interface NativeOverview {
|
|
|
36
36
|
}
|
|
37
37
|
export type NativeOverviewProvider = () => Promise<NativeOverview>;
|
|
38
38
|
interface TargetState {
|
|
39
|
-
client:
|
|
39
|
+
client: CDP.Client | null;
|
|
40
40
|
connected: boolean;
|
|
41
|
-
timer:
|
|
41
|
+
timer: ReturnType<typeof setTimeout> | null;
|
|
42
42
|
consoleLogs: ConsoleLogEntry[];
|
|
43
43
|
networkRequests: NetworkRequestEntry[];
|
|
44
44
|
}
|
|
@@ -74,5 +74,5 @@ export declare function getTargetState(kind: TargetKind): TargetState;
|
|
|
74
74
|
declare function listCdpTargets(): Promise<CDP.Target[]>;
|
|
75
75
|
export { listCdpTargets as listTargets };
|
|
76
76
|
export declare function connectTarget(kind: TargetKind): Promise<void>;
|
|
77
|
-
export declare function getClient(kind: TargetKind):
|
|
77
|
+
export declare function getClient(kind: TargetKind): CDP.Client;
|
|
78
78
|
//# sourceMappingURL=target-manager.d.ts.map
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
* visible to (and clobbered by) project B; two projects open at once cross-
|
|
9
9
|
* contaminate and "clear storage" on one nukes the other.
|
|
10
10
|
*
|
|
11
|
-
* The fix derives a STABLE per-project partition from the project identity
|
|
12
|
-
* miniapp `appId
|
|
13
|
-
* path's `appId`)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The fix derives a STABLE per-project partition from the project identity:
|
|
12
|
+
* the miniapp `appId` (which flows in via the simulator URL `?appId=` and the
|
|
13
|
+
* spawn path's `appId`) PLUS the project path. appId alone is insufficient —
|
|
14
|
+
* it comes from the project manifest, so two DIFFERENT project paths can declare
|
|
15
|
+
* the SAME appId and would otherwise share a partition; folding the path in
|
|
16
|
+
* isolates them. Same project (same appId + path) → same partition (storage
|
|
17
|
+
* survives a relaunch); different projects → different partitions (no cross-
|
|
18
|
+
* contamination). The shape mirrors the IDE state of the art (WeChat's static
|
|
19
|
+
* partition, ByteDance's `persist:miniapp-<id>` dynamic partition).
|
|
17
20
|
*
|
|
18
21
|
* The protocol handlers (`difile://`, `dmb-resource`) and webRequest policies
|
|
19
22
|
* (referer / CORS) that the simulator runtime needs were installed once on the
|
|
@@ -25,6 +28,13 @@
|
|
|
25
28
|
* Partition CLEANUP (reclaiming on-disk `persist:` data) is intentionally NOT
|
|
26
29
|
* done here — leaving the data on disk is the whole point of a `persist:`
|
|
27
30
|
* partition (cache/storage survives a relaunch). That is a separate concern.
|
|
31
|
+
*
|
|
32
|
+
* MIGRATION (intentional one-time reset): folding the project path into the key
|
|
33
|
+
* means a project that previously ran on the appId-only `persist:miniapp-<appId>`
|
|
34
|
+
* now runs on `persist:miniapp-<appId>-p<hash>` and starts with empty
|
|
35
|
+
* cookies/localStorage/cache. Old data is NOT migrated — a best-effort copy
|
|
36
|
+
* could carry already-cross-contaminated state into the newly isolated space,
|
|
37
|
+
* which defeats the isolation. The reset is accepted as a one-time cost.
|
|
28
38
|
*/
|
|
29
39
|
import type { Session } from 'electron';
|
|
30
40
|
/** The legacy single-session partition. Still used by the pre-warm pool (which
|
|
@@ -42,14 +52,14 @@ export declare const SHARED_MINIAPP_PARTITION = "persist:simulator";
|
|
|
42
52
|
* same key (so storage survives a relaunch); different `appId`s yield different
|
|
43
53
|
* keys.
|
|
44
54
|
*/
|
|
45
|
-
export declare function miniappPartitionKey(appId: string): string;
|
|
55
|
+
export declare function miniappPartitionKey(appId: string, projectPath?: string | null): string;
|
|
46
56
|
/**
|
|
47
57
|
* The Electron session partition for a project. Returns the SHARED partition
|
|
48
58
|
* when `appId` is empty/unknown (so the runtime still has a session to load on,
|
|
49
59
|
* matching the legacy behavior) and a `persist:miniapp-<key>` partition
|
|
50
60
|
* otherwise.
|
|
51
61
|
*/
|
|
52
|
-
export declare function miniappPartition(appId: string | null | undefined): string;
|
|
62
|
+
export declare function miniappPartition(appId: string | null | undefined, projectPath?: string | null): string;
|
|
53
63
|
type SessionConfigurator = (sess: Session, partition: string) => void;
|
|
54
64
|
/**
|
|
55
65
|
* Register a configurator that runs against every miniapp partition session.
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
* visible to (and clobbered by) project B; two projects open at once cross-
|
|
9
9
|
* contaminate and "clear storage" on one nukes the other.
|
|
10
10
|
*
|
|
11
|
-
* The fix derives a STABLE per-project partition from the project identity
|
|
12
|
-
* miniapp `appId
|
|
13
|
-
* path's `appId`)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The fix derives a STABLE per-project partition from the project identity:
|
|
12
|
+
* the miniapp `appId` (which flows in via the simulator URL `?appId=` and the
|
|
13
|
+
* spawn path's `appId`) PLUS the project path. appId alone is insufficient —
|
|
14
|
+
* it comes from the project manifest, so two DIFFERENT project paths can declare
|
|
15
|
+
* the SAME appId and would otherwise share a partition; folding the path in
|
|
16
|
+
* isolates them. Same project (same appId + path) → same partition (storage
|
|
17
|
+
* survives a relaunch); different projects → different partitions (no cross-
|
|
18
|
+
* contamination). The shape mirrors the IDE state of the art (WeChat's static
|
|
19
|
+
* partition, ByteDance's `persist:miniapp-<id>` dynamic partition).
|
|
17
20
|
*
|
|
18
21
|
* The protocol handlers (`difile://`, `dmb-resource`) and webRequest policies
|
|
19
22
|
* (referer / CORS) that the simulator runtime needs were installed once on the
|
|
@@ -25,6 +28,13 @@
|
|
|
25
28
|
* Partition CLEANUP (reclaiming on-disk `persist:` data) is intentionally NOT
|
|
26
29
|
* done here — leaving the data on disk is the whole point of a `persist:`
|
|
27
30
|
* partition (cache/storage survives a relaunch). That is a separate concern.
|
|
31
|
+
*
|
|
32
|
+
* MIGRATION (intentional one-time reset): folding the project path into the key
|
|
33
|
+
* means a project that previously ran on the appId-only `persist:miniapp-<appId>`
|
|
34
|
+
* now runs on `persist:miniapp-<appId>-p<hash>` and starts with empty
|
|
35
|
+
* cookies/localStorage/cache. Old data is NOT migrated — a best-effort copy
|
|
36
|
+
* could carry already-cross-contaminated state into the newly isolated space,
|
|
37
|
+
* which defeats the isolation. The reset is accepted as a one-time cost.
|
|
28
38
|
*/
|
|
29
39
|
import * as electron from 'electron';
|
|
30
40
|
/** The legacy single-session partition. Still used by the pre-warm pool (which
|
|
@@ -43,15 +53,26 @@ const PARTITION_PREFIX = 'persist:miniapp-';
|
|
|
43
53
|
* same key (so storage survives a relaunch); different `appId`s yield different
|
|
44
54
|
* keys.
|
|
45
55
|
*/
|
|
46
|
-
export function miniappPartitionKey(appId) {
|
|
56
|
+
export function miniappPartitionKey(appId, projectPath) {
|
|
47
57
|
const safe = appId.replace(/[^A-Za-z0-9_-]/g, '');
|
|
48
58
|
// Folding may have dropped distinguishing characters; if the input was not
|
|
49
59
|
// already fully safe, append a deterministic hash of the RAW appId so two
|
|
50
60
|
// distinct appIds can never alias onto the same key.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
const base = safe === appId && safe.length > 0
|
|
62
|
+
? safe
|
|
63
|
+
: safe.length > 0
|
|
64
|
+
? `${safe}-${djb2(appId).toString(36)}`
|
|
65
|
+
: djb2(appId).toString(36);
|
|
66
|
+
// `appId` comes from the project manifest, not the path — two DIFFERENT
|
|
67
|
+
// project paths can declare the SAME appId. Fold the project path into the key
|
|
68
|
+
// so those projects get isolated partitions (no storage/cache cross-talk),
|
|
69
|
+
// while the same path always yields the same suffix (a relaunch reuses its
|
|
70
|
+
// storage). Path characters aren't partition-safe, so always hash. Omitting
|
|
71
|
+
// projectPath preserves the legacy appId-only key (pre-warm pool / callers
|
|
72
|
+
// that have no project identity).
|
|
73
|
+
if (!projectPath)
|
|
74
|
+
return base;
|
|
75
|
+
return `${base}-p${djb2(projectPath).toString(36)}`;
|
|
55
76
|
}
|
|
56
77
|
/**
|
|
57
78
|
* The Electron session partition for a project. Returns the SHARED partition
|
|
@@ -59,10 +80,10 @@ export function miniappPartitionKey(appId) {
|
|
|
59
80
|
* matching the legacy behavior) and a `persist:miniapp-<key>` partition
|
|
60
81
|
* otherwise.
|
|
61
82
|
*/
|
|
62
|
-
export function miniappPartition(appId) {
|
|
83
|
+
export function miniappPartition(appId, projectPath) {
|
|
63
84
|
if (!appId)
|
|
64
85
|
return SHARED_MINIAPP_PARTITION;
|
|
65
|
-
return `${PARTITION_PREFIX}${miniappPartitionKey(appId)}`;
|
|
86
|
+
return `${PARTITION_PREFIX}${miniappPartitionKey(appId, projectPath)}`;
|
|
66
87
|
}
|
|
67
88
|
/** Small, stable string hash (djb2). Not cryptographic — only needs to be
|
|
68
89
|
* deterministic and collision-resistant enough to disambiguate appIds. */
|