@deeeed/metamask-harness 0.2.0
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/CHANGELOG.md +161 -0
- package/README.md +140 -0
- package/bin/mm-harness +99 -0
- package/docs/CHEATSHEET.md +61 -0
- package/docs/CLI-SPEC.md +915 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/architecture.md +367 -0
- package/docs/extension-runtime-commands.md +60 -0
- package/docs/harness-cli.md +43 -0
- package/docs/live-adapter-contract.md +188 -0
- package/docs/package-boundaries.md +47 -0
- package/docs/perps-flow-catalog.md +235 -0
- package/docs/recipe-libraries.md +95 -0
- package/docs/runtime-file-conventions.md +36 -0
- package/library/actions/core/perps/_controller.mjs +727 -0
- package/library/actions/core/perps/assert_orders.mjs +53 -0
- package/library/actions/core/perps/assert_positions.mjs +52 -0
- package/library/actions/core/perps/close_orders.mjs +97 -0
- package/library/actions/core/perps/close_positions.mjs +118 -0
- package/library/actions/core/perps/ensure_orders.mjs +40 -0
- package/library/actions/core/perps/ensure_positions.mjs +37 -0
- package/library/actions/core/perps/place_order.mjs +201 -0
- package/library/actions/core/perps/read_account.mjs +30 -0
- package/library/actions/core/perps/read_orders.mjs +27 -0
- package/library/actions/core/perps/read_positions.mjs +27 -0
- package/library/actions/core/perps/start_state.mjs +92 -0
- package/library/actions/core/perps/teardown_state.mjs +86 -0
- package/library/actions/extension/perps/assert_orders.mjs +11 -0
- package/library/actions/extension/perps/assert_positions.mjs +11 -0
- package/library/actions/extension/perps/close_orders.mjs +8 -0
- package/library/actions/extension/perps/close_positions.mjs +8 -0
- package/library/actions/extension/perps/ensure_orders.mjs +4 -0
- package/library/actions/extension/perps/ensure_positions.mjs +4 -0
- package/library/actions/extension/perps/perps.mjs +730 -0
- package/library/actions/extension/perps/place_order.mjs +7 -0
- package/library/actions/extension/perps/read_orders.mjs +4 -0
- package/library/actions/extension/perps/read_positions.mjs +3 -0
- package/library/actions/extension/platform/cdp.mjs +541 -0
- package/library/actions/extension/ui/navigate.mjs +44 -0
- package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
- package/library/actions/extension/wallet/read_state.mjs +27 -0
- package/library/actions/extension/wallet/select_account.mjs +48 -0
- package/library/actions/extension/wallet/setup.mjs +35 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
- package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
- package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
- package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
- package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
- package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
- package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
- package/library/actions/mobile/perps/assert_orders.mjs +11 -0
- package/library/actions/mobile/perps/assert_positions.mjs +11 -0
- package/library/actions/mobile/perps/close_orders.mjs +8 -0
- package/library/actions/mobile/perps/close_positions.mjs +8 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
- package/library/actions/mobile/perps/perps.mjs +709 -0
- package/library/actions/mobile/perps/place_order.mjs +7 -0
- package/library/actions/mobile/perps/read_orders.mjs +4 -0
- package/library/actions/mobile/perps/read_positions.mjs +3 -0
- package/library/actions/mobile/platform/bridge.mjs +283 -0
- package/library/actions/mobile/ui/navigate.mjs +38 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
- package/library/actions/mobile/wallet/home.mjs +35 -0
- package/library/actions/mobile/wallet/read_state.mjs +40 -0
- package/library/actions/mobile/wallet/select_account.mjs +48 -0
- package/library/actions/mobile/wallet/setup.mjs +220 -0
- package/library/flows/perps.flows.json +64 -0
- package/library/library.json +7 -0
- package/library/manifests/core.action-manifest.json +1282 -0
- package/library/manifests/extension.action-manifest.json +1749 -0
- package/library/manifests/mobile.action-manifest.json +1753 -0
- package/library/recipes/action-validation.extension.recipe.json +417 -0
- package/library/recipes/action-validation.mobile.recipe.json +422 -0
- package/library/recipes/order-lifecycle.core.recipe.json +78 -0
- package/library/recipes/perps-lifecycle.recipe.json +194 -0
- package/library/recipes/read-markets.core.recipe.json +38 -0
- package/library/recipes/smoke.extension.recipe.json +31 -0
- package/library/recipes/smoke.mobile.recipe.json +31 -0
- package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
- package/orchestration/compat-overlays/README.md +19 -0
- package/orchestration/compat-overlays/mobile/README.md +13 -0
- package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
- package/orchestration/core/cleanup.sh +37 -0
- package/orchestration/core/inject.sh +154 -0
- package/orchestration/doctor.mjs +72 -0
- package/orchestration/extension/cleanup.mjs +60 -0
- package/orchestration/extension/console-tail.mjs +228 -0
- package/orchestration/extension/ensure-browser.sh +416 -0
- package/orchestration/extension/ensure-ready.ts +185 -0
- package/orchestration/extension/extension-id.ts +107 -0
- package/orchestration/extension/inject.mjs +266 -0
- package/orchestration/extension/launch-browser.cjs +216 -0
- package/orchestration/extension/launch.sh +175 -0
- package/orchestration/extension/live.sh +320 -0
- package/orchestration/extension/pin-remote-flags.cjs +45 -0
- package/orchestration/extension/readiness.mjs +414 -0
- package/orchestration/extension/refresh-build.sh +190 -0
- package/orchestration/extension/runtime-decision.ts +445 -0
- package/orchestration/extension/runtime.ts +407 -0
- package/orchestration/extension/seed-fixture.sh +177 -0
- package/orchestration/extension/sidepanel-toggle.sh +291 -0
- package/orchestration/extension/snapshot-dist.sh +84 -0
- package/orchestration/extension/start-watch.sh +339 -0
- package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
- package/orchestration/lib/activate-repo-node.sh +144 -0
- package/orchestration/lib/cli-color.mjs +84 -0
- package/orchestration/lib/cli-commands.mjs +243 -0
- package/orchestration/lib/cli-home.mjs +354 -0
- package/orchestration/lib/cli-ux.sh +252 -0
- package/orchestration/lib/cli-version.mjs +123 -0
- package/orchestration/lib/ensure-runner-deps.sh +56 -0
- package/orchestration/lib/harness-path.sh +55 -0
- package/orchestration/lib/hash-helpers.sh +44 -0
- package/orchestration/lib/json-field.sh +23 -0
- package/orchestration/lib/log-tui.mjs +304 -0
- package/orchestration/lib/open-debug.mjs +317 -0
- package/orchestration/lib/path-defaults.json +4 -0
- package/orchestration/lib/progress.mjs +107 -0
- package/orchestration/lib/recipe-paths.mjs +26 -0
- package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
- package/orchestration/manifest.json +358 -0
- package/orchestration/mobile/cleanup.sh +192 -0
- package/orchestration/mobile/deps-markers.ts +21 -0
- package/orchestration/mobile/inject.sh +681 -0
- package/orchestration/mobile/launch.sh +137 -0
- package/orchestration/mobile/live.sh +125 -0
- package/orchestration/mobile/runtime-decision.ts +292 -0
- package/orchestration/porcelain/metamask-recipe +99 -0
- package/orchestration/porcelain/mm-recipe +1591 -0
- package/orchestration/porcelain/mme-recipe +1181 -0
- package/package.json +59 -0
- package/runner/extension/verify.sh +511 -0
- package/runner/mobile/verify.sh +501 -0
- package/runner/src/adapters.ts +601 -0
- package/runner/src/cli.ts +1820 -0
- package/runner/src/commands/debug.ts +44 -0
- package/runner/src/commands/fixtures.ts +99 -0
- package/runner/src/commands/launch.ts +397 -0
- package/runner/src/commands/logs.ts +60 -0
- package/runner/src/commands/shared.ts +138 -0
- package/runner/src/completions-cache.ts +86 -0
- package/runner/src/doctor.ts +203 -0
- package/runner/src/harness.ts +516 -0
- package/runner/src/heal-bounds.ts +179 -0
- package/runner/src/index.ts +6 -0
- package/runner/src/live-adapter-contract.ts +274 -0
- package/runner/src/manifest.ts +47 -0
- package/runner/src/mm-harness-cli.ts +488 -0
- package/runner/src/paths.ts +198 -0
- package/runner/src/recording-target.ts +147 -0
- package/runner/src/run-recording.ts +329 -0
- package/runner/src/runner.ts +108 -0
- package/runner/src/types.ts +57 -0
- package/scripts/completions.sh +125 -0
- package/scripts/install-completions.sh +62 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('node:http');
|
|
4
|
+
const { loadSimulatorName, loadAndroidDevice } = require('./config.cjs');
|
|
5
|
+
const { createWSClient } = require('./ws-client.cjs');
|
|
6
|
+
|
|
7
|
+
const FETCH_TIMEOUT_MS = Number.parseInt(process.env.CDP_TIMEOUT || '30000', 10);
|
|
8
|
+
const FETCH_RETRIES = Number.parseInt(process.env.CDP_DISCOVERY_RETRIES || '3', 10);
|
|
9
|
+
|
|
10
|
+
/** Fetch JSON from a URL (http only, no external deps) */
|
|
11
|
+
function fetchJSONOnce(url) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const req = http.get(url, (res) => {
|
|
14
|
+
let data = '';
|
|
15
|
+
res.on('data', (chunk) => (data += chunk));
|
|
16
|
+
res.on('end', () => {
|
|
17
|
+
try {
|
|
18
|
+
resolve(JSON.parse(data));
|
|
19
|
+
} catch (e) {
|
|
20
|
+
reject(new Error(`Failed to parse JSON from ${url}: ${e.message}`));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
req.on('error', reject);
|
|
25
|
+
req.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
26
|
+
req.destroy();
|
|
27
|
+
reject(new Error(`Timeout fetching ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function fetchJSON(url) {
|
|
33
|
+
let lastError;
|
|
34
|
+
for (let attempt = 1; attempt <= FETCH_RETRIES; attempt++) {
|
|
35
|
+
try {
|
|
36
|
+
return await fetchJSONOnce(url);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
lastError = error;
|
|
39
|
+
if (attempt === FETCH_RETRIES) break;
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw lastError;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Quick probe: connect to a CDP target, evaluate `__DEV__`, disconnect.
|
|
48
|
+
* Returns true if __AGENTIC__ is installed, false otherwise.
|
|
49
|
+
*/
|
|
50
|
+
async function probeTarget(wsUrl) {
|
|
51
|
+
try {
|
|
52
|
+
const client = await createWSClient(wsUrl, 3000);
|
|
53
|
+
try {
|
|
54
|
+
const result = await client.send('Runtime.evaluate', {
|
|
55
|
+
expression: '(function(){ return typeof globalThis.__AGENTIC__; })()',
|
|
56
|
+
returnByValue: true,
|
|
57
|
+
awaitPromise: false,
|
|
58
|
+
});
|
|
59
|
+
return result?.result?.value === 'object';
|
|
60
|
+
} finally {
|
|
61
|
+
client.close();
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Connection failed — target is not the right one
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Discover the Hermes CDP WebSocket URL from Metro's /json/list endpoint.
|
|
71
|
+
*
|
|
72
|
+
* Multi-simulator support:
|
|
73
|
+
* - When IOS_SIMULATOR is set, filters targets by deviceName
|
|
74
|
+
* - With Hermes bridgeless mode, there are multiple pages per device:
|
|
75
|
+
* page 1 = native C++ runtime, page 2+ = JS runtime (where __AGENTIC__ lives)
|
|
76
|
+
* - We probe candidates to find the one with __AGENTIC__ installed
|
|
77
|
+
*/
|
|
78
|
+
async function discoverTarget(port) {
|
|
79
|
+
const listUrl = `http://localhost:${port}/json/list`;
|
|
80
|
+
let targets;
|
|
81
|
+
try {
|
|
82
|
+
targets = await fetchJSON(listUrl);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Cannot reach Metro at ${listUrl}. Is Metro running?\n ${e.message}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
90
|
+
throw new Error(`No debug targets found at ${listUrl}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Filter to React Native / Hermes targets with a WebSocket URL
|
|
94
|
+
let candidates = targets.filter(
|
|
95
|
+
(t) =>
|
|
96
|
+
t.webSocketDebuggerUrl &&
|
|
97
|
+
// Pre-RN-0.81 titles say "React Native"/"Hermes". RN 0.81+ Bridgeless
|
|
98
|
+
// titles are bundle-id-only (e.g. "io.metamask.MetaMask (mm-5)") and
|
|
99
|
+
// the runtime kind is in `description` instead.
|
|
100
|
+
((t.title &&
|
|
101
|
+
(/react/i.test(t.title) || /hermes/i.test(t.title))) ||
|
|
102
|
+
/bridgeless|hermes/i.test(t.description || '')),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Filter by device name if IOS_SIMULATOR is set
|
|
106
|
+
const simName = loadSimulatorName();
|
|
107
|
+
if (simName && candidates.length > 1) {
|
|
108
|
+
const deviceFiltered = candidates.filter(
|
|
109
|
+
(t) => t.deviceName === simName,
|
|
110
|
+
);
|
|
111
|
+
if (deviceFiltered.length > 0) {
|
|
112
|
+
candidates = deviceFiltered;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Filter by device name if ANDROID_DEVICE is set
|
|
117
|
+
const androidDevice = loadAndroidDevice();
|
|
118
|
+
if (androidDevice && candidates.length > 1) {
|
|
119
|
+
const deviceFiltered = candidates.filter(
|
|
120
|
+
(t) => t.deviceName === androidDevice,
|
|
121
|
+
);
|
|
122
|
+
if (deviceFiltered.length > 0) {
|
|
123
|
+
candidates = deviceFiltered;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (candidates.length === 0) {
|
|
128
|
+
candidates = targets.filter((t) => t.webSocketDebuggerUrl);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (candidates.length === 0) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`No suitable debug target found. Targets:\n${JSON.stringify(targets, null, 2)}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Sort by page number descending (JS runtime has higher page number than C++ native)
|
|
138
|
+
candidates.sort((a, b) => {
|
|
139
|
+
const aPage = Number.parseInt((a.id || '').split('-').pop() || '0', 10);
|
|
140
|
+
const bPage = Number.parseInt((b.id || '').split('-').pop() || '0', 10);
|
|
141
|
+
return bPage - aPage;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Probe candidates to find the one with __AGENTIC__ installed (the JS runtime)
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
const hasAgentic = await probeTarget(candidate.webSocketDebuggerUrl);
|
|
147
|
+
if (hasAgentic) {
|
|
148
|
+
return { wsUrl: candidate.webSocketDebuggerUrl, deviceName: candidate.deviceName || '' };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Fallback: return highest page number (most likely the JS runtime)
|
|
153
|
+
return { wsUrl: candidates[0].webSocketDebuggerUrl, deviceName: candidates[0].deviceName || '' };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Discover ALL Hermes targets with __AGENTIC__ installed (one per platform/device).
|
|
158
|
+
* Groups by deviceName so each device returns at most one target.
|
|
159
|
+
*/
|
|
160
|
+
async function discoverAllTargets(port) {
|
|
161
|
+
const listUrl = `http://localhost:${port}/json/list`;
|
|
162
|
+
let targets;
|
|
163
|
+
try {
|
|
164
|
+
targets = await fetchJSON(listUrl);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Cannot reach Metro at ${listUrl}. Is Metro running?\n ${e.message}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const candidates = (targets || []).filter(
|
|
172
|
+
(t) =>
|
|
173
|
+
t.webSocketDebuggerUrl &&
|
|
174
|
+
// Pre-RN-0.81 titles say "React Native"/"Hermes". RN 0.81+ Bridgeless
|
|
175
|
+
// titles are bundle-id-only (e.g. "io.metamask.MetaMask (mm-5)") and
|
|
176
|
+
// the runtime kind is in `description` instead.
|
|
177
|
+
((t.title &&
|
|
178
|
+
(/react/i.test(t.title) || /hermes/i.test(t.title))) ||
|
|
179
|
+
/bridgeless|hermes/i.test(t.description || '')),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Sort by page number descending (JS runtime has higher page number)
|
|
183
|
+
candidates.sort((a, b) => {
|
|
184
|
+
const aPage = Number.parseInt((a.id || '').split('-').pop() || '0', 10);
|
|
185
|
+
const bPage = Number.parseInt((b.id || '').split('-').pop() || '0', 10);
|
|
186
|
+
return bPage - aPage;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Group by deviceName, probe each to find the JS runtime target
|
|
190
|
+
const seen = new Set();
|
|
191
|
+
const results = [];
|
|
192
|
+
for (const candidate of candidates) {
|
|
193
|
+
const device = candidate.deviceName || candidate.id || candidate.webSocketDebuggerUrl;
|
|
194
|
+
if (seen.has(device)) continue;
|
|
195
|
+
const hasAgentic = await probeTarget(candidate.webSocketDebuggerUrl);
|
|
196
|
+
if (hasAgentic) {
|
|
197
|
+
seen.add(device);
|
|
198
|
+
results.push({ wsUrl: candidate.webSocketDebuggerUrl, deviceName: device });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = { discoverTarget, discoverAllTargets };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* global globalThis */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal CDP client using the built-in ws-like interface over raw WebSocket.
|
|
7
|
+
* Node 22+ has a built-in WebSocket; for older versions we use the ws package
|
|
8
|
+
* that ships with React Native / Metro dev dependencies.
|
|
9
|
+
*/
|
|
10
|
+
function createWSClient(wsUrl, timeout) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
let WebSocketImpl;
|
|
13
|
+
// Node 22+ has globalThis.WebSocket
|
|
14
|
+
if (typeof globalThis.WebSocket === 'function') {
|
|
15
|
+
WebSocketImpl = globalThis.WebSocket;
|
|
16
|
+
} else {
|
|
17
|
+
try {
|
|
18
|
+
// Dynamic require avoids depcheck static analysis — ws is an optional
|
|
19
|
+
// fallback for Node < 22 which has no built-in WebSocket.
|
|
20
|
+
const wsModule = 'ws';
|
|
21
|
+
WebSocketImpl = require(wsModule);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'WebSocket not available. Install "ws" package or use Node >= 22.',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ws = new WebSocketImpl(wsUrl);
|
|
30
|
+
let msgId = 0;
|
|
31
|
+
const pending = new Map();
|
|
32
|
+
|
|
33
|
+
const timer = setTimeout(() => {
|
|
34
|
+
ws.close();
|
|
35
|
+
reject(new Error(`CDP connection timeout after ${timeout}ms`));
|
|
36
|
+
}, timeout);
|
|
37
|
+
|
|
38
|
+
ws.onopen = () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve({
|
|
41
|
+
/** Send a CDP command and wait for the response */
|
|
42
|
+
send(method, params = {}, msgTimeout = timeout) {
|
|
43
|
+
return new Promise((res, rej) => {
|
|
44
|
+
const id = ++msgId;
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
pending.delete(id);
|
|
47
|
+
rej(
|
|
48
|
+
new Error(
|
|
49
|
+
`CDP message timeout after ${msgTimeout}ms for ${method}`,
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
}, msgTimeout);
|
|
53
|
+
pending.set(id, {
|
|
54
|
+
resolve: (v) => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
res(v);
|
|
57
|
+
},
|
|
58
|
+
reject: (e) => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
rej(e);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const msg = JSON.stringify({ id, method, params });
|
|
64
|
+
ws.send(msg);
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
close() {
|
|
68
|
+
ws.close();
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
ws.onmessage = (evt) => {
|
|
74
|
+
const data = typeof evt.data === 'string' ? evt.data : evt.data.toString();
|
|
75
|
+
let msg;
|
|
76
|
+
try {
|
|
77
|
+
msg = JSON.parse(data);
|
|
78
|
+
} catch {
|
|
79
|
+
// Non-JSON frame — ignore
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (msg.id && pending.has(msg.id)) {
|
|
83
|
+
const { resolve: res, reject: rej } = pending.get(msg.id);
|
|
84
|
+
pending.delete(msg.id);
|
|
85
|
+
if (msg.error) {
|
|
86
|
+
rej(new Error(`CDP error: ${JSON.stringify(msg.error)}`));
|
|
87
|
+
} else {
|
|
88
|
+
res(msg.result);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
ws.onerror = (err) => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
reject(new Error(`WebSocket error: ${err.message || err}`));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
ws.onclose = () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
for (const [, { reject: rej }] of pending) {
|
|
101
|
+
rej(new Error('WebSocket closed'));
|
|
102
|
+
}
|
|
103
|
+
pending.clear();
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { createWSClient };
|