@deeeed/metamask-harness 0.86.2 → 0.88.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 +49 -0
- package/adapters/extension/ensure-browser.sh +6 -10
- package/adapters/extension/inject.mjs +1 -0
- package/adapters/extension/launch-browser.cjs +31 -4
- package/adapters/extension/profile-extension-state.cjs +156 -0
- package/adapters/extension/readiness.mjs +21 -2
- package/adapters/manifest.json +8 -0
- package/adapters/mobile/wait-for-bridge.cjs +9 -3
- package/dist/adapters/extension/actions.js +2 -1
- package/dist/adapters/extension/run.js +6 -0
- package/dist/adapters/mobile/action-inputs.js +20 -0
- package/dist/adapters/mobile/actions.js +18 -1
- package/dist/adapters/mobile/android-screenrecord.js +274 -0
- package/dist/adapters/mobile/readiness.js +2 -1
- package/dist/adapters/mobile/recording-outcome.js +54 -0
- package/dist/adapters/mobile/recording-target.js +11 -0
- package/dist/adapters/mobile/run.js +58 -15
- package/dist/adapters/mobile/ui.js +96 -2
- package/dist/adapters/perps-mainnet-cleanup.js +122 -0
- package/dist/adapters/perps-order-inputs.js +43 -28
- package/dist/adapters.js +4 -1
- package/dist/commands/doctor.js +33 -14
- package/dist/commands/status-probe.js +2 -1
- package/dist/engine.js +11 -2
- package/dist/funding-execution-context.js +13 -8
- package/dist/host.js +6 -2
- package/dist/index.js +3 -0
- package/dist/mm-harness-cli.js +4 -2
- package/dist/provider.js +72 -0
- package/dist/recipe-security.js +1 -0
- package/docs/CONTRIBUTING.md +7 -0
- package/docs/RECIPES.md +96 -9
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +5 -1
- package/docs/cli-contract.md +1 -1
- package/library/actions/core/perps/read_account.mjs +4 -2
- package/library/actions/core/perps/read_orders.mjs +4 -2
- package/library/actions/core/perps/read_positions.mjs +4 -2
- package/library/actions/extension/analytics/consent.mjs +7 -2
- package/library/actions/extension/perps/compare_provider_market.mjs +12 -8
- package/library/actions/extension/perps/mutation-receipt.mjs +124 -26
- package/library/actions/extension/perps/perps.mjs +298 -39
- package/library/actions/extension/perps/read_snapshot.mjs +6 -2
- package/library/actions/extension/perps/set_network.mjs +6 -0
- package/library/actions/extension/platform/cdp.mjs +6 -2
- package/library/actions/extension/ui/navigate.mjs +8 -0
- package/library/actions/mobile/perps/perps.mjs +264 -102
- package/library/actions/mobile/perps/place_order.mjs +7 -4
- package/library/actions/mobile/platform/bridge.mjs +171 -10
- package/library/actions/mobile/wallet/home.mjs +7 -2
- package/library/actions/mobile/wallet/select_account.mjs +4 -0
- package/library/actions/shared/app/live-adapter-deadline.mjs +8 -0
- package/library/actions/shared/perps/mainnet-order-ledger.mjs +378 -0
- package/library/actions/shared/perps/order-policy.mjs +56 -19
- package/library/actions/shared/perps/rate-limit.mjs +93 -0
- package/library/actions/shared/perps/visible-state.mjs +3 -2
- package/library/actions/shared/wallet/fixture-accounts.mjs +9 -3
- package/library/manifests/extension.action-manifest.json +36 -5
- package/library/manifests/mobile.action-manifest.json +48 -10
- package/package.json +14 -9
- package/scripts/validate-release-report.mjs +14 -2
- package/site/assets/help-recipes.json +2 -1
|
@@ -29,6 +29,13 @@ const { resolvePort } = configModule;
|
|
|
29
29
|
// second import path into the cjs bridge-runtime.
|
|
30
30
|
export const MOBILE_BRIDGE_ERROR_CODES = BRIDGE_ERROR_CODES;
|
|
31
31
|
export const MOBILE_BRIDGE_RESULT_ERROR_CODES = BRIDGE_RESULT_ERROR_CODES;
|
|
32
|
+
// Budget exhaustion, kept apart from the bridge's CDP_TIMEOUT: ACTION_DEADLINE
|
|
33
|
+
// is a bridge command its caller's deadline refused or cut short;
|
|
34
|
+
// DOMAIN_SETUP_DEADLINE is a setup phase that ran out of its slice.
|
|
35
|
+
export const MOBILE_BUDGET_ERROR_CODES = Object.freeze({
|
|
36
|
+
ACTION_DEADLINE: 'ACTION_DEADLINE',
|
|
37
|
+
DOMAIN_SETUP_DEADLINE: 'DOMAIN_SETUP_DEADLINE',
|
|
38
|
+
});
|
|
32
39
|
|
|
33
40
|
const execFileAsync = promisify(execFile);
|
|
34
41
|
const ACTION_DEADLINE_FIELD = '_action_deadline_epoch_ms';
|
|
@@ -256,7 +263,11 @@ export function resolveMobileTarget(input) {
|
|
|
256
263
|
const iosSimulator = input.node?.simulator ?? input.node?.ios_simulator ?? contextEnv.IOS_SIMULATOR ?? process.env.IOS_SIMULATOR;
|
|
257
264
|
const androidDevice = input.node?.android_device ?? contextEnv.ANDROID_DEVICE ?? process.env.ANDROID_DEVICE;
|
|
258
265
|
const androidTargetDeviceName = input.node?.android_target_device_name ?? input.node?.androidTargetDeviceName ?? contextEnv.ANDROID_TARGET_DEVICE_NAME ?? process.env.ANDROID_TARGET_DEVICE_NAME;
|
|
259
|
-
const
|
|
266
|
+
const explicitAdbSerial = input.node?.adb_serial ?? contextEnv.ADB_SERIAL ?? contextEnv.ANDROID_SERIAL ?? process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL;
|
|
267
|
+
const adbSerial = explicitAdbSerial ?? androidDevice;
|
|
268
|
+
// Only an explicit adb serial says which device class answers: ANDROID_DEVICE
|
|
269
|
+
// alone can be an AVD or Metro device name.
|
|
270
|
+
const physicalAndroid = Boolean(explicitAdbSerial) && !isAndroidEmulatorSerial(explicitAdbSerial);
|
|
260
271
|
if (requestedPlatform === 'ios') {
|
|
261
272
|
return {
|
|
262
273
|
watcherPort,
|
|
@@ -264,6 +275,7 @@ export function resolveMobileTarget(input) {
|
|
|
264
275
|
androidDevice: undefined,
|
|
265
276
|
androidTargetDeviceName: undefined,
|
|
266
277
|
adbSerial: undefined,
|
|
278
|
+
physicalAndroid: false,
|
|
267
279
|
requestedPlatform,
|
|
268
280
|
};
|
|
269
281
|
}
|
|
@@ -274,6 +286,7 @@ export function resolveMobileTarget(input) {
|
|
|
274
286
|
androidDevice,
|
|
275
287
|
androidTargetDeviceName,
|
|
276
288
|
adbSerial,
|
|
289
|
+
physicalAndroid,
|
|
277
290
|
requestedPlatform,
|
|
278
291
|
};
|
|
279
292
|
}
|
|
@@ -283,10 +296,15 @@ export function resolveMobileTarget(input) {
|
|
|
283
296
|
androidDevice,
|
|
284
297
|
androidTargetDeviceName,
|
|
285
298
|
adbSerial,
|
|
299
|
+
physicalAndroid,
|
|
286
300
|
requestedPlatform,
|
|
287
301
|
};
|
|
288
302
|
}
|
|
289
303
|
|
|
304
|
+
export function isAndroidEmulatorSerial(serial) {
|
|
305
|
+
return String(serial ?? '').startsWith('emulator-');
|
|
306
|
+
}
|
|
307
|
+
|
|
290
308
|
export async function openExternalDeeplink(input, url, timeout = 15000) {
|
|
291
309
|
const target = resolveMobileTarget(input);
|
|
292
310
|
const requestedPlatform = String(
|
|
@@ -470,17 +488,17 @@ export async function bridgeCommand(input, args) {
|
|
|
470
488
|
? actionDeadline - Date.now()
|
|
471
489
|
: configuredTimeoutMs;
|
|
472
490
|
if (remainingActionMs <= 0) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
`Mobile CDP bridge command did not start before its action deadline: ${String(args[0] ?? 'unknown')}`,
|
|
476
|
-
),
|
|
477
|
-
BRIDGE_ERROR_CODES.CDP_TIMEOUT,
|
|
491
|
+
const error = new Error(
|
|
492
|
+
`ACTION_DEADLINE: bridge command ${String(args[0] ?? 'unknown')} was not started; its action deadline passed ${Math.round(-remainingActionMs)}ms ago (per-command limit ${configuredTimeoutMs}ms).`,
|
|
478
493
|
);
|
|
494
|
+
error.deadlineEpochMs = actionDeadline;
|
|
495
|
+
throw coded(error, MOBILE_BUDGET_ERROR_CODES.ACTION_DEADLINE);
|
|
479
496
|
}
|
|
480
497
|
const timeoutMs = Math.max(
|
|
481
498
|
1,
|
|
482
499
|
Math.min(configuredTimeoutMs, remainingActionMs),
|
|
483
500
|
);
|
|
501
|
+
const cutByActionDeadline = timeoutMs < configuredTimeoutMs;
|
|
484
502
|
const result = await new Promise((resolve, reject) => {
|
|
485
503
|
const child = spawn(process.execPath, [script, ...args], {
|
|
486
504
|
cwd: input.context.projectRoot,
|
|
@@ -521,8 +539,20 @@ export async function bridgeCommand(input, args) {
|
|
|
521
539
|
});
|
|
522
540
|
if (result.timedOut) {
|
|
523
541
|
const command = ['node', path.relative(input.context.projectRoot, script), ...redactBridgeArgs(args)].join(' ');
|
|
542
|
+
// A timer the action deadline shortened means the caller's budget ran out,
|
|
543
|
+
// not the bridge: say so, and keep it out of the target-transition codes
|
|
544
|
+
// so route waits stop instead of retrying refused commands.
|
|
545
|
+
if (cutByActionDeadline) {
|
|
546
|
+
const error = new Error(
|
|
547
|
+
`ACTION_DEADLINE: bridge command ${String(args[0] ?? 'unknown')} was stopped after ${result.timeoutMs}ms when its action deadline ended (per-command limit ${configuredTimeoutMs}ms): ${command}`,
|
|
548
|
+
);
|
|
549
|
+
error.deadlineEpochMs = actionDeadline;
|
|
550
|
+
throw coded(error, MOBILE_BUDGET_ERROR_CODES.ACTION_DEADLINE);
|
|
551
|
+
}
|
|
524
552
|
throw coded(
|
|
525
|
-
new Error(
|
|
553
|
+
new Error(
|
|
554
|
+
`Mobile CDP bridge command timed out after ${result.timeoutMs}ms (CDP_COMMAND_TIMEOUT: per-command limit ${configuredTimeoutMs}ms${Number.isFinite(actionDeadline) ? `, action budget had ${Math.max(0, Math.round(actionDeadline - Date.now()))}ms left` : ''}): ${command}`,
|
|
555
|
+
),
|
|
526
556
|
BRIDGE_ERROR_CODES.CDP_TIMEOUT,
|
|
527
557
|
);
|
|
528
558
|
}
|
|
@@ -628,6 +658,13 @@ export function isTargetTransition(error) {
|
|
|
628
658
|
return TARGET_TRANSITION_CODES.has(error?.code);
|
|
629
659
|
}
|
|
630
660
|
|
|
661
|
+
// A read a target transition interrupted, or the action deadline cut short,
|
|
662
|
+
// has no answer yet. Polling callers treat it as "not yet" so their own loop
|
|
663
|
+
// ends at the deadline with its own timeout message.
|
|
664
|
+
export function isUnansweredRead(error) {
|
|
665
|
+
return isTargetTransition(error) || error?.code === MOBILE_BUDGET_ERROR_CODES.ACTION_DEADLINE;
|
|
666
|
+
}
|
|
667
|
+
|
|
631
668
|
function valueContains(actual, expected) {
|
|
632
669
|
if (Array.isArray(expected)) {
|
|
633
670
|
return Array.isArray(actual) &&
|
|
@@ -675,19 +712,140 @@ export function earliestDeadline(input, deadline) {
|
|
|
675
712
|
|
|
676
713
|
// The earlier of now + timeoutMs and the calling action's deadline. Route waits
|
|
677
714
|
// use it because past the action deadline bridgeCommand refuses to start
|
|
678
|
-
// get-route (
|
|
679
|
-
// never see a route that did arrive.
|
|
715
|
+
// get-route (ACTION_DEADLINE): polling on would only retry that refusal and
|
|
716
|
+
// could never see a route that did arrive.
|
|
680
717
|
export function deadlineFromTimeout(input, timeoutMs) {
|
|
681
718
|
return earliestDeadline(input, Date.now() + Math.max(0, Number(timeoutMs)));
|
|
682
719
|
}
|
|
683
720
|
|
|
721
|
+
// Default budgets by device class. A physical Android device over USB answers
|
|
722
|
+
// bridge calls in 0.1-0.5 s when idle and slower while the app is busy; a Perps
|
|
723
|
+
// market-details -> home navigation on a Pixel 6a took ~18 s. Emulators and
|
|
724
|
+
// simulators keep the shorter budgets so a real hang there still fails fast.
|
|
725
|
+
const MOBILE_BUDGET_DEFAULTS = Object.freeze({
|
|
726
|
+
virtual: { setupMs: 30_000, navigationMs: 15_000 },
|
|
727
|
+
physical: { setupMs: 60_000, navigationMs: 30_000 },
|
|
728
|
+
});
|
|
729
|
+
// Per bridge command inside a domain setup, so one hung command cannot spend
|
|
730
|
+
// the whole setup budget. Recipes override it with cdp_timeout_ms.
|
|
731
|
+
export const SETUP_COMMAND_TIMEOUT_MS = 15_000;
|
|
732
|
+
|
|
733
|
+
function positiveMs(value) {
|
|
734
|
+
const ms = Number(value);
|
|
735
|
+
return value !== null && value !== '' && Number.isFinite(ms) && ms > 0 ? ms : null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Env overrides (MM_HARNESS_MOBILE_SETUP_TIMEOUT_MS,
|
|
739
|
+
// MM_HARNESS_MOBILE_NAVIGATION_TIMEOUT_MS) replace the device-class defaults;
|
|
740
|
+
// a node's own timeout_ms / navigation_timeout_ms still wins over both.
|
|
741
|
+
export function mobileBudgetDefaults(input) {
|
|
742
|
+
const env = { ...process.env, ...(input.context?.env || {}) };
|
|
743
|
+
const deviceClass = resolveMobileTarget(input).physicalAndroid ? 'physical' : 'virtual';
|
|
744
|
+
const defaults = MOBILE_BUDGET_DEFAULTS[deviceClass];
|
|
745
|
+
return {
|
|
746
|
+
deviceClass,
|
|
747
|
+
setupMs: positiveMs(env.MM_HARNESS_MOBILE_SETUP_TIMEOUT_MS) ?? defaults.setupMs,
|
|
748
|
+
navigationMs:
|
|
749
|
+
positiveMs(env.MM_HARNESS_MOBILE_NAVIGATION_TIMEOUT_MS) ?? defaults.navigationMs,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function mobileNavigationTimeoutMs(input) {
|
|
754
|
+
return positiveMs(input.node?.navigation_timeout_ms) ?? mobileBudgetDefaults(input).navigationMs;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function mobileSetupTimeoutMs(input) {
|
|
758
|
+
return positiveMs(input.node?.timeout_ms) ?? mobileBudgetDefaults(input).setupMs;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// One overall budget for a multi-step domain setup (unlock, navigation, state
|
|
762
|
+
// convergence). Each phase runs on its own deadline: the rest of the setup
|
|
763
|
+
// budget, or less when sliceMs bounds it. Each bridge command inside a phase
|
|
764
|
+
// gets the per-command limit, capped by that phase deadline. A phase that runs
|
|
765
|
+
// out of time fails as DOMAIN_SETUP_DEADLINE naming the phase and both limits,
|
|
766
|
+
// not as a CDP timeout of whichever command happened to be running.
|
|
767
|
+
export function createSetupBudget(input, action) {
|
|
768
|
+
const { deviceClass } = mobileBudgetDefaults(input);
|
|
769
|
+
const navigationMs = mobileNavigationTimeoutMs(input);
|
|
770
|
+
const commandMs =
|
|
771
|
+
positiveMs(input.node?.bridge_timeout_ms ?? input.node?.cdp_timeout_ms) ??
|
|
772
|
+
SETUP_COMMAND_TIMEOUT_MS;
|
|
773
|
+
const startedAt = Date.now();
|
|
774
|
+
const deadline = deadlineFromTimeout(input, mobileSetupTimeoutMs(input));
|
|
775
|
+
const limitMs = deadline - startedAt;
|
|
776
|
+
|
|
777
|
+
function deadlineError(phase, phaseStartedAt, phaseLimitMs, sliced, cause) {
|
|
778
|
+
const now = Date.now();
|
|
779
|
+
const budget = {
|
|
780
|
+
action,
|
|
781
|
+
phase,
|
|
782
|
+
deviceClass,
|
|
783
|
+
phaseElapsedMs: now - phaseStartedAt,
|
|
784
|
+
phaseLimitMs,
|
|
785
|
+
setupElapsedMs: now - startedAt,
|
|
786
|
+
setupLimitMs: limitMs,
|
|
787
|
+
};
|
|
788
|
+
const phaseText = cause === null
|
|
789
|
+
? `phase '${phase}' could not start`
|
|
790
|
+
: `phase '${phase}' ran ${budget.phaseElapsedMs}ms of its ${phaseLimitMs}ms ${sliced ? 'slice' : 'share of the setup budget'}`;
|
|
791
|
+
const error = new Error(
|
|
792
|
+
`DOMAIN_SETUP_DEADLINE: ${action} ${phaseText}; setup ran ${budget.setupElapsedMs}ms of ${limitMs}ms (${deviceClass} device). ` +
|
|
793
|
+
`Raise timeout_ms (setup) or navigation_timeout_ms (navigation slice) on the node, or MM_HARNESS_MOBILE_SETUP_TIMEOUT_MS / MM_HARNESS_MOBILE_NAVIGATION_TIMEOUT_MS.` +
|
|
794
|
+
(cause === null ? '' : `\n Cause: ${String(cause?.message ?? cause)}`),
|
|
795
|
+
cause === null ? undefined : { cause },
|
|
796
|
+
);
|
|
797
|
+
error.budget = budget;
|
|
798
|
+
return coded(error, MOBILE_BUDGET_ERROR_CODES.DOMAIN_SETUP_DEADLINE);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return {
|
|
802
|
+
deadline,
|
|
803
|
+
limitMs,
|
|
804
|
+
navigationMs,
|
|
805
|
+
async phase(name, run, { sliceMs } = {}) {
|
|
806
|
+
const phaseStartedAt = Date.now();
|
|
807
|
+
const sliced = sliceMs !== undefined && phaseStartedAt + sliceMs < deadline;
|
|
808
|
+
const phaseDeadline = sliced ? phaseStartedAt + sliceMs : deadline;
|
|
809
|
+
const phaseLimitMs = phaseDeadline - phaseStartedAt;
|
|
810
|
+
if (phaseLimitMs <= 0) {
|
|
811
|
+
throw deadlineError(name, phaseStartedAt, phaseLimitMs, false, null);
|
|
812
|
+
}
|
|
813
|
+
const commandLimitMs = Math.min(commandMs, phaseLimitMs);
|
|
814
|
+
const phaseInput = {
|
|
815
|
+
...input,
|
|
816
|
+
node: {
|
|
817
|
+
...input.node,
|
|
818
|
+
[ACTION_DEADLINE_FIELD]: phaseDeadline,
|
|
819
|
+
timeout_ms: phaseLimitMs,
|
|
820
|
+
bridge_timeout_ms: commandLimitMs,
|
|
821
|
+
cdp_timeout_ms: commandLimitMs,
|
|
822
|
+
controller_ready_timeout_ms: phaseLimitMs,
|
|
823
|
+
navigation_timeout_ms: Math.min(navigationMs, phaseLimitMs),
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
try {
|
|
827
|
+
return await run(phaseInput);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (error?.code === MOBILE_BUDGET_ERROR_CODES.DOMAIN_SETUP_DEADLINE) throw error;
|
|
830
|
+
// A nested wait with its own shorter deadline is not this phase running out.
|
|
831
|
+
const phaseRanOut =
|
|
832
|
+
Date.now() >= phaseDeadline ||
|
|
833
|
+
(error?.code === MOBILE_BUDGET_ERROR_CODES.ACTION_DEADLINE &&
|
|
834
|
+
error.deadlineEpochMs >= phaseDeadline);
|
|
835
|
+
if (!phaseRanOut) throw error;
|
|
836
|
+
throw deadlineError(name, phaseStartedAt, phaseLimitMs, sliced, error);
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
684
842
|
async function waitForNextRouteProbe(deadline) {
|
|
685
843
|
const remaining = deadline - Date.now();
|
|
686
844
|
if (remaining > 0) await sleep(Math.min(250, remaining));
|
|
687
845
|
}
|
|
688
846
|
|
|
689
847
|
export async function navigate(input, route, params = {}, expectedRoute) {
|
|
690
|
-
const timeoutMs =
|
|
848
|
+
const timeoutMs = mobileNavigationTimeoutMs(input);
|
|
691
849
|
let requestedRoute = String(expectedRoute ?? route);
|
|
692
850
|
let requestedParams = requestedRoute === String(route) ? params : undefined;
|
|
693
851
|
const nestedTarget = expectedRoute == null && typeof params?.screen === 'string';
|
|
@@ -850,6 +1008,9 @@ export async function waitForRoute(
|
|
|
850
1008
|
try {
|
|
851
1009
|
lastRoute = await bridgeCommand(input, ['get-route']);
|
|
852
1010
|
} catch (error) {
|
|
1011
|
+
// The action deadline cut the last poll short: report the route wait
|
|
1012
|
+
// (expected and last route) rather than that one poll.
|
|
1013
|
+
if (error?.code === MOBILE_BUDGET_ERROR_CODES.ACTION_DEADLINE) break;
|
|
853
1014
|
if (!isTargetTransition(error)) throw error;
|
|
854
1015
|
lastTransitionError = error;
|
|
855
1016
|
transitionCodes.push(error.code);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// Open wallet home through WalletView, with a verified HomeNav fallback for older navigators.
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
bridgeCommand,
|
|
4
|
+
mobileNavigationTimeoutMs,
|
|
5
|
+
navigate,
|
|
6
|
+
waitForRoute,
|
|
7
|
+
} from '../platform/bridge.mjs';
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Navigate to the wallet home across MetaMask Mobile navigation generations.
|
|
@@ -19,7 +24,7 @@ export async function navigateWalletHome(input) {
|
|
|
19
24
|
const currentRoute = await waitForRoute(
|
|
20
25
|
input,
|
|
21
26
|
'WalletView',
|
|
22
|
-
|
|
27
|
+
mobileNavigationTimeoutMs(input),
|
|
23
28
|
);
|
|
24
29
|
return {
|
|
25
30
|
navigation: {
|
|
@@ -5,14 +5,18 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
import {
|
|
6
6
|
bridgeCommand,
|
|
7
7
|
MOBILE_BRIDGE_ERROR_CODES,
|
|
8
|
+
MOBILE_BUDGET_ERROR_CODES,
|
|
8
9
|
runAdapter,
|
|
9
10
|
} from '../platform/bridge.mjs';
|
|
10
11
|
|
|
11
12
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
// A switch-account write cut short by the deadline may still have landed, so
|
|
14
|
+
// ACTION_DEADLINE leaves the outcome as unknown as a transport failure does.
|
|
12
15
|
const TRANSIENT_READ_CODES = new Set([
|
|
13
16
|
MOBILE_BRIDGE_ERROR_CODES.NO_TARGET,
|
|
14
17
|
MOBILE_BRIDGE_ERROR_CODES.CDP_TIMEOUT,
|
|
15
18
|
MOBILE_BRIDGE_ERROR_CODES.WS_CLOSED,
|
|
19
|
+
MOBILE_BUDGET_ERROR_CODES.ACTION_DEADLINE,
|
|
16
20
|
]);
|
|
17
21
|
const READ_ATTEMPT_TIMEOUT_MS = 10_000;
|
|
18
22
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The epoch ms by which a live adapter action must finish. The runner kills a
|
|
2
|
+
// live adapter process liveAdapterProcessTimeoutMs(node) after starting it.
|
|
3
|
+
|
|
4
|
+
import { liveAdapterProcessTimeoutMs } from '@farmslot/recipe-cli/harness';
|
|
5
|
+
|
|
6
|
+
export function liveAdapterDeadline(node, reserveMs = 5_000) {
|
|
7
|
+
return performance.timeOrigin + liveAdapterProcessTimeoutMs(node ?? {}) - reserveMs;
|
|
8
|
+
}
|