rnxsim 0.1.392 → 0.1.394
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/cli/cloud-client.ts +74 -52
- package/cli/cloud-dispatch.ts +2 -1
- package/cli/commands/inspect/core.ts +33 -2
- package/cli/commands/platform.ts +34 -9
- package/detox/attempt-events.cjs +23 -0
- package/detox/element-types.ts +11 -2
- package/detox/expectations.ts +7 -3
- package/detox/gestures.ts +23 -2
- package/detox/index.ts +42 -62
- package/detox/navigation-lifecycle.ts +290 -0
- 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-input.cjs +12 -1
- package/dist-lib/bridge-contract-input.mjs +10 -1
- package/dist-lib/bridge-contract.cjs +7 -1
- package/dist-lib/bridge-contract.mjs +5 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +29 -3
- package/dist-lib/cloud-contract.mjs +26 -2
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +240 -48
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- 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/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +23 -3
- 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-production-bundle.cjs +132 -3
- package/dist-lib/metro-production-bundle.mjs +129 -2
- 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/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +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 +24 -3
- package/dist-lib/sdk.mjs +24 -3
- package/dist-lib/skills.cjs +51 -80
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/src/bridge-contract-input.ts +12 -0
- package/src/bridge-contract.ts +7 -0
- package/src/cloud-contract.ts +29 -0
- package/src/metro-production-bundle.ts +200 -0
package/dist-lib/skills.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.394 | (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;
|
|
@@ -1347,35 +1347,23 @@ var init_supported_native_packages = __esm({
|
|
|
1347
1347
|
"expo-apple-authentication",
|
|
1348
1348
|
// preset
|
|
1349
1349
|
"expo-application",
|
|
1350
|
-
//
|
|
1350
|
+
// coverage
|
|
1351
1351
|
"expo-asset",
|
|
1352
1352
|
// preset+coverage
|
|
1353
1353
|
"expo-audio",
|
|
1354
1354
|
// coverage
|
|
1355
1355
|
"expo-av",
|
|
1356
1356
|
// coverage
|
|
1357
|
-
"expo-background-fetch",
|
|
1358
|
-
// visual-proof
|
|
1359
|
-
"expo-background-task",
|
|
1360
|
-
// visual-proof
|
|
1361
1357
|
"expo-battery",
|
|
1362
|
-
//
|
|
1358
|
+
// coverage
|
|
1363
1359
|
"expo-blur",
|
|
1364
1360
|
// preset+coverage
|
|
1365
|
-
"expo-brightness",
|
|
1366
|
-
// visual-proof
|
|
1367
|
-
"expo-calendar",
|
|
1368
|
-
// visual-proof
|
|
1369
1361
|
"expo-camera",
|
|
1370
1362
|
// coverage
|
|
1371
|
-
"expo-cellular",
|
|
1372
|
-
// visual-proof
|
|
1373
1363
|
"expo-clipboard",
|
|
1374
1364
|
// preset+coverage
|
|
1375
1365
|
"expo-constants",
|
|
1376
|
-
// preset+
|
|
1377
|
-
"expo-contacts",
|
|
1378
|
-
// visual-proof
|
|
1366
|
+
// preset+coverage
|
|
1379
1367
|
"expo-crypto",
|
|
1380
1368
|
// preset+coverage
|
|
1381
1369
|
"expo-dev-client",
|
|
@@ -1397,27 +1385,23 @@ var init_supported_native_packages = __esm({
|
|
|
1397
1385
|
"expo-ignore-battery-optimizations",
|
|
1398
1386
|
// coverage
|
|
1399
1387
|
"expo-image",
|
|
1400
|
-
// preset+
|
|
1388
|
+
// preset+coverage
|
|
1401
1389
|
"expo-image-manipulator",
|
|
1402
1390
|
// coverage
|
|
1403
1391
|
"expo-image-picker",
|
|
1404
1392
|
// preset+coverage
|
|
1405
1393
|
"expo-insights",
|
|
1406
1394
|
// coverage
|
|
1407
|
-
"expo-intent-launcher",
|
|
1408
|
-
// visual-proof
|
|
1409
1395
|
"expo-keep-awake",
|
|
1410
1396
|
// coverage
|
|
1411
1397
|
"expo-linear-gradient",
|
|
1412
|
-
// preset+
|
|
1398
|
+
// preset+coverage
|
|
1413
1399
|
"expo-linking",
|
|
1414
1400
|
// preset+coverage
|
|
1415
1401
|
"expo-local-authentication",
|
|
1416
1402
|
// coverage
|
|
1417
1403
|
"expo-localization",
|
|
1418
1404
|
// coverage
|
|
1419
|
-
"expo-mail-composer",
|
|
1420
|
-
// visual-proof
|
|
1421
1405
|
"expo-media-library",
|
|
1422
1406
|
// coverage
|
|
1423
1407
|
"expo-mesh-gradient",
|
|
@@ -1425,51 +1409,35 @@ var init_supported_native_packages = __esm({
|
|
|
1425
1409
|
"expo-modules-core",
|
|
1426
1410
|
// preset
|
|
1427
1411
|
"expo-network",
|
|
1428
|
-
// preset+
|
|
1412
|
+
// preset+coverage
|
|
1429
1413
|
"expo-notifications",
|
|
1430
1414
|
// preset+coverage
|
|
1431
|
-
"expo-print",
|
|
1432
|
-
// visual-proof
|
|
1433
|
-
"expo-screen-capture",
|
|
1434
|
-
// visual-proof
|
|
1435
1415
|
"expo-screen-corner-radius",
|
|
1436
1416
|
// coverage
|
|
1437
1417
|
"expo-screen-orientation",
|
|
1438
|
-
//
|
|
1418
|
+
// coverage
|
|
1439
1419
|
"expo-secure-store",
|
|
1440
|
-
//
|
|
1441
|
-
"expo-sensors",
|
|
1442
|
-
// visual-proof
|
|
1443
|
-
"expo-sharing",
|
|
1444
|
-
// visual-proof
|
|
1445
|
-
"expo-sms",
|
|
1446
|
-
// visual-proof
|
|
1447
|
-
"expo-speech",
|
|
1448
|
-
// visual-proof
|
|
1420
|
+
// coverage
|
|
1449
1421
|
"expo-speech-recognition",
|
|
1450
1422
|
// preset
|
|
1451
1423
|
"expo-splash-screen",
|
|
1452
|
-
//
|
|
1424
|
+
// coverage
|
|
1453
1425
|
"expo-sqlite",
|
|
1454
1426
|
// preset+coverage
|
|
1455
1427
|
"expo-store-review",
|
|
1456
|
-
//
|
|
1428
|
+
// coverage
|
|
1457
1429
|
"expo-symbols",
|
|
1458
1430
|
// coverage
|
|
1459
1431
|
"expo-system-ui",
|
|
1460
1432
|
// coverage
|
|
1461
1433
|
"expo-task-manager",
|
|
1462
|
-
//
|
|
1434
|
+
// coverage
|
|
1463
1435
|
"expo-testflight",
|
|
1464
1436
|
// coverage
|
|
1465
1437
|
"expo-tracking-transparency",
|
|
1466
|
-
//
|
|
1438
|
+
// coverage
|
|
1467
1439
|
"expo-updates",
|
|
1468
1440
|
// preset+coverage
|
|
1469
|
-
"expo-video",
|
|
1470
|
-
// visual-proof
|
|
1471
|
-
"expo-video-thumbnails",
|
|
1472
|
-
// visual-proof
|
|
1473
1441
|
"expo-web-browser",
|
|
1474
1442
|
// preset
|
|
1475
1443
|
"expo-winter-fetch",
|
|
@@ -1493,7 +1461,7 @@ var init_supported_native_packages = __esm({
|
|
|
1493
1461
|
"posthog-react-native-session-replay",
|
|
1494
1462
|
// preset+coverage
|
|
1495
1463
|
"react-native",
|
|
1496
|
-
// preset+
|
|
1464
|
+
// preset+coverage
|
|
1497
1465
|
"react-native-actions-shortcuts",
|
|
1498
1466
|
// coverage
|
|
1499
1467
|
"react-native-adjust",
|
|
@@ -1517,7 +1485,7 @@ var init_supported_native_packages = __esm({
|
|
|
1517
1485
|
"react-native-bootsplash",
|
|
1518
1486
|
// preset+coverage
|
|
1519
1487
|
"react-native-bottom-tabs",
|
|
1520
|
-
// preset
|
|
1488
|
+
// preset
|
|
1521
1489
|
"react-native-branch",
|
|
1522
1490
|
// coverage
|
|
1523
1491
|
"react-native-config",
|
|
@@ -1539,7 +1507,7 @@ var init_supported_native_packages = __esm({
|
|
|
1539
1507
|
"react-native-fs",
|
|
1540
1508
|
// coverage
|
|
1541
1509
|
"react-native-gesture-handler",
|
|
1542
|
-
// preset+
|
|
1510
|
+
// preset+coverage
|
|
1543
1511
|
"react-native-get-device-locale",
|
|
1544
1512
|
// coverage
|
|
1545
1513
|
"react-native-get-location",
|
|
@@ -1567,7 +1535,7 @@ var init_supported_native_packages = __esm({
|
|
|
1567
1535
|
"react-native-key-command",
|
|
1568
1536
|
// coverage
|
|
1569
1537
|
"react-native-keyboard-controller",
|
|
1570
|
-
// preset
|
|
1538
|
+
// preset
|
|
1571
1539
|
"react-native-keychain",
|
|
1572
1540
|
// coverage
|
|
1573
1541
|
"react-native-keys",
|
|
@@ -1597,7 +1565,7 @@ var init_supported_native_packages = __esm({
|
|
|
1597
1565
|
"react-native-notification-badge",
|
|
1598
1566
|
// coverage
|
|
1599
1567
|
"react-native-pager-view",
|
|
1600
|
-
// preset
|
|
1568
|
+
// preset
|
|
1601
1569
|
"react-native-pdf",
|
|
1602
1570
|
// coverage
|
|
1603
1571
|
"react-native-permissions",
|
|
@@ -1617,7 +1585,7 @@ var init_supported_native_packages = __esm({
|
|
|
1617
1585
|
"react-native-randombytes",
|
|
1618
1586
|
// coverage
|
|
1619
1587
|
"react-native-reanimated",
|
|
1620
|
-
// preset+
|
|
1588
|
+
// preset+coverage
|
|
1621
1589
|
"react-native-restart",
|
|
1622
1590
|
// coverage
|
|
1623
1591
|
"react-native-restart-newarch",
|
|
@@ -1625,11 +1593,11 @@ var init_supported_native_packages = __esm({
|
|
|
1625
1593
|
"react-native-rsa-signer",
|
|
1626
1594
|
// coverage
|
|
1627
1595
|
"react-native-safe-area-context",
|
|
1628
|
-
// preset+
|
|
1596
|
+
// preset+coverage
|
|
1629
1597
|
"react-native-screen-corner-radius",
|
|
1630
1598
|
// coverage
|
|
1631
1599
|
"react-native-screens",
|
|
1632
|
-
// preset+
|
|
1600
|
+
// preset+coverage
|
|
1633
1601
|
"react-native-securerandom",
|
|
1634
1602
|
// coverage
|
|
1635
1603
|
"react-native-settings",
|
|
@@ -1645,7 +1613,7 @@ var init_supported_native_packages = __esm({
|
|
|
1645
1613
|
"react-native-splash-screen",
|
|
1646
1614
|
// coverage
|
|
1647
1615
|
"react-native-svg",
|
|
1648
|
-
// preset
|
|
1616
|
+
// preset
|
|
1649
1617
|
"react-native-system-bars",
|
|
1650
1618
|
// coverage
|
|
1651
1619
|
"react-native-teleport",
|
|
@@ -1659,7 +1627,7 @@ var init_supported_native_packages = __esm({
|
|
|
1659
1627
|
"react-native-turbo-haptics",
|
|
1660
1628
|
// coverage
|
|
1661
1629
|
"react-native-uitextview",
|
|
1662
|
-
//
|
|
1630
|
+
// coverage
|
|
1663
1631
|
"react-native-ulid-jsi",
|
|
1664
1632
|
// coverage
|
|
1665
1633
|
"react-native-ux-cam",
|
|
@@ -1679,7 +1647,7 @@ var init_supported_native_packages = __esm({
|
|
|
1679
1647
|
"react-native-webp-format",
|
|
1680
1648
|
// coverage
|
|
1681
1649
|
"react-native-webview",
|
|
1682
|
-
// preset
|
|
1650
|
+
// preset
|
|
1683
1651
|
"react-native-worklets",
|
|
1684
1652
|
// preset+coverage
|
|
1685
1653
|
"react-native-xxhash",
|
|
@@ -21428,12 +21396,34 @@ var init_cli_constants = __esm({
|
|
|
21428
21396
|
});
|
|
21429
21397
|
|
|
21430
21398
|
// src/cloud-contract.ts
|
|
21431
|
-
|
|
21399
|
+
function isRnxCloudCommandType(value) {
|
|
21400
|
+
return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
|
|
21401
|
+
}
|
|
21402
|
+
var RNX_CLOUD_MAX_ARTIFACT_BYTES, RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES, RNX_CLOUD_COMMAND_TYPES, RNX_CLOUD_COMMAND_TYPE_SET;
|
|
21432
21403
|
var init_cloud_contract = __esm({
|
|
21433
21404
|
"src/cloud-contract.ts"() {
|
|
21434
21405
|
"use strict";
|
|
21435
21406
|
RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
|
|
21436
21407
|
RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
|
|
21408
|
+
RNX_CLOUD_COMMAND_TYPES = Object.freeze([
|
|
21409
|
+
"state",
|
|
21410
|
+
"query",
|
|
21411
|
+
"resolve",
|
|
21412
|
+
"perform",
|
|
21413
|
+
"waitFor",
|
|
21414
|
+
"reset",
|
|
21415
|
+
"screenshot",
|
|
21416
|
+
"capture",
|
|
21417
|
+
"captureRegions",
|
|
21418
|
+
"settle",
|
|
21419
|
+
"setAppearance",
|
|
21420
|
+
"openUrl",
|
|
21421
|
+
"tree",
|
|
21422
|
+
"tap",
|
|
21423
|
+
"longPress",
|
|
21424
|
+
"memory"
|
|
21425
|
+
]);
|
|
21426
|
+
RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
|
|
21437
21427
|
}
|
|
21438
21428
|
});
|
|
21439
21429
|
|
|
@@ -21552,27 +21542,7 @@ function cloudCommand(command) {
|
|
|
21552
21542
|
for (const [key, value] of Object.entries(command)) {
|
|
21553
21543
|
if (key !== "id" && key !== "simId") clean[key] = value;
|
|
21554
21544
|
}
|
|
21555
|
-
if (command.type
|
|
21556
|
-
const query = command.query;
|
|
21557
|
-
if (!isRecord4(query) || query.kind !== "tree" && query.kind !== "find" && query.kind !== "count") {
|
|
21558
|
-
throw new Error("RNX Cloud supports only structured tree, node, and count queries");
|
|
21559
|
-
}
|
|
21560
|
-
return clean;
|
|
21561
|
-
}
|
|
21562
|
-
if (command.type === "waitFor") {
|
|
21563
|
-
const options = command.waitForOptions;
|
|
21564
|
-
const condition = isRecord4(options) ? options.condition : null;
|
|
21565
|
-
if (!isRecord4(condition) || condition.type !== "ready" && condition.type !== "present" && condition.type !== "absent") {
|
|
21566
|
-
throw new Error("RNX Cloud supports only ready and selector waits");
|
|
21567
|
-
}
|
|
21568
|
-
return clean;
|
|
21569
|
-
}
|
|
21570
|
-
if (command.type === "state") return clean;
|
|
21571
|
-
if (command.type === "capture" || command.type === "captureRegions") return clean;
|
|
21572
|
-
if (command.type === "setAppearance" || command.type === "openUrl") return clean;
|
|
21573
|
-
if (command.type === "screenshot" && command.crop === void 0 && command.captureFilter === void 0 && (command.layers === void 0 || command.layers === "tenant")) {
|
|
21574
|
-
return clean;
|
|
21575
|
-
}
|
|
21545
|
+
if (isRnxCloudCommandType(command.type)) return clean;
|
|
21576
21546
|
throw new Error(`RNX Cloud does not support bridge command type ${command.type}`);
|
|
21577
21547
|
}
|
|
21578
21548
|
function createCloudBridgeForParsed(parsed) {
|
|
@@ -23636,8 +23606,9 @@ async function inspectWaitReady(bridge, timeoutMs = 2e4, options = {}) {
|
|
|
23636
23606
|
};
|
|
23637
23607
|
}
|
|
23638
23608
|
const probe3 = result.probe;
|
|
23609
|
+
const externalError = probe3?.externalError ?? "";
|
|
23639
23610
|
const status = {
|
|
23640
|
-
ready: result.matched,
|
|
23611
|
+
ready: result.matched && !externalError,
|
|
23641
23612
|
elapsedMs: result.elapsedMs,
|
|
23642
23613
|
nodes: probe3?.nodes ?? 0,
|
|
23643
23614
|
targets: probe3?.targets ?? 0,
|
|
@@ -23648,7 +23619,7 @@ async function inspectWaitReady(bridge, timeoutMs = 2e4, options = {}) {
|
|
|
23648
23619
|
loadingText: probe3?.loadingText ?? "",
|
|
23649
23620
|
externalReady: probe3?.externalReady ?? null,
|
|
23650
23621
|
externalStatus: probe3?.externalStatus ?? "",
|
|
23651
|
-
externalError
|
|
23622
|
+
externalError,
|
|
23652
23623
|
suppressedEntryError: probe3?.suppressedEntryError ?? "",
|
|
23653
23624
|
errors: probe3?.errors ?? 0,
|
|
23654
23625
|
bridgeError: result.error ?? ""
|
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.394 | (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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// WebSocket routes) validates its request bodies here rather than growing a
|
|
4
4
|
// second reader for the same shapes.
|
|
5
5
|
|
|
6
|
+
import { SIM_LONG_PRESS_MAX_MS } from './bridge-contract'
|
|
6
7
|
import type {
|
|
7
8
|
ResetOptions,
|
|
8
9
|
SemanticWaitOptions,
|
|
@@ -130,6 +131,17 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
|
|
|
130
131
|
}
|
|
131
132
|
case 'longPress': {
|
|
132
133
|
const durationMs = optionalNumber(step, 'durationMs', path)
|
|
134
|
+
if (
|
|
135
|
+
durationMs !== undefined &&
|
|
136
|
+
(!Number.isInteger(durationMs) ||
|
|
137
|
+
durationMs < 1 ||
|
|
138
|
+
durationMs > SIM_LONG_PRESS_MAX_MS)
|
|
139
|
+
) {
|
|
140
|
+
validationFailure(
|
|
141
|
+
`${path}.durationMs`,
|
|
142
|
+
`must be an integer between 1 and ${SIM_LONG_PRESS_MAX_MS}`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
133
145
|
return {
|
|
134
146
|
type,
|
|
135
147
|
x: numberValue(step.x, `${path}.x`),
|
package/src/bridge-contract.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type BridgeCommandType =
|
|
|
22
22
|
| 'settle'
|
|
23
23
|
| 'setAppearance'
|
|
24
24
|
| 'openUrl'
|
|
25
|
+
| 'memory'
|
|
25
26
|
| 'tap'
|
|
26
27
|
| 'longPress'
|
|
27
28
|
| 'keyboard'
|
|
@@ -216,6 +217,12 @@ export interface SemanticReadyProbe {
|
|
|
216
217
|
suppressedEntryError: string
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
// react native's own Pressable/Touchable delayLongPress default, and the
|
|
221
|
+
// ceiling any transport accepts for a press-and-hold. a driver that holds
|
|
222
|
+
// longer than this is describing a drag, not a long press.
|
|
223
|
+
export const SIM_LONG_PRESS_DEFAULT_MS = 500
|
|
224
|
+
export const SIM_LONG_PRESS_MAX_MS = 5_000
|
|
225
|
+
|
|
219
226
|
export const SEMANTIC_READY_CONTENT_NODE_FLOOR = 100
|
|
220
227
|
export const SEMANTIC_READY_NODE_STABLE_MS = 750
|
|
221
228
|
export const SEMANTIC_READY_POLL_MS = 150
|
package/src/cloud-contract.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
+
import type { BridgeCommandType } from './bridge-contract'
|
|
2
|
+
|
|
1
3
|
export const RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024
|
|
2
4
|
export const RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT = 16
|
|
3
5
|
export const RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256
|
|
4
6
|
export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
|
|
5
7
|
|
|
8
|
+
export const RNX_CLOUD_COMMAND_TYPES = Object.freeze([
|
|
9
|
+
'state',
|
|
10
|
+
'query',
|
|
11
|
+
'resolve',
|
|
12
|
+
'perform',
|
|
13
|
+
'waitFor',
|
|
14
|
+
'reset',
|
|
15
|
+
'screenshot',
|
|
16
|
+
'capture',
|
|
17
|
+
'captureRegions',
|
|
18
|
+
'settle',
|
|
19
|
+
'setAppearance',
|
|
20
|
+
'openUrl',
|
|
21
|
+
'tree',
|
|
22
|
+
'tap',
|
|
23
|
+
'longPress',
|
|
24
|
+
'memory',
|
|
25
|
+
]) satisfies readonly BridgeCommandType[]
|
|
26
|
+
|
|
27
|
+
const RNX_CLOUD_COMMAND_TYPE_SET = new Set<string>(RNX_CLOUD_COMMAND_TYPES)
|
|
28
|
+
|
|
29
|
+
export function isRnxCloudCommandType(
|
|
30
|
+
value: string,
|
|
31
|
+
): value is (typeof RNX_CLOUD_COMMAND_TYPES)[number] {
|
|
32
|
+
return RNX_CLOUD_COMMAND_TYPE_SET.has(value)
|
|
33
|
+
}
|
|
34
|
+
|
|
6
35
|
export interface RnxCloudClaim {
|
|
7
36
|
id: string
|
|
8
37
|
expiresAt: number
|
|
@@ -49,3 +49,203 @@ export function readRNXMetroModuleIdentity(
|
|
|
49
49
|
}
|
|
50
50
|
return { source: bundleText.slice(0, footerStart), modulePaths }
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// the exact layout Metro's baseJSBundle emits and the rnx runtime contract
|
|
54
|
+
// depends on: a prelude, then one `__d(` or `__r(` statement per line, each
|
|
55
|
+
// running until the next statement starts its own line. one reader owns this
|
|
56
|
+
// recognition so the loader's identity validation, the source-map attribution
|
|
57
|
+
// pass, and the cloud artifact producer cannot drift apart.
|
|
58
|
+
export interface RNXMetroDefine {
|
|
59
|
+
kind: 'define'
|
|
60
|
+
moduleId: number
|
|
61
|
+
start: number
|
|
62
|
+
end: number
|
|
63
|
+
lineIndex: number
|
|
64
|
+
endLineIndex: number
|
|
65
|
+
/** the factory expression, between `__d(` and the module ID. */
|
|
66
|
+
factory: { start: number; end: number }
|
|
67
|
+
/** the dependency array, brackets included. */
|
|
68
|
+
dependencies: { start: number; end: number }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface RNXMetroRun {
|
|
72
|
+
kind: 'run'
|
|
73
|
+
start: number
|
|
74
|
+
end: number
|
|
75
|
+
lineIndex: number
|
|
76
|
+
endLineIndex: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type RNXMetroStatement = RNXMetroDefine | RNXMetroRun
|
|
80
|
+
|
|
81
|
+
export interface RNXMetroBundleLayout {
|
|
82
|
+
/** everything before the first `__d(`/`__r(` line: prelude vars and polyfills. */
|
|
83
|
+
prelude: string
|
|
84
|
+
statements: RNXMetroStatement[]
|
|
85
|
+
/** `__d(` statements whose module ID could not be read. */
|
|
86
|
+
moduleIdFailures: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface DefineTail {
|
|
90
|
+
moduleId: number
|
|
91
|
+
factoryEnd: number
|
|
92
|
+
dependenciesStart: number
|
|
93
|
+
dependenciesEnd: number
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// each `__d(...)` ends with `),<id>,[dep,dep,...]);?`. the dependency array can
|
|
97
|
+
// be hundreds of entries long, so walk back from `end` instead of searching a
|
|
98
|
+
// fixed tail window: skip trailing `;` and whitespace, consume the closing `)`,
|
|
99
|
+
// balance `[...]` to find its opening bracket, then read the integer before it.
|
|
100
|
+
function readDefineTail(source: string, start: number, end: number): DefineTail | null {
|
|
101
|
+
let cursor = end
|
|
102
|
+
while (
|
|
103
|
+
cursor > start &&
|
|
104
|
+
(source[cursor] === ' ' ||
|
|
105
|
+
source[cursor] === '\n' ||
|
|
106
|
+
source[cursor] === '\t' ||
|
|
107
|
+
source[cursor] === ';')
|
|
108
|
+
) {
|
|
109
|
+
cursor--
|
|
110
|
+
}
|
|
111
|
+
if (source[cursor] !== ')') return null
|
|
112
|
+
cursor--
|
|
113
|
+
while (cursor > start && /\s/.test(source[cursor])) cursor--
|
|
114
|
+
if (source[cursor] !== ']') return null
|
|
115
|
+
const dependenciesEnd = cursor + 1
|
|
116
|
+
let depth = 1
|
|
117
|
+
cursor--
|
|
118
|
+
while (cursor > start && depth > 0) {
|
|
119
|
+
const character = source[cursor]
|
|
120
|
+
if (character === ']') depth++
|
|
121
|
+
else if (character === '[') depth--
|
|
122
|
+
if (depth === 0) break
|
|
123
|
+
cursor--
|
|
124
|
+
}
|
|
125
|
+
if (depth !== 0) return null
|
|
126
|
+
const dependenciesStart = cursor
|
|
127
|
+
let idEnd = cursor - 1
|
|
128
|
+
while (idEnd > start && /\s/.test(source[idEnd])) idEnd--
|
|
129
|
+
if (source[idEnd] !== ',') return null
|
|
130
|
+
// a minifier is free to write the module ID as `1e3` or `0x3e8`, so read the
|
|
131
|
+
// whole numeric literal rather than a run of decimal digits. the factory
|
|
132
|
+
// before it always ends in `}` or `)`, which stops this walk immediately.
|
|
133
|
+
let idStart = idEnd
|
|
134
|
+
while (idStart > start && /[0-9a-fA-FxX.+\-]/.test(source[idStart - 1])) idStart--
|
|
135
|
+
const literal = source.slice(idStart, idEnd)
|
|
136
|
+
if (!/^-?(?:0[xXbBoO][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)$/.test(literal)) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
const moduleId = Number(literal)
|
|
140
|
+
if (!Number.isSafeInteger(moduleId)) return null
|
|
141
|
+
let factoryEnd = idStart - 1
|
|
142
|
+
while (factoryEnd > start && /\s/.test(source[factoryEnd])) factoryEnd--
|
|
143
|
+
if (source[factoryEnd] !== ',') return null
|
|
144
|
+
return {
|
|
145
|
+
moduleId,
|
|
146
|
+
factoryEnd,
|
|
147
|
+
dependenciesStart,
|
|
148
|
+
dependenciesEnd,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** reads the canonical Metro production layout from footer-stripped source. */
|
|
153
|
+
export function readRNXMetroBundleLayout(source: string): RNXMetroBundleLayout {
|
|
154
|
+
const lineOffsets = [0]
|
|
155
|
+
for (let index = 0; index < source.length; index++) {
|
|
156
|
+
if (source.charCodeAt(index) === 10) lineOffsets.push(index + 1)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const boundaries: number[] = []
|
|
160
|
+
for (let lineIndex = 0; lineIndex < lineOffsets.length; lineIndex++) {
|
|
161
|
+
const start = lineOffsets[lineIndex]
|
|
162
|
+
if (source.startsWith('__d(', start) || source.startsWith('__r(', start)) {
|
|
163
|
+
boundaries.push(lineIndex)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const statements: RNXMetroStatement[] = []
|
|
168
|
+
let moduleIdFailures = 0
|
|
169
|
+
for (let index = 0; index < boundaries.length; index++) {
|
|
170
|
+
const lineIndex = boundaries[index]
|
|
171
|
+
const start = lineOffsets[lineIndex]
|
|
172
|
+
const nextLineIndex = boundaries[index + 1]
|
|
173
|
+
const endLineIndex =
|
|
174
|
+
nextLineIndex !== undefined ? nextLineIndex - 1 : lineOffsets.length - 1
|
|
175
|
+
const end = nextLineIndex !== undefined ? lineOffsets[nextLineIndex] : source.length
|
|
176
|
+
if (!source.startsWith('__d(', start)) {
|
|
177
|
+
statements.push({ kind: 'run', start, end, lineIndex, endLineIndex })
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
const tail = readDefineTail(source, start, end - 1)
|
|
181
|
+
if (!tail) {
|
|
182
|
+
moduleIdFailures++
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
statements.push({
|
|
186
|
+
kind: 'define',
|
|
187
|
+
moduleId: tail.moduleId,
|
|
188
|
+
start,
|
|
189
|
+
end,
|
|
190
|
+
lineIndex,
|
|
191
|
+
endLineIndex,
|
|
192
|
+
factory: { start: start + '__d('.length, end: tail.factoryEnd },
|
|
193
|
+
dependencies: { start: tail.dependenciesStart, end: tail.dependenciesEnd },
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
prelude: source.slice(
|
|
199
|
+
0,
|
|
200
|
+
boundaries.length > 0 ? lineOffsets[boundaries[0]] : source.length,
|
|
201
|
+
),
|
|
202
|
+
statements,
|
|
203
|
+
moduleIdFailures,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* validates the exact identity contract before a loader installs a Metro
|
|
209
|
+
* factory trap or a producer ships an artifact: every define carries a unique,
|
|
210
|
+
* readable module ID, and the identity map names those IDs and no others.
|
|
211
|
+
* serializers that emit another layout need an ingestion adapter that
|
|
212
|
+
* normalizes them to this format.
|
|
213
|
+
*/
|
|
214
|
+
export function validateRNXMetroModulePaths(
|
|
215
|
+
source: string,
|
|
216
|
+
modulePaths: RNXMetroModulePathMap,
|
|
217
|
+
): void {
|
|
218
|
+
const layout = readRNXMetroBundleLayout(source)
|
|
219
|
+
if (layout.moduleIdFailures > 0) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Metro module identity could not read ${layout.moduleIdFailures} module IDs`,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
const definedIds = new Set<number>()
|
|
225
|
+
for (const statement of layout.statements) {
|
|
226
|
+
if (statement.kind !== 'define') continue
|
|
227
|
+
if (definedIds.has(statement.moduleId)) {
|
|
228
|
+
throw new Error(`Metro module identity has duplicate module ${statement.moduleId}`)
|
|
229
|
+
}
|
|
230
|
+
definedIds.add(statement.moduleId)
|
|
231
|
+
if (!Object.prototype.hasOwnProperty.call(modulePaths, String(statement.moduleId))) {
|
|
232
|
+
throw new Error(`Metro module identity is missing ${statement.moduleId}`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (definedIds.size === 0) {
|
|
236
|
+
throw new Error('Metro module identity found no canonical JavaScript modules')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const [key, modulePath] of Object.entries(modulePaths)) {
|
|
240
|
+
const moduleId = Number(key)
|
|
241
|
+
if (!Number.isSafeInteger(moduleId) || String(moduleId) !== key) {
|
|
242
|
+
throw new Error(`Metro module identity has invalid module ID ${key}`)
|
|
243
|
+
}
|
|
244
|
+
if (typeof modulePath !== 'string' || modulePath.length === 0) {
|
|
245
|
+
throw new Error(`Metro module identity has no path for ${key}`)
|
|
246
|
+
}
|
|
247
|
+
if (!definedIds.has(moduleId)) {
|
|
248
|
+
throw new Error(`Metro module identity has no module ${key}`)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|