@dimina-kit/devtools 0.3.2-dev.20260601115343 → 0.3.2-dev.20260610114009
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 +49 -59
- package/dist/main/api.d.ts +1 -2
- package/dist/main/api.js +5 -1
- package/dist/main/app/app.d.ts +15 -5
- package/dist/main/app/app.js +249 -122
- 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 +6071 -1863
- 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 +1333 -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 +1 -1
- 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 +9 -1
- package/dist/main/ipc/simulator.d.ts +2 -2
- package/dist/main/ipc/simulator.js +29 -5
- package/dist/main/ipc/toolbar.d.ts +1 -1
- package/dist/main/ipc/views.d.ts +1 -1
- package/dist/main/ipc/views.js +40 -2
- 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 +22 -0
- package/dist/main/services/layout/index.js +25 -0
- 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/index.d.ts +1 -1
- package/dist/main/services/simulator-temp-files/index.js +39 -17
- 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/toolbar/toolbar-store.d.ts +3 -3
- package/dist/main/services/toolbar/toolbar-store.js +1 -18
- 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/miniapp-partition.d.ts +80 -0
- package/dist/main/services/views/miniapp-partition.js +138 -0
- package/dist/main/services/views/simulator-session-policy.d.ts +28 -0
- package/dist/main/services/views/simulator-session-policy.js +82 -0
- package/dist/main/services/views/view-manager.d.ts +125 -0
- package/dist/main/services/views/view-manager.js +852 -4
- package/dist/main/services/workbench-context.d.ts +67 -7
- package/dist/main/services/workbench-context.js +6 -1
- package/dist/main/services/workspace/workspace-service.js +7 -13
- package/dist/main/utils/ipc-registry.d.ts +1 -1
- package/dist/main/utils/ipc-registry.js +21 -4
- 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 +6 -80
- 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 +67 -0
- package/dist/main/windows/service-host-window/create.js +138 -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 +30 -12
- 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 +1935 -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 +244 -574
- 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/index-CcJKzatF.js +50 -0
- package/dist/renderer/assets/{input-CCYS8Ave.js → input-QGx6FFX1.js} +2 -2
- package/dist/renderer/assets/ipc-transport-BT4QYQqu.css +1 -0
- package/dist/renderer/assets/{ipc-transport-Bn4KXvhu.js → ipc-transport-DvZvwK-Z.js} +2 -2
- package/dist/renderer/assets/{popover-CH4rwpdt.js → popover-BOH1If1J.js} +2 -2
- package/dist/renderer/assets/select-C7JQ8G5N.js +2 -0
- package/dist/renderer/assets/{settings-CMTB-t0x.js → settings-DpAbTJyF.js} +2 -2
- package/dist/renderer/assets/{settings-api-Csdngf9Z.js → settings-api-RD2IZF53.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-CODFCzOg.js → workbenchSettings-CNJX4Qna.js} +2 -2
- package/dist/renderer/entries/main/index.html +5 -5
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- 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-geometry.js +8 -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 +378 -0
- package/dist/shared/bridge-channels.js +81 -0
- package/dist/shared/ipc-channels.d.ts +84 -1
- package/dist/shared/ipc-channels.js +90 -10
- package/dist/shared/ipc-schemas.d.ts +59 -2
- package/dist/shared/ipc-schemas.js +64 -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 -39
- package/dist/simulator/assets/device-shell-DsdcNEkt.css +1 -0
- package/dist/simulator/assets/device-shell-fH2cI-3q.js +2 -0
- package/dist/simulator/assets/jsx-runtime-P6ZegaOf.js +2 -0
- package/dist/simulator/assets/simulator-DHylZf9Z.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 +12 -12
- package/dist/main/utils/disposable.d.ts +0 -18
- package/dist/main/utils/disposable.js +0 -67
- package/dist/renderer/assets/index-BDMxXUfn.js +0 -50
- package/dist/renderer/assets/ipc-transport-Cu7tubyg.css +0 -1
- package/dist/renderer/assets/select-Ct4-sijm.js +0 -2
- package/dist/simulator/assets/simulator-B4e3c6_O.js +0 -9
|
@@ -1,12 +1,41 @@
|
|
|
1
|
-
import { evalInSim,
|
|
2
|
-
import { parseLocationRoute } from '../../../../shared/simulator-route.js';
|
|
1
|
+
import { evalInSim, getActivePageWc, getSimulator } from '../exec.js';
|
|
2
|
+
import { decodePageSpec, parseLocationRoute } from '../../../../shared/simulator-route.js';
|
|
3
|
+
import { waitForActivePage } from '../wait-active-page.js';
|
|
3
4
|
export const appHandlers = {};
|
|
5
|
+
/** Nav methods that take a `{ url }` arg (navigateBack is handled separately). */
|
|
6
|
+
const NAV_METHODS = new Set(['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']);
|
|
4
7
|
// -- App domain --
|
|
5
8
|
async function readRoute(ctx) {
|
|
6
9
|
const { search } = await evalInSim(ctx, `({ search: location.search })`);
|
|
7
10
|
return parseLocationRoute(search);
|
|
8
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Native-host: derive the visible page's `{ pagePath, query }` from the active
|
|
14
|
+
* render guest. The page-stack depth isn't surfaced by the bridge handle, so the
|
|
15
|
+
* native page stack is reported as the single active page (see App.getPageStack).
|
|
16
|
+
* Reads the guest's own `location.search` — the render-host preload encodes
|
|
17
|
+
* `pagePath` (+ per-page query) there.
|
|
18
|
+
*/
|
|
19
|
+
async function readNativeActivePage(ctx) {
|
|
20
|
+
const renderWc = getActivePageWc(ctx);
|
|
21
|
+
if (!renderWc)
|
|
22
|
+
return null;
|
|
23
|
+
const search = await renderWc
|
|
24
|
+
.executeJavaScript('location.search')
|
|
25
|
+
.catch(() => '');
|
|
26
|
+
const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
|
|
27
|
+
const pagePath = params.get('pagePath') || '';
|
|
28
|
+
if (!pagePath)
|
|
29
|
+
return null;
|
|
30
|
+
// The guest URL carries the full page spec on `pagePath` (may include `?k=v`).
|
|
31
|
+
const spec = decodePageSpec(pagePath);
|
|
32
|
+
return { pagePath: spec.pagePath, query: spec.query };
|
|
33
|
+
}
|
|
9
34
|
appHandlers['App.getCurrentPage'] = async (ctx) => {
|
|
35
|
+
if (ctx.bridge?.isNativeHost()) {
|
|
36
|
+
const page = await readNativeActivePage(ctx);
|
|
37
|
+
return { pageId: 1, path: page?.pagePath ?? '', query: page?.query ?? {} };
|
|
38
|
+
}
|
|
10
39
|
const route = await readRoute(ctx);
|
|
11
40
|
const iframeCount = await evalInSim(ctx, `document.querySelectorAll('iframe').length`);
|
|
12
41
|
return {
|
|
@@ -16,8 +45,28 @@ appHandlers['App.getCurrentPage'] = async (ctx) => {
|
|
|
16
45
|
};
|
|
17
46
|
};
|
|
18
47
|
appHandlers['App.getPageStack'] = async (ctx) => {
|
|
19
|
-
const route = await readRoute(ctx);
|
|
20
48
|
const pageStack = [];
|
|
49
|
+
if (ctx.bridge?.isNativeHost()) {
|
|
50
|
+
// DeviceShell reports its full ordered stack (bottom→top) via PAGE_STACK;
|
|
51
|
+
// the bridge stores it. Report that. Before the first signal (or in a mock
|
|
52
|
+
// without the accessor) fall back to the single visible page.
|
|
53
|
+
const stack = ctx.bridge.getPageStack?.();
|
|
54
|
+
if (stack && stack.length > 0) {
|
|
55
|
+
stack.forEach((entry, i) => {
|
|
56
|
+
pageStack.push({
|
|
57
|
+
pageId: i + 1,
|
|
58
|
+
path: entry.pagePath,
|
|
59
|
+
query: Object.fromEntries(Object.entries(entry.query ?? {}).map(([k, v]) => [k, String(v)])),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
return { pageStack };
|
|
63
|
+
}
|
|
64
|
+
const page = await readNativeActivePage(ctx);
|
|
65
|
+
if (page)
|
|
66
|
+
pageStack.push({ pageId: 1, path: page.pagePath, query: page.query });
|
|
67
|
+
return { pageStack };
|
|
68
|
+
}
|
|
69
|
+
const route = await readRoute(ctx);
|
|
21
70
|
if (!route)
|
|
22
71
|
return { pageStack };
|
|
23
72
|
// Mirrors upstream HashRouter.parseSearch: `[entry]` or `[entry, current]`.
|
|
@@ -30,24 +79,88 @@ appHandlers['App.getPageStack'] = async (ctx) => {
|
|
|
30
79
|
appHandlers['App.callWxMethod'] = async (ctx, params) => {
|
|
31
80
|
const method = params.method;
|
|
32
81
|
const args = params.args || [];
|
|
82
|
+
// NATIVE-HOST: the authoritative `wx.*` runs in the hidden service-host
|
|
83
|
+
// window (the simulator / render-guest context has no `wx`). Run EVERY method
|
|
84
|
+
// there — nav so the page stack goes through the real runtime path, and
|
|
85
|
+
// non-nav (setNavigationBarTitle / getSystemInfoSync / tabBar APIs / …) so
|
|
86
|
+
// they don't fall through to the default-arch eval-in-simulator path below,
|
|
87
|
+
// which throws "wx is not defined" under native-host.
|
|
88
|
+
if (ctx.bridge?.isNativeHost()) {
|
|
89
|
+
const serviceWc = ctx.bridge.getServiceWc();
|
|
90
|
+
if (!serviceWc)
|
|
91
|
+
throw new Error('Service host not connected');
|
|
92
|
+
if (NAV_METHODS.has(method) || method === 'navigateBack') {
|
|
93
|
+
const arg = method === 'navigateBack'
|
|
94
|
+
? (args[0] ?? { delta: 1 })
|
|
95
|
+
: (args[0] ?? {});
|
|
96
|
+
// Capture the active page BEFORE navigating so we can wait for it to
|
|
97
|
+
// actually change, instead of blindly sleeping a fixed duration.
|
|
98
|
+
const since = ctx.bridge.getActiveBridgeId();
|
|
99
|
+
await serviceWc.executeJavaScript(`wx.${method}(${JSON.stringify(arg)})`);
|
|
100
|
+
// Wait for the bridge's `activePage` signal (the new page mounted), with a
|
|
101
|
+
// timeout floor = the old blind-wait duration. Resolves as soon as the new
|
|
102
|
+
// page is active — far faster than the fixed sleep for the common case —
|
|
103
|
+
// and still bounded for edges (e.g. switchTab back to an already-mounted
|
|
104
|
+
// tab keeps the same bridgeId, so it falls through to the floor, same as
|
|
105
|
+
// the old behaviour). See wait-active-page.ts.
|
|
106
|
+
const timeoutMs = method === 'navigateBack' ? 1500 : 2000;
|
|
107
|
+
await waitForActivePage(ctx.bridge, {
|
|
108
|
+
since,
|
|
109
|
+
timeoutMs,
|
|
110
|
+
onTimeout: () => console.warn(`[automation] ${method}: activePage signal not seen within ${timeoutMs}ms — proceeding on timeout floor`),
|
|
111
|
+
});
|
|
112
|
+
return { result: undefined };
|
|
113
|
+
}
|
|
114
|
+
// Non-nav: invoke on the service-host wx and return its (sync) value. Async
|
|
115
|
+
// methods that report via a success callback return undefined here, same as
|
|
116
|
+
// the default-arch generic path below.
|
|
117
|
+
const argsStr = args.map((a) => JSON.stringify(a)).join(', ');
|
|
118
|
+
const result = await serviceWc.executeJavaScript(`
|
|
119
|
+
new Promise((resolve, reject) => {
|
|
120
|
+
try {
|
|
121
|
+
if (typeof wx === 'undefined' || typeof wx[${JSON.stringify(method)}] !== 'function') {
|
|
122
|
+
reject(new Error('wx.' + ${JSON.stringify(method)} + ' is not a function in service host'))
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
resolve(wx.${method}(${argsStr}))
|
|
126
|
+
} catch (e) { reject(e && e.message ? e.message : String(e)) }
|
|
127
|
+
})
|
|
128
|
+
`);
|
|
129
|
+
return { result };
|
|
130
|
+
}
|
|
33
131
|
// For navigation methods, handle specially via DOM click / wx.* on the iframe
|
|
34
132
|
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].includes(method)) {
|
|
35
133
|
const opts = args[0];
|
|
36
134
|
const url = opts?.url;
|
|
37
135
|
if (url) {
|
|
38
136
|
const cleanUrl = url.startsWith('/') ? url : `/${url}`;
|
|
39
|
-
// Try clicking a matching DOM element first
|
|
40
|
-
|
|
137
|
+
// Try clicking a matching DOM element first. We can't use `inIframe`
|
|
138
|
+
// here — tabBar mini-apps keep prior tabs' iframes around (display:none)
|
|
139
|
+
// and `iframes[length-1]` ends up being the hidden, freshly-cached tab
|
|
140
|
+
// rather than the visible one. Pick the visible iframe explicitly.
|
|
141
|
+
const clicked = await evalInSim(ctx, `(() => {
|
|
142
|
+
const iframes = Array.from(document.querySelectorAll('iframe'))
|
|
143
|
+
const visible = iframes.filter((f) => {
|
|
144
|
+
const cs = window.getComputedStyle(f)
|
|
145
|
+
if (cs.display === 'none' || cs.visibility === 'hidden') return false
|
|
146
|
+
const rect = f.getBoundingClientRect()
|
|
147
|
+
return rect.width > 0 && rect.height > 0
|
|
148
|
+
})
|
|
149
|
+
const target = visible[visible.length - 1] || iframes[iframes.length - 1]
|
|
150
|
+
if (!target || !target.contentDocument) return false
|
|
41
151
|
const path = ${JSON.stringify(cleanUrl)}
|
|
42
|
-
const el = Array.from(
|
|
43
|
-
.find(e => e.getAttribute('data-path') === path)
|
|
152
|
+
const el = Array.from(target.contentDocument.querySelectorAll('[data-path]'))
|
|
153
|
+
.find((e) => e.getAttribute('data-path') === path)
|
|
44
154
|
if (el) { el.click(); return true }
|
|
45
155
|
return false
|
|
46
|
-
`)
|
|
156
|
+
})()`).catch(() => false);
|
|
47
157
|
if (!clicked) {
|
|
48
|
-
// Fallback
|
|
49
|
-
//
|
|
50
|
-
//
|
|
158
|
+
// Fallback cascade for navigation methods:
|
|
159
|
+
// 1. iframe wx[method] — page-iframe surface (older arch, where
|
|
160
|
+
// jdimina installed `window.wx` on each page frame).
|
|
161
|
+
// 2. top-window wx[method] — set up by simulator/main.tsx, binds
|
|
162
|
+
// MiniApp.switchTab / navigateTo / etc. Required for switchTab
|
|
163
|
+
// since page-iframe wx (jdimina) doesn't expose it.
|
|
51
164
|
const apiName = JSON.stringify(method);
|
|
52
165
|
const urlJson = JSON.stringify(cleanUrl);
|
|
53
166
|
await evalInSim(ctx, `(() => {
|
|
@@ -60,6 +173,9 @@ appHandlers['App.callWxMethod'] = async (ctx, params) => {
|
|
|
60
173
|
}
|
|
61
174
|
} catch (_) {}
|
|
62
175
|
}
|
|
176
|
+
if (typeof wx !== 'undefined' && wx && typeof wx[${apiName}] === 'function') {
|
|
177
|
+
wx[${apiName}]({ url: ${urlJson} })
|
|
178
|
+
}
|
|
63
179
|
})()`);
|
|
64
180
|
}
|
|
65
181
|
// Wait for navigation
|
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evalInActivePage, evalInSim } from '../exec.js';
|
|
2
2
|
import { registerElement } from '../registry.js';
|
|
3
3
|
export const pageHandlers = {};
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a dotted/indexed path (`a.b`, `a[0].b`) against an appdata object,
|
|
6
|
+
* or return the whole object when no path is given. Shared by both Page.getData
|
|
7
|
+
* branches so the traversal stays identical.
|
|
8
|
+
*/
|
|
9
|
+
function resolveAppDataPath(appdata, pathKey) {
|
|
10
|
+
if (!pathKey)
|
|
11
|
+
return appdata;
|
|
12
|
+
const keys = pathKey.replace(/\[(\d+)\]/g, '.$1').split('.');
|
|
13
|
+
let val = appdata;
|
|
14
|
+
for (const k of keys) {
|
|
15
|
+
if (val == null || typeof val !== 'object') {
|
|
16
|
+
val = undefined;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
val = val[k];
|
|
20
|
+
}
|
|
21
|
+
return val;
|
|
22
|
+
}
|
|
4
23
|
// -- Page domain --
|
|
5
24
|
pageHandlers['Page.getElement'] = async (ctx, params) => {
|
|
6
25
|
const selector = params.selector;
|
|
7
26
|
const pageId = params.pageId || 1;
|
|
8
27
|
const escaped = selector.replace(/'/g, "\\'");
|
|
9
|
-
const info = await
|
|
28
|
+
const info = await evalInActivePage(ctx, `
|
|
10
29
|
const el = _doc.querySelector('${escaped}')
|
|
11
30
|
if (!el) return null
|
|
12
31
|
return { tagName: el.tagName.toLowerCase() }
|
|
13
|
-
`)
|
|
32
|
+
`);
|
|
14
33
|
if (!info)
|
|
15
34
|
throw new Error(`Element not found: ${selector}`);
|
|
16
35
|
const elementId = registerElement(selector, 0, pageId);
|
|
@@ -20,11 +39,11 @@ pageHandlers['Page.getElements'] = async (ctx, params) => {
|
|
|
20
39
|
const selector = params.selector;
|
|
21
40
|
const pageId = params.pageId || 1;
|
|
22
41
|
const escaped = selector.replace(/'/g, "\\'");
|
|
23
|
-
const items = await
|
|
42
|
+
const items = await evalInActivePage(ctx, `
|
|
24
43
|
return Array.from(_doc.querySelectorAll('${escaped}')).map(el => ({
|
|
25
44
|
tagName: el.tagName.toLowerCase(),
|
|
26
45
|
}))
|
|
27
|
-
`)
|
|
46
|
+
`);
|
|
28
47
|
return {
|
|
29
48
|
elements: items.map((item, i) => ({
|
|
30
49
|
elementId: registerElement(selector, i, pageId),
|
|
@@ -34,19 +53,17 @@ pageHandlers['Page.getElements'] = async (ctx, params) => {
|
|
|
34
53
|
};
|
|
35
54
|
pageHandlers['Page.getData'] = async (ctx, params) => {
|
|
36
55
|
const pathKey = params.path;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
val = val[k];
|
|
56
|
+
// NATIVE-HOST: under native-host the page AppData lives in the service window's
|
|
57
|
+
// Worker-less runtime (tracked centrally via ctx.appData), NOT in the render
|
|
58
|
+
// guest's DOM. Source the active bridge's reactive state from the central
|
|
59
|
+
// accumulator, then run the SAME path traversal as the default branch.
|
|
60
|
+
if (ctx.bridge?.isNativeHost()) {
|
|
61
|
+
const bridgeId = ctx.bridge.getActiveBridgeId();
|
|
62
|
+
const appdata = bridgeId ? (ctx.appData?.getPageData(bridgeId) ?? {}) : {};
|
|
63
|
+
return { data: resolveAppDataPath(appdata, pathKey) };
|
|
48
64
|
}
|
|
49
|
-
|
|
65
|
+
const appdata = await evalInSim(ctx, `window.__simulatorData ? window.__simulatorData.getAppdata() : {}`);
|
|
66
|
+
return { data: resolveAppDataPath(appdata, pathKey) };
|
|
50
67
|
};
|
|
51
68
|
pageHandlers['Page.setData'] = async () => {
|
|
52
69
|
throw new Error('Page.setData is not supported (requires AppService layer)');
|
|
@@ -56,11 +73,11 @@ pageHandlers['Page.callMethod'] = async (_ctx, params) => {
|
|
|
56
73
|
};
|
|
57
74
|
pageHandlers['Page.getWindowProperties'] = async (ctx, params) => {
|
|
58
75
|
const names = params.names;
|
|
59
|
-
const properties = await
|
|
76
|
+
const properties = await evalInActivePage(ctx, `
|
|
60
77
|
return ${JSON.stringify(names)}.map(n => {
|
|
61
78
|
try { return eval(n) } catch { return undefined }
|
|
62
79
|
})
|
|
63
|
-
`)
|
|
80
|
+
`);
|
|
64
81
|
return { properties };
|
|
65
82
|
};
|
|
66
83
|
//# sourceMappingURL=page.js.map
|
|
@@ -54,6 +54,23 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
54
54
|
ws.send(msg);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
// Native-host console forwarding: under native-host the page/service console
|
|
58
|
+
// doesn't flow through the simulator guest's `ipc-message-host` channel (there
|
|
59
|
+
// is no Worker/MiniApp in the guest). Instead the render-host / service-host
|
|
60
|
+
// preloads post each console entry to main, bridge-router routes it to the
|
|
61
|
+
// always-on ConsoleForwarder, and we SUBSCRIBE to rebroadcast every entry
|
|
62
|
+
// (both layers) as an `App.logAdded` event — same shape the default-arch path
|
|
63
|
+
// emits below in `setupConsoleForwarding`. We no longer set `ctx.guestConsole`
|
|
64
|
+
// ourselves (the forwarder owns it); subscribing means render + service both
|
|
65
|
+
// reach automation while the forwarder also mirrors render into the service
|
|
66
|
+
// host's console for the embedded DevTools. Unsubscribed on close().
|
|
67
|
+
const consoleSub = ctx.consoleForwarder?.subscribe((entry) => {
|
|
68
|
+
const e = (entry ?? {});
|
|
69
|
+
broadcast({
|
|
70
|
+
method: 'App.logAdded',
|
|
71
|
+
params: { type: e.level || 'log', args: e.args || [] },
|
|
72
|
+
});
|
|
73
|
+
});
|
|
57
74
|
// Forward simulator console logs as App.logAdded events.
|
|
58
75
|
// Track the polling interval + stop timer + currently-attached sim and
|
|
59
76
|
// the named handler so we can fully detach on close() or sim destruction.
|
|
@@ -64,7 +81,11 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
64
81
|
let pollStopTimer = null;
|
|
65
82
|
let attachedSim = null;
|
|
66
83
|
let ipcMessageHostHandler = null;
|
|
67
|
-
|
|
84
|
+
// Handle for the destroyed-teardown owned by the sim's connection (replaces a
|
|
85
|
+
// bespoke `sim.once('destroyed')` + manual removeListener). Disposing it on
|
|
86
|
+
// graceful close()/detach releases the disposer from the connection segment so
|
|
87
|
+
// it can't accumulate across automation create/dispose cycles on the same sim.
|
|
88
|
+
let simDestroyedDisposer = null;
|
|
68
89
|
function detachConsoleForwarding() {
|
|
69
90
|
if (attachedSim) {
|
|
70
91
|
if (ipcMessageHostHandler) {
|
|
@@ -77,18 +98,16 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
77
98
|
}
|
|
78
99
|
catch { /* noop */ }
|
|
79
100
|
}
|
|
80
|
-
if (simDestroyedHandler) {
|
|
81
|
-
try {
|
|
82
|
-
if (!attachedSim.isDestroyed()) {
|
|
83
|
-
attachedSim.removeListener('destroyed', simDestroyedHandler);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch { /* noop */ }
|
|
87
|
-
}
|
|
88
101
|
}
|
|
102
|
+
// Release the connection-owned destroyed disposer (idempotent; safe even if
|
|
103
|
+
// the sim is already destroyed — the disposer body only nulls local refs).
|
|
104
|
+
try {
|
|
105
|
+
simDestroyedDisposer?.dispose();
|
|
106
|
+
}
|
|
107
|
+
catch { /* noop */ }
|
|
89
108
|
attachedSim = null;
|
|
90
109
|
ipcMessageHostHandler = null;
|
|
91
|
-
|
|
110
|
+
simDestroyedDisposer = null;
|
|
92
111
|
}
|
|
93
112
|
function setupConsoleForwarding() {
|
|
94
113
|
if (consoleForwardingSetup)
|
|
@@ -116,14 +135,17 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
116
135
|
// sender would throw) and allow re-attach to a new simulator.
|
|
117
136
|
attachedSim = null;
|
|
118
137
|
ipcMessageHostHandler = null;
|
|
119
|
-
|
|
138
|
+
simDestroyedDisposer = null;
|
|
120
139
|
consoleForwardingSetup = false;
|
|
121
140
|
};
|
|
122
141
|
attachedSim = sim;
|
|
123
142
|
ipcMessageHostHandler = onIpcMessageHost;
|
|
124
|
-
simDestroyedHandler = onSimDestroyed;
|
|
125
143
|
sim.on('ipc-message-host', onIpcMessageHost);
|
|
126
|
-
|
|
144
|
+
// Route the destroyed-teardown through the per-webContents connection
|
|
145
|
+
// registry: the connection arms a single `wc.once('destroyed')` and runs
|
|
146
|
+
// owned disposers on hard-destroy (or soft reset for pool reuse), so we no
|
|
147
|
+
// longer manage a bespoke 'destroyed' listener here.
|
|
148
|
+
simDestroyedDisposer = ctx.connections.acquire(sim).own(onSimDestroyed);
|
|
127
149
|
}, 1000);
|
|
128
150
|
// Stop polling after 30s
|
|
129
151
|
pollStopTimer = setTimeout(() => {
|
|
@@ -176,6 +198,11 @@ export async function startAutomationServer(ctx, port = 0) {
|
|
|
176
198
|
pollStopTimer = null;
|
|
177
199
|
}
|
|
178
200
|
detachConsoleForwarding();
|
|
201
|
+
// Unsubscribe from the always-on ConsoleForwarder so a late guest console
|
|
202
|
+
// entry can't broadcast against a torn-down server. The forwarder keeps
|
|
203
|
+
// owning `ctx.guestConsole` (render→service mirroring continues without an
|
|
204
|
+
// automation client). No-op if the forwarder was absent.
|
|
205
|
+
consoleSub?.dispose();
|
|
179
206
|
for (const ws of clients) {
|
|
180
207
|
try {
|
|
181
208
|
ws.close();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process readiness primitive (shared abstraction).
|
|
3
|
+
*
|
|
4
|
+
* Replaces the blind `setTimeout(1500/2000)` navigation waits in automation
|
|
5
|
+
* handlers with "wait for the bridge's `activePage` signal, with a timeout
|
|
6
|
+
* floor". The bridge (`BridgeRouterHandle`) already fan-outs an `activePage`
|
|
7
|
+
* render event whenever the DeviceShell reports a new top-of-stack page, so we
|
|
8
|
+
* wait for that real signal instead of guessing how long a navigation takes.
|
|
9
|
+
*
|
|
10
|
+
* Design contract:
|
|
11
|
+
* - Subscribe to `bridge.onRenderEvent` and resolve on the first matching
|
|
12
|
+
* `activePage` event. "Matching" = `opts.match(bridgeId, pagePath)` when a
|
|
13
|
+
* predicate is supplied, otherwise `bridgeId !== opts.since` (i.e. the
|
|
14
|
+
* active page actually changed away from where navigation started).
|
|
15
|
+
* - Timeout floor: resolve (NEVER reject, never hang) at `opts.timeoutMs` so a
|
|
16
|
+
* caller awaiting this can always make progress even if the signal is lost.
|
|
17
|
+
* - Race close: navigation can land before we subscribe, so read
|
|
18
|
+
* `getActiveBridgeId()` once right after subscribing; when no `match`
|
|
19
|
+
* predicate is given and it is already `!== since`, resolve immediately.
|
|
20
|
+
* (With a `match` predicate we cannot decide from the bridgeId alone — there
|
|
21
|
+
* is no pagePath in the snapshot — so we wait for an event.)
|
|
22
|
+
* - Resolve exactly once; later events are inert and the subscription is torn
|
|
23
|
+
* down on resolve.
|
|
24
|
+
*/
|
|
25
|
+
/** The render-event shape `waitForActivePage` reads (subset of `RenderEvent`). */
|
|
26
|
+
export interface ActivePageEventLike {
|
|
27
|
+
kind: 'domReady' | 'activePage';
|
|
28
|
+
appId: string;
|
|
29
|
+
bridgeId: string;
|
|
30
|
+
pagePath?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Minimal bridge surface this helper is allowed to touch. */
|
|
33
|
+
export interface WaitActivePageBridge {
|
|
34
|
+
onRenderEvent(listener: (event: ActivePageEventLike) => void): () => void;
|
|
35
|
+
getActiveBridgeId(): string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface WaitActivePageOptions {
|
|
38
|
+
/** The active bridgeId before navigation started (or null if unknown). */
|
|
39
|
+
since: string | null;
|
|
40
|
+
/** Timeout floor in ms — resolve unconditionally once this elapses. */
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
/**
|
|
43
|
+
* Optional predicate deciding which `activePage` event ends the wait. When
|
|
44
|
+
* omitted, any `activePage` whose `bridgeId !== since` ends it.
|
|
45
|
+
*/
|
|
46
|
+
match?: (bridgeId: string, pagePath?: string) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Fired exactly once if the wait resolves via the timeout floor rather than a
|
|
49
|
+
* matching signal — i.e. the expected `activePage` was never observed. Lets
|
|
50
|
+
* the caller surface "signal not seen, proceeding on timeout" instead of
|
|
51
|
+
* silently masking a lost signal. Never fired on a signal/race-close resolve.
|
|
52
|
+
*/
|
|
53
|
+
onTimeout?: () => void;
|
|
54
|
+
}
|
|
55
|
+
export declare function waitForActivePage(bridge: WaitActivePageBridge, opts: WaitActivePageOptions): Promise<void>;
|
|
56
|
+
//# sourceMappingURL=wait-active-page.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process readiness primitive (shared abstraction).
|
|
3
|
+
*
|
|
4
|
+
* Replaces the blind `setTimeout(1500/2000)` navigation waits in automation
|
|
5
|
+
* handlers with "wait for the bridge's `activePage` signal, with a timeout
|
|
6
|
+
* floor". The bridge (`BridgeRouterHandle`) already fan-outs an `activePage`
|
|
7
|
+
* render event whenever the DeviceShell reports a new top-of-stack page, so we
|
|
8
|
+
* wait for that real signal instead of guessing how long a navigation takes.
|
|
9
|
+
*
|
|
10
|
+
* Design contract:
|
|
11
|
+
* - Subscribe to `bridge.onRenderEvent` and resolve on the first matching
|
|
12
|
+
* `activePage` event. "Matching" = `opts.match(bridgeId, pagePath)` when a
|
|
13
|
+
* predicate is supplied, otherwise `bridgeId !== opts.since` (i.e. the
|
|
14
|
+
* active page actually changed away from where navigation started).
|
|
15
|
+
* - Timeout floor: resolve (NEVER reject, never hang) at `opts.timeoutMs` so a
|
|
16
|
+
* caller awaiting this can always make progress even if the signal is lost.
|
|
17
|
+
* - Race close: navigation can land before we subscribe, so read
|
|
18
|
+
* `getActiveBridgeId()` once right after subscribing; when no `match`
|
|
19
|
+
* predicate is given and it is already `!== since`, resolve immediately.
|
|
20
|
+
* (With a `match` predicate we cannot decide from the bridgeId alone — there
|
|
21
|
+
* is no pagePath in the snapshot — so we wait for an event.)
|
|
22
|
+
* - Resolve exactly once; later events are inert and the subscription is torn
|
|
23
|
+
* down on resolve.
|
|
24
|
+
*/
|
|
25
|
+
export function waitForActivePage(bridge, opts) {
|
|
26
|
+
const { since, timeoutMs, match, onTimeout } = opts;
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let isSettled = false;
|
|
29
|
+
let unsubscribe = null;
|
|
30
|
+
let timer = null;
|
|
31
|
+
const finish = () => {
|
|
32
|
+
if (isSettled)
|
|
33
|
+
return;
|
|
34
|
+
isSettled = true;
|
|
35
|
+
if (timer !== null) {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
timer = null;
|
|
38
|
+
}
|
|
39
|
+
if (unsubscribe) {
|
|
40
|
+
unsubscribe();
|
|
41
|
+
unsubscribe = null;
|
|
42
|
+
}
|
|
43
|
+
resolve();
|
|
44
|
+
};
|
|
45
|
+
const matches = (bridgeId, pagePath) => match ? match(bridgeId, pagePath) : bridgeId !== since;
|
|
46
|
+
unsubscribe = bridge.onRenderEvent((event) => {
|
|
47
|
+
if (isSettled)
|
|
48
|
+
return;
|
|
49
|
+
if (event.kind !== 'activePage')
|
|
50
|
+
return;
|
|
51
|
+
if (matches(event.bridgeId, event.pagePath))
|
|
52
|
+
finish();
|
|
53
|
+
});
|
|
54
|
+
// Timeout floor — resolve (never reject) so awaiters never hang. Fire the
|
|
55
|
+
// optional onTimeout hook first so a lost signal is visible, not masked.
|
|
56
|
+
timer = setTimeout(() => {
|
|
57
|
+
if (isSettled)
|
|
58
|
+
return;
|
|
59
|
+
onTimeout?.();
|
|
60
|
+
finish();
|
|
61
|
+
}, timeoutMs);
|
|
62
|
+
// Race close: the page may already have advanced before we subscribed.
|
|
63
|
+
// Only decidable without a pagePath when no `match` predicate is supplied.
|
|
64
|
+
if (!match) {
|
|
65
|
+
const current = bridge.getActiveBridgeId();
|
|
66
|
+
if (current !== null && current !== since)
|
|
67
|
+
finish();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=wait-active-page.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Disposable } from '@dimina-kit/electron-deck/main';
|
|
2
|
+
import type { BridgeRouterHandle } from '../../ipc/bridge-router.js';
|
|
3
|
+
/**
|
|
4
|
+
* One console entry posted by a guest preload. Shape mirrors
|
|
5
|
+
* render-host/preload.cjs + service-host/preload.cjs `emitConsoleLog`:
|
|
6
|
+
* - `source` which layer it came from ('render' | 'service'); ONLY 'render'
|
|
7
|
+
* is forwarded into the service host (see loop-safety above).
|
|
8
|
+
* - `bridgeId` present for render entries; identifies the owning page so we
|
|
9
|
+
* can target the matching app's service host (multi-app safe).
|
|
10
|
+
* - `level` console method name ('log' | 'warn' | 'error' | 'info' | 'debug').
|
|
11
|
+
* - `args` already-`safeSerialize`d arguments (structured-cloneable).
|
|
12
|
+
*/
|
|
13
|
+
export interface GuestConsoleEntry {
|
|
14
|
+
source?: string;
|
|
15
|
+
bridgeId?: string;
|
|
16
|
+
level?: string;
|
|
17
|
+
args?: unknown[];
|
|
18
|
+
ts?: number;
|
|
19
|
+
}
|
|
20
|
+
/** A consumer of every guest console entry (render AND service). */
|
|
21
|
+
export type ConsoleSink = (entry: GuestConsoleEntry) => void;
|
|
22
|
+
export interface ConsoleForwarder extends Disposable {
|
|
23
|
+
/**
|
|
24
|
+
* The single sink bridge-router invokes (wired onto `ctx.guestConsole.emit`).
|
|
25
|
+
* Fans every entry out to subscribers, then runs the render→service forward.
|
|
26
|
+
*/
|
|
27
|
+
emit(entry: unknown): void;
|
|
28
|
+
/**
|
|
29
|
+
* Register an external sink (e.g. automation WS broadcast). Returns a
|
|
30
|
+
* Disposable that unregisters it. Sinks see EVERY entry (both layers) — the
|
|
31
|
+
* render→service forward is internal and not exposed as a sink.
|
|
32
|
+
*/
|
|
33
|
+
subscribe(sink: ConsoleSink): Disposable;
|
|
34
|
+
}
|
|
35
|
+
export declare function createConsoleForwarder(bridge: Pick<BridgeRouterHandle, 'getServiceWc' | 'getServiceWcForBridge'>): ConsoleForwarder;
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { DisposableRegistry, toDisposable } from '@dimina-kit/electron-deck/main';
|
|
2
|
+
/** Console levels we re-emit into the service host. Anything else maps to 'log'. */
|
|
3
|
+
const FORWARDABLE_LEVELS = new Set(['log', 'warn', 'error', 'info', 'debug']);
|
|
4
|
+
/**
|
|
5
|
+
* Build the `executeJavaScript` source that re-emits a render entry inside the
|
|
6
|
+
* service host. Args are carried as a JSON string and parsed service-side, so no
|
|
7
|
+
* guest-controlled value is ever interpolated into executable JS — the only
|
|
8
|
+
* thing that reaches the parser is a JSON literal. The `[视图]` prefix lets the
|
|
9
|
+
* DevTools console filter isolate the render layer.
|
|
10
|
+
*/
|
|
11
|
+
function buildForwardScript(level, args) {
|
|
12
|
+
const method = FORWARDABLE_LEVELS.has(level) ? level : 'log';
|
|
13
|
+
// JSON.stringify('…') yields a valid JS string literal; embedding it and
|
|
14
|
+
// re-parsing keeps args as data, never code. JSON.stringify can throw on a
|
|
15
|
+
// value that slipped past safeSerialize (e.g. a BigInt) — caller guards.
|
|
16
|
+
const argsJson = JSON.stringify(args ?? []);
|
|
17
|
+
// `console[method]` (not `console.log`) preserves the original level so the
|
|
18
|
+
// DevTools severity filter still works on the forwarded line.
|
|
19
|
+
return `(()=>{try{const a=JSON.parse(${JSON.stringify(argsJson)});console[${JSON.stringify(method)}]('[视图]',...a)}catch(_){}})()`;
|
|
20
|
+
}
|
|
21
|
+
export function createConsoleForwarder(bridge) {
|
|
22
|
+
const sinks = new Set();
|
|
23
|
+
const registry = new DisposableRegistry();
|
|
24
|
+
/**
|
|
25
|
+
* Render→service forward. Resolves the service host for the entry's owning
|
|
26
|
+
* page (multi-app safe), falling back to the active app's service host when
|
|
27
|
+
* the bridgeId is unknown — that's the host the embedded DevTools inspects.
|
|
28
|
+
* No-op on a destroyed/missing host so we never write to a torn-down wc (pool
|
|
29
|
+
* reuse / session swap can swap the host out under us).
|
|
30
|
+
*/
|
|
31
|
+
function forwardRenderToServiceHost(entry) {
|
|
32
|
+
let wc = null;
|
|
33
|
+
if (entry.bridgeId && bridge.getServiceWcForBridge) {
|
|
34
|
+
wc = bridge.getServiceWcForBridge(entry.bridgeId);
|
|
35
|
+
}
|
|
36
|
+
if (!wc)
|
|
37
|
+
wc = bridge.getServiceWc();
|
|
38
|
+
if (!wc || wc.isDestroyed())
|
|
39
|
+
return;
|
|
40
|
+
let script;
|
|
41
|
+
try {
|
|
42
|
+
script = buildForwardScript(entry.level ?? 'log', entry.args ?? []);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Args that escaped safeSerialize and aren't JSON-stringifiable — drop
|
|
46
|
+
// rather than break the forward. Console capture must never throw.
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// executeJavaScript rejects if the host navigates/teardowns mid-call; the
|
|
50
|
+
// injected body is itself try/caught. Swallow either — best-effort mirror.
|
|
51
|
+
wc.executeJavaScript(script, true).catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
// The single sink bridge-router calls (`ctx.guestConsole.emit`). Fans out
|
|
55
|
+
// to external subscribers (both layers) then runs the render→service forward
|
|
56
|
+
// (render only — see loop-safety invariant in the module header).
|
|
57
|
+
emit(raw) {
|
|
58
|
+
const entry = (raw ?? {});
|
|
59
|
+
for (const sink of sinks) {
|
|
60
|
+
try {
|
|
61
|
+
sink(entry);
|
|
62
|
+
}
|
|
63
|
+
catch { /* a sink must never break the others */ }
|
|
64
|
+
}
|
|
65
|
+
// Forward ONLY the render layer. Service entries are already shown
|
|
66
|
+
// natively in the attached DevTools, and forwarding them would loop
|
|
67
|
+
// (see header).
|
|
68
|
+
if (entry.source === 'render')
|
|
69
|
+
forwardRenderToServiceHost(entry);
|
|
70
|
+
},
|
|
71
|
+
subscribe(sink) {
|
|
72
|
+
sinks.add(sink);
|
|
73
|
+
return registry.add(toDisposable(() => { sinks.delete(sink); }));
|
|
74
|
+
},
|
|
75
|
+
dispose() {
|
|
76
|
+
sinks.clear();
|
|
77
|
+
return registry.disposeAll();
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DiminaResourceServer {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
close(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function startDiminaResourceServer(rootDir: string): Promise<DiminaResourceServer>;
|
|
6
|
+
export declare function toSourceUrl(filePath: string): string;
|
|
7
|
+
//# sourceMappingURL=dimina-resource-server.d.ts.map
|