rnxsim 0.1.312 → 0.1.314
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/LICENSE +11 -10
- package/README.md +5 -0
- package/cli/app-config.ts +65 -0
- package/cli/app-fonts.ts +408 -0
- package/cli/app-project.ts +231 -0
- package/cli/app-splash.ts +185 -0
- package/cli/app-state-reset.ts +24 -0
- package/cli/auth.ts +155 -0
- package/cli/bin.ts +594 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2830 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/commands/agent-wrapper.ts +986 -0
- package/cli/commands/agent.ts +423 -0
- package/cli/commands/app-fonts.ts +98 -0
- package/cli/commands/assert.ts +541 -0
- package/cli/commands/auth.ts +59 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +87 -0
- package/cli/commands/config.ts +32 -0
- package/cli/commands/control.ts +2142 -0
- package/cli/commands/cpu-profile.ts +269 -0
- package/cli/commands/daemon-mac-app.ts +169 -0
- package/cli/commands/daemon.ts +874 -0
- package/cli/commands/debug.ts +719 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +197 -0
- package/cli/commands/detox.ts +385 -0
- package/cli/commands/device.ts +133 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/electron.ts +95 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1124 -0
- package/cli/commands/inspect/actions.ts +622 -0
- package/cli/commands/inspect/core.ts +2405 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +192 -0
- package/cli/commands/inspect/env.ts +23 -0
- package/cli/commands/inspect/find.ts +171 -0
- package/cli/commands/inspect/get-layout.ts +39 -0
- package/cli/commands/inspect/keyboard.ts +52 -0
- package/cli/commands/inspect/list.ts +58 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +82 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +22 -0
- package/cli/commands/inspect/settling.ts +158 -0
- package/cli/commands/inspect/shared.ts +353 -0
- package/cli/commands/inspect/sleep.ts +14 -0
- package/cli/commands/inspect/tree.ts +32 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +210 -0
- package/cli/commands/inspect/wait-idle.ts +24 -0
- package/cli/commands/inspect/wait-ready.ts +74 -0
- package/cli/commands/inspect/wait-selector.ts +54 -0
- package/cli/commands/inspect/wait.ts +31 -0
- package/cli/commands/inspect.ts +4519 -0
- package/cli/commands/install-desktop.ts +351 -0
- package/cli/commands/login.ts +331 -0
- package/cli/commands/logout.ts +31 -0
- package/cli/commands/maestro-generate.ts +361 -0
- package/cli/commands/maestro.ts +453 -0
- package/cli/commands/mode.ts +57 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +66 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/profile.ts +108 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1434 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +85 -0
- package/cli/commands/runtime.ts +351 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +143 -0
- package/cli/commands/screenshot-mode.ts +37 -0
- package/cli/commands/screenshot.ts +488 -0
- package/cli/commands/screenshots-capture.ts +607 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +168 -0
- package/cli/commands/setup.ts +545 -0
- package/cli/commands/shell-boolean-mode.ts +81 -0
- package/cli/commands/skills.ts +467 -0
- package/cli/commands/slides.ts +361 -0
- package/cli/commands/state.ts +87 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +54 -0
- package/cli/commands/three-mode.ts +763 -0
- package/cli/commands/timeline.ts +122 -0
- package/cli/commands/upgrade.ts +208 -0
- package/cli/commands/upload.ts +1225 -0
- package/cli/commands/version.ts +54 -0
- package/cli/commands/what-happened.ts +327 -0
- package/cli/current-sim.ts +204 -0
- package/cli/desktop-companion.ts +300 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +180 -0
- package/cli/drivers/playwright.ts +698 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +120 -0
- package/cli/flow-session.ts +187 -0
- package/cli/help.ts +80 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +216 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/maestro-js.ts +334 -0
- package/cli/open-url.ts +8 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +211 -0
- package/cli/prompt.ts +51 -0
- package/cli/recording-access.ts +107 -0
- package/cli/registry.ts +1 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/run-registry.ts +226 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/setup-repository.ts +187 -0
- package/cli/telemetry.ts +187 -0
- package/cli/ws-bridge.ts +798 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-NXVDDCFG.js → agent-7YBDCYMA.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-DXJCFKL3.js → agent-wrapper-2GHFBHCR.js} +2 -2
- package/dist-cli/chunks/{app-fonts-W5AJZQJF.js → app-fonts-RSNVPQSU.js} +2 -2
- package/dist-cli/chunks/{assert-3LOJEUDT.js → assert-XCMX3XJX.js} +2 -2
- package/dist-cli/chunks/{auth-MFTYYPNX.js → auth-B442HRAX.js} +2 -2
- package/dist-cli/chunks/{beta-GT3NN67B.js → beta-XJ55JK3M.js} +2 -2
- package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
- package/dist-cli/chunks/{chunk-EUAVBTKU.js → chunk-277AEQZX.js} +2 -2
- package/dist-cli/chunks/{chunk-5OX7ANRA.js → chunk-2JNSK774.js} +2 -2
- package/dist-cli/chunks/{chunk-L24Z3CRS.js → chunk-32WOTSTR.js} +3 -3
- package/dist-cli/chunks/{chunk-BKNOLP3J.js → chunk-3S753SNQ.js} +2 -2
- package/dist-cli/chunks/{chunk-N6A3SXMR.js → chunk-46ZOLOYA.js} +2 -2
- package/dist-cli/chunks/{chunk-GZDHEONW.js → chunk-5L7ELDQL.js} +8 -9
- package/dist-cli/chunks/{chunk-YZRFF4YA.js → chunk-7OOPFSQS.js} +2 -2
- package/dist-cli/chunks/{chunk-TBMVBI26.js → chunk-7SV3RPRW.js} +2 -2
- package/dist-cli/chunks/{chunk-JG6S4RSH.js → chunk-7ZC35MOU.js} +1 -1
- package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
- package/dist-cli/chunks/{chunk-MENV7VLD.js → chunk-APWNH3A4.js} +1 -1
- package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
- package/dist-cli/chunks/{chunk-3D57IBPC.js → chunk-C2NL26TD.js} +1 -1
- package/dist-cli/chunks/chunk-D2FNUWAB.js +15 -0
- package/dist-cli/chunks/{chunk-4BXCZHAU.js → chunk-E5T4XSJ3.js} +2 -2
- package/dist-cli/chunks/{chunk-FTC4M26Q.js → chunk-EAC34EQS.js} +1 -1
- package/dist-cli/chunks/{chunk-NPVGMH2N.js → chunk-EG32ML36.js} +2 -2
- package/dist-cli/chunks/{chunk-DM6KUAOZ.js → chunk-FXUAC6D5.js} +1 -1
- package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
- package/dist-cli/chunks/{chunk-U4PN6JOT.js → chunk-HAXW27SS.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMZTHU7V.js → chunk-IZAHPAN6.js} +1 -1
- package/dist-cli/chunks/{chunk-SI3BUTQR.js → chunk-J62KM5TB.js} +2 -2
- package/dist-cli/chunks/{chunk-Y2FSZSO7.js → chunk-JEMCD5E4.js} +1 -1
- package/dist-cli/chunks/{chunk-T4FT6CM7.js → chunk-JZS3Q37N.js} +2 -2
- package/dist-cli/chunks/{chunk-2SA2VTGP.js → chunk-KR4JON7D.js} +1 -1
- package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
- package/dist-cli/chunks/{chunk-HG7ORJMT.js → chunk-MCWPL644.js} +2 -2
- package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
- package/dist-cli/chunks/{chunk-YRV7HR3P.js → chunk-O6TRIZNS.js} +2 -2
- package/dist-cli/chunks/{chunk-WBT32R6N.js → chunk-P7XL2E73.js} +3 -3
- package/dist-cli/chunks/{chunk-MNGQ42QE.js → chunk-PFQTUKQ4.js} +62 -87
- package/dist-cli/chunks/{chunk-Q3RGLERJ.js → chunk-PG5RZCTN.js} +2 -2
- package/dist-cli/chunks/{chunk-OR3XTXEB.js → chunk-QGRI2Z4M.js} +2 -2
- package/dist-cli/chunks/chunk-QLXXE7GE.js +125 -0
- package/dist-cli/chunks/{chunk-KZW5WBGU.js → chunk-QOJJJWJE.js} +89 -133
- package/dist-cli/chunks/{chunk-BRIZOR4K.js → chunk-RWZY5427.js} +2 -2
- package/dist-cli/chunks/{chunk-TYE3WBSR.js → chunk-RZKU2K3J.js} +2 -2
- package/dist-cli/chunks/{chunk-7VWOSTD5.js → chunk-SBV4IK4H.js} +1 -1
- package/dist-cli/chunks/{chunk-FWYYCUAZ.js → chunk-SGMVFFMK.js} +1 -1
- package/dist-cli/chunks/{chunk-LDQ7HVCW.js → chunk-TAX4UT2N.js} +1 -1
- package/dist-cli/chunks/{chunk-G74YIEVS.js → chunk-TUOFAWXT.js} +1 -1
- package/dist-cli/chunks/{chunk-274UD5FD.js → chunk-UHZLOHGP.js} +1 -1
- package/dist-cli/chunks/{chunk-37I5AIS7.js → chunk-VC7V76U3.js} +1 -1
- package/dist-cli/chunks/{chunk-B7KMNHX7.js → chunk-VQVMLW4U.js} +1 -1
- package/dist-cli/chunks/{chunk-VZBLFMYC.js → chunk-VUKKYPZN.js} +2 -2
- package/dist-cli/chunks/{chunk-VUFRB347.js → chunk-WGGRJDRE.js} +1 -1
- package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
- package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
- package/dist-cli/chunks/{chunk-TCWQJILS.js → chunk-XULEACM4.js} +2 -2
- package/dist-cli/chunks/{chunk-XRGEEPJ5.js → chunk-YFSDM7AX.js} +4 -4
- package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
- package/dist-cli/chunks/{chunk-TGQNQ6XT.js → chunk-ZBSJO4NB.js} +10 -9
- package/dist-cli/chunks/{cleanup-H3GWQB63.js → cleanup-EYLGCA6Z.js} +2 -2
- package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
- package/dist-cli/chunks/{compat-5WG2XJV7.js → compat-TLJYHB4E.js} +2 -2
- package/dist-cli/chunks/{config-ANMBVVLL.js → config-4JWUOEXK.js} +2 -2
- package/dist-cli/chunks/{control-NSGNQFJG.js → control-HPAOYF4N.js} +2 -2
- package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
- package/dist-cli/chunks/{debug-CHV2O2KF.js → debug-4BKXF6KI.js} +5 -5
- package/dist-cli/chunks/{desktop-FIPIK4CH.js → desktop-ZQ6ZD2S6.js} +3 -3
- package/dist-cli/chunks/{detox-CVUJP7K6.js → detox-WNPASSS3.js} +2 -2
- package/dist-cli/chunks/{device-6WCUM4W6.js → device-BXLXVG3V.js} +2 -2
- package/dist-cli/chunks/{diagnose-I6EMWEII.js → diagnose-3QX7W5RW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-P27CYFTN.js → disk-cleanup-KSWKI7WB.js} +2 -2
- package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
- package/dist-cli/chunks/{film-UOQFFYNF.js → film-GDMR33VO.js} +3 -3
- package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
- package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-NJYFSJKZ.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
- package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
- package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
- package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
- package/dist-cli/chunks/{login-BPFQENCX.js → login-IJAPUZHI.js} +4 -4
- package/dist-cli/chunks/{logout-5A45ZGY7.js → logout-QMXDFU2X.js} +2 -2
- package/dist-cli/chunks/{maestro-E4CSDMLM.js → maestro-ZXU3YCVX.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-CKMOJQX2.js → maestro-generate-PYB5QY7K.js} +3 -3
- package/dist-cli/chunks/{mode-SZYNMIQF.js → mode-WGUCL5FZ.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-FRH4SFVG.js → optional-demo-registry-WH2O6H36.js} +2 -2
- package/dist-cli/chunks/{perf-VXU2XJAX.js → perf-TOD3UFAH.js} +2 -2
- package/dist-cli/chunks/{permissions-IRIAOVND.js → permissions-I5BRJGTB.js} +2 -2
- package/dist-cli/chunks/{record-RY7FPLLH.js → record-ZYL2FYSK.js} +3 -3
- package/dist-cli/chunks/{report-issue-KNWXOE53.js → report-issue-TAI6DYZO.js} +2 -2
- package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
- package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
- package/dist-cli/chunks/{screenshot-command-QLALR6GY.js → screenshot-command-7ANLODZY.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-OQFMFVRL.js → screenshot-layers-A7FYXSVU.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-6TSRJFGB.js → screenshots-capture-WT2ZY6CB.js} +2 -2
- package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
- package/dist-cli/chunks/{setup-7V6UKX4U.js → setup-PCWLD22V.js} +2 -2
- package/dist-cli/chunks/{skills-QQBOSETH.js → skills-27ZHWCQS.js} +2 -2
- package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
- package/dist-cli/chunks/{storage-TAPMKK5O.js → storage-OYC57C4X.js} +6 -6
- package/dist-cli/chunks/store-32HSPZHI.js +4 -0
- package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
- package/dist-cli/chunks/{timeline-IAM564NA.js → timeline-RAJJFQT6.js} +2 -2
- package/dist-cli/chunks/{upgrade-CD55QXIX.js → upgrade-OEFNZRIP.js} +2 -2
- package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
- package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
- package/dist-cli/chunks/{web-6NW6YLPI.js → web-XBUTBVGR.js} +2 -2
- package/dist-cli/chunks/{what-happened-CZZDOOVG.js → what-happened-YNHRFUDX.js} +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract.cjs +20 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +67 -28
- package/dist-lib/host/bridge-host.cjs +140 -12
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2815 -40
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +2549 -2061
- package/dist-lib/sdk.mjs +2543 -2061
- package/dist-lib/skills.cjs +493 -282
- package/dist-lib/vite.cjs +1 -1
- package/package.json +8 -2
- package/src/bridge-constants.ts +3 -4
- package/src/bridge-contract.ts +251 -0
- package/src/connect.ts +83 -0
- package/src/disk-cleanup.ts +30 -0
- package/src/home-paths.ts +81 -38
- package/src/host/bridge-host.ts +134 -6
- package/src/index.ts +27 -1
- package/src/sdk.ts +8 -0
- package/src/sim-client.ts +660 -0
- package/dist-cli/chunks/camera-6EAK7CUL.js +0 -22
- package/dist-cli/chunks/chunk-33FKQH54.js +0 -5
- package/dist-cli/chunks/chunk-66NF2KNG.js +0 -9
- package/dist-cli/chunks/chunk-6UKJB2VC.js +0 -125
- package/dist-cli/chunks/chunk-7INBNA3U.js +0 -26
- package/dist-cli/chunks/chunk-AY47WKS5.js +0 -23
- package/dist-cli/chunks/chunk-HMDOLCKG.js +0 -15
- package/dist-cli/chunks/chunk-KR5BGMBZ.js +0 -4
- package/dist-cli/chunks/chunk-NLEMQ4PY.js +0 -6
- package/dist-cli/chunks/chunk-PEPUGJVI.js +0 -4
- package/dist-cli/chunks/cli-version-3QPAJ3LC.js +0 -4
- package/dist-cli/chunks/daemon-JHF4BJ3U.js +0 -4
- package/dist-cli/chunks/drivers-HQ6AKZT6.js +0 -4
- package/dist-cli/chunks/flow-N27MRRNL.js +0 -4
- package/dist-cli/chunks/help-SF6FEGEE.js +0 -4
- package/dist-cli/chunks/home-paths-XOV44JEF.js +0 -4
- package/dist-cli/chunks/inspect-CWFRFQTY.js +0 -4
- package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +0 -4
- package/dist-cli/chunks/runtime-AR3GJTGK.js +0 -4
- package/dist-cli/chunks/serve-F54J2NQO.js +0 -44
- package/dist-cli/chunks/store-7NX3S43H.js +0 -4
- package/dist-cli/chunks/telemetry-E7BUK6RA.js +0 -4
- package/dist-cli/chunks/upload-GZ6LIMYM.js +0 -4
- package/dist-cli/chunks/version-3DYJ5AS3.js +0 -6
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
// SimClient — the typed, promise-based client every rnxsim frontend drives a
|
|
2
|
+
// sim through. it wraps a bridge transport (the CLI's ws bridge, or any
|
|
3
|
+
// InspectBridge-shaped channel) and reuses the existing inspect verbs as its
|
|
4
|
+
// implementation; it never re-implements tap resolution, find, settling, or
|
|
5
|
+
// readiness.
|
|
6
|
+
//
|
|
7
|
+
// MUST stay browser-safe: no node builtins, no `process`, no `ws`. the ws
|
|
8
|
+
// transport and `connect()` live in ./connect (node-only) so this class can
|
|
9
|
+
// also back in-page transports later. runtime imports here may only reach the
|
|
10
|
+
// browser-safe inspect kernels.
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
isTapSuccess,
|
|
14
|
+
tapBest,
|
|
15
|
+
tapById,
|
|
16
|
+
tapByText,
|
|
17
|
+
type TapOutcome,
|
|
18
|
+
type TapTextOptions,
|
|
19
|
+
} from '../cli/commands/inspect/actions'
|
|
20
|
+
import {
|
|
21
|
+
filterLogEntries,
|
|
22
|
+
getShellState,
|
|
23
|
+
inspectAccessibilityTree,
|
|
24
|
+
inspectDescribe,
|
|
25
|
+
inspectErrors,
|
|
26
|
+
inspectFind,
|
|
27
|
+
inspectKeyboard,
|
|
28
|
+
inspectLogs,
|
|
29
|
+
inspectTree,
|
|
30
|
+
inspectUrl,
|
|
31
|
+
inspectWaitReady,
|
|
32
|
+
inspectWarnings,
|
|
33
|
+
waitReadyReason,
|
|
34
|
+
type AccessibilityTreeNode,
|
|
35
|
+
type ConsoleEntry,
|
|
36
|
+
type DescribeResult,
|
|
37
|
+
type FindQuery,
|
|
38
|
+
type InspectBridge,
|
|
39
|
+
type InteractiveNode,
|
|
40
|
+
type KeyboardState,
|
|
41
|
+
type LogEntry,
|
|
42
|
+
type LogFilterOptions,
|
|
43
|
+
type WaitReadyStatus,
|
|
44
|
+
} from '../cli/commands/inspect/core'
|
|
45
|
+
import { resolveTargetCoords } from '../cli/commands/inspect/resolve-target'
|
|
46
|
+
import {
|
|
47
|
+
waitForSootsimIdle,
|
|
48
|
+
type WaitForSootsimIdleResult,
|
|
49
|
+
} from '../cli/commands/inspect/settling'
|
|
50
|
+
import type { BridgeClaimResult, BridgeSimInfo, WsBridge } from '../cli/ws-bridge'
|
|
51
|
+
import type {
|
|
52
|
+
PerformOptions,
|
|
53
|
+
PerformResult,
|
|
54
|
+
PerformStep,
|
|
55
|
+
ResetOptions,
|
|
56
|
+
ResetResult,
|
|
57
|
+
SimStateOptions,
|
|
58
|
+
SimStateResult,
|
|
59
|
+
} from './bridge-contract'
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// typed errors — extend the BridgeSimLockedError precedent so scripts can
|
|
63
|
+
// branch with instanceof instead of parsing message strings.
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
export class SimClientError extends Error {
|
|
67
|
+
constructor(message: string) {
|
|
68
|
+
super(message)
|
|
69
|
+
this.name = new.target.name
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** no daemon/bridge reachable, no sim connected, or the target sim is gone. */
|
|
74
|
+
export class SimConnectError extends SimClientError {}
|
|
75
|
+
|
|
76
|
+
/** `waitReady` ran out of budget. carries the last probe status. */
|
|
77
|
+
export class SimReadyTimeoutError extends SimClientError {
|
|
78
|
+
status: WaitReadyStatus
|
|
79
|
+
constructor(status: WaitReadyStatus) {
|
|
80
|
+
super(`sim not ready: ${waitReadyReason(status)}`)
|
|
81
|
+
this.status = status
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** a tap could not resolve its target or the dispatched press missed. */
|
|
86
|
+
export class SimTapError extends SimClientError {
|
|
87
|
+
outcome: TapOutcome | null
|
|
88
|
+
constructor(message: string, outcome: TapOutcome | null = null) {
|
|
89
|
+
super(message)
|
|
90
|
+
this.outcome = outcome
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `type`/`press` with no visible keyboard (nothing focused to receive it). */
|
|
95
|
+
export class SimKeyboardError extends SimClientError {}
|
|
96
|
+
|
|
97
|
+
/** a `perform` batch reported failure. carries the full step-by-step result. */
|
|
98
|
+
export class SimPerformError extends SimClientError {
|
|
99
|
+
result: PerformResult
|
|
100
|
+
constructor(result: PerformResult) {
|
|
101
|
+
const failed = result.steps.find((step) => !step.ok)
|
|
102
|
+
const at = failed ? `${failed.index} (${failed.type})` : String(result.completed)
|
|
103
|
+
const cause = failed?.error ?? result.error
|
|
104
|
+
super(`perform failed at step ${at}${cause ? `: ${cause}` : ''}`)
|
|
105
|
+
this.result = result
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** a `reset` reported failure. carries the engine's result. */
|
|
110
|
+
export class SimResetError extends SimClientError {
|
|
111
|
+
result: ResetResult
|
|
112
|
+
constructor(result: ResetResult) {
|
|
113
|
+
super(`reset (${result.strategy}) failed${result.error ? `: ${result.error}` : ''}`)
|
|
114
|
+
this.result = result
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// public option/target shapes
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
export interface SimFindOptions {
|
|
123
|
+
testId?: string
|
|
124
|
+
text?: string
|
|
125
|
+
role?: string
|
|
126
|
+
type?: string
|
|
127
|
+
pressable?: boolean
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type SimTapTarget =
|
|
131
|
+
| string
|
|
132
|
+
| { x: number; y: number }
|
|
133
|
+
| { testId: string }
|
|
134
|
+
| ({ text: string } & TapTextOptions)
|
|
135
|
+
| InteractiveNode
|
|
136
|
+
|
|
137
|
+
// the contract deliberately has no text-targeted step (text matching is fuzzy
|
|
138
|
+
// and lives once in the CLI's tap resolution). the SDK accepts one as sugar and
|
|
139
|
+
// resolves it to a coordinate `tap` step client-side before sending ONE batch.
|
|
140
|
+
export type SimPerformStep = PerformStep | { type: 'tapText'; text: string }
|
|
141
|
+
|
|
142
|
+
export interface SimScreenshot {
|
|
143
|
+
dataUrl: string
|
|
144
|
+
bytes: Uint8Array
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface SimClientOptions {
|
|
148
|
+
/** default per-command bridge timeout. */
|
|
149
|
+
commandTimeoutMs?: number
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function sleep(ms: number): Promise<void> {
|
|
153
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function decodePngDataUrl(dataUrl: unknown): SimScreenshot {
|
|
157
|
+
if (typeof dataUrl !== 'string' || !dataUrl.startsWith('data:image/png;base64,')) {
|
|
158
|
+
throw new SimClientError(
|
|
159
|
+
`screenshot bridge returned a non-png payload: ${JSON.stringify(
|
|
160
|
+
typeof dataUrl === 'string' ? dataUrl.slice(0, 80) : dataUrl,
|
|
161
|
+
)}`,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
const base64 = dataUrl.slice('data:image/png;base64,'.length)
|
|
165
|
+
const binary = atob(base64)
|
|
166
|
+
const bytes = new Uint8Array(binary.length)
|
|
167
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
|
|
168
|
+
return { dataUrl, bytes }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// derive the shell base for navigating this sim to a new target, from the
|
|
172
|
+
// sim's current URL: keep the origin and every non-target param (device,
|
|
173
|
+
// renderMode, debug…), drop the params and `/rn` path segment that name the
|
|
174
|
+
// current target. mirrors the CLI open command's base derivation; the shared
|
|
175
|
+
// module extraction is tracked with the driver.
|
|
176
|
+
const TARGET_SEARCH_PARAMS = [
|
|
177
|
+
'bundle',
|
|
178
|
+
'demo',
|
|
179
|
+
'app',
|
|
180
|
+
'open',
|
|
181
|
+
'port',
|
|
182
|
+
'inspectOpen',
|
|
183
|
+
'appFonts',
|
|
184
|
+
'appSplash',
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
export function buildSimOpenUrl(currentUrl: string, target: number | string): string {
|
|
188
|
+
const url = new URL(currentUrl)
|
|
189
|
+
for (const param of TARGET_SEARCH_PARAMS) url.searchParams.delete(param)
|
|
190
|
+
// shell route grammar: an optional /__soot prefix, then /rn, /rn/<port>, or
|
|
191
|
+
// /app/<name>. the current route is replaced wholesale, keeping the prefix.
|
|
192
|
+
const pathname = url.pathname.replace(/\/+$/, '') || '/'
|
|
193
|
+
const prefix =
|
|
194
|
+
pathname === '/__soot' || pathname.startsWith('/__soot/') ? '/__soot' : ''
|
|
195
|
+
const normalized = String(target).trim()
|
|
196
|
+
if (/^\d+$/.test(normalized)) {
|
|
197
|
+
// `/rn/<port>` — the shell's own connect route resolves the metro bundle
|
|
198
|
+
// in-page, so the SDK never grows a second bundle-resolution path.
|
|
199
|
+
url.pathname = `${prefix}/rn/${normalized}`
|
|
200
|
+
} else {
|
|
201
|
+
url.pathname = `${prefix}/rn`
|
|
202
|
+
url.searchParams.set('open', normalized)
|
|
203
|
+
}
|
|
204
|
+
return url.toString()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// a `{ text, ...TapTextOptions }` target, as distinct from a node returned by
|
|
208
|
+
// find() (which carries layout/absolutePosition alongside its text).
|
|
209
|
+
function isTextTapTarget(
|
|
210
|
+
target: SimTapTarget,
|
|
211
|
+
): target is { text: string } & TapTextOptions {
|
|
212
|
+
return (
|
|
213
|
+
typeof target === 'object' &&
|
|
214
|
+
'text' in target &&
|
|
215
|
+
typeof target.text === 'string' &&
|
|
216
|
+
!('layout' in target) &&
|
|
217
|
+
!('absolutePosition' in target)
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// SimClient
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
// implements the full WsBridge surface (not just InspectBridge) so it can be
|
|
226
|
+
// handed to every existing verb and CLI helper unchanged, with each command
|
|
227
|
+
// pinned to the sim this client was connected to.
|
|
228
|
+
export class SimClient implements WsBridge, InspectBridge {
|
|
229
|
+
readonly simId: string
|
|
230
|
+
private readonly bridge: WsBridge
|
|
231
|
+
private closed = false
|
|
232
|
+
|
|
233
|
+
constructor(bridge: WsBridge, simId: string, _options: SimClientOptions = {}) {
|
|
234
|
+
this.bridge = bridge
|
|
235
|
+
this.simId = simId
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --- transport (WsBridge/InspectBridge) ---
|
|
239
|
+
|
|
240
|
+
send(
|
|
241
|
+
cmd: { type: string; [k: string]: any },
|
|
242
|
+
opts?: { timeoutMs?: number },
|
|
243
|
+
): Promise<any> {
|
|
244
|
+
if (this.closed) {
|
|
245
|
+
return Promise.reject(new SimConnectError('sim client is closed'))
|
|
246
|
+
}
|
|
247
|
+
const payload = cmd.simId === undefined ? { ...cmd, simId: this.simId } : cmd
|
|
248
|
+
return this.bridge.send(payload, opts)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
listSims(): Promise<BridgeSimInfo[]> {
|
|
252
|
+
return this.bridge.listSims()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
focusSim(simId: string = this.simId): Promise<any> {
|
|
256
|
+
return this.bridge.focusSim(simId)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
closeSim(simId: string = this.simId): Promise<any> {
|
|
260
|
+
return this.bridge.closeSim(simId)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
claim(
|
|
264
|
+
simId: string = this.simId,
|
|
265
|
+
opts?: { force?: boolean },
|
|
266
|
+
): Promise<BridgeClaimResult> {
|
|
267
|
+
return this.bridge.claim(simId, opts)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** release the socket. a script that connected must close to exit cleanly. */
|
|
271
|
+
close(): void {
|
|
272
|
+
if (this.closed) return
|
|
273
|
+
this.closed = true
|
|
274
|
+
this.bridge.close()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// --- navigation + readiness ---
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* point this sim at a dev target: a metro port (`8081`), or anything the
|
|
281
|
+
* shell's connect flow accepts (`localhost:8081`, an app slug…). resolves
|
|
282
|
+
* once the reloaded page answers on the new target route.
|
|
283
|
+
*/
|
|
284
|
+
async open(target: number | string, opts: { timeoutMs?: number } = {}): Promise<void> {
|
|
285
|
+
const sims = await this.listSims()
|
|
286
|
+
const sim = sims.find((entry) => entry.id === this.simId)
|
|
287
|
+
if (!sim) {
|
|
288
|
+
throw new SimConnectError(`sim ${this.simId} is no longer connected`)
|
|
289
|
+
}
|
|
290
|
+
const currentUrl = sim.url ?? sim.origin
|
|
291
|
+
if (!currentUrl) {
|
|
292
|
+
throw new SimConnectError(
|
|
293
|
+
`sim ${this.simId} reported no URL to derive the shell from`,
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
const nextUrl = buildSimOpenUrl(currentUrl, target)
|
|
297
|
+
const expected = new URL(nextUrl)
|
|
298
|
+
await this.send({
|
|
299
|
+
type: 'evaluate',
|
|
300
|
+
code: `window.location.href = ${JSON.stringify(nextUrl)}`,
|
|
301
|
+
})
|
|
302
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 30_000)
|
|
303
|
+
while (Date.now() < deadline) {
|
|
304
|
+
await sleep(250)
|
|
305
|
+
try {
|
|
306
|
+
const { url } = await inspectUrl(this)
|
|
307
|
+
const current = new URL(url)
|
|
308
|
+
if (
|
|
309
|
+
current.pathname === expected.pathname &&
|
|
310
|
+
current.searchParams.get('open') === expected.searchParams.get('open')
|
|
311
|
+
) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
// page is mid-reload; keep polling until the deadline.
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
throw new SimConnectError(
|
|
319
|
+
`timed out waiting for sim ${this.simId} to load ${nextUrl}`,
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* block until the guest app has mounted and painted real content.
|
|
325
|
+
* throws SimReadyTimeoutError (with the last probe status) on timeout.
|
|
326
|
+
*/
|
|
327
|
+
async waitReady(
|
|
328
|
+
opts: {
|
|
329
|
+
timeoutMs?: number
|
|
330
|
+
onProgress?: (status: WaitReadyStatus) => void
|
|
331
|
+
} = {},
|
|
332
|
+
): Promise<WaitReadyStatus> {
|
|
333
|
+
const status = await inspectWaitReady(this, opts.timeoutMs ?? 20_000, {
|
|
334
|
+
onProgress: opts.onProgress,
|
|
335
|
+
})
|
|
336
|
+
if (!status.ready) throw new SimReadyTimeoutError(status)
|
|
337
|
+
return status
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** wait for layout/animation/transition settle after an interaction. */
|
|
341
|
+
settle(
|
|
342
|
+
opts: { maxMs?: number; strict?: boolean } = {},
|
|
343
|
+
): Promise<WaitForSootsimIdleResult> {
|
|
344
|
+
return waitForSootsimIdle({
|
|
345
|
+
bridge: this,
|
|
346
|
+
maxMs: opts.maxMs ?? 3_000,
|
|
347
|
+
strict: opts.strict,
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// --- read verbs ---
|
|
352
|
+
|
|
353
|
+
/** first matching node, or null. query precedence matches the CLI's find. */
|
|
354
|
+
async find(query: SimFindOptions): Promise<InteractiveNode | null> {
|
|
355
|
+
const nodes = await this.findAll(query)
|
|
356
|
+
return nodes[0] ?? null
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async findAll(query: SimFindOptions): Promise<InteractiveNode[]> {
|
|
360
|
+
const mapped: FindQuery = {
|
|
361
|
+
testId: query.testId ?? null,
|
|
362
|
+
role: query.role ?? null,
|
|
363
|
+
type: query.type ?? null,
|
|
364
|
+
text: query.text ?? null,
|
|
365
|
+
pressable: query.pressable,
|
|
366
|
+
}
|
|
367
|
+
const found = await inspectFind(this, mapped)
|
|
368
|
+
if (!found) {
|
|
369
|
+
throw new SimClientError(
|
|
370
|
+
'find() needs at least one of testId, text, role, type, or pressable',
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
const { result } = found
|
|
374
|
+
if (Array.isArray(result)) return result
|
|
375
|
+
return result ? [result as InteractiveNode] : []
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async tree(depth = 5): Promise<unknown> {
|
|
379
|
+
const { tree } = await inspectTree(this, depth)
|
|
380
|
+
return tree
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** compact visible-UI dump with shell + keyboard state, as the CLI prints. */
|
|
384
|
+
describe(opts: { filter?: string; verbose?: boolean } = {}): Promise<DescribeResult> {
|
|
385
|
+
return inspectDescribe(this, {
|
|
386
|
+
describe: true,
|
|
387
|
+
verbose: opts.verbose ?? false,
|
|
388
|
+
filter: opts.filter ?? '',
|
|
389
|
+
compact: !opts.verbose,
|
|
390
|
+
hideXy: false,
|
|
391
|
+
fullText: true,
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
accessibilityTree(): Promise<AccessibilityTreeNode[]> {
|
|
396
|
+
return inspectAccessibilityTree(this)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async logs(opts: LogFilterOptions = {}): Promise<LogEntry[]> {
|
|
400
|
+
return filterLogEntries(await inspectLogs(this), opts)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
errors(limit = 20): Promise<ConsoleEntry[]> {
|
|
404
|
+
return inspectErrors(this, limit)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
warnings(limit = 20): Promise<ConsoleEntry[]> {
|
|
408
|
+
return inspectWarnings(this, limit)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
keyboard(): Promise<KeyboardState | { error: string }> {
|
|
412
|
+
return inspectKeyboard(this)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
shellState(readyTimeoutMs = 0): Promise<Record<string, any> | null> {
|
|
416
|
+
return getShellState(this, readyTimeoutMs)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async url(): Promise<string> {
|
|
420
|
+
const { url } = await inspectUrl(this)
|
|
421
|
+
return url
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async screenshot(
|
|
425
|
+
opts: {
|
|
426
|
+
layers?: 'full' | 'tenant' | 'shell'
|
|
427
|
+
crop?: { x: number; y: number; w: number; h: number }
|
|
428
|
+
} = {},
|
|
429
|
+
): Promise<SimScreenshot> {
|
|
430
|
+
const result = await this.send({
|
|
431
|
+
type: 'screenshot',
|
|
432
|
+
...(opts.layers ? { layers: opts.layers } : {}),
|
|
433
|
+
...(opts.crop ? { crop: opts.crop } : {}),
|
|
434
|
+
})
|
|
435
|
+
return decodePngDataUrl(result)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// --- input verbs ---
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* tap by testID, visible text, node, or coordinates. resolution, retry, and
|
|
442
|
+
* hit verification are the CLI's shared tap kernels. throws SimTapError when
|
|
443
|
+
* the target is missing, covered, offscreen, or the press missed.
|
|
444
|
+
*/
|
|
445
|
+
async tap(target: SimTapTarget): Promise<void> {
|
|
446
|
+
if (typeof target === 'string') {
|
|
447
|
+
return this.throwUnlessTapped(await tapBest(this, target), target)
|
|
448
|
+
}
|
|
449
|
+
if ('testId' in target && typeof target.testId === 'string') {
|
|
450
|
+
return this.throwUnlessTapped(await tapById(this, target.testId), target.testId)
|
|
451
|
+
}
|
|
452
|
+
if (isTextTapTarget(target)) {
|
|
453
|
+
const { text, ...textOpts } = target
|
|
454
|
+
return this.throwUnlessTapped(await tapByText(this, text, textOpts), text)
|
|
455
|
+
}
|
|
456
|
+
if ('testID' in target && typeof target.testID === 'string') {
|
|
457
|
+
return this.throwUnlessTapped(await tapById(this, target.testID), target.testID)
|
|
458
|
+
}
|
|
459
|
+
const coords = this.coordsForTapTarget(target)
|
|
460
|
+
const result = await this.send({ type: 'tap', x: coords.x, y: coords.y })
|
|
461
|
+
if (!isTapSuccess(result)) {
|
|
462
|
+
throw new SimTapError(
|
|
463
|
+
`tap at ${coords.x},${coords.y} missed: ${JSON.stringify(result)}`,
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async longPress(
|
|
469
|
+
target: SimTapTarget,
|
|
470
|
+
opts: { durationMs?: number } = {},
|
|
471
|
+
): Promise<void> {
|
|
472
|
+
const coords = await this.resolveCoords(target)
|
|
473
|
+
const result = await this.send(
|
|
474
|
+
{
|
|
475
|
+
type: 'longPress',
|
|
476
|
+
x: coords.x,
|
|
477
|
+
y: coords.y,
|
|
478
|
+
durationMs: opts.durationMs ?? 600,
|
|
479
|
+
},
|
|
480
|
+
{ timeoutMs: (opts.durationMs ?? 600) + 15_000 },
|
|
481
|
+
)
|
|
482
|
+
if (!result?.ok) {
|
|
483
|
+
throw new SimTapError(`longPress at ${coords.x},${coords.y} missed`)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** type through the visual iOS keyboard. requires a focused input. */
|
|
488
|
+
async type(text: string): Promise<void> {
|
|
489
|
+
await this.requireVisibleKeyboard('type')
|
|
490
|
+
await this.send({ type: 'keyboard', action: 'type', text })
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** press a keyboard key ('return', 'delete', 'space', …). */
|
|
494
|
+
async press(key: string): Promise<void> {
|
|
495
|
+
await this.requireVisibleKeyboard('press')
|
|
496
|
+
await this.send({ type: 'keyboard', action: 'press', text: key })
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async dismissKeyboard(): Promise<void> {
|
|
500
|
+
await this.send({ type: 'keyboard', action: 'dismiss' })
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// --- batched input, compound state, reset ---
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* execute an ordered step batch inside the page in ONE round-trip, so waits
|
|
507
|
+
* and gesture physics run on real wall-clock timing. `tapText` steps are
|
|
508
|
+
* resolved to coordinates client-side before the batch is sent. throws
|
|
509
|
+
* SimPerformError (carrying the per-step result) when the batch fails.
|
|
510
|
+
*/
|
|
511
|
+
async perform(
|
|
512
|
+
steps: SimPerformStep[],
|
|
513
|
+
options?: PerformOptions,
|
|
514
|
+
): Promise<PerformResult> {
|
|
515
|
+
const resolved: PerformStep[] = []
|
|
516
|
+
for (const step of steps) {
|
|
517
|
+
if (step.type !== 'tapText') {
|
|
518
|
+
resolved.push(step)
|
|
519
|
+
continue
|
|
520
|
+
}
|
|
521
|
+
const target = await resolveTargetCoords(this, {
|
|
522
|
+
mode: 'text',
|
|
523
|
+
value: step.text,
|
|
524
|
+
})
|
|
525
|
+
if (!target) {
|
|
526
|
+
throw new SimTapError(
|
|
527
|
+
`perform tapText: no node with text ${JSON.stringify(step.text)}`,
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
resolved.push({
|
|
531
|
+
type: 'tap',
|
|
532
|
+
x: target.x,
|
|
533
|
+
y: target.y,
|
|
534
|
+
target: {
|
|
535
|
+
testID: target.testID ?? undefined,
|
|
536
|
+
text: target.text ?? undefined,
|
|
537
|
+
type: target.type ?? undefined,
|
|
538
|
+
},
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
const batchTimeoutMs = options?.timeoutMs ?? 30_000
|
|
542
|
+
const result = (await this.send(
|
|
543
|
+
{
|
|
544
|
+
type: 'perform',
|
|
545
|
+
steps: resolved,
|
|
546
|
+
...(options ? { performOptions: options } : {}),
|
|
547
|
+
},
|
|
548
|
+
{ timeoutMs: batchTimeoutMs + 5_000 },
|
|
549
|
+
)) as PerformResult
|
|
550
|
+
if (!result?.ok) throw new SimPerformError(result)
|
|
551
|
+
return result
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** screenshot + tree + route + recent errors in one round-trip. */
|
|
555
|
+
state(options?: SimStateOptions): Promise<SimStateResult> {
|
|
556
|
+
return this.send({
|
|
557
|
+
type: 'state',
|
|
558
|
+
...(options ? { stateOptions: options } : {}),
|
|
559
|
+
}) as Promise<SimStateResult>
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* two-tier app state wipe ('data' clears storage/caches, 'full' also clears
|
|
564
|
+
* keychain/permissions/native-module state). throws SimResetError on failure.
|
|
565
|
+
*/
|
|
566
|
+
async reset(options?: ResetOptions): Promise<ResetResult> {
|
|
567
|
+
const result = (await this.send(
|
|
568
|
+
{ type: 'reset', ...(options ? { resetOptions: options } : {}) },
|
|
569
|
+
{ timeoutMs: 60_000 },
|
|
570
|
+
)) as ResetResult
|
|
571
|
+
if (!result?.ok) throw new SimResetError(result)
|
|
572
|
+
return result
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// --- internals ---
|
|
576
|
+
|
|
577
|
+
private throwUnlessTapped(outcome: TapOutcome, label: string): void {
|
|
578
|
+
if (!outcome.failure && isTapSuccess(outcome.result)) return
|
|
579
|
+
const reason =
|
|
580
|
+
outcome.failure === 'not-found'
|
|
581
|
+
? 'no matching node'
|
|
582
|
+
: outcome.failure === 'missed'
|
|
583
|
+
? (outcome.result?.reason ?? 'press missed')
|
|
584
|
+
: outcome.payload?.ambiguous
|
|
585
|
+
? `ambiguous: ${outcome.payload.total} matches`
|
|
586
|
+
: (outcome.result?.reason ?? outcome.payload?.error ?? 'tap failed')
|
|
587
|
+
throw new SimTapError(`tap ${JSON.stringify(label)} failed: ${reason}`, outcome)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private coordsForTapTarget(target: SimTapTarget): { x: number; y: number } {
|
|
591
|
+
if (
|
|
592
|
+
typeof target === 'object' &&
|
|
593
|
+
'x' in target &&
|
|
594
|
+
'y' in target &&
|
|
595
|
+
typeof target.x === 'number' &&
|
|
596
|
+
typeof target.y === 'number'
|
|
597
|
+
) {
|
|
598
|
+
return { x: target.x, y: target.y }
|
|
599
|
+
}
|
|
600
|
+
if (
|
|
601
|
+
typeof target === 'object' &&
|
|
602
|
+
'absolutePosition' in target &&
|
|
603
|
+
target.absolutePosition &&
|
|
604
|
+
target.layout
|
|
605
|
+
) {
|
|
606
|
+
return {
|
|
607
|
+
x: target.absolutePosition.x + (target.layout.width ?? 0) / 2,
|
|
608
|
+
y: target.absolutePosition.y + (target.layout.height ?? 0) / 2,
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
throw new SimClientError(
|
|
612
|
+
'tap target needs coordinates, a testId, text, or a node from find()',
|
|
613
|
+
)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private async resolveCoords(target: SimTapTarget): Promise<{ x: number; y: number }> {
|
|
617
|
+
if (typeof target === 'string') {
|
|
618
|
+
const resolved =
|
|
619
|
+
(await resolveTargetCoords(this, { mode: 'testid', value: target })) ??
|
|
620
|
+
(await resolveTargetCoords(this, { mode: 'text', value: target }))
|
|
621
|
+
if (!resolved) throw new SimTapError(`no node matching ${JSON.stringify(target)}`)
|
|
622
|
+
return resolved
|
|
623
|
+
}
|
|
624
|
+
if ('testId' in target && typeof target.testId === 'string') {
|
|
625
|
+
const resolved = await resolveTargetCoords(this, {
|
|
626
|
+
mode: 'testid',
|
|
627
|
+
value: target.testId,
|
|
628
|
+
})
|
|
629
|
+
if (!resolved) {
|
|
630
|
+
throw new SimTapError(`no node with testID ${JSON.stringify(target.testId)}`)
|
|
631
|
+
}
|
|
632
|
+
return resolved
|
|
633
|
+
}
|
|
634
|
+
if ('text' in target && typeof target.text === 'string' && !('layout' in target)) {
|
|
635
|
+
const resolved = await resolveTargetCoords(this, {
|
|
636
|
+
mode: 'text',
|
|
637
|
+
value: target.text,
|
|
638
|
+
})
|
|
639
|
+
if (!resolved) {
|
|
640
|
+
throw new SimTapError(`no node with text ${JSON.stringify(target.text)}`)
|
|
641
|
+
}
|
|
642
|
+
return resolved
|
|
643
|
+
}
|
|
644
|
+
return this.coordsForTapTarget(target)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private async requireVisibleKeyboard(action: string): Promise<void> {
|
|
648
|
+
const deadline = Date.now() + 1_500
|
|
649
|
+
for (;;) {
|
|
650
|
+
const state = await inspectKeyboard(this)
|
|
651
|
+
if (!('error' in state) && state.visible) return
|
|
652
|
+
if (Date.now() >= deadline) {
|
|
653
|
+
throw new SimKeyboardError(
|
|
654
|
+
`${action} requires the iOS keyboard to be visible — focus an input first (tap a TextInput)`,
|
|
655
|
+
)
|
|
656
|
+
}
|
|
657
|
+
await sleep(150)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*! rnx v0.1.312 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
-
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
3
|
-
var require = __sootsimCreateRequire(import.meta.url)
|
|
4
|
-
import{m as d}from"./chunk-XRGEEPJ5.js";import"./chunk-TGQNQ6XT.js";import{h as l,j as m,l as p}from"./chunk-7INBNA3U.js";import"./chunk-33FKQH54.js";import"./chunk-7VWOSTD5.js";import"./chunk-66NF2KNG.js";import"./chunk-MENV7VLD.js";function f(){console.log(`
|
|
5
|
-
rnxsim do scan \u2014 inject a barcode scan
|
|
6
|
-
|
|
7
|
-
usage:
|
|
8
|
-
rnxsim do scan <text> [--format <web-format>]
|
|
9
|
-
|
|
10
|
-
subcommands:
|
|
11
|
-
scan <text> inject a synthetic barcode result into the active scan
|
|
12
|
-
session. fails when no guest CameraView is scanning.
|
|
13
|
-
|
|
14
|
-
options:
|
|
15
|
-
--format <f> BarcodeDetector format string (default: qr_code)
|
|
16
|
-
--sim <id> target a specific sim
|
|
17
|
-
--port <p> WS bridge port (default: 7668)
|
|
18
|
-
|
|
19
|
-
examples:
|
|
20
|
-
rnxsim do scan "tm://connect?url=http://10.0.0.5:7777&token=abc"
|
|
21
|
-
rnxsim do scan "https://example.com" --format qr_code
|
|
22
|
-
`)}async function b(e,g={}){let r=l(e,{port:g.port,stripBooleanFlags:["--help","-h"],stripValueFlags:["--format"]}),a=r.positional,o=a[0];(!o||o==="help"||e.includes("--help")||e.includes("-h"))&&(f(),process.exit(o?0:1)),o!=="scan"&&(console.error(` unknown scan subcommand: ${o}`),f(),process.exit(1));let n=a[1];n||(console.error(" usage: rnxsim do scan <text>"),process.exit(1));let i=e.indexOf("--format"),c=i>=0?e[i+1]:"qr_code",t=m(r);try{await p(t,`globalThis.__sootsimCameraInjectScan?.(${JSON.stringify(n)}, ${JSON.stringify(c)}) ?? false`)?console.log(` injected ${c} scan: ${n}`):(console.error(" no active barcode scan session \u2014 open a screen with a scanning <CameraView> first"),process.exit(1))}catch(s){console.error(` scan failed: ${s instanceof Error?s.message:s}`),await d(t),process.exit(1)}finally{t.close()}}export{b as runCamera};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/*! rnx v0.1.312 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
-
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
3
|
-
var require = __sootsimCreateRequire(import.meta.url)
|
|
4
|
-
import{a as u,b as c}from"./chunk-7VWOSTD5.js";var w="http://localhost:7668/";var _=5173;var I=2e3;function y(t){let r=t-7668;return r<0||r>I?null:`http://localhost:${_+r}/`}var A=4001,C="sootsim close";import{existsSync as T,mkdirSync as O,readFileSync as d,rmSync as l,writeFileSync as P}from"fs";import{tmpdir as E}from"os";import{dirname as g,join as R,resolve as b}from"path";var p=1,S="SOOTSIM_CLI_CURRENT_SIM_PATH",h=["SOOTSIM_CLI_IDENTITY","TM_SESSION","CLAUDE_CODE_SESSION_ID","CODEX_THREAD_ID","TERM_SESSION_ID","ITERM_SESSION_ID","TMUX_PANE","STY","KITTY_WINDOW_ID","WEZTERM_PANE","ALACRITTY_WINDOW_ID","WINDOWID","VSCODE_INJECTION"],m=/^(?:tmux|screen)|^tm$/i;function a(t,r=20){let e=t,n=u(t);if(!n||n<=1){let o=c(t);return t>1&&o&&!m.test(o)?t:null}for(let o=0;o<r;o++){let s=c(n);if(s&&m.test(s))return e;let i=u(n);if(!i||i<=1)return s?n:e;e=n,n=i}return n}function x(){for(let r of h){let e=process.env[r];if(e&&e.trim())return{key:`${r}:${e.trim()}`,source:r,stable:!0}}let t=a(process.ppid);return t&&t>1?{key:`gppid-${t}`,source:"grand-ppid",stable:!0}:{key:`pid-${process.ppid}`,source:"ppid",stable:!1}}function D(){return x().key}function H(){return a(process.ppid)}function f(){if(process.env[S])return b(process.env[S]);let t=D();return R(E(),`sootsim-current-sim-${t}.json`)}function U(){let t=f();if(!T(t))return null;try{let r=JSON.parse(d(t,"utf8"));return r.version!==p||typeof r.simId!="string"||!r.simId.trim()||typeof r.updatedAt!="string"?(l(t,{force:!0}),null):{version:p,simId:r.simId.trim(),updatedAt:r.updatedAt}}catch{return l(t,{force:!0}),null}}function v(){return U()?.simId||null}function F(t){let r=t.trim();if(!r)return;let e=f();O(g(e),{recursive:!0}),P(e,JSON.stringify({version:p,simId:r,updatedAt:new Date().toISOString()},null,2)+`
|
|
5
|
-
`)}function V(){l(f(),{force:!0})}export{w as a,y as b,A as c,C as d,x as e,D as f,H as g,v as h,F as i,V as j};
|