@dimina-kit/devtools 0.4.0-dev.20260729062524 → 0.4.0-dev.20260801163345
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/app/app.js +7 -0
- package/dist/main/app/bootstrap.d.ts +8 -5
- package/dist/main/app/bootstrap.js +15 -15
- package/dist/main/index.bundle.js +2207 -5152
- package/dist/main/ipc/app-lifecycle.d.ts +1 -31
- package/dist/main/ipc/app-lifecycle.js +1 -53
- package/dist/main/ipc/bridge-router.d.ts +19 -152
- package/dist/main/ipc/bridge-router.js +28 -2186
- package/dist/main/ipc/page-scroll.d.ts +1 -13
- package/dist/main/ipc/page-scroll.js +1 -16
- package/dist/main/ipc/session-listener-bag.d.ts +1 -40
- package/dist/main/ipc/session-listener-bag.js +1 -39
- package/dist/main/services/console-forward/index.d.ts +1 -60
- package/dist/main/services/console-forward/index.js +1 -225
- package/dist/main/services/console-forward/internal-log.d.ts +1 -10
- package/dist/main/services/console-forward/internal-log.js +1 -22
- package/dist/main/services/diagnostics/index.d.ts +1 -58
- package/dist/main/services/diagnostics/index.js +1 -60
- package/dist/main/services/dimina-resource-server.d.ts +1 -6
- package/dist/main/services/dimina-resource-server.js +1 -98
- package/dist/main/services/mcp/target-manager.js +5 -1
- package/dist/main/services/network-forward/index.d.ts +15 -3
- package/dist/main/services/network-forward/index.js +30 -4
- package/dist/main/services/network-forward/websocket.d.ts +36 -0
- package/dist/main/services/network-forward/websocket.js +120 -0
- package/dist/main/services/service-host-pool/pool.d.ts +1 -196
- package/dist/main/services/service-host-pool/pool.js +1 -363
- package/dist/main/services/simulator/custom-apis.d.ts +1 -42
- package/dist/main/services/simulator/custom-apis.js +1 -46
- package/dist/main/services/simulator/referer.d.ts +1 -9
- package/dist/main/services/simulator/referer.js +1 -19
- package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
- package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
- package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
- package/dist/main/services/simulator-temp-files/disk.js +1 -237
- package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
- package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
- package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
- package/dist/main/services/simulator-temp-files/index.js +1 -181
- package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
- package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
- package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
- package/dist/main/services/simulator-temp-files/resolver.js +1 -21
- package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
- package/dist/main/services/simulator-temp-files/store.js +1 -23
- package/dist/main/services/views/miniapp-partition.d.ts +1 -89
- package/dist/main/services/views/miniapp-partition.js +1 -158
- package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
- package/dist/main/services/views/simulator-session-policy.js +1 -98
- package/dist/main/services/workbench-context.d.ts +4 -1
- package/dist/main/services/workbench-context.js +17 -0
- package/dist/main/utils/ipc-registry.d.ts +21 -3
- package/dist/main/utils/ipc-registry.js +26 -23
- package/dist/main/utils/paths.d.ts +14 -0
- package/dist/main/utils/paths.js +14 -0
- package/dist/main/windows/service-host-window/create.d.ts +1 -92
- package/dist/main/windows/service-host-window/create.js +1 -154
- package/dist/native-host/common/common.js +154 -58
- package/dist/native-host/container/pageFrame.css +1 -1
- package/dist/native-host/render/render.js +6611 -3312
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/index.d.ts +2 -0
- package/dist/preload/index.js +1 -0
- package/dist/preload/runtime/clipboard.d.ts +6 -0
- package/dist/preload/runtime/clipboard.js +12 -0
- package/dist/preload/runtime/native-host.d.ts +2 -0
- package/dist/preload/runtime/native-host.js +5 -10
- package/dist/preload/runtime/node-bindings.d.ts +18 -0
- package/dist/preload/runtime/node-bindings.js +44 -0
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +1195 -897
- package/dist/preload/windows/main.cjs.map +4 -4
- package/dist/preload/windows/simulator.cjs +149 -60
- package/dist/preload/windows/simulator.cjs.map +4 -4
- package/dist/preload/windows/simulator.js +149 -60
- package/dist/render-host/pageFrame.html +3 -3
- package/dist/shared/bridge-channels.d.ts +1 -419
- package/dist/shared/bridge-channels.js +1 -89
- package/dist/shared/dmb-resource-url.d.ts +2 -0
- package/dist/shared/dmb-resource-url.js +2 -0
- package/dist/shared/ipc-channels.d.ts +2 -32
- package/dist/shared/ipc-channels.js +2 -5
- package/dist/shared/node-bindings.d.ts +2 -0
- package/dist/shared/node-bindings.js +2 -0
- package/dist/shared/request-core.d.ts +1 -83
- package/dist/shared/request-core.js +1 -167
- package/dist/simulator/assets/device-shell-Cw7c-IkG.js +2 -0
- package/dist/simulator/assets/{device-shell-BHz1v3Yo.css → device-shell-DbC5ccs8.css} +1 -1
- package/dist/simulator/assets/{jsx-runtime-hBv-rHqQ.js → jsx-runtime-DkKmJyBb.js} +2 -2
- package/dist/simulator/assets/simulator-COIE6R49.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-BLn7XV8e.js +2 -0
- package/dist/simulator/simulator.html +2 -2
- package/dist/simulator/temp-files.d.ts +29 -0
- package/dist/simulator/temp-files.js +79 -0
- package/dist/simulator/types.d.ts +43 -0
- package/dist/simulator/types.js +2 -0
- package/package.json +6 -5
- package/dist/shared/simulator-api-metadata.d.ts +0 -26
- package/dist/shared/simulator-api-metadata.js +0 -59
- package/dist/shared/vpath.d.ts +0 -42
- package/dist/shared/vpath.js +0 -105
- package/dist/simulator/assets/device-shell-CH_DMYaz.js +0 -2
- package/dist/simulator/assets/simulator-DXraYerh.js +0 -10
- package/dist/simulator/assets/simulator-mini-app-w1Bhwkld.js +0 -2
|
@@ -1,99 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
const MIME_TYPES = {
|
|
6
|
-
'.css': 'text/css; charset=utf-8',
|
|
7
|
-
'.html': 'text/html; charset=utf-8',
|
|
8
|
-
'.js': 'text/javascript; charset=utf-8',
|
|
9
|
-
'.json': 'application/json; charset=utf-8',
|
|
10
|
-
'.map': 'application/json; charset=utf-8',
|
|
11
|
-
'.svg': 'image/svg+xml',
|
|
12
|
-
'.png': 'image/png',
|
|
13
|
-
'.jpg': 'image/jpeg',
|
|
14
|
-
'.jpeg': 'image/jpeg',
|
|
15
|
-
'.gif': 'image/gif',
|
|
16
|
-
'.webp': 'image/webp',
|
|
17
|
-
'.wasm': 'application/wasm',
|
|
18
|
-
};
|
|
19
|
-
export async function startDiminaResourceServer(rootDir) {
|
|
20
|
-
const root = path.resolve(rootDir);
|
|
21
|
-
const server = http.createServer((req, res) => {
|
|
22
|
-
void handleRequest(root, req, res);
|
|
23
|
-
});
|
|
24
|
-
await new Promise((resolve, reject) => {
|
|
25
|
-
server.once('error', reject);
|
|
26
|
-
server.listen(0, '127.0.0.1', () => {
|
|
27
|
-
server.off('error', reject);
|
|
28
|
-
resolve();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
const address = server.address();
|
|
32
|
-
return {
|
|
33
|
-
baseUrl: `http://127.0.0.1:${address.port}/`,
|
|
34
|
-
close() {
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
36
|
-
server.close((err) => err ? reject(err) : resolve());
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
async function handleRequest(root, req, res) {
|
|
42
|
-
setCorsHeaders(res);
|
|
43
|
-
if (req.method === 'OPTIONS') {
|
|
44
|
-
res.writeHead(204);
|
|
45
|
-
res.end();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
49
|
-
res.writeHead(405);
|
|
50
|
-
res.end('Method Not Allowed');
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
54
|
-
const filePath = resolveContainedPath(root, decodeURIComponent(url.pathname));
|
|
55
|
-
if (!filePath) {
|
|
56
|
-
res.writeHead(403);
|
|
57
|
-
res.end('Forbidden');
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
const stat = await fs.promises.stat(filePath);
|
|
62
|
-
if (!stat.isFile()) {
|
|
63
|
-
res.writeHead(404);
|
|
64
|
-
res.end('Not Found');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
res.writeHead(200, {
|
|
68
|
-
'content-type': MIME_TYPES[path.extname(filePath)] ?? 'application/octet-stream',
|
|
69
|
-
'content-length': stat.size,
|
|
70
|
-
'cache-control': 'no-store',
|
|
71
|
-
});
|
|
72
|
-
if (req.method === 'HEAD') {
|
|
73
|
-
res.end();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
fs.createReadStream(filePath).pipe(res);
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
res.writeHead(404);
|
|
80
|
-
res.end('Not Found');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function resolveContainedPath(root, pathname) {
|
|
84
|
-
const relative = pathname.replace(/^\/+/, '') || 'index.html';
|
|
85
|
-
const resolved = path.resolve(root, relative);
|
|
86
|
-
if (resolved !== root && !resolved.startsWith(root + path.sep))
|
|
87
|
-
return null;
|
|
88
|
-
return resolved;
|
|
89
|
-
}
|
|
90
|
-
function setCorsHeaders(res) {
|
|
91
|
-
res.setHeader('access-control-allow-origin', '*');
|
|
92
|
-
res.setHeader('access-control-allow-headers', '*');
|
|
93
|
-
res.setHeader('access-control-allow-methods', 'GET,HEAD,OPTIONS');
|
|
94
|
-
res.setHeader('cross-origin-resource-policy', 'cross-origin');
|
|
95
|
-
}
|
|
96
|
-
export function toSourceUrl(filePath) {
|
|
97
|
-
return pathToFileURL(filePath).toString();
|
|
98
|
-
}
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/dimina-resource-server';
|
|
99
2
|
//# sourceMappingURL=dimina-resource-server.js.map
|
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import CDP from 'chrome-remote-interface';
|
|
13
13
|
import { DEFAULT_CDP_PORT } from '../../../shared/constants.js';
|
|
14
|
+
import { DMB_PAGEFRAME_DOC_NAME } from '../../../shared/dmb-resource-url.js';
|
|
14
15
|
const SIMULATOR_URL_PATTERN = 'localhost:7788';
|
|
15
16
|
// Native-host: the real mini-app page runs in a nested render-host <webview>
|
|
16
17
|
// guest whose CDP target URL carries the render frame + the page's bridgeId.
|
|
17
|
-
|
|
18
|
+
// Import the shared reserved doc name rather than hand-writing a second
|
|
19
|
+
// literal — that duplication is exactly what let this pattern drift out of
|
|
20
|
+
// sync with the actual document URL shape (dmb-resource-url.ts).
|
|
21
|
+
const RENDER_GUEST_PATTERN = DMB_PAGEFRAME_DOC_NAME;
|
|
18
22
|
const MAX_BUFFER = 500;
|
|
19
23
|
const RECONNECT_INTERVAL_MS = 3000;
|
|
20
24
|
let cdpPort = DEFAULT_CDP_PORT;
|
|
@@ -68,13 +68,16 @@
|
|
|
68
68
|
* Chrome DevTools window) self-heals via the lease's `onDetach` — capture
|
|
69
69
|
* re-wires itself even though `attachRenderGuest` is only ever called ONCE
|
|
70
70
|
* per guest (at webview creation).
|
|
71
|
-
* ⚠️ NOT observable by any `webContents.debugger`:
|
|
72
|
-
* directly from the MAIN process.
|
|
73
|
-
* (
|
|
71
|
+
* ⚠️ NOT observable by any `webContents.debugger`: traffic a host module issues
|
|
72
|
+
* directly from the MAIN process. Plain requests need an explicit `report()`
|
|
73
|
+
* call (console fallback path); `wx.connectSocket` sockets (Node `ws`
|
|
74
|
+
* transport) arrive as trace events via `reportWebSocketTrace` and are
|
|
75
|
+
* synthesized into `Network.webSocket*` CDP messages (see websocket.ts).
|
|
74
76
|
*/
|
|
75
77
|
import type { WebContents } from 'electron';
|
|
76
78
|
import { type ConnectionRegistry, type Disposable } from '@dimina-kit/electron-deck/main';
|
|
77
79
|
import { type CdpSessionBroker } from '../cdp-session/index.js';
|
|
80
|
+
import type { NativeWebSocketTrace } from '../../ipc/bridge-router.js';
|
|
78
81
|
/**
|
|
79
82
|
* Namespace prefix of every virtual requestId this forwarder injects into the
|
|
80
83
|
* DevTools front-end. SINGLE SOURCE for the literal: the front-end outbound
|
|
@@ -215,6 +218,15 @@ export interface NetworkForwarder extends Disposable {
|
|
|
215
218
|
* (e.g. a main-process direct send). Uses the console fallback sink.
|
|
216
219
|
*/
|
|
217
220
|
report(record: NetworkRequestRecord): void;
|
|
221
|
+
/**
|
|
222
|
+
* Surface one main-process WebSocket trace event (wx.connectSocket traffic
|
|
223
|
+
* lives on the Node `ws` transport, invisible to any debugger) as a
|
|
224
|
+
* synthesized `Network.webSocket*` CDP event, injected through the SAME
|
|
225
|
+
* channels as forwarded simulator traffic: the global mirror unfiltered,
|
|
226
|
+
* plus the user-facing native sink when the socket's url classified
|
|
227
|
+
* user-facing. Best-effort like every injection point — never throws.
|
|
228
|
+
*/
|
|
229
|
+
reportWebSocketTrace(sessionId: string, event: NativeWebSocketTrace): void;
|
|
218
230
|
/**
|
|
219
231
|
* Body/post-data lookups for the virtual requestIds this forwarder injected,
|
|
220
232
|
* backed by the loadingFinished-time prefetch cache. Keyed by virtual id, so
|
|
@@ -6,6 +6,7 @@ import { PROBE_DEVTOOLS_API, MAX_SINGLE_DISPATCH_CHARS, CHUNK_CHARS, buildChunke
|
|
|
6
6
|
import { createCdpSessionBroker } from '../cdp-session/index.js';
|
|
7
7
|
import { isUserFacingRequest } from './user-facing.js';
|
|
8
8
|
import { installGlobalNetworkBodyGate } from './global-body-gate.js';
|
|
9
|
+
import { WebSocketTraceSynthesizer } from './websocket.js';
|
|
9
10
|
/**
|
|
10
11
|
* Namespace prefix of every virtual requestId this forwarder injects into the
|
|
11
12
|
* DevTools front-end. SINGLE SOURCE for the literal: the front-end outbound
|
|
@@ -553,14 +554,17 @@ export function createNetworkForwarder(bridge) {
|
|
|
553
554
|
queueMicrotask(flushDispatch);
|
|
554
555
|
}
|
|
555
556
|
/** Trim the native queue to its cap, preferring to keep request-opening events.
|
|
556
|
-
* Active requests' first events (requestWillBeSent / ...ExtraInfo
|
|
557
|
-
* so later responseReceived/loadingFinished
|
|
558
|
-
*
|
|
557
|
+
* Active requests' first events (requestWillBeSent / ...ExtraInfo /
|
|
558
|
+
* webSocketCreated) are retained so later responseReceived/loadingFinished/
|
|
559
|
+
* webSocket* events never become orphans in the panel (the front-end
|
|
560
|
+
* silently drops events for an unknown requestId); we drop the oldest
|
|
561
|
+
* NON-opening (low-value / completion) events first. */
|
|
559
562
|
function trimQueue() {
|
|
560
563
|
if (dispatchQueue.length <= MAX_DISPATCH_QUEUE)
|
|
561
564
|
return;
|
|
562
565
|
const isOpener = (json) => json.includes('"Network.requestWillBeSent"')
|
|
563
|
-
|| json.includes('"Network.requestWillBeSentExtraInfo"')
|
|
566
|
+
|| json.includes('"Network.requestWillBeSentExtraInfo"')
|
|
567
|
+
|| json.includes('"Network.webSocketCreated"');
|
|
564
568
|
// First pass: drop oldest non-opener events.
|
|
565
569
|
const kept = [];
|
|
566
570
|
let over = dispatchQueue.length - MAX_DISPATCH_QUEUE;
|
|
@@ -1293,6 +1297,27 @@ export function createNetworkForwarder(bridge) {
|
|
|
1293
1297
|
if (ownsBroker)
|
|
1294
1298
|
broker.dispose();
|
|
1295
1299
|
});
|
|
1300
|
+
// ── Main-process WebSocket trace → synthesized Network.webSocket* CDP ─────
|
|
1301
|
+
// One synthesizer for the forwarder's lifetime: its virtual-id epoch keeps
|
|
1302
|
+
// `dimina:ws:` ids collision-free across simulator re-attaches, and its
|
|
1303
|
+
// per-socket verdict cache mirrors resolveUserFacing's decide-once rule.
|
|
1304
|
+
const wsSynthesizer = new WebSocketTraceSynthesizer({
|
|
1305
|
+
epoch: String(Date.now()),
|
|
1306
|
+
internalOrigins: () => [bridge.getResourceServerBaseUrl?.(), bridge.getSimulatorServerBaseUrl?.()],
|
|
1307
|
+
});
|
|
1308
|
+
function reportWebSocketTrace(sessionId, event) {
|
|
1309
|
+
if (disposed)
|
|
1310
|
+
return;
|
|
1311
|
+
const message = wsSynthesizer.synthesize(sessionId, event);
|
|
1312
|
+
if (!message)
|
|
1313
|
+
return;
|
|
1314
|
+
// Global mirror first (full, unfiltered), then the user-facing native
|
|
1315
|
+
// sink gated on the created-time verdict — the same routing
|
|
1316
|
+
// wireNetworkCapture's onMessage uses for simulator traffic.
|
|
1317
|
+
dispatchToGlobal(message.method, message.params);
|
|
1318
|
+
if (message.userFacing)
|
|
1319
|
+
enqueueNative(message.method, message.params);
|
|
1320
|
+
}
|
|
1296
1321
|
return {
|
|
1297
1322
|
attachSimulator,
|
|
1298
1323
|
detachSimulator,
|
|
@@ -1302,6 +1327,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
1302
1327
|
// report() has no observing debugger, so there's no live CDP event to push
|
|
1303
1328
|
// natively — surface it via the console fallback line.
|
|
1304
1329
|
report: (record) => forwardToConsole(record),
|
|
1330
|
+
reportWebSocketTrace,
|
|
1305
1331
|
bodies: {
|
|
1306
1332
|
getResponseBody: (requestId) => bodyCache.lookup(requestId),
|
|
1307
1333
|
getRequestPostData: (requestId) => postDataCache.lookup(requestId),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { NativeWebSocketTrace } from '../../ipc/bridge-router.js';
|
|
2
|
+
/** One CDP message ready for `DevToolsAPI.dispatchMessage` injection. */
|
|
3
|
+
export interface SynthesizedWebSocketMessage {
|
|
4
|
+
method: string;
|
|
5
|
+
params: unknown;
|
|
6
|
+
/**
|
|
7
|
+
* The verdict `isUserFacingRequest` produced for this socket's url at
|
|
8
|
+
* `created` time, cached for the socket's whole lifetime — every later
|
|
9
|
+
* event reuses it (they carry no url of their own). The user-facing sink
|
|
10
|
+
* gates on this; the global mirror ignores it.
|
|
11
|
+
*/
|
|
12
|
+
userFacing: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface WebSocketSynthesizerOptions {
|
|
15
|
+
/** Per-forwarder instance tag keeping virtual ids collision-free. */
|
|
16
|
+
epoch: string;
|
|
17
|
+
/**
|
|
18
|
+
* Origins the app itself serves (resource server / simulator shell), the
|
|
19
|
+
* same inputs `resolveUserFacing` feeds `isUserFacingRequest` for HTTP
|
|
20
|
+
* capture. Re-read at each `created`.
|
|
21
|
+
*/
|
|
22
|
+
internalOrigins?: () => ReadonlyArray<string | null | undefined>;
|
|
23
|
+
}
|
|
24
|
+
export declare class WebSocketTraceSynthesizer {
|
|
25
|
+
private readonly options;
|
|
26
|
+
private readonly sockets;
|
|
27
|
+
private seq;
|
|
28
|
+
constructor(options: WebSocketSynthesizerOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Map one trace event to its CDP message, or null when the event must be
|
|
31
|
+
* dropped (a non-`created` event for a socketId this synthesizer never saw
|
|
32
|
+
* created — defensive against out-of-order delivery).
|
|
33
|
+
*/
|
|
34
|
+
synthesize(sessionId: string, event: NativeWebSocketTrace): SynthesizedWebSocketMessage | null;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesize Chrome DevTools Protocol `Network.webSocket*` messages from the
|
|
3
|
+
* main-process WebSocket trace stream (see electron-runtime
|
|
4
|
+
* `native-websocket/trace.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: `wx.connectSocket` sockets live on the Node `ws` transport
|
|
7
|
+
* in the MAIN process — no `webContents.debugger` can observe them — so the
|
|
8
|
+
* embedded DevTools Network tab would never show them. The trace stream gives
|
|
9
|
+
* one ordered fact per lifecycle moment; this module re-shapes each fact into
|
|
10
|
+
* the exact CDP event the DevTools front-end already renders natively
|
|
11
|
+
* (`webSocketCreated` creates the row, handshake events fill it, frames land
|
|
12
|
+
* in the Messages view, `webSocketClosed` settles it).
|
|
13
|
+
*
|
|
14
|
+
* Ordering contract this module relies on (guaranteed by the trace layer):
|
|
15
|
+
* `created` strictly precedes every other event for its socketId, and exactly
|
|
16
|
+
* one `closed` terminates each created socket. The front-end silently drops
|
|
17
|
+
* frame/closed events for an unknown requestId, so this module mirrors that
|
|
18
|
+
* discipline defensively: any non-created event for an unknown socketId is
|
|
19
|
+
* dropped rather than synthesized.
|
|
20
|
+
*
|
|
21
|
+
* Pure and self-contained (no Electron imports) so it is unit-testable.
|
|
22
|
+
*/
|
|
23
|
+
import { isUserFacingRequest } from './user-facing.js';
|
|
24
|
+
export class WebSocketTraceSynthesizer {
|
|
25
|
+
options;
|
|
26
|
+
sockets = new Map();
|
|
27
|
+
seq = 0;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Map one trace event to its CDP message, or null when the event must be
|
|
33
|
+
* dropped (a non-`created` event for a socketId this synthesizer never saw
|
|
34
|
+
* created — defensive against out-of-order delivery).
|
|
35
|
+
*/
|
|
36
|
+
synthesize(sessionId, event) {
|
|
37
|
+
const key = `${sessionId} ${event.socketId}`;
|
|
38
|
+
if (event.type === 'created') {
|
|
39
|
+
const requestId = `dimina:ws:${this.options.epoch}:${this.seq++}`;
|
|
40
|
+
const userFacing = isUserFacingRequest(event.url, this.options.internalOrigins?.());
|
|
41
|
+
this.sockets.set(key, { requestId, userFacing });
|
|
42
|
+
return {
|
|
43
|
+
method: 'Network.webSocketCreated',
|
|
44
|
+
params: { requestId, url: event.url },
|
|
45
|
+
userFacing,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const state = this.sockets.get(key);
|
|
49
|
+
if (!state)
|
|
50
|
+
return null;
|
|
51
|
+
const { requestId, userFacing } = state;
|
|
52
|
+
const timestamp = event.time / 1000;
|
|
53
|
+
switch (event.type) {
|
|
54
|
+
case 'handshake-request':
|
|
55
|
+
return {
|
|
56
|
+
method: 'Network.webSocketWillSendHandshakeRequest',
|
|
57
|
+
params: {
|
|
58
|
+
requestId,
|
|
59
|
+
timestamp,
|
|
60
|
+
wallTime: timestamp,
|
|
61
|
+
request: { headers: event.headers },
|
|
62
|
+
},
|
|
63
|
+
userFacing,
|
|
64
|
+
};
|
|
65
|
+
case 'handshake-response':
|
|
66
|
+
return {
|
|
67
|
+
method: 'Network.webSocketHandshakeResponseReceived',
|
|
68
|
+
params: {
|
|
69
|
+
requestId,
|
|
70
|
+
timestamp,
|
|
71
|
+
wallTime: timestamp,
|
|
72
|
+
response: {
|
|
73
|
+
status: event.status,
|
|
74
|
+
statusText: event.statusText,
|
|
75
|
+
headers: event.headers,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
userFacing,
|
|
79
|
+
};
|
|
80
|
+
case 'frame-sent':
|
|
81
|
+
return {
|
|
82
|
+
method: 'Network.webSocketFrameSent',
|
|
83
|
+
params: { requestId, timestamp, response: frame(event, true) },
|
|
84
|
+
userFacing,
|
|
85
|
+
};
|
|
86
|
+
case 'frame-received':
|
|
87
|
+
return {
|
|
88
|
+
method: 'Network.webSocketFrameReceived',
|
|
89
|
+
params: { requestId, timestamp, response: frame(event, false) },
|
|
90
|
+
userFacing,
|
|
91
|
+
};
|
|
92
|
+
case 'frame-error':
|
|
93
|
+
return {
|
|
94
|
+
method: 'Network.webSocketFrameError',
|
|
95
|
+
params: { requestId, timestamp, errorMessage: event.errorMessage },
|
|
96
|
+
userFacing,
|
|
97
|
+
};
|
|
98
|
+
case 'closed':
|
|
99
|
+
// Terminal: release the id mapping so a long session can't grow it.
|
|
100
|
+
this.sockets.delete(key);
|
|
101
|
+
return {
|
|
102
|
+
method: 'Network.webSocketClosed',
|
|
103
|
+
params: { requestId, timestamp },
|
|
104
|
+
userFacing,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** CDP `Network.WebSocketFrame`: client-sent frames are masked, received are not. */
|
|
110
|
+
function frame(event, mask) {
|
|
111
|
+
const result = {
|
|
112
|
+
opcode: event.opcode,
|
|
113
|
+
mask,
|
|
114
|
+
payloadData: event.payloadData,
|
|
115
|
+
};
|
|
116
|
+
if (event.base64Encoded)
|
|
117
|
+
result.base64Encoded = true;
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -1,197 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* ServiceHostPool — main-process pre-warm pool for simulator host windows.
|
|
4
|
-
*
|
|
5
|
-
* Design source: `packages/devtools/docs/prewarm-webview.md`. WIRED into
|
|
6
|
-
* native-host via `bridge-router.handleSpawn` (acquire) + `disposeAppSession`
|
|
7
|
-
* (release / releaseDestroyed), opt-in behind `DIMINA_PREWARM_POOL_SIZE`
|
|
8
|
-
* (default OFF). Only the SERVICE-HOST window is pooled; the simulator content
|
|
9
|
-
* WebContentsView (`view-manager.attachNativeSimulator`) is created fresh per
|
|
10
|
-
* attach. (A `<webview>`-tag arch is not poolable — Electron can't reparent a
|
|
11
|
-
* pre-warmed WebContents into a `<webview>`.)
|
|
12
|
-
*
|
|
13
|
-
* Lifecycle of a pool entry:
|
|
14
|
-
* warming — BrowserWindow constructed, page loading
|
|
15
|
-
* ready — load settled; can be handed to a caller immediately
|
|
16
|
-
* in-use — acquired by a caller (caller owns the window until release)
|
|
17
|
-
* resetting — released; navigated back to a blank page, then storage cleared
|
|
18
|
-
* disposing — being torn down; removed from the pool
|
|
19
|
-
*
|
|
20
|
-
* ── Phase-3 blockers, both RESOLVED in the wiring (kept here for context) ──
|
|
21
|
-
* 1. Shared-session storage wipe — the service host shares the
|
|
22
|
-
* `persist:simulator` partition with live projects, so clearing it on reset
|
|
23
|
-
* would wipe other projects. Resolved by `ServiceHostSpec.clearStorageOnReset`:
|
|
24
|
-
* `serviceHostSpec()` sets it `false`, so reset only navigates the
|
|
25
|
-
* window to blank (resetting the JS realm) and never clears the shared
|
|
26
|
-
* session; cross-spawn isolation rides on appId-namespacing + fresh nav,
|
|
27
|
-
* matching the already-shared simulator `<webview>` behavior.
|
|
28
|
-
* 2. Warming vs preload contract — warming loads `about:blank` (no bridgeId).
|
|
29
|
-
* `src/service-host/preload.cjs` now idles (early `return`) when `bridgeId`
|
|
30
|
-
* is absent instead of throwing, so a warmed window survives until the real
|
|
31
|
-
* spawn navigation re-runs the preload with a bridgeId.
|
|
32
|
-
*
|
|
33
|
-
* ── Window-death observation ───────────────────────────────────────────────
|
|
34
|
-
* A pooled/in-use window can die three ways: a render crash
|
|
35
|
-
* (`render-process-gone`), a graceful close (`'closed'`), or the owner closing
|
|
36
|
-
* an acquired window (reported via `releaseDestroyed`). All three funnel through
|
|
37
|
-
* `reclaim()`: a POOLED death frees a spare → refill toward target; an IN-USE
|
|
38
|
-
* death is the owner's window → drop the bookkeeping WITHOUT destroying it from
|
|
39
|
-
* under them, and do not refill (the spare count is unaffected).
|
|
40
|
-
*/
|
|
41
|
-
/** Entry state machine (see class doc). */
|
|
42
|
-
export type EntryState = 'warming' | 'ready' | 'in-use' | 'resetting' | 'disposing';
|
|
43
|
-
/**
|
|
44
|
-
* A spec fully determines whether a warm window can serve a caller. Reuse is
|
|
45
|
-
* keyed on `preloadPath`: a different preload means a different runtime surface,
|
|
46
|
-
* so the pool will not hand a mismatched entry to a caller.
|
|
47
|
-
*
|
|
48
|
-
* The process-model flags (`contextIsolation` / `sandbox` / `nodeIntegration`)
|
|
49
|
-
* default to the simulator service-host contract (all `false`, mirroring
|
|
50
|
-
* `createServiceHostWindow`) so a warmed window is a faithful stand-in for the
|
|
51
|
-
* window the caller will actually use. They are part of the spec — not
|
|
52
|
-
* hardcoded — so a caller with a different runtime can override them.
|
|
53
|
-
*/
|
|
54
|
-
export interface ServiceHostSpec {
|
|
55
|
-
/** Session partition for the window. Opaque to the pool (see KNOWN BLOCKER 1). */
|
|
56
|
-
partition: string;
|
|
57
|
-
/** Absolute path to the preload bundle. The reuse key. */
|
|
58
|
-
preloadPath: string;
|
|
59
|
-
/** Initial window size (affects first layout). */
|
|
60
|
-
size?: {
|
|
61
|
-
width: number;
|
|
62
|
-
height: number;
|
|
63
|
-
};
|
|
64
|
-
/** Open DevTools on the window (dev only). */
|
|
65
|
-
devTools?: boolean;
|
|
66
|
-
/** webPreferences.contextIsolation. Default false (service-host contract). */
|
|
67
|
-
contextIsolation?: boolean;
|
|
68
|
-
/** webPreferences.sandbox. Default false (service-host contract). */
|
|
69
|
-
sandbox?: boolean;
|
|
70
|
-
/** webPreferences.nodeIntegration. Default false (service-host contract). */
|
|
71
|
-
nodeIntegration?: boolean;
|
|
72
|
-
/**
|
|
73
|
-
* Whether `release` clears this window's session storage. Default `true`.
|
|
74
|
-
* Set `false` when the window shares a session with live consumers (e.g. the
|
|
75
|
-
* service host on the shared `persist:simulator` partition) — clearing there
|
|
76
|
-
* would wipe other projects' storage. With `false`, reset still navigates the
|
|
77
|
-
* window to a blank page (tearing down the old JS realm); cross-spawn state is
|
|
78
|
-
* handled by appId-namespacing + the fresh navigation, matching the
|
|
79
|
-
* already-shared, never-cleared simulator `<webview>` behavior.
|
|
80
|
-
*/
|
|
81
|
-
clearStorageOnReset?: boolean;
|
|
82
|
-
}
|
|
83
|
-
/** Snapshot of pool occupancy, for dev panels / IPC `prewarm:status`. */
|
|
84
|
-
export interface ServiceHostPoolStats {
|
|
85
|
-
total: number;
|
|
86
|
-
ready: number;
|
|
87
|
-
inUse: number;
|
|
88
|
-
warming: number;
|
|
89
|
-
resetting: number;
|
|
90
|
-
spec: ServiceHostSpec | null;
|
|
91
|
-
}
|
|
92
|
-
export interface ServiceHostPoolInitOptions {
|
|
93
|
-
/** Number of entries to keep warm. Default target after init. */
|
|
94
|
-
defaultPoolSize: number;
|
|
95
|
-
/** Spec used to warm the initial entries. */
|
|
96
|
-
defaultSpec: ServiceHostSpec;
|
|
97
|
-
/** Hard ceiling on pooled entries. Clamped to ≤ 4 (see prewarm-webview.md). Default 3. */
|
|
98
|
-
maxPoolSize?: number;
|
|
99
|
-
}
|
|
100
|
-
export declare class ServiceHostPool {
|
|
101
|
-
private readonly entries;
|
|
102
|
-
private currentSpec;
|
|
103
|
-
private targetSize;
|
|
104
|
-
private maxPoolSize;
|
|
105
|
-
private nextId;
|
|
106
|
-
private disposed;
|
|
107
|
-
/**
|
|
108
|
-
* Warm `defaultPoolSize` entries with `defaultSpec`. Normally resolves once the
|
|
109
|
-
* pool has reached `defaultPoolSize` ready entries — even if a warming window
|
|
110
|
-
* crashes mid-init and is refilled. NOTE: `warmUpToTarget` caps its refill loop
|
|
111
|
-
* with a bounded guard, so under SUSTAINED warm crashes `init` resolves
|
|
112
|
-
* best-effort (possibly under-filled) rather than spinning forever — treat
|
|
113
|
-
* `getStats().ready` as advisory after init, not a hard invariant.
|
|
114
|
-
*/
|
|
115
|
-
init(opts: ServiceHostPoolInitOptions): Promise<void>;
|
|
116
|
-
/**
|
|
117
|
-
* Hand a ready window matching `spec` to the caller. If none is ready, fall
|
|
118
|
-
* back to synchronously constructing a fresh window — never blocking on warm
|
|
119
|
-
* (see prewarm-webview.md's invariants section). Fallback windows carry `entryId === null` and
|
|
120
|
-
* are destroyed (not pooled) on release.
|
|
121
|
-
*
|
|
122
|
-
* A spec change (different `preloadPath`) tears down all *pooled* entries and
|
|
123
|
-
* re-targets the pool to the new spec; in-use entries are left to their owners
|
|
124
|
-
* and disposed when returned (see `release`).
|
|
125
|
-
*/
|
|
126
|
-
acquire(spec: ServiceHostSpec): Promise<{
|
|
127
|
-
win: BrowserWindow;
|
|
128
|
-
entryId: string | null;
|
|
129
|
-
}>;
|
|
130
|
-
/**
|
|
131
|
-
* Return a window. Fallback windows (`entryId === null`) and entries whose
|
|
132
|
-
* spec no longer matches the current pool spec are torn down. Otherwise the
|
|
133
|
-
* window is reset (navigated to blank, then storage cleared) and re-pooled as
|
|
134
|
-
* `ready`, unless that would exceed `maxPoolSize`.
|
|
135
|
-
*/
|
|
136
|
-
release(entryId: string | null, win: BrowserWindow): Promise<void>;
|
|
137
|
-
/**
|
|
138
|
-
* The owner of an acquired window reports it died externally (graceful close /
|
|
139
|
-
* crash) — reclaim its in-use slot so it isn't leaked in `entries` forever
|
|
140
|
-
* (bridge-router calls this on the `serviceAlreadyClosed` teardown path, where
|
|
141
|
-
* `release` is intentionally skipped). No-op for an unknown id; idempotent;
|
|
142
|
-
* never touches the (already-gone) window.
|
|
143
|
-
*/
|
|
144
|
-
releaseDestroyed(entryId: string | null): void;
|
|
145
|
-
/**
|
|
146
|
-
* Re-target the pool. `target` is clamped to `[0, maxPoolSize]`. Pooled
|
|
147
|
-
* entries beyond the target are disposed OLDEST-FIRST (see prewarm-webview.md); in-use
|
|
148
|
-
* entries are untouched. Does not warm new entries up (use `init` / refill).
|
|
149
|
-
*/
|
|
150
|
-
resize(target: number): void;
|
|
151
|
-
/** Occupancy snapshot. */
|
|
152
|
-
getStats(): ServiceHostPoolStats;
|
|
153
|
-
/** Tear down every entry and stop accepting work. */
|
|
154
|
-
dispose(): Promise<void>;
|
|
155
|
-
private matches;
|
|
156
|
-
/** ready + warming + resetting entries are "pooled" (owned by the pool). */
|
|
157
|
-
private isWarm;
|
|
158
|
-
private warmCount;
|
|
159
|
-
private readyCount;
|
|
160
|
-
/**
|
|
161
|
-
* Drive the pool to `targetSize` ready entries and resolve only then. Loops so
|
|
162
|
-
* a crash that drops a warming entry mid-init (which refills asynchronously
|
|
163
|
-
* via `onGone`) is awaited too, instead of resolving on a stale snapshot. A
|
|
164
|
-
* generous iteration guard prevents an unbounded spin if windows keep dying.
|
|
165
|
-
*/
|
|
166
|
-
private warmUpToTarget;
|
|
167
|
-
private createWindow;
|
|
168
|
-
private loadBlank;
|
|
169
|
-
/** Construct + load a fresh pooled entry, registering crash recovery. */
|
|
170
|
-
private warmOne;
|
|
171
|
-
/**
|
|
172
|
-
* Reset a released window back to a clean, blank state. Order matters
|
|
173
|
-
* (see prewarm-webview.md's state-machine and reset-ordering sections): navigate to blank FIRST so the old document
|
|
174
|
-
* stops running and in-flight requests are aborted, THEN clear storage — so a
|
|
175
|
-
* still-live old page cannot re-populate storage after the clear.
|
|
176
|
-
*/
|
|
177
|
-
private reset;
|
|
178
|
-
/** A spec change tears down pooled entries and re-targets the pool. */
|
|
179
|
-
private onSpecChange;
|
|
180
|
-
/**
|
|
181
|
-
* Reclaim an entry whose window died externally (render crash, graceful close,
|
|
182
|
-
* or an owner-reported destruction). A POOLED death frees a spare → refill
|
|
183
|
-
* toward target; an IN-USE death is the caller's window → drop the bookkeeping
|
|
184
|
-
* WITHOUT destroying it from under them, and do not refill (the spare count is
|
|
185
|
-
* unaffected). `destroyIfPooled` destroys the husk for a pooled render crash
|
|
186
|
-
* (the BrowserWindow object can outlive a dead render process); skip it for a
|
|
187
|
-
* `'closed'`/owner-reported window that is already gone. No-op if the entry was
|
|
188
|
-
* already disposed by us (disposeEntry deletes before its own destroy fires
|
|
189
|
-
* `'closed'`, so this finds nothing).
|
|
190
|
-
*/
|
|
191
|
-
private reclaim;
|
|
192
|
-
/** A pooled window's render process died — reclaim + refill (destroy the husk). */
|
|
193
|
-
private onGone;
|
|
194
|
-
private disposeEntry;
|
|
195
|
-
private destroyWindow;
|
|
196
|
-
}
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/service-host-pool';
|
|
197
2
|
//# sourceMappingURL=pool.d.ts.map
|