@deeeed/metamask-harness 0.26.4 → 0.26.5

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.26.5 - 2026-07-31
6
+
7
+ ### Fixed
8
+
9
+ - Forward Mobile launch's resolved Metro port to every runtime leaf so stale slot context cannot start, compile, launch, or observe a second Metro.
10
+ - Propagate inline Segment configuration into detached Mobile Metro runs and invalidate the Metro cache when it changes.
11
+ - Navigate through Mobile's root settings navigator before changing analytics consent.
12
+
5
13
  ## 0.26.4 - 2026-07-31
6
14
 
7
15
  ### Fixed
@@ -57,6 +57,7 @@ LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
57
57
  mkdir -p "$LOG_DIR"
58
58
  LOG_FILE="$LOG_DIR/metro.log"
59
59
  PID_FILE="$LOG_DIR/metro.pid"
60
+ INLINE_ENV_FILE="$LOG_DIR/metro-inline-env.sh"
60
61
 
61
62
  # --- helpers ------------------------------------------------------------------
62
63
 
@@ -73,6 +74,24 @@ default_metro_workers() {
73
74
  fi
74
75
  }
75
76
 
77
+ write_inline_metro_env() {
78
+ local temporary name
79
+ temporary="$(mktemp "$LOG_DIR/.metro-inline-env.XXXXXX")" || return 1
80
+ chmod 600 "$temporary" || { rm -f "$temporary"; return 1; }
81
+ for name in \
82
+ SEGMENT_PROXY_URL \
83
+ SEGMENT_WRITE_KEY \
84
+ SEGMENT_FLUSH_INTERVAL \
85
+ SEGMENT_FLUSH_EVENT_LIMIT
86
+ do
87
+ if declare -p "$name" >/dev/null 2>&1; then
88
+ printf 'export %s=%q\n' "$name" "${!name}" >> "$temporary" \
89
+ || { rm -f "$temporary"; return 1; }
90
+ fi
91
+ done
92
+ mv -f "$temporary" "$INLINE_ENV_FILE"
93
+ }
94
+
76
95
  # Log-tail viewer window (session-safe): sourced so its run-owned-session rule is
77
96
  # unit-testable and can never leak a window into an unrelated user session.
78
97
  # shellcheck disable=SC1091
@@ -96,6 +115,10 @@ set +a
96
115
  # build.sh remaps MM_SENTRY_DSN from MM_SENTRY_DSN_DEV for dev builds; quick-launch
97
116
  # bypasses it, so without this Sentry never initializes in the dev client.
98
117
  [ -n "\${MM_SENTRY_DSN:-}" ] || export MM_SENTRY_DSN="\${MM_SENTRY_DSN_DEV:-}"
118
+ [ ! -f $(printf '%q' "$INLINE_ENV_FILE") ] || {
119
+ . $(printf '%q' "$INLINE_ENV_FILE")
120
+ rm -f $(printf '%q' "$INLINE_ENV_FILE")
121
+ }
99
122
  export EXPO_NO_TYPESCRIPT_SETUP=1
100
123
  export WATCHER_PORT="$(printf '%q' "$PORT")" METRO_PORT="$(printf '%q' "$PORT")"
101
124
  export METRO_MAX_WORKERS="$(printf '%q' "$METRO_WORKERS")"
@@ -165,6 +188,11 @@ printf 'Starting Metro on port %s (workers=%s%s)\n' \
165
188
  "$PORT" "$METRO_WORKERS" "$CLEAR_LABEL" >&2
166
189
  printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FILE" >&2
167
190
 
191
+ write_inline_metro_env || {
192
+ printf 'start-metro: could not stage inline Metro environment\n' >&2
193
+ exit 1
194
+ }
195
+
168
196
  if ! start_metro_tmux; then
169
197
  # Metro runs detached, writing to the log. The tmux window is a read-only tail.
170
198
  (
@@ -189,6 +217,11 @@ if ! start_metro_tmux; then
189
217
  # = build.sh ios main dev); quick-launch bypasses build.sh, so without this the
190
218
  # dev client's MM_SENTRY_DSN stays empty and Sentry never initializes.
191
219
  [ -n "${MM_SENTRY_DSN:-}" ] || export MM_SENTRY_DSN="${MM_SENTRY_DSN_DEV:-}"
220
+ [ ! -f "$INLINE_ENV_FILE" ] || {
221
+ # shellcheck disable=SC1090
222
+ . "$INLINE_ENV_FILE"
223
+ rm -f "$INLINE_ENV_FILE"
224
+ }
192
225
  export EXPO_NO_TYPESCRIPT_SETUP=1
193
226
  export WATCHER_PORT="${PORT}" METRO_PORT="${PORT}"
194
227
  export METRO_MAX_WORKERS="${METRO_WORKERS}"
@@ -3,6 +3,12 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { recipeRuntimePath } from "../../paths.js";
5
5
  const ENV_INPUTS = [".js.env", ".env", ".env.local"];
6
+ const PROCESS_ENV_INPUTS = [
7
+ "SEGMENT_PROXY_URL",
8
+ "SEGMENT_WRITE_KEY",
9
+ "SEGMENT_FLUSH_INTERVAL",
10
+ "SEGMENT_FLUSH_EVENT_LIMIT"
11
+ ];
6
12
  const BASELINE_FILE = "metro-env-baseline.json";
7
13
  function mobileMetroEnvFingerprint(target) {
8
14
  const hash = createHash("sha256");
@@ -13,6 +19,9 @@ function mobileMetroEnvFingerprint(target) {
13
19
  else hash.update("<absent>");
14
20
  hash.update("\0");
15
21
  }
22
+ for (const name of PROCESS_ENV_INPUTS) {
23
+ hash.update(`${name}\0${process.env[name] ?? "<absent>"}\0`);
24
+ }
16
25
  return hash.digest("hex");
17
26
  }
18
27
  function mobileMetroEnvCheck(target) {
@@ -56,20 +56,34 @@ async function prepareMobile(target, opts = {}) {
56
56
  target,
57
57
  platform,
58
58
  json,
59
- preflightMode
59
+ preflightMode,
60
+ opts.watcherPort
60
61
  );
61
- return launch2.status === 0 ? startMobileConsoleForwarder(target, platform, json) : launch2;
62
+ return launch2.status === 0 ? startMobileConsoleForwarder(target, platform, json, opts.watcherPort) : launch2;
62
63
  }
63
- const surface = await ensureMobileHumanSurface(target, platform, json);
64
+ const surface = await ensureMobileHumanSurface(
65
+ target,
66
+ platform,
67
+ json,
68
+ opts.watcherPort
69
+ );
64
70
  if (surface.status !== 0) return surface;
65
71
  const confirm = await dispatchAction(
66
72
  { id: "wait-for-bridge", cwd: target, argv: ["--max-polls", String(READY_BRIDGE_CONFIRM_POLLS)] },
67
73
  target,
68
74
  platform,
69
75
  json,
70
- preflightMode
76
+ preflightMode,
77
+ opts.watcherPort
71
78
  );
72
- if (confirm.status === 0) return startMobileConsoleForwarder(target, platform, json);
79
+ if (confirm.status === 0) {
80
+ return startMobileConsoleForwarder(
81
+ target,
82
+ platform,
83
+ json,
84
+ opts.watcherPort
85
+ );
86
+ }
73
87
  if (!json) {
74
88
  process.stderr.write(
75
89
  `launch: runtime looked ready but no ${platform} bridge target answered; launching the app.
@@ -81,9 +95,10 @@ async function prepareMobile(target, opts = {}) {
81
95
  target,
82
96
  platform,
83
97
  json,
84
- preflightMode
98
+ preflightMode,
99
+ opts.watcherPort
85
100
  );
86
- return launch.status === 0 ? startMobileConsoleForwarder(target, platform, json) : launch;
101
+ return launch.status === 0 ? startMobileConsoleForwarder(target, platform, json, opts.watcherPort) : launch;
87
102
  }
88
103
  if (report.decision === "unknown") {
89
104
  const msg = "mobile prepare: runtime state unknown\n Next: run mm-harness verify --adapter mobile --target <checkout>";
@@ -97,7 +112,14 @@ async function prepareMobile(target, opts = {}) {
97
112
  ) : report.actions,
98
113
  restartApp
99
114
  );
100
- const actionResult = await dispatchActionSequence(actions, target, platform, json, preflightMode);
115
+ const actionResult = await dispatchActionSequence(
116
+ actions,
117
+ target,
118
+ platform,
119
+ json,
120
+ preflightMode,
121
+ opts.watcherPort
122
+ );
101
123
  if (actionResult.status !== 0) return actionResult;
102
124
  if (report.decision === "install" && !process.env["RECIPE_UP_INSTALL_ATTEMPTED"]) {
103
125
  process.env["RECIPE_UP_INSTALL_ATTEMPTED"] = "1";
@@ -116,9 +138,21 @@ async function prepareMobile(target, opts = {}) {
116
138
  return { status: EXIT.runtime, output: msg };
117
139
  }
118
140
  case "ready": {
119
- const surface = await ensureMobileHumanSurface(target, platform, json);
141
+ const surface = await ensureMobileHumanSurface(
142
+ target,
143
+ platform,
144
+ json,
145
+ opts.watcherPort
146
+ );
120
147
  if (surface.status !== 0) return surface;
121
- const bridge = await dispatchAction({ id: "wait-for-bridge", cwd: target }, target, platform, json, preflightMode);
148
+ const bridge = await dispatchAction(
149
+ { id: "wait-for-bridge", cwd: target },
150
+ target,
151
+ platform,
152
+ json,
153
+ preflightMode,
154
+ opts.watcherPort
155
+ );
122
156
  if (bridge.status !== 0) return bridge;
123
157
  break;
124
158
  }
@@ -128,7 +162,8 @@ async function prepareMobile(target, opts = {}) {
128
162
  target,
129
163
  platform,
130
164
  json,
131
- preflightMode
165
+ preflightMode,
166
+ opts.watcherPort
132
167
  );
133
168
  if (result.status !== 0) return result;
134
169
  break;
@@ -142,19 +177,33 @@ async function prepareMobile(target, opts = {}) {
142
177
  }
143
178
  }
144
179
  }
145
- return startMobileConsoleForwarder(target, platform, json);
180
+ return startMobileConsoleForwarder(
181
+ target,
182
+ platform,
183
+ json,
184
+ opts.watcherPort
185
+ );
146
186
  }
147
- function startMobileConsoleForwarder(target, platform, json) {
187
+ function startMobileConsoleForwarder(target, platform, json, watcherPort) {
148
188
  return dispatchAction(
149
189
  { id: "start-console-forwarder", cwd: target, argv: json ? ["--quiet"] : [] },
150
190
  target,
151
191
  platform,
152
- json
192
+ json,
193
+ "fast",
194
+ watcherPort
153
195
  );
154
196
  }
155
- function ensureMobileHumanSurface(target, platform, json) {
197
+ function ensureMobileHumanSurface(target, platform, json, watcherPort) {
156
198
  if (platform !== "ios") return Promise.resolve({ status: 0, output: "" });
157
- return dispatchAction({ id: "ensure-device-ui", cwd: target }, target, platform, json);
199
+ return dispatchAction(
200
+ { id: "ensure-device-ui", cwd: target },
201
+ target,
202
+ platform,
203
+ json,
204
+ "fast",
205
+ watcherPort
206
+ );
158
207
  }
159
208
  function withAppRestart(actions, restartApp) {
160
209
  if (!restartApp) return actions;
@@ -162,7 +211,7 @@ function withAppRestart(actions, restartApp) {
162
211
  (action) => action.id === "launch-mobile-runtime" ? { ...action, argv: [...action.argv ?? [], "--restart"] } : action
163
212
  );
164
213
  }
165
- async function dispatchActionSequence(requestedActions, target, platform, json, preflightMode) {
214
+ async function dispatchActionSequence(requestedActions, target, platform, json, preflightMode, watcherPort) {
166
215
  const resolved = path.resolve(target);
167
216
  const actions = applyMobileMetroEnvPolicy(resolved, requestedActions);
168
217
  let pendingMetroEnvFingerprint;
@@ -170,7 +219,14 @@ async function dispatchActionSequence(requestedActions, target, platform, json,
170
219
  if (action.id === "start-metro") {
171
220
  pendingMetroEnvFingerprint = mobileMetroEnvCheck(resolved).fingerprint;
172
221
  }
173
- const result = await dispatchAction(action, target, platform, json, preflightMode);
222
+ const result = await dispatchAction(
223
+ action,
224
+ target,
225
+ platform,
226
+ json,
227
+ preflightMode,
228
+ watcherPort
229
+ );
174
230
  if (result.status !== 0) return result;
175
231
  if (action.id === "yarn-setup") recordDepsBaseline(resolved);
176
232
  if (action.id === "prewarm-bundle" && pendingMetroEnvFingerprint) {
@@ -184,7 +240,7 @@ async function dispatchActionSequence(requestedActions, target, platform, json,
184
240
  }
185
241
  return { status: 0, output: "" };
186
242
  }
187
- async function dispatchAction(action, target, platform, json, preflightMode = "fast") {
243
+ async function dispatchAction(action, target, platform, json, preflightMode = "fast", watcherPort) {
188
244
  const cwd = action.cwd ?? target;
189
245
  switch (action.id) {
190
246
  case "yarn-setup": {
@@ -194,34 +250,62 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
194
250
  case "start-metro": {
195
251
  const leaf = path.join(runnerDir, "adapters/mobile/start-metro.sh");
196
252
  const extra = action.argv ?? [];
197
- return spawnScriptStreaming(leaf, ["--target", cwd, ...extra], target);
253
+ return spawnScriptStreaming(
254
+ leaf,
255
+ withWatcherPort(["--target", cwd, ...extra], watcherPort),
256
+ target
257
+ );
198
258
  }
199
259
  case "start-console-forwarder": {
200
260
  const leaf = path.join(runnerDir, "adapters/mobile/start-console-forwarder.sh");
201
- return spawnScriptStreaming(leaf, ["--target", cwd, ...action.argv ?? []], target);
261
+ return spawnScriptStreaming(
262
+ leaf,
263
+ withWatcherPort(
264
+ ["--target", cwd, ...action.argv ?? []],
265
+ watcherPort
266
+ ),
267
+ target
268
+ );
202
269
  }
203
270
  case "prewarm-bundle": {
204
271
  const leaf = path.join(runnerDir, "adapters/mobile/prewarm-bundle.sh");
205
- return spawnScriptStreaming(leaf, ["--platform", platform, "--target", cwd], target);
272
+ return spawnScriptStreaming(
273
+ leaf,
274
+ withWatcherPort(
275
+ ["--platform", platform, "--target", cwd, ...action.argv ?? []],
276
+ watcherPort
277
+ ),
278
+ target
279
+ );
206
280
  }
207
281
  case "wait-for-bridge": {
208
282
  const leaf = path.join(runnerDir, "adapters/mobile/wait-for-bridge.sh");
209
283
  const extra = action.argv ?? [];
210
- return spawnScriptStreaming(leaf, ["--target", cwd, "--platform", platform, ...extra], target);
284
+ return spawnScriptStreaming(
285
+ leaf,
286
+ withWatcherPort(
287
+ ["--target", cwd, "--platform", platform, ...extra],
288
+ watcherPort
289
+ ),
290
+ target
291
+ );
211
292
  }
212
293
  case "launch-mobile-runtime": {
213
294
  const leaf = path.join(runnerDir, "adapters/mobile/open-device.sh");
214
295
  return spawnScriptStreaming(
215
296
  leaf,
216
- [
217
- "--platform",
218
- platform,
219
- "--target",
220
- cwd,
221
- "--preflight-mode",
222
- preflightMode,
223
- ...action.argv ?? []
224
- ],
297
+ withWatcherPort(
298
+ [
299
+ "--platform",
300
+ platform,
301
+ "--target",
302
+ cwd,
303
+ "--preflight-mode",
304
+ preflightMode,
305
+ ...action.argv ?? []
306
+ ],
307
+ watcherPort
308
+ ),
225
309
  target,
226
310
  POD_PROBE_ENV
227
311
  );
@@ -230,7 +314,10 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
230
314
  const leaf = path.join(runnerDir, "adapters/mobile/open-device.sh");
231
315
  return spawnScriptStreaming(
232
316
  leaf,
233
- ["--platform", platform, "--target", cwd, "--ui-only"],
317
+ withWatcherPort(
318
+ ["--platform", platform, "--target", cwd, "--ui-only"],
319
+ watcherPort
320
+ ),
234
321
  target,
235
322
  POD_PROBE_ENV
236
323
  );
@@ -245,6 +332,10 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
245
332
  return { status: 0, output: "" };
246
333
  }
247
334
  }
335
+ function withWatcherPort(args, watcherPort) {
336
+ if (watcherPort === void 0 || args.includes("--port")) return args;
337
+ return [...args, "--port", String(watcherPort)];
338
+ }
248
339
  export {
249
340
  mobileRuntimeStatus,
250
341
  prepareMobile
@@ -6,11 +6,8 @@ const SWITCH_IDS = {
6
6
  marketing: 'data-collection-switch',
7
7
  };
8
8
 
9
- function toggleExpression(testId, expected, timeoutMs) {
10
- return `(async () => {
11
- const deadline = Date.now() + ${JSON.stringify(timeoutMs)};
12
- let invoked = false;
13
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
9
+ function toggleExpression(testId, expected, invoke) {
10
+ return `(() => {
14
11
  const find = () => {
15
12
  const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
16
13
  const rootsFor = hook?.getFiberRoots;
@@ -28,57 +25,96 @@ function toggleExpression(testId, expected, timeoutMs) {
28
25
  }
29
26
  return null;
30
27
  };
31
- while (Date.now() < deadline) {
32
- const target = find();
33
- const props = target?.memoizedProps;
34
- if (props && Boolean(props.value) === ${JSON.stringify(expected)}) {
35
- return { ok: true, testId: ${JSON.stringify(testId)}, changed: invoked };
36
- }
37
- if (props && !invoked) {
38
- if (typeof props.onValueChange !== 'function') {
39
- throw new Error('Consent switch has no onValueChange handler: ${testId}');
40
- }
41
- invoked = true;
42
- await props.onValueChange(${JSON.stringify(expected)});
43
- }
44
- await delay(100);
28
+ const target = find();
29
+ const props = target?.memoizedProps;
30
+ if (!props) return { matched: false, found: false, invoked: false };
31
+ if (Boolean(props.value) === ${JSON.stringify(expected)}) {
32
+ return { matched: true, found: true, invoked: false };
33
+ }
34
+ if (!${JSON.stringify(invoke)}) {
35
+ return { matched: false, found: true, invoked: false };
36
+ }
37
+ if (typeof props.onValueChange !== 'function') {
38
+ throw new Error('Consent switch has no onValueChange handler: ${testId}');
45
39
  }
46
- throw new Error('Timed out setting consent switch ${testId} to ${expected}.');
40
+ return Promise.resolve(
41
+ props.onValueChange(${JSON.stringify(expected)})
42
+ ).then(() => ({
43
+ matched: false,
44
+ found: true,
45
+ invoked: true
46
+ }));
47
47
  })()`;
48
48
  }
49
49
 
50
- function stateExpression(participate, marketing, timeoutMs) {
51
- return `(async () => {
52
- const deadline = Date.now() + ${JSON.stringify(timeoutMs)};
53
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
54
- while (Date.now() < deadline) {
55
- const state = globalThis.store?.getState?.();
56
- const analytics = state?.engine?.backgroundState?.AnalyticsController ?? {};
57
- const consent = {
58
- optedIn: Boolean(analytics.optedIn),
59
- dataCollectionForMarketing: Boolean(state?.security?.dataCollectionForMarketing),
60
- analyticsId: analytics.analyticsId ? 'set' : null
61
- };
62
- if (
50
+ function stateExpression(participate, marketing) {
51
+ return `(() => {
52
+ const state = globalThis.store?.getState?.();
53
+ const analytics = state?.engine?.backgroundState?.AnalyticsController ?? {};
54
+ const consent = {
55
+ optedIn: Boolean(analytics.optedIn),
56
+ dataCollectionForMarketing: Boolean(state?.security?.dataCollectionForMarketing),
57
+ analyticsId: analytics.analyticsId ? 'set' : null
58
+ };
59
+ return {
60
+ ready:
63
61
  consent.optedIn === ${JSON.stringify(participate)} &&
64
62
  consent.dataCollectionForMarketing === ${JSON.stringify(marketing)} &&
65
- (!${JSON.stringify(participate)} || consent.analyticsId === 'set')
66
- ) return consent;
67
- await delay(100);
68
- }
69
- throw new Error('Timed out reading back Mobile analytics consent.');
63
+ (!${JSON.stringify(participate)} || consent.analyticsId === 'set'),
64
+ consent
65
+ };
70
66
  })()`;
71
67
  }
72
68
 
69
+ function delay(ms) {
70
+ return new Promise((resolve) => setTimeout(resolve, ms));
71
+ }
72
+
73
+ async function setSwitch(input, testId, expected, timeoutMs) {
74
+ const deadline = Date.now() + timeoutMs;
75
+ let invoked = false;
76
+ while (Date.now() < deadline) {
77
+ const result = await evalAsync(
78
+ input,
79
+ toggleExpression(testId, expected, !invoked),
80
+ );
81
+ invoked ||= Boolean(result?.invoked);
82
+ if (result?.matched) {
83
+ return { ...result, changed: invoked };
84
+ }
85
+ await delay(100);
86
+ }
87
+ throw new Error(`Timed out setting consent switch ${testId} to ${expected}.`);
88
+ }
89
+
90
+ async function readConsent(input, participate, marketing, timeoutMs) {
91
+ const deadline = Date.now() + timeoutMs;
92
+ while (Date.now() < deadline) {
93
+ const result = await evalAsync(
94
+ input,
95
+ stateExpression(participate, marketing),
96
+ );
97
+ if (result?.ready) {
98
+ return result.consent;
99
+ }
100
+ await delay(100);
101
+ }
102
+ throw new Error('Timed out reading back Mobile analytics consent.');
103
+ }
104
+
73
105
  runAdapter(async (input) => {
74
106
  const { participate, marketing, timeoutMs } = consentParams(input.node);
75
107
 
76
- const navigation = await navigate(input, 'SecuritySettings');
77
- await evalAsync(input, toggleExpression(SWITCH_IDS.participate, participate, timeoutMs));
78
- await evalAsync(input, toggleExpression(SWITCH_IDS.marketing, marketing, timeoutMs));
79
- const consent = await evalAsync(
108
+ const navigation = await navigate(input, 'SettingsView', {
109
+ screen: 'SecuritySettings',
110
+ }, 'SecuritySettings');
111
+ await setSwitch(input, SWITCH_IDS.participate, participate, timeoutMs);
112
+ await setSwitch(input, SWITCH_IDS.marketing, marketing, timeoutMs);
113
+ const consent = await readConsent(
80
114
  input,
81
- stateExpression(participate, marketing, timeoutMs),
115
+ participate,
116
+ marketing,
117
+ timeoutMs,
82
118
  );
83
119
 
84
120
  return {
@@ -355,11 +355,14 @@ export async function evalSync(input, expression) {
355
355
  return parseMaybeJson(await bridgeCommand(input, ['eval', expression]));
356
356
  }
357
357
 
358
- export async function navigate(input, route, params = {}) {
358
+ export async function navigate(input, route, params = {}, expectedRoute) {
359
359
  const navigation = await bridgeCommand(input, ['navigate', route, JSON.stringify(params)]);
360
- const verifiedRoute = navigation && typeof navigation === 'object' && navigation.navigated
361
- ? String(navigation.navigated)
362
- : String(route);
360
+ const verifiedRoute = String(
361
+ expectedRoute ??
362
+ (navigation && typeof navigation === 'object' && navigation.navigated
363
+ ? navigation.navigated
364
+ : route),
365
+ );
363
366
  const currentRoute = await waitForRoute(input, verifiedRoute, Number(input.node?.navigation_timeout_ms ?? 15000));
364
367
  return { ...navigation, currentRoute, verifiedRoute };
365
368
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.26.4",
3
+ "version": "0.26.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"