rnxsim 0.1.486 → 0.1.488

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.
Files changed (57) hide show
  1. package/cli/outbound-endpoints.ts +2 -2
  2. package/cli/ws-bridge.ts +1 -1
  3. package/detox/index.ts +212 -20
  4. package/dist-lib/agent-daemon-client.cjs +2 -2
  5. package/dist-lib/agent-events.cjs +1 -1
  6. package/dist-lib/agent-identity.cjs +1 -1
  7. package/dist-lib/agent-sessions.cjs +1 -1
  8. package/dist-lib/attached-projects.cjs +1 -1
  9. package/dist-lib/auth/shared-session.cjs +1 -1
  10. package/dist-lib/backend-origin.cjs +1 -1
  11. package/dist-lib/beta.cjs +1 -1
  12. package/dist-lib/beta.mjs +1 -1
  13. package/dist-lib/bridge-constants.cjs +2 -2
  14. package/dist-lib/bridge-contract-input.cjs +1 -1
  15. package/dist-lib/bridge-contract-input.mjs +1 -1
  16. package/dist-lib/bridge-contract.cjs +1 -1
  17. package/dist-lib/bridge-contract.mjs +1 -1
  18. package/dist-lib/capture-contract.cjs +1 -1
  19. package/dist-lib/capture-contract.mjs +1 -1
  20. package/dist-lib/cli-constants.cjs +1 -1
  21. package/dist-lib/cloud-contract.cjs +1 -1
  22. package/dist-lib/cloud-contract.mjs +1 -1
  23. package/dist-lib/cloud.cjs +1 -1
  24. package/dist-lib/cloud.mjs +1 -1
  25. package/dist-lib/config.cjs +1 -1
  26. package/dist-lib/detox/index.cjs +191 -21
  27. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  28. package/dist-lib/home-paths.cjs +1 -1
  29. package/dist-lib/host/bridge-host.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  32. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  33. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  34. package/dist-lib/host/websocket-proxy.cjs +1 -1
  35. package/dist-lib/index.cjs +2 -2
  36. package/dist-lib/jump-to-source-babel.cjs +1 -1
  37. package/dist-lib/jump-to-source-native.cjs +1 -1
  38. package/dist-lib/menu.cjs +1 -1
  39. package/dist-lib/menu.mjs +1 -1
  40. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  41. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  42. package/dist-lib/metro-production-bundle.cjs +1 -1
  43. package/dist-lib/metro-production-bundle.mjs +1 -1
  44. package/dist-lib/metro.cjs +1 -1
  45. package/dist-lib/profiles.cjs +1 -1
  46. package/dist-lib/public-brand.cjs +1 -1
  47. package/dist-lib/react-native-host-modules.cjs +1 -1
  48. package/dist-lib/react-native-host-modules.mjs +1 -1
  49. package/dist-lib/render-mode.cjs +1 -1
  50. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  51. package/dist-lib/sdk.cjs +1 -1
  52. package/dist-lib/sdk.mjs +1 -1
  53. package/dist-lib/skills.cjs +3 -3
  54. package/dist-lib/vite.cjs +1 -1
  55. package/package.json +1 -1
  56. package/src/agent-daemon-client.ts +1 -1
  57. package/src/bridge-constants.ts +6 -1
@@ -485,7 +485,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
485
485
  count: 1,
486
486
  },
487
487
  // the inventory records the call expression, not the URL, so this entry can
488
- // no longer show its own destination. bridgeWsUrl() is localhost-only today;
488
+ // no longer show its own destination. bridgeWsUrl() is loopback-only today;
489
489
  // when a cloud box makes it return a remote wss:// endpoint, that egress
490
490
  // would land under this same local_development entry and go unnoticed. split
491
491
  // the declaration then, rather than letting one line cover both.
@@ -497,7 +497,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
497
497
  category: 'local_development',
498
498
  count: 1,
499
499
  },
500
- 'packages/sootsim/src/agent-daemon-client.ts :: websocket :: `ws://localhost:${this.port}`':
500
+ 'packages/sootsim/src/agent-daemon-client.ts :: websocket :: `ws://127.0.0.1:${this.port}`':
501
501
  { category: 'local_development', count: 1 },
502
502
  "packages/sootsim/src/auth/shared-session.ts :: fetch :: `${origin.replace(/\\/$/, '')}/api/auth/me`":
503
503
  { category: 'contrast_user_action', count: 1 },
package/cli/ws-bridge.ts CHANGED
@@ -75,7 +75,7 @@ export function resolvePlacement(): RnxPlacement {
75
75
  * this function. a cloud endpoint added here must get its own declaration in
76
76
  * `outbound-endpoints.ts` instead of inheriting the local_development one. */
77
77
  export function bridgeWsUrl(wsPort: number): string {
78
- return `ws://localhost:${wsPort}`
78
+ return `ws://127.0.0.1:${wsPort}`
79
79
  }
80
80
 
81
81
  // resolved once per CLI process so a multi-step command keeps one world for
package/detox/index.ts CHANGED
@@ -55,11 +55,45 @@ let _context: BrowserContext | null = null
55
55
  let _page: Page | null = null
56
56
  let _synchronizationEnabled = false
57
57
  let _suspendedAppId: string | null = null
58
+ let _browserCompromised: string | null = null
58
59
  let browserIdentity = 0
59
60
  let contextIdentity = 0
60
61
  let nextBrowserIdentity = 0
61
62
  let nextContextIdentity = 0
62
63
 
64
+ // maximum duration for browser/context lifecycle operations (closeContext,
65
+ // newContext, newPage). playwright does not bound context.close() or context
66
+ // creation; this bound converts an opaque 180s Jest hook timeout into a
67
+ // named failure that identifies which lifecycle step stalled.
68
+ const BROWSER_LIFECYCLE_TIMEOUT_MS = 30_000
69
+
70
+ async function withLifecycleTimeout<T>(
71
+ actionName: string,
72
+ targetContextId: number,
73
+ fn: () => Promise<T>,
74
+ timeoutMs = BROWSER_LIFECYCLE_TIMEOUT_MS,
75
+ ): Promise<T> {
76
+ const start = Date.now()
77
+ let timer: ReturnType<typeof setTimeout> | undefined
78
+ try {
79
+ return await Promise.race([
80
+ fn(),
81
+ new Promise<never>((_, reject) => {
82
+ timer = setTimeout(() => {
83
+ const elapsed = Date.now() - start
84
+ reject(
85
+ new Error(
86
+ `sootsim browser lifecycle error: ${actionName} did not settle after ${elapsed}ms (context=${targetContextId})`,
87
+ ),
88
+ )
89
+ }, timeoutMs)
90
+ }),
91
+ ])
92
+ } finally {
93
+ if (timer) clearTimeout(timer)
94
+ }
95
+ }
96
+
63
97
  const STATUS_BAR_OVERRIDE_EVENT = 'sootsim:statusBarOverride'
64
98
 
65
99
  type StatusBarConfig = {
@@ -75,6 +109,9 @@ type StatusBarConfig = {
75
109
  }
76
110
 
77
111
  function getPage(): Page {
112
+ if (_browserCompromised) {
113
+ throw new Error(`sootsim browser is compromised: ${_browserCompromised}`)
114
+ }
78
115
  if (!_page)
79
116
  throw new Error('sootsim driver not initialized -- call device.launchApp() first')
80
117
  return _page
@@ -280,13 +317,33 @@ async function waitForSootsimWorkletsIdle(page: Page, timeout = 3000): Promise<v
280
317
  }
281
318
  }
282
319
 
320
+ function reapOwnedBrowser(): void {
321
+ const reap = _reapBrowserNow
322
+ _reapBrowserNow = null
323
+ _browser = null
324
+ try {
325
+ reap?.()
326
+ } catch {}
327
+ }
328
+
283
329
  async function closeContext() {
330
+ if (_browserCompromised) {
331
+ throw new Error(`sootsim browser is compromised: ${_browserCompromised}`)
332
+ }
333
+ if (!_context) return
284
334
  const context = _context
285
- _context = null
286
- _page = null
287
- contextIdentity = 0
288
- _synchronizationEnabled = false
289
- if (context) await context.close()
335
+ const closingContextId = contextIdentity
336
+ try {
337
+ await withLifecycleTimeout('context.close()', closingContextId, () => context.close())
338
+ _context = null
339
+ _page = null
340
+ contextIdentity = 0
341
+ _synchronizationEnabled = false
342
+ } catch (error) {
343
+ _browserCompromised = error instanceof Error ? error.message : String(error)
344
+ reapOwnedBrowser()
345
+ throw error
346
+ }
290
347
  }
291
348
 
292
349
  async function closeBrowser() {
@@ -296,11 +353,25 @@ async function closeBrowser() {
296
353
  _browser = null
297
354
  _reapBrowserNow = null
298
355
  browserIdentity = 0
299
- await closeContext()
356
+
357
+ let closeError: Error | null = null
358
+ try {
359
+ await closeContext()
360
+ } catch (error) {
361
+ closeError = error instanceof Error ? error : new Error(String(error))
362
+ }
363
+
364
+ // reap Chrome directly without waiting on an unbounded browser.close()
300
365
  try {
301
- await browser.close()
302
- } finally {
303
366
  reapBrowserNow?.()
367
+ } catch {}
368
+
369
+ try {
370
+ await browser.close()
371
+ } catch {}
372
+
373
+ if (closeError) {
374
+ throw closeError
304
375
  }
305
376
  }
306
377
 
@@ -730,6 +801,102 @@ async function dispatchStatusBarOverride(config: StatusBarConfig) {
730
801
  await page.waitForTimeout(50)
731
802
  }
732
803
 
804
+ const MAX_SETTLED_SPEED_PT_PER_SEC = 2.0
805
+
806
+ async function waitForFrameClock(page: Page, minMs = 25): Promise<void> {
807
+ const start = performance.now()
808
+ await page.evaluate(
809
+ () =>
810
+ new Promise<void>((resolve) => {
811
+ const timeoutId = setTimeout(() => resolve(), 50)
812
+ requestAnimationFrame(() => {
813
+ clearTimeout(timeoutId)
814
+ resolve()
815
+ })
816
+ }),
817
+ )
818
+ const elapsed = performance.now() - start
819
+ if (elapsed < minMs) {
820
+ await page.waitForTimeout(minMs - elapsed)
821
+ }
822
+ }
823
+
824
+ async function findStableNodeByMatcher(
825
+ matcher: Matcher,
826
+ action = 'tap',
827
+ maxWaitMs = 1500,
828
+ ): Promise<any> {
829
+ const page = getPage()
830
+ const start = performance.now()
831
+ let lastPos: { x: number; y: number } | null = null
832
+ let lastTime: number | null = null
833
+ let lastRate: { vx: number; vy: number; speed: number } | null = null
834
+ let sampleCount = 0
835
+ let stablePolls = 0
836
+ const requiredStable = 2
837
+ let hadNumericLayout = false
838
+ let disappearedMidWait = false
839
+
840
+ while (performance.now() - start < maxWaitMs) {
841
+ const node = await findNodeByMatcher(matcher)
842
+ if (!node) {
843
+ if (hadNumericLayout) {
844
+ disappearedMidWait = true
845
+ } else {
846
+ return null
847
+ }
848
+ } else {
849
+ disappearedMidWait = false
850
+ const pos = node.absolutePosition ?? node.layout
851
+ if (pos && typeof pos.x === 'number' && typeof pos.y === 'number') {
852
+ hadNumericLayout = true
853
+ sampleCount++
854
+ const now = performance.now()
855
+ if (lastPos && lastTime !== null) {
856
+ const dtSec = (now - lastTime) / 1000
857
+ if (dtSec > 0.005) {
858
+ const vx = Math.abs(pos.x - lastPos.x) / dtSec
859
+ const vy = Math.abs(pos.y - lastPos.y) / dtSec
860
+ const speed = Math.sqrt(vx * vx + vy * vy)
861
+ lastRate = { vx, vy, speed }
862
+ if (speed < MAX_SETTLED_SPEED_PT_PER_SEC) {
863
+ stablePolls++
864
+ if (stablePolls >= requiredStable) {
865
+ return node
866
+ }
867
+ } else {
868
+ stablePolls = 0
869
+ }
870
+ }
871
+ }
872
+ lastPos = { x: pos.x, y: pos.y }
873
+ lastTime = now
874
+ }
875
+ }
876
+ await waitForFrameClock(page, 25)
877
+ }
878
+
879
+ const elapsed = Math.round(performance.now() - start)
880
+ if (disappearedMidWait) {
881
+ throw new Error(
882
+ `element disappeared while waiting to come to rest for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`,
883
+ )
884
+ }
885
+ if (!hadNumericLayout) {
886
+ throw new Error(
887
+ `element layout is not available for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`,
888
+ )
889
+ }
890
+ if (sampleCount < 2 || lastRate === null) {
891
+ throw new Error(
892
+ `element did not come to rest for ${action}: ${JSON.stringify(matcher)}; only one sample obtained after ${elapsed}ms`,
893
+ )
894
+ }
895
+ throw new Error(
896
+ `element did not come to rest for ${action}: ${JSON.stringify(matcher)}; still moving ${lastRate.vx.toFixed(1)} points/sec x / ${lastRate.vy.toFixed(1)} points/sec y after ${elapsed}ms`,
897
+ )
898
+ }
899
+
733
900
  function createSootSimElement(matcher: Matcher): SootSimElement {
734
901
  const el: SootSimElement = {
735
902
  _matcher: matcher,
@@ -737,7 +904,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
737
904
  async tap(point) {
738
905
  const page = getPage()
739
906
  await waitForSootsimWorkletsIdle(page, 3000)
740
- const node = await findNodeByMatcher(matcher)
907
+ const node = await findStableNodeByMatcher(matcher, 'tap')
741
908
  if (!node) throw new Error(`element not found for tap: ${JSON.stringify(matcher)}`)
742
909
  if (!isNodeVisible(node)) {
743
910
  throw new Error(
@@ -766,7 +933,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
766
933
  // center with a small inter-tap pause that stays inside RNGH's
767
934
  // default maxDelay window of 200ms.
768
935
  const page = getPage()
769
- const node = await findNodeByMatcher(matcher)
936
+ const node = await findStableNodeByMatcher(matcher, 'multiTap')
770
937
  if (!node)
771
938
  throw new Error(`element not found for multiTap: ${JSON.stringify(matcher)}`)
772
939
  if (!isNodeVisible(node)) {
@@ -796,7 +963,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
796
963
  durationArg = 1000,
797
964
  ) {
798
965
  const page = getPage()
799
- const node = await findNodeByMatcher(matcher)
966
+ const node = await findStableNodeByMatcher(matcher, 'longPress')
800
967
  if (!node)
801
968
  throw new Error(`element not found for longPress: ${JSON.stringify(matcher)}`)
802
969
  if (!isNodeVisible(node)) {
@@ -840,12 +1007,15 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
840
1007
  ) {
841
1008
  const page = getPage()
842
1009
  // resolve source element
843
- const srcNode = await findNodeByMatcher(matcher)
1010
+ const srcNode = await findStableNodeByMatcher(matcher, 'longPressAndDrag')
844
1011
  if (!srcNode)
845
1012
  throw new Error(`source element not found: ${JSON.stringify(matcher)}`)
846
1013
 
847
1014
  // resolve target element
848
- const tgtNode = await findNodeByMatcher(targetElement._matcher)
1015
+ const tgtNode = await findStableNodeByMatcher(
1016
+ targetElement._matcher,
1017
+ 'longPressAndDrag',
1018
+ )
849
1019
  if (!tgtNode)
850
1020
  throw new Error(
851
1021
  `target element not found: ${JSON.stringify(targetElement._matcher)}`,
@@ -1027,7 +1197,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
1027
1197
  normalizedStartingPointX = 0.5,
1028
1198
  normalizedStartingPointY = 0.5,
1029
1199
  ) {
1030
- const node = await findNodeByMatcher(matcher)
1200
+ const node = await findStableNodeByMatcher(matcher, 'swipe')
1031
1201
  if (!node)
1032
1202
  throw new Error(`element not found for swipe: ${JSON.stringify(matcher)}`)
1033
1203
 
@@ -1101,7 +1271,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
1101
1271
  throw new Error(`picker value must be a string, got ${typeof value}`)
1102
1272
  }
1103
1273
 
1104
- const picker = await findNodeByMatcher(matcher)
1274
+ const picker = await findStableNodeByMatcher(matcher, 'setColumnToValue')
1105
1275
  if (!picker) {
1106
1276
  throw new Error(
1107
1277
  `element not found for setColumnToValue: ${JSON.stringify(matcher)}`,
@@ -1178,7 +1348,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
1178
1348
  // velocity tracking sees a realistic delta. angle (radians) tilts the
1179
1349
  // pinch axis off horizontal — matches Detox's third arg.
1180
1350
  const page = getPage()
1181
- const node = await findNodeByMatcher(matcher)
1351
+ const node = await findStableNodeByMatcher(matcher, 'pinch')
1182
1352
  if (!node)
1183
1353
  throw new Error(`element not found for pinch: ${JSON.stringify(matcher)}`)
1184
1354
  if (!node.nativePinchEnabled) {
@@ -1213,7 +1383,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
1213
1383
  // sign (its 'rotation' delta is positive for a counter-clockwise
1214
1384
  // motion of the rear finger relative to the front finger).
1215
1385
  const page = getPage()
1216
- const node = await findNodeByMatcher(matcher)
1386
+ const node = await findStableNodeByMatcher(matcher, 'rotate')
1217
1387
  if (!node)
1218
1388
  throw new Error(`element not found for rotate: ${JSON.stringify(matcher)}`)
1219
1389
  await waitForSootsimGestureHandler(page, node.nodeId, 'RotationGestureHandler')
@@ -1299,6 +1469,9 @@ export const device = {
1299
1469
  // toggled on an existing context.
1300
1470
  recordVideoDir?: string
1301
1471
  }) {
1472
+ if (_browserCompromised) {
1473
+ throw new Error(`sootsim browser is compromised: ${_browserCompromised}`)
1474
+ }
1302
1475
  if (
1303
1476
  _page &&
1304
1477
  opts?.newInstance === false &&
@@ -1362,7 +1535,18 @@ export const device = {
1362
1535
  size: { width: 500, height: 900 },
1363
1536
  }
1364
1537
  }
1365
- _context = await _browser.newContext(contextOpts)
1538
+ const newContextIdentity = ++nextContextIdentity
1539
+ try {
1540
+ _context = await withLifecycleTimeout(
1541
+ '_browser.newContext()',
1542
+ newContextIdentity,
1543
+ () => _browser!.newContext(contextOpts),
1544
+ )
1545
+ } catch (error) {
1546
+ _browserCompromised = error instanceof Error ? error.message : String(error)
1547
+ reapOwnedBrowser()
1548
+ throw error
1549
+ }
1366
1550
  await _context.exposeBinding('__sootsimHostCapture', async (source, value) => {
1367
1551
  const request = readBrowserCompositeCaptureRequest(value)
1368
1552
  const session = await source.page.context().newCDPSession(source.page)
@@ -1381,8 +1565,16 @@ export const device = {
1381
1565
  await session.detach()
1382
1566
  }
1383
1567
  })
1384
- _page = await _context.newPage()
1385
- contextIdentity = ++nextContextIdentity
1568
+ try {
1569
+ _page = await withLifecycleTimeout('_context.newPage()', newContextIdentity, () =>
1570
+ _context!.newPage(),
1571
+ )
1572
+ } catch (error) {
1573
+ _browserCompromised = error instanceof Error ? error.message : String(error)
1574
+ reapOwnedBrowser()
1575
+ throw error
1576
+ }
1577
+ contextIdentity = newContextIdentity
1386
1578
  }
1387
1579
 
1388
1580
  const url = opts?.url || BASE_URL
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -241,7 +241,7 @@ var AgentDaemonClient = class {
241
241
  constructor(opts = {}) {
242
242
  this.port = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT;
243
243
  this.commandTimeoutMs = opts.commandTimeoutMs ?? 15e3;
244
- this.ws = new import_ws.WebSocket(`ws://localhost:${this.port}`);
244
+ this.ws = new import_ws.WebSocket(`ws://127.0.0.1:${this.port}`);
245
245
  this.ready = new Promise((resolve, reject) => {
246
246
  const onOpen = () => {
247
247
  this.ws.off("error", onError);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -136,7 +136,7 @@ function resolveSootsimWebSocketProxyUrl(input) {
136
136
  }
137
137
  function resolveSootsimBridgeWebSocketUrl(input) {
138
138
  return resolveSootsimWebSocketProxyUrl({
139
- targetUrl: `ws://localhost:${input.bridgePort}`,
139
+ targetUrl: `ws://127.0.0.1:${input.bridgePort}`,
140
140
  href: input.href
141
141
  });
142
142
  }
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_MAX_MS = 5e3;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_DEFAULT_MS = 500;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/capture-contract.ts
4
4
  var RNX_SCREEN_CAPTURE_VERSION = 1;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1881,12 +1881,38 @@ var _context = null;
1881
1881
  var _page = null;
1882
1882
  var _synchronizationEnabled = false;
1883
1883
  var _suspendedAppId = null;
1884
+ var _browserCompromised = null;
1884
1885
  var browserIdentity = 0;
1885
1886
  var contextIdentity = 0;
1886
1887
  var nextBrowserIdentity = 0;
1887
1888
  var nextContextIdentity = 0;
1889
+ var BROWSER_LIFECYCLE_TIMEOUT_MS = 3e4;
1890
+ async function withLifecycleTimeout(actionName, targetContextId, fn, timeoutMs = BROWSER_LIFECYCLE_TIMEOUT_MS) {
1891
+ const start = Date.now();
1892
+ let timer;
1893
+ try {
1894
+ return await Promise.race([
1895
+ fn(),
1896
+ new Promise((_, reject) => {
1897
+ timer = setTimeout(() => {
1898
+ const elapsed = Date.now() - start;
1899
+ reject(
1900
+ new Error(
1901
+ `sootsim browser lifecycle error: ${actionName} did not settle after ${elapsed}ms (context=${targetContextId})`
1902
+ )
1903
+ );
1904
+ }, timeoutMs);
1905
+ })
1906
+ ]);
1907
+ } finally {
1908
+ if (timer) clearTimeout(timer);
1909
+ }
1910
+ }
1888
1911
  var STATUS_BAR_OVERRIDE_EVENT = "sootsim:statusBarOverride";
1889
1912
  function getPage() {
1913
+ if (_browserCompromised) {
1914
+ throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
1915
+ }
1890
1916
  if (!_page)
1891
1917
  throw new Error("sootsim driver not initialized -- call device.launchApp() first");
1892
1918
  return _page;
@@ -2052,13 +2078,33 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
2052
2078
  );
2053
2079
  }
2054
2080
  }
2081
+ function reapOwnedBrowser() {
2082
+ const reap = _reapBrowserNow;
2083
+ _reapBrowserNow = null;
2084
+ _browser = null;
2085
+ try {
2086
+ reap?.();
2087
+ } catch {
2088
+ }
2089
+ }
2055
2090
  async function closeContext() {
2091
+ if (_browserCompromised) {
2092
+ throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
2093
+ }
2094
+ if (!_context) return;
2056
2095
  const context = _context;
2057
- _context = null;
2058
- _page = null;
2059
- contextIdentity = 0;
2060
- _synchronizationEnabled = false;
2061
- if (context) await context.close();
2096
+ const closingContextId = contextIdentity;
2097
+ try {
2098
+ await withLifecycleTimeout("context.close()", closingContextId, () => context.close());
2099
+ _context = null;
2100
+ _page = null;
2101
+ contextIdentity = 0;
2102
+ _synchronizationEnabled = false;
2103
+ } catch (error) {
2104
+ _browserCompromised = error instanceof Error ? error.message : String(error);
2105
+ reapOwnedBrowser();
2106
+ throw error;
2107
+ }
2062
2108
  }
2063
2109
  async function closeBrowser() {
2064
2110
  if (!_browser) return;
@@ -2067,11 +2113,22 @@ async function closeBrowser() {
2067
2113
  _browser = null;
2068
2114
  _reapBrowserNow = null;
2069
2115
  browserIdentity = 0;
2070
- await closeContext();
2116
+ let closeError = null;
2117
+ try {
2118
+ await closeContext();
2119
+ } catch (error) {
2120
+ closeError = error instanceof Error ? error : new Error(String(error));
2121
+ }
2071
2122
  try {
2072
- await browser.close();
2073
- } finally {
2074
2123
  reapBrowserNow?.();
2124
+ } catch {
2125
+ }
2126
+ try {
2127
+ await browser.close();
2128
+ } catch {
2129
+ }
2130
+ if (closeError) {
2131
+ throw closeError;
2075
2132
  }
2076
2133
  }
2077
2134
  async function waitForSootsimIdle(page, maxMs = 3e3, strict = false) {
@@ -2391,13 +2448,99 @@ async function dispatchStatusBarOverride(config) {
2391
2448
  );
2392
2449
  await page.waitForTimeout(50);
2393
2450
  }
2451
+ var MAX_SETTLED_SPEED_PT_PER_SEC = 2;
2452
+ async function waitForFrameClock(page, minMs = 25) {
2453
+ const start = performance.now();
2454
+ await page.evaluate(
2455
+ () => new Promise((resolve) => {
2456
+ const timeoutId = setTimeout(() => resolve(), 50);
2457
+ requestAnimationFrame(() => {
2458
+ clearTimeout(timeoutId);
2459
+ resolve();
2460
+ });
2461
+ })
2462
+ );
2463
+ const elapsed = performance.now() - start;
2464
+ if (elapsed < minMs) {
2465
+ await page.waitForTimeout(minMs - elapsed);
2466
+ }
2467
+ }
2468
+ async function findStableNodeByMatcher(matcher, action = "tap", maxWaitMs = 1500) {
2469
+ const page = getPage();
2470
+ const start = performance.now();
2471
+ let lastPos = null;
2472
+ let lastTime = null;
2473
+ let lastRate = null;
2474
+ let sampleCount = 0;
2475
+ let stablePolls = 0;
2476
+ const requiredStable = 2;
2477
+ let hadNumericLayout = false;
2478
+ let disappearedMidWait = false;
2479
+ while (performance.now() - start < maxWaitMs) {
2480
+ const node = await findNodeByMatcher(matcher);
2481
+ if (!node) {
2482
+ if (hadNumericLayout) {
2483
+ disappearedMidWait = true;
2484
+ } else {
2485
+ return null;
2486
+ }
2487
+ } else {
2488
+ disappearedMidWait = false;
2489
+ const pos = node.absolutePosition ?? node.layout;
2490
+ if (pos && typeof pos.x === "number" && typeof pos.y === "number") {
2491
+ hadNumericLayout = true;
2492
+ sampleCount++;
2493
+ const now = performance.now();
2494
+ if (lastPos && lastTime !== null) {
2495
+ const dtSec = (now - lastTime) / 1e3;
2496
+ if (dtSec > 5e-3) {
2497
+ const vx = Math.abs(pos.x - lastPos.x) / dtSec;
2498
+ const vy = Math.abs(pos.y - lastPos.y) / dtSec;
2499
+ const speed = Math.sqrt(vx * vx + vy * vy);
2500
+ lastRate = { vx, vy, speed };
2501
+ if (speed < MAX_SETTLED_SPEED_PT_PER_SEC) {
2502
+ stablePolls++;
2503
+ if (stablePolls >= requiredStable) {
2504
+ return node;
2505
+ }
2506
+ } else {
2507
+ stablePolls = 0;
2508
+ }
2509
+ }
2510
+ }
2511
+ lastPos = { x: pos.x, y: pos.y };
2512
+ lastTime = now;
2513
+ }
2514
+ }
2515
+ await waitForFrameClock(page, 25);
2516
+ }
2517
+ const elapsed = Math.round(performance.now() - start);
2518
+ if (disappearedMidWait) {
2519
+ throw new Error(
2520
+ `element disappeared while waiting to come to rest for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`
2521
+ );
2522
+ }
2523
+ if (!hadNumericLayout) {
2524
+ throw new Error(
2525
+ `element layout is not available for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`
2526
+ );
2527
+ }
2528
+ if (sampleCount < 2 || lastRate === null) {
2529
+ throw new Error(
2530
+ `element did not come to rest for ${action}: ${JSON.stringify(matcher)}; only one sample obtained after ${elapsed}ms`
2531
+ );
2532
+ }
2533
+ throw new Error(
2534
+ `element did not come to rest for ${action}: ${JSON.stringify(matcher)}; still moving ${lastRate.vx.toFixed(1)} points/sec x / ${lastRate.vy.toFixed(1)} points/sec y after ${elapsed}ms`
2535
+ );
2536
+ }
2394
2537
  function createSootSimElement(matcher) {
2395
2538
  const el = {
2396
2539
  _matcher: matcher,
2397
2540
  async tap(point) {
2398
2541
  const page = getPage();
2399
2542
  await waitForSootsimWorkletsIdle(page, 3e3);
2400
- const node = await findNodeByMatcher(matcher);
2543
+ const node = await findStableNodeByMatcher(matcher, "tap");
2401
2544
  if (!node) throw new Error(`element not found for tap: ${JSON.stringify(matcher)}`);
2402
2545
  if (!isNodeVisible(node)) {
2403
2546
  throw new Error(
@@ -2414,7 +2557,7 @@ function createSootSimElement(matcher) {
2414
2557
  },
2415
2558
  async multiTap(times) {
2416
2559
  const page = getPage();
2417
- const node = await findNodeByMatcher(matcher);
2560
+ const node = await findStableNodeByMatcher(matcher, "multiTap");
2418
2561
  if (!node)
2419
2562
  throw new Error(`element not found for multiTap: ${JSON.stringify(matcher)}`);
2420
2563
  if (!isNodeVisible(node)) {
@@ -2439,7 +2582,7 @@ function createSootSimElement(matcher) {
2439
2582
  // where the point is relative to the element's origin
2440
2583
  async longPress(pointOrDuration, durationArg = 1e3) {
2441
2584
  const page = getPage();
2442
- const node = await findNodeByMatcher(matcher);
2585
+ const node = await findStableNodeByMatcher(matcher, "longPress");
2443
2586
  if (!node)
2444
2587
  throw new Error(`element not found for longPress: ${JSON.stringify(matcher)}`);
2445
2588
  if (!isNodeVisible(node)) {
@@ -2469,10 +2612,13 @@ function createSootSimElement(matcher) {
2469
2612
  },
2470
2613
  async longPressAndDrag(duration, normalizedStartX, normalizedStartY, targetElement, normalizedEndX, normalizedEndY, speed = "fast", holdDuration = 0) {
2471
2614
  const page = getPage();
2472
- const srcNode = await findNodeByMatcher(matcher);
2615
+ const srcNode = await findStableNodeByMatcher(matcher, "longPressAndDrag");
2473
2616
  if (!srcNode)
2474
2617
  throw new Error(`source element not found: ${JSON.stringify(matcher)}`);
2475
- const tgtNode = await findNodeByMatcher(targetElement._matcher);
2618
+ const tgtNode = await findStableNodeByMatcher(
2619
+ targetElement._matcher,
2620
+ "longPressAndDrag"
2621
+ );
2476
2622
  if (!tgtNode)
2477
2623
  throw new Error(
2478
2624
  `target element not found: ${JSON.stringify(targetElement._matcher)}`
@@ -2594,7 +2740,7 @@ function createSootSimElement(matcher) {
2594
2740
  }
2595
2741
  },
2596
2742
  async swipe(direction, speed = "fast", percentage = 0.75, normalizedStartingPointX = 0.5, normalizedStartingPointY = 0.5) {
2597
- const node = await findNodeByMatcher(matcher);
2743
+ const node = await findStableNodeByMatcher(matcher, "swipe");
2598
2744
  if (!node)
2599
2745
  throw new Error(`element not found for swipe: ${JSON.stringify(matcher)}`);
2600
2746
  const page = getPage();
@@ -2647,7 +2793,7 @@ function createSootSimElement(matcher) {
2647
2793
  if (typeof value !== "string") {
2648
2794
  throw new Error(`picker value must be a string, got ${typeof value}`);
2649
2795
  }
2650
- const picker = await findNodeByMatcher(matcher);
2796
+ const picker = await findStableNodeByMatcher(matcher, "setColumnToValue");
2651
2797
  if (!picker) {
2652
2798
  throw new Error(
2653
2799
  `element not found for setColumnToValue: ${JSON.stringify(matcher)}`
@@ -2703,7 +2849,7 @@ function createSootSimElement(matcher) {
2703
2849
  },
2704
2850
  async pinch(scale, speed = "fast", angle = 0) {
2705
2851
  const page = getPage();
2706
- const node = await findNodeByMatcher(matcher);
2852
+ const node = await findStableNodeByMatcher(matcher, "pinch");
2707
2853
  if (!node)
2708
2854
  throw new Error(`element not found for pinch: ${JSON.stringify(matcher)}`);
2709
2855
  if (!node.nativePinchEnabled) {
@@ -2732,7 +2878,7 @@ function createSootSimElement(matcher) {
2732
2878
  },
2733
2879
  async rotate(radians, speed = "fast") {
2734
2880
  const page = getPage();
2735
- const node = await findNodeByMatcher(matcher);
2881
+ const node = await findStableNodeByMatcher(matcher, "rotate");
2736
2882
  if (!node)
2737
2883
  throw new Error(`element not found for rotate: ${JSON.stringify(matcher)}`);
2738
2884
  await waitForSootsimGestureHandler(page, node.nodeId, "RotationGestureHandler");
@@ -2798,6 +2944,9 @@ var device = {
2798
2944
  _currentUrl: "",
2799
2945
  _platform: DETOX_PLATFORM,
2800
2946
  async launchApp(opts) {
2947
+ if (_browserCompromised) {
2948
+ throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
2949
+ }
2801
2950
  if (_page && opts?.newInstance === false && !opts.delete && !opts.recordVideoDir && _suspendedAppId) {
2802
2951
  const page = _page;
2803
2952
  const appId = _suspendedAppId;
@@ -2842,7 +2991,18 @@ var device = {
2842
2991
  size: { width: 500, height: 900 }
2843
2992
  };
2844
2993
  }
2845
- _context = await _browser.newContext(contextOpts);
2994
+ const newContextIdentity = ++nextContextIdentity;
2995
+ try {
2996
+ _context = await withLifecycleTimeout(
2997
+ "_browser.newContext()",
2998
+ newContextIdentity,
2999
+ () => _browser.newContext(contextOpts)
3000
+ );
3001
+ } catch (error) {
3002
+ _browserCompromised = error instanceof Error ? error.message : String(error);
3003
+ reapOwnedBrowser();
3004
+ throw error;
3005
+ }
2846
3006
  await _context.exposeBinding("__sootsimHostCapture", async (source, value) => {
2847
3007
  const request = readBrowserCompositeCaptureRequest(value);
2848
3008
  const session = await source.page.context().newCDPSession(source.page);
@@ -2861,8 +3021,18 @@ var device = {
2861
3021
  await session.detach();
2862
3022
  }
2863
3023
  });
2864
- _page = await _context.newPage();
2865
- contextIdentity = ++nextContextIdentity;
3024
+ try {
3025
+ _page = await withLifecycleTimeout(
3026
+ "_context.newPage()",
3027
+ newContextIdentity,
3028
+ () => _context.newPage()
3029
+ );
3030
+ } catch (error) {
3031
+ _browserCompromised = error instanceof Error ? error.message : String(error);
3032
+ reapOwnedBrowser();
3033
+ throw error;
3034
+ }
3035
+ contextIdentity = newContextIdentity;
2866
3036
  }
2867
3037
  const url = opts?.url || BASE_URL;
2868
3038
  device._currentUrl = url;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/host/fetch-proxy-overrides.ts
4
4
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -904,7 +904,7 @@ var DEFAULT_SOOTSIM_SHELL_URL = "http://localhost:5173/";
904
904
 
905
905
  // cli/ws-bridge.ts
906
906
  function bridgeWsUrl(wsPort) {
907
- return `ws://localhost:${wsPort}`;
907
+ return `ws://127.0.0.1:${wsPort}`;
908
908
  }
909
909
  var resolvedWorld = null;
910
910
  function liveDevBridgeLockfiles() {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/dist-lib/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/menu.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/public-brand.ts
4
4
  var name = "rnx";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-fingerprint-registry.ts
4
4
  var RNX_METRO_FINGERPRINT_SCHEMA_VERSION = 2;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-production-bundle.ts
4
4
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/react-native-host-modules.ts
4
4
  var REACT_NATIVE_PASSTHROUGH_SPECIFIERS = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/dist-lib/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/sdk.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -19233,7 +19233,7 @@ function resolveSootsimWebSocketProxyUrl(input) {
19233
19233
  }
19234
19234
  function resolveSootsimBridgeWebSocketUrl(input) {
19235
19235
  return resolveSootsimWebSocketProxyUrl({
19236
- targetUrl: `ws://localhost:${input.bridgePort}`,
19236
+ targetUrl: `ws://127.0.0.1:${input.bridgePort}`,
19237
19237
  href: input.href
19238
19238
  });
19239
19239
  }
@@ -30949,7 +30949,7 @@ function resolvePlacement() {
30949
30949
  return process.env[RNX_PLACEMENT_ENV] === "local-box" ? "local-box" : "host";
30950
30950
  }
30951
30951
  function bridgeWsUrl(wsPort) {
30952
- return `ws://localhost:${wsPort}`;
30952
+ return `ws://127.0.0.1:${wsPort}`;
30953
30953
  }
30954
30954
  function liveDevBridgeLockfiles() {
30955
30955
  return readDevBridgeLockfiles().filter((lockfile) => isDevBridgeLockfileFresh(lockfile));
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.486 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnxsim",
3
- "version": "0.1.486",
3
+ "version": "0.1.488",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for rnx.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -95,7 +95,7 @@ export class AgentDaemonClient {
95
95
  constructor(opts: AgentDaemonClientOptions = {}) {
96
96
  this.port = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT
97
97
  this.commandTimeoutMs = opts.commandTimeoutMs ?? 15_000
98
- this.ws = new WebSocket(`ws://localhost:${this.port}`)
98
+ this.ws = new WebSocket(`ws://127.0.0.1:${this.port}`)
99
99
  this.ready = new Promise<void>((resolve, reject) => {
100
100
  const onOpen = () => {
101
101
  this.ws.off('error', onError)
@@ -148,8 +148,13 @@ export function resolveSootsimBridgeWebSocketUrl(input: {
148
148
  bridgePort: number
149
149
  href: string
150
150
  }): string {
151
+ // 127.0.0.1, not localhost. the daemon binds ipv4 only (bridge-host bindOnce)
152
+ // so that localhost's ::1/127.0.0.1 coinflip cannot hide it; the health probe
153
+ // already talks to 127.0.0.1. targeting localhost here undoes that: when node
154
+ // tries ::1 first and does not fall back, vite's proxy answers 502 and the
155
+ // browser logs a console error that fails e2e.
151
156
  return resolveSootsimWebSocketProxyUrl({
152
- targetUrl: `ws://localhost:${input.bridgePort}`,
157
+ targetUrl: `ws://127.0.0.1:${input.bridgePort}`,
153
158
  href: input.href,
154
159
  })
155
160
  }