@dimina-kit/devtools 0.3.2-dev.20260525152421 → 0.3.2-dev.20260610082053
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/README.md +43 -40
- package/dist/main/api.d.ts +0 -1
- package/dist/main/api.js +5 -1
- package/dist/main/app/app.d.ts +15 -5
- package/dist/main/app/app.js +247 -110
- package/dist/main/app/bootstrap.d.ts +16 -0
- package/dist/main/app/bootstrap.js +20 -0
- package/dist/main/app/launch.d.ts +8 -4
- package/dist/main/app/launch.js +9 -4
- package/dist/main/index.bundle.js +6607 -2102
- package/dist/main/index.js +9 -1
- package/dist/main/ipc/app.d.ts +1 -1
- package/dist/main/ipc/bridge-router.d.ts +61 -0
- package/dist/main/ipc/bridge-router.js +1300 -0
- package/dist/main/ipc/panels.d.ts +1 -1
- package/dist/main/ipc/panels.js +6 -2
- package/dist/main/ipc/popover.d.ts +1 -1
- package/dist/main/ipc/project-fs.d.ts +57 -0
- package/dist/main/ipc/project-fs.js +448 -0
- package/dist/main/ipc/projects.d.ts +1 -1
- package/dist/main/ipc/session.d.ts +1 -1
- package/dist/main/ipc/settings.d.ts +1 -1
- package/dist/main/ipc/simulator-module.d.ts +6 -0
- package/dist/main/ipc/simulator-module.js +11 -1
- package/dist/main/ipc/simulator.d.ts +2 -2
- package/dist/main/ipc/simulator.js +49 -5
- package/dist/main/ipc/toolbar.d.ts +1 -1
- package/dist/main/ipc/views.d.ts +19 -0
- package/dist/main/ipc/views.js +65 -0
- package/dist/main/runtime/devtools-backend.d.ts +17 -0
- package/dist/main/runtime/devtools-backend.js +49 -0
- package/dist/main/runtime/miniapp-runtime.d.ts +39 -0
- package/dist/main/runtime/miniapp-runtime.js +17 -0
- package/dist/main/services/automation/exec.d.ts +27 -2
- package/dist/main/services/automation/exec.js +37 -3
- package/dist/main/services/automation/handlers/app.js +127 -11
- package/dist/main/services/automation/handlers/page.js +36 -19
- package/dist/main/services/automation/index.js +40 -13
- package/dist/main/services/automation/wait-active-page.d.ts +56 -0
- package/dist/main/services/automation/wait-active-page.js +71 -0
- package/dist/main/services/console-forward/index.d.ts +36 -0
- package/dist/main/services/console-forward/index.js +81 -0
- package/dist/main/services/dimina-resource-server.d.ts +7 -0
- package/dist/main/services/dimina-resource-server.js +99 -0
- package/dist/main/services/elements-forward/index.d.ts +66 -0
- package/dist/main/services/elements-forward/index.js +583 -0
- package/dist/main/services/layout/index.d.ts +23 -1
- package/dist/main/services/layout/index.js +28 -3
- package/dist/main/services/mcp/index.d.ts +1 -0
- package/dist/main/services/mcp/index.js +3 -0
- package/dist/main/services/mcp/server.d.ts +1 -1
- package/dist/main/services/mcp/server.js +1 -1
- package/dist/main/services/mcp/target-manager.d.ts +35 -0
- package/dist/main/services/mcp/target-manager.js +63 -1
- package/dist/main/services/mcp/tools/context-tools.js +15 -1
- package/dist/main/services/module.d.ts +1 -1
- package/dist/main/services/network-forward/index.d.ts +190 -0
- package/dist/main/services/network-forward/index.js +765 -0
- package/dist/main/services/notifications/renderer-notifier.d.ts +11 -0
- package/dist/main/services/notifications/renderer-notifier.js +7 -1
- package/dist/main/services/render-inspect/index.d.ts +41 -0
- package/dist/main/services/render-inspect/index.js +88 -0
- package/dist/main/services/safe-area/index.d.ts +17 -0
- package/dist/main/services/safe-area/index.js +59 -0
- package/dist/main/services/service-host-pool/pool.d.ts +197 -0
- package/dist/main/services/service-host-pool/pool.js +364 -0
- package/dist/main/services/simulator/custom-apis.d.ts +34 -0
- package/dist/main/services/simulator/custom-apis.js +22 -0
- package/dist/main/services/simulator-appdata/index.d.ts +35 -0
- package/dist/main/services/simulator-appdata/index.js +71 -0
- package/dist/main/services/simulator-current-page/index.d.ts +24 -0
- package/dist/main/services/simulator-current-page/index.js +19 -0
- package/dist/main/services/simulator-storage/index.d.ts +37 -2
- package/dist/main/services/simulator-storage/index.js +209 -32
- package/dist/main/services/simulator-storage/service-storage-ops.d.ts +40 -0
- package/dist/main/services/simulator-storage/service-storage-ops.js +67 -0
- package/dist/main/services/simulator-temp-files/fs-channels.js +1 -1
- package/dist/main/services/simulator-temp-files/index.d.ts +1 -1
- package/dist/main/services/simulator-temp-files/index.js +1 -1
- package/dist/main/services/simulator-temp-files/request-handler.js +1 -1
- package/dist/main/services/simulator-wxml/index.d.ts +29 -0
- package/dist/main/services/simulator-wxml/index.js +28 -0
- package/dist/main/services/update/update-manager.d.ts +10 -0
- package/dist/main/services/update/update-manager.js +16 -3
- package/dist/main/services/views/devtools-tabs.d.ts +47 -0
- package/dist/main/services/views/devtools-tabs.js +95 -0
- package/dist/main/services/views/view-manager.d.ts +142 -4
- package/dist/main/services/views/view-manager.js +909 -17
- package/dist/main/services/workbench-context.d.ts +66 -1
- package/dist/main/services/workbench-context.js +6 -1
- package/dist/main/services/workspace/workspace-service.d.ts +8 -0
- package/dist/main/services/workspace/workspace-service.js +12 -0
- package/dist/main/utils/ipc-registry.d.ts +11 -2
- package/dist/main/utils/ipc-registry.js +63 -3
- package/dist/main/utils/paths.d.ts +15 -0
- package/dist/main/utils/paths.js +19 -0
- package/dist/main/utils/sender-policy.d.ts +9 -3
- package/dist/main/utils/sender-policy.js +18 -4
- package/dist/main/utils/theme.d.ts +1 -1
- package/dist/main/utils/theme.js +1 -1
- package/dist/main/windows/main-window/create.d.ts +0 -6
- package/dist/main/windows/main-window/create.js +11 -59
- package/dist/main/windows/main-window/events.d.ts +1 -1
- package/dist/main/windows/main-window/events.js +1 -1
- package/dist/main/windows/service-host-window/create.d.ts +52 -0
- package/dist/main/windows/service-host-window/create.js +112 -0
- package/dist/native-host/common/common.js +368 -0
- package/dist/native-host/container/pageFrame.css +1 -0
- package/dist/native-host/render/render.js +9334 -0
- package/dist/native-host/service/service.js +6 -0
- package/dist/preload/index.d.ts +2 -0
- package/dist/preload/index.js +6 -0
- package/dist/preload/instrumentation/app-data.d.ts +18 -20
- package/dist/preload/instrumentation/app-data.js +23 -160
- package/dist/preload/instrumentation/wxml-extract.d.ts +1 -1
- package/dist/preload/instrumentation/wxml-extract.js +1 -1
- package/dist/preload/runtime/bridge.d.ts +3 -8
- package/dist/preload/runtime/bridge.js +2 -32
- package/dist/preload/runtime/custom-apis.js +20 -17
- package/dist/preload/runtime/host-toolbar-advertiser.d.ts +27 -0
- package/dist/preload/runtime/host-toolbar-advertiser.js +55 -0
- package/dist/preload/runtime/main-api-runner.d.ts +9 -0
- package/dist/preload/runtime/main-api-runner.js +85 -0
- package/dist/preload/runtime/native-host.d.ts +43 -0
- package/dist/preload/runtime/native-host.js +83 -0
- package/dist/preload/shared/expose.d.ts +10 -5
- package/dist/preload/shared/expose.js +29 -11
- package/dist/preload/shared/sid-registry.d.ts +11 -0
- package/dist/preload/shared/sid-registry.js +38 -0
- package/dist/preload/windows/host-toolbar.cjs +161 -0
- package/dist/preload/windows/host-toolbar.cjs.map +7 -0
- package/dist/preload/windows/host-toolbar.d.ts +2 -0
- package/dist/preload/windows/host-toolbar.js +12 -0
- package/dist/preload/windows/main.cjs +1939 -0
- package/dist/preload/windows/main.cjs.map +4 -4
- package/dist/preload/windows/simulator.cjs +925 -0
- package/dist/preload/windows/simulator.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +243 -573
- package/dist/render-host/pageFrame.html +29 -0
- package/dist/render-host/preload.cjs +154 -0
- package/dist/render-host/render-inspect.js +295 -0
- package/dist/renderer/assets/abap-DiwvWnMr.js +2 -0
- package/dist/renderer/assets/apex-CmtZjKlf.js +2 -0
- package/dist/renderer/assets/azcli-DL2My_i-.js +2 -0
- package/dist/renderer/assets/bat-B-nC98wG.js +2 -0
- package/dist/renderer/assets/bicep-Ju5MwOgh.js +3 -0
- package/dist/renderer/assets/cameligo-8Eu1TyBr.js +2 -0
- package/dist/renderer/assets/chunk-DPI0Y_ll.js +1 -0
- package/dist/renderer/assets/clojure-u-RpMkH3.js +2 -0
- package/dist/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/renderer/assets/coffee-CdA7bbTe.js +2 -0
- package/dist/renderer/assets/constants-CLAQX6VZ.js +2 -0
- package/dist/renderer/assets/cpp-CzNFP8ks.js +2 -0
- package/dist/renderer/assets/csharp-j1LThmcE.js +2 -0
- package/dist/renderer/assets/csp-CLRC61y6.js +2 -0
- package/dist/renderer/assets/css-r6rC_7P2.js +4 -0
- package/dist/renderer/assets/css.worker-DyDu5ynT.js +90 -0
- package/dist/renderer/assets/cssMode-BCBgxrnA.js +2 -0
- package/dist/renderer/assets/cypher-CW08XVUh.js +2 -0
- package/dist/renderer/assets/dart-Cs9aL5T_.js +2 -0
- package/dist/renderer/assets/dockerfile-BWM0M184.js +2 -0
- package/dist/renderer/assets/ecl-MJJuer5P.js +2 -0
- package/dist/renderer/assets/editor-CWI9TnMM.css +1 -0
- package/dist/renderer/assets/editor.api2-BTaYmRq7.js +873 -0
- package/dist/renderer/assets/editor.worker-Dpt_xPrs.js +27 -0
- package/dist/renderer/assets/elixir-D2AIuXqn.js +2 -0
- package/dist/renderer/assets/flow9-B2H24giC.js +2 -0
- package/dist/renderer/assets/freemarker2-DGDGzimW.js +4 -0
- package/dist/renderer/assets/fsharp-CMk2OIJN.js +2 -0
- package/dist/renderer/assets/go-BrMkuJg0.js +2 -0
- package/dist/renderer/assets/graphql-PSR1UKGv.js +2 -0
- package/dist/renderer/assets/handlebars-bOHhw3EG.js +2 -0
- package/dist/renderer/assets/hcl-DAQrbDOW.js +2 -0
- package/dist/renderer/assets/html-BMyuX0_c.js +2 -0
- package/dist/renderer/assets/html.worker-IWNXhnIC.js +503 -0
- package/dist/renderer/assets/htmlMode-DR4kXJ7l.js +2 -0
- package/dist/renderer/assets/index-CcJKzatF.js +50 -0
- package/dist/renderer/assets/ini-0TG5BxW0.js +2 -0
- package/dist/renderer/assets/input-QGx6FFX1.js +2 -0
- package/dist/renderer/assets/ipc-transport-BT4QYQqu.css +1 -0
- package/dist/renderer/assets/{ipc-transport-BzPzxPwf.js → ipc-transport-DvZvwK-Z.js} +5 -5
- package/dist/renderer/assets/java-rgorz17v.js +2 -0
- package/dist/renderer/assets/javascript-Bu-tl1P7.js +2 -0
- package/dist/renderer/assets/json.worker-c4h5s80L.js +59 -0
- package/dist/renderer/assets/jsonMode-MQV_ffLR.js +8 -0
- package/dist/renderer/assets/julia-C8VMdHm8.js +2 -0
- package/dist/renderer/assets/kotlin-AhhC4Qhm.js +2 -0
- package/dist/renderer/assets/less-Cgca25AP.js +3 -0
- package/dist/renderer/assets/lexon-D0GHdBaw.js +2 -0
- package/dist/renderer/assets/liquid-ClkfOvhB.js +2 -0
- package/dist/renderer/assets/lspLanguageFeatures-Bke_4Ie9.js +5 -0
- package/dist/renderer/assets/lua-DmRsNG-P.js +2 -0
- package/dist/renderer/assets/m3-BgL5dNKT.js +2 -0
- package/dist/renderer/assets/markdown-BuJfycGS.js +2 -0
- package/dist/renderer/assets/mdx-RZdUskPu.js +2 -0
- package/dist/renderer/assets/mips-C9m_93PR.js +2 -0
- package/dist/renderer/assets/monaco.contribution-B7rUMaEv.js +3 -0
- package/dist/renderer/assets/msdax-CpFHC9OI.js +2 -0
- package/dist/renderer/assets/mysql-qFvltsqN.js +2 -0
- package/dist/renderer/assets/objective-c-Bnmr858J.js +2 -0
- package/dist/renderer/assets/pascal-WP0_D5AO.js +2 -0
- package/dist/renderer/assets/pascaligo-Blom4Rij.js +2 -0
- package/dist/renderer/assets/perl-B-vk8g64.js +2 -0
- package/dist/renderer/assets/pgsql-Cgvz6v67.js +2 -0
- package/dist/renderer/assets/php-8a3Lrw9m.js +2 -0
- package/dist/renderer/assets/pla-DuFqEZ8V.js +2 -0
- package/dist/renderer/assets/popover-BOH1If1J.js +2 -0
- package/dist/renderer/assets/postiats-DkLtSgkp.js +2 -0
- package/dist/renderer/assets/powerquery-BJ1aNepW.js +2 -0
- package/dist/renderer/assets/powershell-rE98k687.js +2 -0
- package/dist/renderer/assets/protobuf-CUheFacr.js +3 -0
- package/dist/renderer/assets/pug-LDcAMD8w.js +2 -0
- package/dist/renderer/assets/python-Cobrm_-8.js +2 -0
- package/dist/renderer/assets/qsharp-DUKSQoR1.js +2 -0
- package/dist/renderer/assets/r-D-QApv87.js +2 -0
- package/dist/renderer/assets/razor-BEUIQFNJ.js +2 -0
- package/dist/renderer/assets/redis-SXdDyWR9.js +2 -0
- package/dist/renderer/assets/redshift-Y6lsCryn.js +2 -0
- package/dist/renderer/assets/restructuredtext-edObr9a8.js +2 -0
- package/dist/renderer/assets/ruby-CNnUfF-8.js +2 -0
- package/dist/renderer/assets/rust-IHUZWzBr.js +2 -0
- package/dist/renderer/assets/sb-DrUvY44N.js +2 -0
- package/dist/renderer/assets/scala-B4hbXGLM.js +2 -0
- package/dist/renderer/assets/scheme-BGrd12j3.js +2 -0
- package/dist/renderer/assets/scss-x5G1ES4U.js +4 -0
- package/dist/renderer/assets/select-C7JQ8G5N.js +2 -0
- package/dist/renderer/assets/settings-DpAbTJyF.js +2 -0
- package/dist/renderer/assets/{settings-api-BXCw9vOZ.js → settings-api-RD2IZF53.js} +2 -2
- package/dist/renderer/assets/shell-DOehe2Y8.js +2 -0
- package/dist/renderer/assets/solidity-BeRvcwWV.js +2 -0
- package/dist/renderer/assets/sophia-DZbkUNjy.js +2 -0
- package/dist/renderer/assets/sparql-B7_oi5-h.js +2 -0
- package/dist/renderer/assets/sql-CTlsFWVE.js +2 -0
- package/dist/renderer/assets/st-DJVEJdPE.js +2 -0
- package/dist/renderer/assets/swift-CwhT3fYa.js +2 -0
- package/dist/renderer/assets/systemverilog-BQN63pkN.js +2 -0
- package/dist/renderer/assets/tcl-DqwfpskA.js +2 -0
- package/dist/renderer/assets/ts.worker-BwM5Ha3L.js +67720 -0
- package/dist/renderer/assets/tsMode-BHa7dl9v.js +12 -0
- package/dist/renderer/assets/twig-BiyenUgc.js +2 -0
- package/dist/renderer/assets/typescript-15aT0Yj8.js +2 -0
- package/dist/renderer/assets/typespec-CWOJribt.js +2 -0
- package/dist/renderer/assets/vb-Cq5F87m3.js +2 -0
- package/dist/renderer/assets/wgsl-BAvW2lVr.js +299 -0
- package/dist/renderer/assets/workbenchSettings-CNJX4Qna.js +8 -0
- package/dist/renderer/assets/workers-CRBS0W-v.js +2 -0
- package/dist/renderer/assets/xml-bR2Zh-HV.js +2 -0
- package/dist/renderer/assets/yaml-Bdk54Qrb.js +2 -0
- package/dist/renderer/entries/main/index.html +29 -6
- package/dist/renderer/entries/popover/index.html +7 -6
- package/dist/renderer/entries/settings/index.html +6 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -5
- package/dist/service-host/preload.cjs +254 -0
- package/dist/service-host/service.html +10 -0
- package/dist/service-host/sourcemap-rewrite.cjs +58 -0
- package/dist/service-host/sync-api-patch.js +20 -0
- package/dist/service-host/sync-impls/menu-button.js +16 -0
- package/dist/service-host/sync-impls/storage.js +50 -0
- package/dist/service-host/sync-impls/system-info.js +54 -0
- package/dist/shared/appdata-accumulator.d.ts +95 -0
- package/dist/shared/appdata-accumulator.js +213 -0
- package/dist/shared/bridge-channels.d.ts +364 -0
- package/dist/shared/bridge-channels.js +54 -0
- package/dist/shared/constants.d.ts +0 -4
- package/dist/shared/constants.js +0 -4
- package/dist/shared/ipc-channels.d.ts +108 -1
- package/dist/shared/ipc-channels.js +127 -10
- package/dist/shared/ipc-schemas.d.ts +65 -2
- package/dist/shared/ipc-schemas.js +79 -3
- package/dist/shared/open-in-editor.d.ts +71 -0
- package/dist/shared/open-in-editor.js +138 -0
- package/dist/shared/simulator-api-metadata.d.ts +21 -0
- package/dist/shared/simulator-api-metadata.js +23 -0
- package/dist/shared/types.d.ts +2 -2
- package/dist/shared/vpath.d.ts +42 -0
- package/dist/shared/vpath.js +105 -0
- package/dist/simulator/assets/device-shell-DsdcNEkt.css +1 -0
- package/dist/simulator/assets/device-shell-vwYSOVrh.js +2 -0
- package/dist/simulator/assets/jsx-runtime-P6ZegaOf.js +2 -0
- package/dist/simulator/assets/simulator-DddliUL5.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-BDNu5n26.js +2 -0
- package/dist/simulator/device-shell/menu-button-geometry.js +8 -0
- package/dist/simulator/simulator.html +2 -112
- package/package.json +9 -9
- package/dist/main/utils/disposable.d.ts +0 -18
- package/dist/main/utils/disposable.js +0 -67
- package/dist/renderer/assets/constants-DI382mTP.js +0 -2
- package/dist/renderer/assets/index-4lkyF644.js +0 -46
- package/dist/renderer/assets/input-D4byFOLS.js +0 -2
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +0 -1
- package/dist/renderer/assets/popover-cxKrg_os.js +0 -2
- package/dist/renderer/assets/select-BgSkkdHW.js +0 -2
- package/dist/renderer/assets/settings-oD2K1jdT.js +0 -2
- package/dist/renderer/assets/workbenchSettings-CTNBpTft.js +0 -8
- package/dist/simulator/assets/simulator-CVOFTpSj.js +0 -9
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { DisposableRegistry, toDisposable } from '@dimina-kit/electron-deck/main';
|
|
2
|
+
// ── requestId namespacing (pure, testable) ──────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* CDP events whose `params.requestId` we rewrite into the virtual namespace.
|
|
5
|
+
* Anything carrying a requestId must be namespaced consistently — even methods
|
|
6
|
+
* we don't (yet) forward — so the raw→virtual map stays coherent if forwarding
|
|
7
|
+
* is widened later. `requestServedFromCache` and `resourceChangedPriority` are
|
|
8
|
+
* included for that reason (MINOR: rewrite-only today, not forwarded).
|
|
9
|
+
*/
|
|
10
|
+
export const REWRITE_REQUEST_ID_METHODS = new Set([
|
|
11
|
+
'Network.requestWillBeSent',
|
|
12
|
+
'Network.requestWillBeSentExtraInfo',
|
|
13
|
+
'Network.responseReceived',
|
|
14
|
+
'Network.responseReceivedExtraInfo',
|
|
15
|
+
'Network.dataReceived',
|
|
16
|
+
'Network.loadingFinished',
|
|
17
|
+
'Network.loadingFailed',
|
|
18
|
+
'Network.requestServedFromCache',
|
|
19
|
+
'Network.resourceChangedPriority',
|
|
20
|
+
// 二期 (when WebSocket/EventSource forwarding lands): Network.webSocket*,
|
|
21
|
+
// Network.eventSourceMessageReceived — keep ids namespaced once added here.
|
|
22
|
+
]);
|
|
23
|
+
/** The Network.* methods this one-shot pass forwards to the front-end. */
|
|
24
|
+
export const FORWARDED_METHODS = new Set([
|
|
25
|
+
'Network.requestWillBeSent',
|
|
26
|
+
'Network.requestWillBeSentExtraInfo',
|
|
27
|
+
'Network.responseReceived',
|
|
28
|
+
'Network.responseReceivedExtraInfo',
|
|
29
|
+
'Network.loadingFinished',
|
|
30
|
+
'Network.loadingFailed',
|
|
31
|
+
// `dataReceived` deliberately omitted (二期) — see header.
|
|
32
|
+
]);
|
|
33
|
+
/** Methods that mark a request finished, so its id mapping can age out. */
|
|
34
|
+
const TERMINAL_METHODS = new Set([
|
|
35
|
+
'Network.loadingFinished',
|
|
36
|
+
'Network.loadingFailed',
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Bounded, TTL'd raw→virtual requestId map with an active/retired split.
|
|
40
|
+
*
|
|
41
|
+
* An ACTIVE request (seen `requestWillBeSent`/ExtraInfo, not yet terminal) must
|
|
42
|
+
* NEVER lose its mapping — otherwise a later `responseReceived`/`loadingFinished`
|
|
43
|
+
* would mint a fresh virtual id and the front-end would see an orphaned event.
|
|
44
|
+
* So active entries are exempt from both TTL and the LRU cap.
|
|
45
|
+
*
|
|
46
|
+
* Only once a request goes terminal (`loadingFinished`/`loadingFailed`) is it
|
|
47
|
+
* RETIRED: its mapping is kept a while longer so genuinely-late events (an
|
|
48
|
+
* extra-info after completion) still correlate, but it now ages out by TTL and
|
|
49
|
+
* by an LRU cap so a long session never grows unbounded. Eviction only ever
|
|
50
|
+
* touches the retired pool.
|
|
51
|
+
*/
|
|
52
|
+
export class RequestIdNamespace {
|
|
53
|
+
epoch;
|
|
54
|
+
ttlMs;
|
|
55
|
+
max;
|
|
56
|
+
now;
|
|
57
|
+
map = new Map();
|
|
58
|
+
seq = 0;
|
|
59
|
+
constructor(epoch, ttlMs = 5 * 60_000, max = 1000, now = () => Date.now()) {
|
|
60
|
+
this.epoch = epoch;
|
|
61
|
+
this.ttlMs = ttlMs;
|
|
62
|
+
this.max = max;
|
|
63
|
+
this.now = now;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve (lazily creating) the virtual id for a raw id. Lazy creation lets an
|
|
67
|
+
* ExtraInfo event that arrives BEFORE its `requestWillBeSent` still get a
|
|
68
|
+
* stable id that the later events reuse — a redirect chain reuses the same raw
|
|
69
|
+
* id and therefore the same virtual id (we never mint a new one per redirect).
|
|
70
|
+
*
|
|
71
|
+
* A freshly-resolved (or any non-retired) entry is ACTIVE and thus immune to
|
|
72
|
+
* eviction. We only TTL-expire entries that have already been retired; an
|
|
73
|
+
* active entry never "expires" no matter how long it stays in flight.
|
|
74
|
+
*/
|
|
75
|
+
resolve(rawId) {
|
|
76
|
+
const t = this.now();
|
|
77
|
+
const existing = this.map.get(rawId);
|
|
78
|
+
if (existing && (existing.active || existing.expires > t)) {
|
|
79
|
+
// Refresh recency (LRU) on touch; refresh TTL only for retired entries
|
|
80
|
+
// (active entries don't use TTL at all).
|
|
81
|
+
if (!existing.active)
|
|
82
|
+
existing.expires = t + this.ttlMs;
|
|
83
|
+
this.map.delete(rawId);
|
|
84
|
+
this.map.set(rawId, existing);
|
|
85
|
+
return existing.virtual;
|
|
86
|
+
}
|
|
87
|
+
const virtual = `dimina:sim:${this.epoch}:${this.seq++}:${rawId}`;
|
|
88
|
+
this.map.set(rawId, { virtual, expires: t + this.ttlMs, active: true });
|
|
89
|
+
this.evict(t);
|
|
90
|
+
return virtual;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Mark a request terminal: it leaves the active set and enters the retired
|
|
94
|
+
* pool with a fresh TTL, where it becomes eligible for TTL/LRU eviction.
|
|
95
|
+
*/
|
|
96
|
+
retire(rawId) {
|
|
97
|
+
const e = this.map.get(rawId);
|
|
98
|
+
if (e) {
|
|
99
|
+
e.active = false;
|
|
100
|
+
e.expires = this.now() + this.ttlMs;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
evict(t) {
|
|
104
|
+
// Drop expired retired entries first (active entries never expire).
|
|
105
|
+
for (const [k, v] of this.map) {
|
|
106
|
+
if (!v.active && v.expires <= t)
|
|
107
|
+
this.map.delete(k);
|
|
108
|
+
}
|
|
109
|
+
// Then LRU-trim the RETIRED pool to the cap. Active entries are exempt:
|
|
110
|
+
// we walk in insertion/refresh order and skip any still-active entry, so an
|
|
111
|
+
// in-flight request is never evicted even past the cap.
|
|
112
|
+
if (this.map.size <= this.max)
|
|
113
|
+
return;
|
|
114
|
+
for (const [k, v] of this.map) {
|
|
115
|
+
if (this.map.size <= this.max)
|
|
116
|
+
break;
|
|
117
|
+
if (!v.active)
|
|
118
|
+
this.map.delete(k);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Total entries (active + retired). */
|
|
122
|
+
get size() {
|
|
123
|
+
return this.map.size;
|
|
124
|
+
}
|
|
125
|
+
/** Entries still in flight (resolved, not yet retired). */
|
|
126
|
+
get activeSize() {
|
|
127
|
+
let n = 0;
|
|
128
|
+
for (const v of this.map.values())
|
|
129
|
+
if (v.active)
|
|
130
|
+
n++;
|
|
131
|
+
return n;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Rewrite a single CDP message's `requestId` into the namespace, returning a new
|
|
136
|
+
* `{ method, params }` (never mutating the input). Returns the message unchanged
|
|
137
|
+
* when the method carries no requestId we track or params is malformed. Pure
|
|
138
|
+
* aside from the namespace map it threads through.
|
|
139
|
+
*/
|
|
140
|
+
export function rewriteRequestId(method, params, ns) {
|
|
141
|
+
if (!REWRITE_REQUEST_ID_METHODS.has(method))
|
|
142
|
+
return { method, params };
|
|
143
|
+
const p = params;
|
|
144
|
+
if (!p || typeof p.requestId !== 'string')
|
|
145
|
+
return { method, params };
|
|
146
|
+
const virtual = ns.resolve(p.requestId);
|
|
147
|
+
if (TERMINAL_METHODS.has(method))
|
|
148
|
+
ns.retire(p.requestId);
|
|
149
|
+
return { method, params: { ...p, requestId: virtual } };
|
|
150
|
+
}
|
|
151
|
+
// ── DevTools front-end injection (the primary sink) ─────────────────────────
|
|
152
|
+
/** Probe the front-end realm exposes `DevToolsAPI.dispatchMessage`. */
|
|
153
|
+
const PROBE_DEVTOOLS_API = `(window.DevToolsAPI && typeof window.DevToolsAPI.dispatchMessage === 'function')`;
|
|
154
|
+
/**
|
|
155
|
+
* Build the `executeJavaScript` source that dispatches a BATCH of raw CDP
|
|
156
|
+
* messages into the DevTools front-end. Each message is carried as a JSON
|
|
157
|
+
* literal (data, never interpolated code — same discipline as console-forward)
|
|
158
|
+
* and dispatched via `DevToolsAPI.dispatchMessage`. Returns false out of the IIFE
|
|
159
|
+
* when the API isn't there yet, so the main side knows to retry / fall back.
|
|
160
|
+
*/
|
|
161
|
+
function buildDispatchScript(messages) {
|
|
162
|
+
const arr = JSON.stringify(messages);
|
|
163
|
+
return `(()=>{try{`
|
|
164
|
+
+ `if(!${PROBE_DEVTOOLS_API})return false;`
|
|
165
|
+
+ `const ms=JSON.parse(${JSON.stringify(arr)});`
|
|
166
|
+
+ `for(const m of ms){try{window.DevToolsAPI.dispatchMessage(m)}catch(_){}}`
|
|
167
|
+
+ `return true;`
|
|
168
|
+
+ `}catch(_){return false}})()`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build the `executeJavaScript` source for a chunked dispatch of one large CDP
|
|
172
|
+
* message — DevTools' own transport caps a single `dispatchMessage` payload, so
|
|
173
|
+
* the front-end exposes `dispatchMessageChunk(messageChunk, messageSize)` where
|
|
174
|
+
* the FIRST chunk carries the total size and EVERY SUBSEQUENT chunk is called
|
|
175
|
+
* with the chunk ONLY (second arg omitted). This matches Chromium's
|
|
176
|
+
* `devtools_ui_bindings.cc` DispatchProtocolMessage, whose front-end treats a
|
|
177
|
+
* call with a second argument as the start of a new message and a call without
|
|
178
|
+
* one as a continuation. Passing 0 (the previous behaviour) risked the
|
|
179
|
+
* front-end mis-reading a continuation as a fresh 0-size message. Returns false
|
|
180
|
+
* when the API isn't available.
|
|
181
|
+
*/
|
|
182
|
+
function buildChunkedDispatchScript(chunks, totalSize) {
|
|
183
|
+
const arr = JSON.stringify(chunks);
|
|
184
|
+
return `(()=>{try{`
|
|
185
|
+
+ `if(!(window.DevToolsAPI&&typeof window.DevToolsAPI.dispatchMessageChunk==='function'))return false;`
|
|
186
|
+
+ `const cs=JSON.parse(${JSON.stringify(arr)});`
|
|
187
|
+
+ `for(let i=0;i<cs.length;i++){`
|
|
188
|
+
+ `try{`
|
|
189
|
+
// First chunk: (chunk, totalSize). Subsequent chunks: (chunk) — second arg
|
|
190
|
+
// omitted, NOT 0, per Chromium's continuation contract.
|
|
191
|
+
+ `if(i===0){window.DevToolsAPI.dispatchMessageChunk(cs[i], ${totalSize})}`
|
|
192
|
+
+ `else{window.DevToolsAPI.dispatchMessageChunk(cs[i])}`
|
|
193
|
+
+ `}catch(_){}`
|
|
194
|
+
+ `}`
|
|
195
|
+
+ `return true;`
|
|
196
|
+
+ `}catch(_){return false}})()`;
|
|
197
|
+
}
|
|
198
|
+
/** A single dispatch payload may not exceed this many UTF-16 chars; chunk above. */
|
|
199
|
+
const MAX_SINGLE_DISPATCH_CHARS = 1_000_000;
|
|
200
|
+
const CHUNK_CHARS = 256 * 1024;
|
|
201
|
+
/**
|
|
202
|
+
* Upper bound on the COMBINED size (in UTF-16 chars) of the messages packed into
|
|
203
|
+
* one `executeJavaScript` batch. 2000 small messages otherwise stitch into a
|
|
204
|
+
* single script that can blow past the IPC / script-size limit and reject the
|
|
205
|
+
* whole batch. We pack greedily up to this many chars, then flush and start a
|
|
206
|
+
* new batch, so each `executeJavaScript` stays well-sized.
|
|
207
|
+
*/
|
|
208
|
+
const MAX_BATCH_CHARS = 512 * 1024;
|
|
209
|
+
// ── console fallback sink ───────────────────────────────────────────────────
|
|
210
|
+
/**
|
|
211
|
+
* Build the `executeJavaScript` source that logs one request line into the
|
|
212
|
+
* service host. Carried as a JSON literal and re-parsed service-side — no
|
|
213
|
+
* captured value is ever interpolated into executable JS (data-not-code).
|
|
214
|
+
*/
|
|
215
|
+
function buildForwardScript(record) {
|
|
216
|
+
const json = JSON.stringify(record);
|
|
217
|
+
return `(()=>{try{const r=JSON.parse(${JSON.stringify(json)});`
|
|
218
|
+
+ `const tag='[网络]['+r.source+']';`
|
|
219
|
+
+ `const head=r.method+' '+(r.status||'-')+' '+r.url;`
|
|
220
|
+
+ `if(r.errorText){console.warn(tag,head,r.errorText)}`
|
|
221
|
+
+ `else if(r.status>=400){console.warn(tag,head)}`
|
|
222
|
+
+ `else{console.log(tag,head)}`
|
|
223
|
+
+ `}catch(_){}})()`;
|
|
224
|
+
}
|
|
225
|
+
/** Cap so a long session can't grow the fallback pending map unboundedly. */
|
|
226
|
+
const MAX_PENDING = 1000;
|
|
227
|
+
/** Cap on the native dispatch queue (chars-agnostic count) while no/ready host. */
|
|
228
|
+
const MAX_DISPATCH_QUEUE = 2000;
|
|
229
|
+
/**
|
|
230
|
+
* How long we wait for `window.DevToolsAPI.dispatchMessage` to answer ready once
|
|
231
|
+
* a host wc IS set, before giving up on the native path for that host and
|
|
232
|
+
* degrading to the console sink. Prevents the infinite-requeue-never-fallback
|
|
233
|
+
* loop when a host exists but its front-end never finishes booting.
|
|
234
|
+
*/
|
|
235
|
+
const DEVTOOLS_READY_TIMEOUT_MS = 5_000;
|
|
236
|
+
/** Poll interval while probing for the front-end API to become ready. */
|
|
237
|
+
const READY_RETRY_MS = 100;
|
|
238
|
+
export function createNetworkForwarder(bridge) {
|
|
239
|
+
const registry = new DisposableRegistry();
|
|
240
|
+
// The simulator WCV we currently have a debugger session on, and the
|
|
241
|
+
// per-attach teardown (debugger listeners + detach). Null when not attached.
|
|
242
|
+
let simWc = null;
|
|
243
|
+
let attachDisposables = null;
|
|
244
|
+
// The DevTools front-end host wc (primary sink), set by the ViewManager.
|
|
245
|
+
let devtoolsWc = null;
|
|
246
|
+
// Teardown for the wc 'destroyed' watcher on the current host (clears the host
|
|
247
|
+
// here, in this file — view-manager is owned by another change and untouched).
|
|
248
|
+
let devtoolsHostDisposable = null;
|
|
249
|
+
// ── Native-sink state machine (MAJOR 1) ───────────────────────────────────
|
|
250
|
+
let sink = 'idle';
|
|
251
|
+
// Buffered completed-request records while 'probing' — flushed to console if we
|
|
252
|
+
// degrade, dropped if we go ready (so a request shows in exactly one sink).
|
|
253
|
+
let probeConsoleBuffer = [];
|
|
254
|
+
let readyTimeoutTimer = null;
|
|
255
|
+
// ── Batched native dispatch into the DevTools front-end ───────────────────
|
|
256
|
+
// Queued raw CDP messages (already namespaced + JSON-stringified) awaiting a
|
|
257
|
+
// microtask flush — coalescing many events into one executeJavaScript avoids
|
|
258
|
+
// high-frequency IPC.
|
|
259
|
+
let dispatchQueue = [];
|
|
260
|
+
let flushScheduled = false;
|
|
261
|
+
let readyRetryTimer = null;
|
|
262
|
+
function resolveDevtoolsWc() {
|
|
263
|
+
const wc = devtoolsWc ?? bridge.getDevtoolsWc?.() ?? null;
|
|
264
|
+
return wc && !wc.isDestroyed() ? wc : null;
|
|
265
|
+
}
|
|
266
|
+
/** Enter the 'probing' state and arm the ready-timeout (idempotent). */
|
|
267
|
+
function beginProbing() {
|
|
268
|
+
if (sink === 'probing')
|
|
269
|
+
return;
|
|
270
|
+
sink = 'probing';
|
|
271
|
+
if (readyTimeoutTimer)
|
|
272
|
+
clearTimeout(readyTimeoutTimer);
|
|
273
|
+
readyTimeoutTimer = setTimeout(() => {
|
|
274
|
+
readyTimeoutTimer = null;
|
|
275
|
+
// Still not ready after the grace period → abandon native for this host.
|
|
276
|
+
if (sink === 'probing')
|
|
277
|
+
degradeToConsole();
|
|
278
|
+
}, DEVTOOLS_READY_TIMEOUT_MS);
|
|
279
|
+
}
|
|
280
|
+
/** Native path confirmed live: console buffer is moot, drop it. */
|
|
281
|
+
function markReady() {
|
|
282
|
+
sink = 'ready';
|
|
283
|
+
if (readyTimeoutTimer) {
|
|
284
|
+
clearTimeout(readyTimeoutTimer);
|
|
285
|
+
readyTimeoutTimer = null;
|
|
286
|
+
}
|
|
287
|
+
if (readyRetryTimer) {
|
|
288
|
+
clearTimeout(readyRetryTimer);
|
|
289
|
+
readyRetryTimer = null;
|
|
290
|
+
}
|
|
291
|
+
// Native rendered these requests; their buffered console copies would dup.
|
|
292
|
+
probeConsoleBuffer = [];
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Give up on the native path for the current host: drop the native queue so it
|
|
296
|
+
* can't later double-render, mark 'degraded' so the hot path stops retrying,
|
|
297
|
+
* and flush the buffered completed-request records to the console sink.
|
|
298
|
+
*/
|
|
299
|
+
function degradeToConsole() {
|
|
300
|
+
sink = 'degraded';
|
|
301
|
+
dispatchQueue = [];
|
|
302
|
+
if (readyTimeoutTimer) {
|
|
303
|
+
clearTimeout(readyTimeoutTimer);
|
|
304
|
+
readyTimeoutTimer = null;
|
|
305
|
+
}
|
|
306
|
+
if (readyRetryTimer) {
|
|
307
|
+
clearTimeout(readyRetryTimer);
|
|
308
|
+
readyRetryTimer = null;
|
|
309
|
+
}
|
|
310
|
+
const buffered = probeConsoleBuffer;
|
|
311
|
+
probeConsoleBuffer = [];
|
|
312
|
+
for (const r of buffered)
|
|
313
|
+
forwardToConsole(r);
|
|
314
|
+
}
|
|
315
|
+
function scheduleFlush() {
|
|
316
|
+
if (flushScheduled)
|
|
317
|
+
return;
|
|
318
|
+
flushScheduled = true;
|
|
319
|
+
queueMicrotask(flushDispatch);
|
|
320
|
+
}
|
|
321
|
+
/** Trim the native queue to its cap, preferring to keep request-opening events.
|
|
322
|
+
* Active requests' first events (requestWillBeSent / ...ExtraInfo) are retained
|
|
323
|
+
* so later responseReceived/loadingFinished never become orphans in the panel;
|
|
324
|
+
* we drop the oldest NON-opening (low-value / completion) events first. (MAJOR 2) */
|
|
325
|
+
function trimQueue() {
|
|
326
|
+
if (dispatchQueue.length <= MAX_DISPATCH_QUEUE)
|
|
327
|
+
return;
|
|
328
|
+
const isOpener = (json) => json.includes('"Network.requestWillBeSent"')
|
|
329
|
+
|| json.includes('"Network.requestWillBeSentExtraInfo"');
|
|
330
|
+
// First pass: drop oldest non-opener events.
|
|
331
|
+
const kept = [];
|
|
332
|
+
let over = dispatchQueue.length - MAX_DISPATCH_QUEUE;
|
|
333
|
+
for (const json of dispatchQueue) {
|
|
334
|
+
if (over > 0 && !isOpener(json)) {
|
|
335
|
+
over--;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
kept.push(json);
|
|
339
|
+
}
|
|
340
|
+
// If openers alone still exceed the cap, fall back to dropping oldest openers.
|
|
341
|
+
if (kept.length > MAX_DISPATCH_QUEUE) {
|
|
342
|
+
dispatchQueue = kept.slice(kept.length - MAX_DISPATCH_QUEUE);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
dispatchQueue = kept;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function flushDispatch() {
|
|
349
|
+
flushScheduled = false;
|
|
350
|
+
if (dispatchQueue.length === 0)
|
|
351
|
+
return;
|
|
352
|
+
if (sink === 'degraded') {
|
|
353
|
+
dispatchQueue = [];
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const wc = resolveDevtoolsWc();
|
|
357
|
+
if (!wc) {
|
|
358
|
+
// Host went away mid-flight. Keep the queue bounded (MAJOR 2: cap applies on
|
|
359
|
+
// EVERY path, not just no-host) and wait — setDevtoolsHost re-arms probing.
|
|
360
|
+
trimQueue();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (sink === 'idle')
|
|
364
|
+
beginProbing();
|
|
365
|
+
// Pack greedily up to MAX_BATCH_CHARS so one executeJavaScript stays sized
|
|
366
|
+
// (MAJOR 5); oversized single messages go via the chunked transport.
|
|
367
|
+
const batch = [];
|
|
368
|
+
let batchChars = 0;
|
|
369
|
+
let i = 0;
|
|
370
|
+
for (; i < dispatchQueue.length; i++) {
|
|
371
|
+
const msg = dispatchQueue[i];
|
|
372
|
+
if (msg.length > MAX_SINGLE_DISPATCH_CHARS) {
|
|
373
|
+
// Flush whatever's accumulated first to preserve ordering, then chunk.
|
|
374
|
+
if (batch.length > 0)
|
|
375
|
+
break;
|
|
376
|
+
dispatchChunked(wc, msg);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (batch.length > 0 && batchChars + msg.length > MAX_BATCH_CHARS)
|
|
380
|
+
break;
|
|
381
|
+
batch.push(msg);
|
|
382
|
+
batchChars += msg.length;
|
|
383
|
+
}
|
|
384
|
+
// Everything up to i was either batched or chunk-dispatched; keep the rest.
|
|
385
|
+
const remaining = dispatchQueue.slice(i);
|
|
386
|
+
if (batch.length === 0) {
|
|
387
|
+
// Only chunked messages were processed this turn; continue with the rest.
|
|
388
|
+
dispatchQueue = remaining;
|
|
389
|
+
if (dispatchQueue.length > 0)
|
|
390
|
+
scheduleFlush();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
let script;
|
|
394
|
+
try {
|
|
395
|
+
script = buildDispatchScript(batch);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
dispatchQueue = remaining;
|
|
399
|
+
if (dispatchQueue.length > 0)
|
|
400
|
+
scheduleFlush();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
// Hold the rest of the queue (un-flushed) until this batch resolves so a
|
|
404
|
+
// not-ready answer can re-queue the in-flight batch ahead of it in order.
|
|
405
|
+
dispatchQueue = remaining;
|
|
406
|
+
if (remaining.length > 0)
|
|
407
|
+
scheduleFlush();
|
|
408
|
+
wc.executeJavaScript(script, true).then((ok) => {
|
|
409
|
+
if (ok === true) {
|
|
410
|
+
if (sink !== 'ready')
|
|
411
|
+
markReady();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// API not present yet — front-end still booting. Re-queue this batch ahead
|
|
415
|
+
// of any newer events and poll until ready (or the timeout degrades us).
|
|
416
|
+
if (sink === 'degraded')
|
|
417
|
+
return;
|
|
418
|
+
dispatchQueue = batch.concat(dispatchQueue);
|
|
419
|
+
trimQueue();
|
|
420
|
+
scheduleReadyRetry();
|
|
421
|
+
}).catch(() => {
|
|
422
|
+
// wc navigated / torn down mid-call, OR the script overflowed IPC. Re-queue
|
|
423
|
+
// (bounded) and let the next flush re-resolve the host. Best-effort; the
|
|
424
|
+
// ready-timeout still governs giving up. (MAJOR 5: backoff via retry timer.)
|
|
425
|
+
if (sink === 'degraded')
|
|
426
|
+
return;
|
|
427
|
+
dispatchQueue = batch.concat(dispatchQueue);
|
|
428
|
+
trimQueue();
|
|
429
|
+
scheduleReadyRetry();
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function dispatchChunked(wc, msg) {
|
|
433
|
+
const totalSize = msg.length;
|
|
434
|
+
const chunks = [];
|
|
435
|
+
for (let i = 0; i < msg.length; i += CHUNK_CHARS) {
|
|
436
|
+
chunks.push(msg.slice(i, i + CHUNK_CHARS));
|
|
437
|
+
}
|
|
438
|
+
let script;
|
|
439
|
+
try {
|
|
440
|
+
script = buildChunkedDispatchScript(chunks, totalSize);
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
wc.executeJavaScript(script, true).catch(() => { });
|
|
446
|
+
}
|
|
447
|
+
function scheduleReadyRetry() {
|
|
448
|
+
if (readyRetryTimer || sink === 'ready' || sink === 'degraded')
|
|
449
|
+
return;
|
|
450
|
+
readyRetryTimer = setTimeout(() => {
|
|
451
|
+
readyRetryTimer = null;
|
|
452
|
+
if (sink === 'degraded')
|
|
453
|
+
return;
|
|
454
|
+
if (dispatchQueue.length > 0)
|
|
455
|
+
scheduleFlush();
|
|
456
|
+
}, READY_RETRY_MS);
|
|
457
|
+
}
|
|
458
|
+
/** Queue one raw (already-namespaced) CDP message for native dispatch. */
|
|
459
|
+
function enqueueNative(method, params) {
|
|
460
|
+
// The native queue only accumulates when a host EXISTS (probing/ready). In
|
|
461
|
+
// 'idle' (no host) or 'degraded' the console is the sole sink for these
|
|
462
|
+
// requests — queueing them would let them double-render if a host later
|
|
463
|
+
// arrives / swaps in. resolveDevtoolsWc() guards the idle case where a host
|
|
464
|
+
// is set on the bridge but applyDevtoolsHost hasn't run yet.
|
|
465
|
+
if (sink === 'degraded')
|
|
466
|
+
return;
|
|
467
|
+
if (sink === 'idle' && !resolveDevtoolsWc())
|
|
468
|
+
return;
|
|
469
|
+
let json;
|
|
470
|
+
try {
|
|
471
|
+
json = JSON.stringify({ method, params });
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
// A value CDP serialized but we can't re-serialize — drop, never throw.
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
dispatchQueue.push(json);
|
|
478
|
+
trimQueue();
|
|
479
|
+
scheduleFlush();
|
|
480
|
+
}
|
|
481
|
+
// ── console fallback ──────────────────────────────────────────────────────
|
|
482
|
+
/** Re-emit a completed request into the service host's console (fallback). */
|
|
483
|
+
function forwardToConsole(record) {
|
|
484
|
+
const wc = bridge.getServiceWc();
|
|
485
|
+
if (!wc || wc.isDestroyed())
|
|
486
|
+
return;
|
|
487
|
+
let script;
|
|
488
|
+
try {
|
|
489
|
+
script = buildForwardScript(record);
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
wc.executeJavaScript(script, true).catch(() => { });
|
|
495
|
+
}
|
|
496
|
+
function detachSimulator() {
|
|
497
|
+
const wc = simWc;
|
|
498
|
+
simWc = null;
|
|
499
|
+
const ad = attachDisposables;
|
|
500
|
+
attachDisposables = null;
|
|
501
|
+
if (ad)
|
|
502
|
+
void ad.disposeAll().catch(() => { });
|
|
503
|
+
// Drop any queued-but-unflushed messages so a new attach starts clean, and
|
|
504
|
+
// reset the native-sink state machine (the next attach re-probes the host).
|
|
505
|
+
dispatchQueue = [];
|
|
506
|
+
probeConsoleBuffer = [];
|
|
507
|
+
// A new simulator attach restarts the native sink: 'idle' until the next
|
|
508
|
+
// event re-probes the (possibly already-set) host.
|
|
509
|
+
sink = 'idle';
|
|
510
|
+
if (readyRetryTimer) {
|
|
511
|
+
clearTimeout(readyRetryTimer);
|
|
512
|
+
readyRetryTimer = null;
|
|
513
|
+
}
|
|
514
|
+
if (readyTimeoutTimer) {
|
|
515
|
+
clearTimeout(readyTimeoutTimer);
|
|
516
|
+
readyTimeoutTimer = null;
|
|
517
|
+
}
|
|
518
|
+
if (!wc || wc.isDestroyed())
|
|
519
|
+
return;
|
|
520
|
+
try {
|
|
521
|
+
if (wc.debugger.isAttached())
|
|
522
|
+
wc.debugger.detach();
|
|
523
|
+
}
|
|
524
|
+
catch { /* already detached / mid-destroy */ }
|
|
525
|
+
}
|
|
526
|
+
function attachSimulator(wc) {
|
|
527
|
+
if (!wc || wc.isDestroyed())
|
|
528
|
+
return;
|
|
529
|
+
if (simWc === wc && !wc.isDestroyed())
|
|
530
|
+
return;
|
|
531
|
+
detachSimulator();
|
|
532
|
+
try {
|
|
533
|
+
if (!wc.debugger.isAttached()) {
|
|
534
|
+
wc.debugger.attach('1.3');
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
console.warn('[network-forward] debugger.attach failed; simulator network not captured:', err instanceof Error ? err.message : err);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
simWc = wc;
|
|
542
|
+
const attach = new DisposableRegistry();
|
|
543
|
+
attachDisposables = attach;
|
|
544
|
+
// Per-attach requestId namespace: a fresh epoch each attach guarantees ids
|
|
545
|
+
// from a previous simulator session can never collide with this one.
|
|
546
|
+
const ns = new RequestIdNamespace(String(Date.now()));
|
|
547
|
+
// Fallback bookkeeping: requestId → in-flight request, for the console line
|
|
548
|
+
// when the native dispatch path is unusable.
|
|
549
|
+
const pending = new Map();
|
|
550
|
+
const onMessage = (_event, method, params) => {
|
|
551
|
+
// ── Primary sink: forward the raw CDP event into the DevTools front-end ──
|
|
552
|
+
if (FORWARDED_METHODS.has(method)) {
|
|
553
|
+
const rewritten = rewriteRequestId(method, params, ns);
|
|
554
|
+
enqueueNative(rewritten.method, rewritten.params);
|
|
555
|
+
}
|
|
556
|
+
else if (REWRITE_REQUEST_ID_METHODS.has(method)) {
|
|
557
|
+
// Methods we namespace but don't forward (dataReceived, 二期): still
|
|
558
|
+
// resolve so the id mapping stays coherent if forwarding is added later.
|
|
559
|
+
rewriteRequestId(method, params, ns);
|
|
560
|
+
}
|
|
561
|
+
// ── Fallback bookkeeping (used only when native dispatch is unusable) ──
|
|
562
|
+
switch (method) {
|
|
563
|
+
case 'Network.requestWillBeSent': {
|
|
564
|
+
const p = params;
|
|
565
|
+
if (!p?.request)
|
|
566
|
+
return;
|
|
567
|
+
if (pending.size >= MAX_PENDING)
|
|
568
|
+
pending.clear();
|
|
569
|
+
pending.set(p.requestId, { url: p.request.url, method: p.request.method, status: 0 });
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
case 'Network.responseReceived': {
|
|
573
|
+
const p = params;
|
|
574
|
+
const req = pending.get(p.requestId);
|
|
575
|
+
if (req)
|
|
576
|
+
req.status = p.response?.status ?? 0;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case 'Network.loadingFinished': {
|
|
580
|
+
const p = params;
|
|
581
|
+
const req = pending.get(p.requestId);
|
|
582
|
+
if (!req)
|
|
583
|
+
return;
|
|
584
|
+
pending.delete(p.requestId);
|
|
585
|
+
maybeFallback({ source: 'service', url: req.url, method: req.method, status: req.status });
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
case 'Network.loadingFailed': {
|
|
589
|
+
const p = params;
|
|
590
|
+
const req = pending.get(p.requestId);
|
|
591
|
+
if (!req)
|
|
592
|
+
return;
|
|
593
|
+
pending.delete(p.requestId);
|
|
594
|
+
maybeFallback({
|
|
595
|
+
source: 'service',
|
|
596
|
+
url: req.url,
|
|
597
|
+
method: req.method,
|
|
598
|
+
status: req.status,
|
|
599
|
+
errorText: p.canceled ? 'canceled' : (p.errorText || 'failed'),
|
|
600
|
+
});
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
default:
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
wc.debugger.on('message', onMessage);
|
|
608
|
+
attach.add(() => {
|
|
609
|
+
try {
|
|
610
|
+
wc.debugger.removeListener('message', onMessage);
|
|
611
|
+
}
|
|
612
|
+
catch { /* wc gone */ }
|
|
613
|
+
});
|
|
614
|
+
const onDetach = () => { if (simWc === wc) {
|
|
615
|
+
simWc = null;
|
|
616
|
+
} };
|
|
617
|
+
wc.debugger.on('detach', onDetach);
|
|
618
|
+
attach.add(() => {
|
|
619
|
+
try {
|
|
620
|
+
wc.debugger.removeListener('detach', onDetach);
|
|
621
|
+
}
|
|
622
|
+
catch { /* wc gone */ }
|
|
623
|
+
});
|
|
624
|
+
const onDestroyed = () => {
|
|
625
|
+
if (simWc === wc) {
|
|
626
|
+
simWc = null;
|
|
627
|
+
const ad = attachDisposables;
|
|
628
|
+
attachDisposables = null;
|
|
629
|
+
if (ad)
|
|
630
|
+
void ad.disposeAll().catch(() => { });
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
// Route the destroyed-teardown through the connection registry when one is
|
|
634
|
+
// available (deterministic disposal on wc destroy / connection reset);
|
|
635
|
+
// otherwise keep the bespoke `once('destroyed')` watcher. WHAT onDestroyed
|
|
636
|
+
// does is unchanged — only WHERE it's registered.
|
|
637
|
+
const reg = bridge.connections;
|
|
638
|
+
if (reg) {
|
|
639
|
+
const owned = reg.acquire(wc).own(onDestroyed);
|
|
640
|
+
attach.add(() => owned.dispose());
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
wc.once('destroyed', onDestroyed);
|
|
644
|
+
attach.add(() => {
|
|
645
|
+
try {
|
|
646
|
+
wc.removeListener('destroyed', onDestroyed);
|
|
647
|
+
}
|
|
648
|
+
catch { /* wc gone */ }
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
void wc.debugger.sendCommand('Network.enable').catch((err) => {
|
|
652
|
+
console.warn('[network-forward] Network.enable failed:', err instanceof Error ? err.message : err);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Route a completed request to EXACTLY ONE sink, per the native-sink state
|
|
657
|
+
* machine (MAJOR 1 — no double-display):
|
|
658
|
+
* - 'ready' : native path already rendered it → suppress console.
|
|
659
|
+
* - 'degraded' : native abandoned → console.
|
|
660
|
+
* - 'idle' : no host configured → console (the native queue never flushes).
|
|
661
|
+
* - 'probing' : undecided → buffer the record; we'll either drop it (on ready)
|
|
662
|
+
* or flush it to console (on degrade). Bounded so it can't grow.
|
|
663
|
+
*/
|
|
664
|
+
function maybeFallback(record) {
|
|
665
|
+
// 'idle' but a host is resolvable (set via the bridge, applyDevtoolsHost not
|
|
666
|
+
// run): the native path is in play, so promote to 'probing' instead of
|
|
667
|
+
// console — otherwise this completion would later double-render natively.
|
|
668
|
+
if (sink === 'idle' && resolveDevtoolsWc())
|
|
669
|
+
beginProbing();
|
|
670
|
+
switch (sink) {
|
|
671
|
+
case 'ready':
|
|
672
|
+
return;
|
|
673
|
+
case 'probing':
|
|
674
|
+
if (probeConsoleBuffer.length >= MAX_PENDING)
|
|
675
|
+
probeConsoleBuffer.shift();
|
|
676
|
+
probeConsoleBuffer.push(record);
|
|
677
|
+
return;
|
|
678
|
+
case 'degraded':
|
|
679
|
+
case 'idle':
|
|
680
|
+
default:
|
|
681
|
+
forwardToConsole(record);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/** Apply a new (or cleared) DevTools host: reset sink state and (re)probe. */
|
|
685
|
+
function applyDevtoolsHost(wc) {
|
|
686
|
+
devtoolsHostDisposable?.dispose();
|
|
687
|
+
devtoolsHostDisposable = null;
|
|
688
|
+
devtoolsWc = wc && !wc.isDestroyed() ? wc : null;
|
|
689
|
+
// Reset the native-sink state machine for the new host.
|
|
690
|
+
if (readyTimeoutTimer) {
|
|
691
|
+
clearTimeout(readyTimeoutTimer);
|
|
692
|
+
readyTimeoutTimer = null;
|
|
693
|
+
}
|
|
694
|
+
if (readyRetryTimer) {
|
|
695
|
+
clearTimeout(readyRetryTimer);
|
|
696
|
+
readyRetryTimer = null;
|
|
697
|
+
}
|
|
698
|
+
// Records buffered while probing the OLD host are stale — drop, don't flush
|
|
699
|
+
// (their native copies were already queued; on a host swap we restart clean).
|
|
700
|
+
probeConsoleBuffer = [];
|
|
701
|
+
if (!devtoolsWc) {
|
|
702
|
+
// No host → 'idle': completions go straight to console; native queue is
|
|
703
|
+
// moot, drop it so it can't double-render if a host later appears.
|
|
704
|
+
sink = 'idle';
|
|
705
|
+
dispatchQueue = [];
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
// Host present: watch it so its destruction equals setDevtoolsHost(null)
|
|
709
|
+
// WITHOUT touching view-manager (host-destroyed cleanup lives here). Begin
|
|
710
|
+
// probing and flush anything already queued.
|
|
711
|
+
const host = devtoolsWc;
|
|
712
|
+
const onHostDestroyed = () => { applyDevtoolsHost(null); };
|
|
713
|
+
// Route host-destroyed teardown through the connection registry when present
|
|
714
|
+
// (the Connection fires onHostDestroyed on wc destroy / reset, and the
|
|
715
|
+
// returned Disposable releases the ownership early on host swap/clear);
|
|
716
|
+
// otherwise keep the bespoke `once('destroyed')` watcher. The
|
|
717
|
+
// `typeof host.once === 'function'` guard stays on the fallback so minimal
|
|
718
|
+
// test fakes / odd hosts don't throw.
|
|
719
|
+
const reg = bridge.connections;
|
|
720
|
+
// `acquire(host)` internally arms `host.once('destroyed')`, so it must be
|
|
721
|
+
// gated by the SAME `typeof host.once === 'function'` guard the fallback
|
|
722
|
+
// uses — otherwise a minimal/fake DevTools host (no emitter) throws on the
|
|
723
|
+
// connection path where the fallback would safely no-op.
|
|
724
|
+
if (reg && typeof host.once === 'function') {
|
|
725
|
+
const owned = reg.acquire(host).own(onHostDestroyed);
|
|
726
|
+
devtoolsHostDisposable = toDisposable(() => owned.dispose());
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
if (typeof host.once === 'function')
|
|
730
|
+
host.once('destroyed', onHostDestroyed);
|
|
731
|
+
devtoolsHostDisposable = toDisposable(() => {
|
|
732
|
+
try {
|
|
733
|
+
host.removeListener?.('destroyed', onHostDestroyed);
|
|
734
|
+
}
|
|
735
|
+
catch { /* gone */ }
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
beginProbing();
|
|
739
|
+
if (dispatchQueue.length > 0)
|
|
740
|
+
scheduleFlush();
|
|
741
|
+
}
|
|
742
|
+
registry.add(() => {
|
|
743
|
+
devtoolsHostDisposable?.dispose();
|
|
744
|
+
devtoolsHostDisposable = null;
|
|
745
|
+
if (readyTimeoutTimer) {
|
|
746
|
+
clearTimeout(readyTimeoutTimer);
|
|
747
|
+
readyTimeoutTimer = null;
|
|
748
|
+
}
|
|
749
|
+
if (readyRetryTimer) {
|
|
750
|
+
clearTimeout(readyRetryTimer);
|
|
751
|
+
readyRetryTimer = null;
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
registry.add(() => detachSimulator());
|
|
755
|
+
return {
|
|
756
|
+
attachSimulator,
|
|
757
|
+
detachSimulator,
|
|
758
|
+
setDevtoolsHost: (wc) => applyDevtoolsHost(wc),
|
|
759
|
+
// report() has no observing debugger, so there's no live CDP event to push
|
|
760
|
+
// natively — surface it via the console fallback line.
|
|
761
|
+
report: (record) => forwardToConsole(record),
|
|
762
|
+
dispose: () => registry.disposeAll(),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
//# sourceMappingURL=index.js.map
|