@deeeed/metamask-harness 0.66.1 → 0.66.2
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 +7 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +3 -6
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -2
- package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +9 -24
- package/adapters/mobile/wait-for-bridge.cjs +1 -1
- package/dist/adapters/mobile/provision.js +32 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.66.2 - 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Resolve the latest iOS Runway build from named artifacts rather than the last 30 workflow runs, and fail instead of silently selecting an older build when GitHub artifact lookup fails.
|
|
10
|
+
- Discover Metro targets through its canonical loopback address and send the matching Origin from both the Mobile CDP bridge and console collector, fixing rejected inspector connections on current Expo clients.
|
|
11
|
+
|
|
5
12
|
## 0.66.1 - 2026-09-25
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -36,9 +36,8 @@ const {
|
|
|
36
36
|
} = require('./lib/cdp-broker.cjs');
|
|
37
37
|
const { createDevtoolsProxy } = require('./lib/devtools-proxy.cjs');
|
|
38
38
|
const { resolvePort } = require('./lib/config.cjs');
|
|
39
|
+
const { createInspectorWebSocket } = require('./lib/ws-client.cjs');
|
|
39
40
|
|
|
40
|
-
// Hermes uses Node's built-in WebSocket client. The local DevTools proxy uses
|
|
41
|
-
// the package's pinned `ws` server implementation.
|
|
42
41
|
const HANDSHAKE_TIMEOUT_MS = 3000;
|
|
43
42
|
const RUNTIME_ENABLE_TIMEOUT_MS = 60_000;
|
|
44
43
|
|
|
@@ -288,7 +287,7 @@ function connect(target) {
|
|
|
288
287
|
const name = deviceNameFromTitle(target.title);
|
|
289
288
|
let ws;
|
|
290
289
|
try {
|
|
291
|
-
ws =
|
|
290
|
+
ws = createInspectorWebSocket(target.webSocketDebuggerUrl);
|
|
292
291
|
} catch {
|
|
293
292
|
return;
|
|
294
293
|
}
|
|
@@ -302,8 +301,6 @@ function connect(target) {
|
|
|
302
301
|
pending: new Map(),
|
|
303
302
|
};
|
|
304
303
|
sessions.set(deviceId, session);
|
|
305
|
-
// The built-in WebSocket has no handshake timeout; a hung connect would hold
|
|
306
|
-
// the session slot forever and block every future re-attach for this device.
|
|
307
304
|
const handshakeTimer = setTimeout(() => {
|
|
308
305
|
try {
|
|
309
306
|
ws.close();
|
|
@@ -419,7 +416,7 @@ function discover() {
|
|
|
419
416
|
return;
|
|
420
417
|
}
|
|
421
418
|
http
|
|
422
|
-
.get({ host: '
|
|
419
|
+
.get({ host: '127.0.0.1', port, path: '/json/list', timeout: 3000 }, (res) => {
|
|
423
420
|
let body = '';
|
|
424
421
|
res.on('data', (c) => (body += c));
|
|
425
422
|
res.on('end', () => {
|
|
@@ -139,7 +139,7 @@ function targetDeviceIdentity(target) {
|
|
|
139
139
|
* - We probe candidates to find the one with __AGENTIC__ installed
|
|
140
140
|
*/
|
|
141
141
|
async function discoverTarget(port, { probe = true } = {}) {
|
|
142
|
-
const listUrl = `http://
|
|
142
|
+
const listUrl = `http://127.0.0.1:${port}/json/list`;
|
|
143
143
|
const androidTargetName = loadAndroidTargetDeviceName();
|
|
144
144
|
const androidDevice = loadAndroidDevice();
|
|
145
145
|
const adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || '';
|
|
@@ -358,7 +358,7 @@ async function discoverTarget(port, { probe = true } = {}) {
|
|
|
358
358
|
* Groups by deviceName so each device returns at most one target.
|
|
359
359
|
*/
|
|
360
360
|
async function discoverAllTargets(port) {
|
|
361
|
-
const listUrl = `http://
|
|
361
|
+
const listUrl = `http://127.0.0.1:${port}/json/list`;
|
|
362
362
|
let targets;
|
|
363
363
|
try {
|
|
364
364
|
targets = await fetchJSON(listUrl);
|
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/* global globalThis */
|
|
4
|
-
|
|
5
3
|
const { BRIDGE_ERROR_CODES, coded } = require('./bridge-errors.cjs');
|
|
4
|
+
const WebSocket = require('ws');
|
|
5
|
+
|
|
6
|
+
function createInspectorWebSocket(wsUrl) {
|
|
7
|
+
const origin = new URL(wsUrl).origin.replace(/^ws/, 'http');
|
|
8
|
+
return new WebSocket(wsUrl, { origin });
|
|
9
|
+
}
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
|
-
* Minimal CDP client
|
|
9
|
-
* Node 22+ has a built-in WebSocket; for older versions we use the ws package
|
|
10
|
-
* that ships with React Native / Metro dev dependencies.
|
|
12
|
+
* Minimal CDP client that supplies the debugger Origin required by Metro.
|
|
11
13
|
*/
|
|
12
14
|
function createWSClient(wsUrl, timeout) {
|
|
13
15
|
return new Promise((resolve, reject) => {
|
|
14
|
-
|
|
15
|
-
// Node 22+ has globalThis.WebSocket
|
|
16
|
-
if (typeof globalThis.WebSocket === 'function') {
|
|
17
|
-
WebSocketImpl = globalThis.WebSocket;
|
|
18
|
-
} else {
|
|
19
|
-
try {
|
|
20
|
-
// Dynamic require avoids depcheck static analysis — ws is an optional
|
|
21
|
-
// fallback for Node < 22 which has no built-in WebSocket.
|
|
22
|
-
const wsModule = 'ws';
|
|
23
|
-
WebSocketImpl = require(wsModule);
|
|
24
|
-
} catch {
|
|
25
|
-
throw new Error(
|
|
26
|
-
'WebSocket not available. Install "ws" package or use Node >= 22.',
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const ws = new WebSocketImpl(wsUrl);
|
|
16
|
+
const ws = createInspectorWebSocket(wsUrl);
|
|
32
17
|
let msgId = 0;
|
|
33
18
|
const pending = new Map();
|
|
34
19
|
const eventHandlers = new Map();
|
|
@@ -128,4 +113,4 @@ function createWSClient(wsUrl, timeout) {
|
|
|
128
113
|
});
|
|
129
114
|
}
|
|
130
115
|
|
|
131
|
-
module.exports = { createWSClient };
|
|
116
|
+
module.exports = { createWSClient, createInspectorWebSocket };
|
|
@@ -101,7 +101,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
101
101
|
function fetchJson(port, pathname, timeoutMs) {
|
|
102
102
|
return new Promise((resolve, reject) => {
|
|
103
103
|
const request = http.get(
|
|
104
|
-
{ host: '
|
|
104
|
+
{ host: '127.0.0.1', port, path: pathname },
|
|
105
105
|
(response) => {
|
|
106
106
|
let body = '';
|
|
107
107
|
response.setEncoding('utf8');
|
|
@@ -241,35 +241,33 @@ function resolveArtifactRun(repo, branch, defaultBranch, runOverride) {
|
|
|
241
241
|
return resolvedArtifact(repo, branch, runOverride, revision, artifact);
|
|
242
242
|
}
|
|
243
243
|
const tried = [.../* @__PURE__ */ new Set([branch, defaultBranch])];
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (run) return resolvedArtifact(repo, candidate, run.runId, run.revision, run.artifact);
|
|
247
|
-
}
|
|
244
|
+
const run = latestRunWithArtifact(repo, tried, RUNWAY_IOS_METADATA.artifactName);
|
|
245
|
+
if (run) return resolvedArtifact(repo, run.branch, run.runId, run.revision, run.artifact);
|
|
248
246
|
throw new Error(`no Runway artifact found for ${RUNWAY_IOS_METADATA.artifactName} on ${tried.join(", ")}.`);
|
|
249
247
|
}
|
|
250
|
-
function latestRunWithArtifact(repo,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
"
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
248
|
+
function latestRunWithArtifact(repo, branches, artifactName) {
|
|
249
|
+
const output = execFileSync("gh", [
|
|
250
|
+
"api",
|
|
251
|
+
`repos/${repo}/actions/artifacts?name=${encodeURIComponent(artifactName)}&per_page=100`,
|
|
252
|
+
"--paginate",
|
|
253
|
+
"--jq",
|
|
254
|
+
".artifacts[] | select(.expired == false)"
|
|
255
|
+
], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
256
|
+
const artifacts = output.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)).filter((artifact) => artifact.name === artifactName).sort((left, right) => (right.created_at ?? "").localeCompare(left.created_at ?? ""));
|
|
257
|
+
for (const branch of branches) {
|
|
258
|
+
for (const artifact of artifacts.filter((candidate) => candidate.workflow_run?.head_branch === branch)) {
|
|
259
|
+
const runId = artifact.workflow_run?.id;
|
|
260
|
+
if (!runId) continue;
|
|
261
|
+
const run = execJson("gh", [
|
|
262
|
+
"api",
|
|
263
|
+
`repos/${repo}/actions/runs/${runId}`
|
|
264
|
+
]);
|
|
265
|
+
if (run.status === "completed" && run.conclusion === "success" && run.path === `.github/workflows/${RUNWAY_IOS_METADATA.workflow}`) {
|
|
266
|
+
return { branch, runId: String(runId), revision: artifact.workflow_run?.head_sha || String(runId), artifact };
|
|
267
|
+
}
|
|
268
|
+
process.stderr.write(`runway: skipping artifact from run ${runId} (${run.status}/${run.conclusion}, ${run.path})
|
|
269
|
+
`);
|
|
270
|
+
}
|
|
273
271
|
}
|
|
274
272
|
return void 0;
|
|
275
273
|
}
|
|
@@ -278,16 +276,13 @@ function assertValidRunId(runId) {
|
|
|
278
276
|
}
|
|
279
277
|
function findRunArtifact(repo, runId, artifactName) {
|
|
280
278
|
assertValidRunId(runId);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
} catch {
|
|
289
|
-
return void 0;
|
|
290
|
-
}
|
|
279
|
+
const pages = execJson("gh", [
|
|
280
|
+
"api",
|
|
281
|
+
`repos/${repo}/actions/runs/${runId}/artifacts`,
|
|
282
|
+
"--paginate",
|
|
283
|
+
"--slurp"
|
|
284
|
+
]);
|
|
285
|
+
return pages.flatMap((page) => page.artifacts ?? []).find((artifact) => artifact.expired === false && artifact.name === artifactName);
|
|
291
286
|
}
|
|
292
287
|
function runRevision(repo, runId) {
|
|
293
288
|
try {
|