rnxsim 0.1.513 → 0.1.514

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 (56) hide show
  1. package/cli/outbound-endpoints.ts +1 -7
  2. package/dist-lib/agent-daemon-client.cjs +1 -1
  3. package/dist-lib/agent-events.cjs +1 -1
  4. package/dist-lib/agent-identity.cjs +1 -1
  5. package/dist-lib/agent-sessions.cjs +1 -1
  6. package/dist-lib/attached-projects.cjs +1 -1
  7. package/dist-lib/auth/shared-session.cjs +1 -1
  8. package/dist-lib/backend-origin.cjs +1 -1
  9. package/dist-lib/beta.cjs +1 -1
  10. package/dist-lib/beta.mjs +1 -1
  11. package/dist-lib/bridge-constants.cjs +1 -1
  12. package/dist-lib/bridge-contract-input.cjs +1 -1
  13. package/dist-lib/bridge-contract-input.mjs +1 -1
  14. package/dist-lib/bridge-contract.cjs +1 -1
  15. package/dist-lib/bridge-contract.mjs +1 -1
  16. package/dist-lib/capture-contract.cjs +1 -1
  17. package/dist-lib/capture-contract.mjs +1 -1
  18. package/dist-lib/cli-constants.cjs +1 -1
  19. package/dist-lib/cloud-contract.cjs +57 -3
  20. package/dist-lib/cloud-contract.mjs +49 -2
  21. package/dist-lib/cloud.cjs +66 -8
  22. package/dist-lib/cloud.mjs +66 -8
  23. package/dist-lib/config.cjs +1 -1
  24. package/dist-lib/detox/index.cjs +1 -1
  25. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  26. package/dist-lib/home-paths.cjs +1 -1
  27. package/dist-lib/host/bridge-host.cjs +1 -1
  28. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  29. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  31. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  32. package/dist-lib/host/websocket-proxy.cjs +1 -1
  33. package/dist-lib/index.cjs +1 -1
  34. package/dist-lib/jump-to-source-babel.cjs +1 -1
  35. package/dist-lib/jump-to-source-native.cjs +1 -1
  36. package/dist-lib/menu.cjs +1 -1
  37. package/dist-lib/menu.mjs +1 -1
  38. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  39. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  40. package/dist-lib/metro-production-bundle.cjs +1 -1
  41. package/dist-lib/metro-production-bundle.mjs +1 -1
  42. package/dist-lib/metro.cjs +1 -1
  43. package/dist-lib/profiles.cjs +1 -1
  44. package/dist-lib/public-brand.cjs +1 -1
  45. package/dist-lib/react-native-host-modules.cjs +1 -1
  46. package/dist-lib/react-native-host-modules.mjs +1 -1
  47. package/dist-lib/render-mode.cjs +1 -1
  48. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  49. package/dist-lib/sdk.cjs +1 -1
  50. package/dist-lib/sdk.mjs +1 -1
  51. package/dist-lib/skills.cjs +1 -1
  52. package/dist-lib/swift.cjs +1 -1
  53. package/dist-lib/vite.cjs +1 -1
  54. package/package.json +1 -1
  55. package/src/cloud-contract.ts +120 -0
  56. package/src/cloud.ts +94 -7
@@ -164,16 +164,10 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
164
164
  category: 'contrast_user_action',
165
165
  count: 1,
166
166
  },
167
- 'packages/sootsim/src/cloud.ts :: fetch :: `${origin}/v1/boxes`': {
167
+ 'packages/sootsim/src/cloud.ts :: fetch :: url': {
168
168
  category: 'rnx_cloud_sim',
169
169
  count: 1,
170
170
  },
171
- 'packages/sootsim/src/cloud.ts :: fetch :: `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`':
172
- { category: 'rnx_cloud_sim', count: 1 },
173
- 'packages/sootsim/src/cloud.ts :: fetch :: `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/claim`':
174
- { category: 'rnx_cloud_sim', count: 1 },
175
- 'packages/sootsim/src/cloud.ts :: fetch :: `${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}`':
176
- { category: 'rnx_cloud_sim', count: 1 },
177
171
  // maestro's flow `http` API. the url is whatever the flow author wrote, and
178
172
  // it is fetched from a child process because that API is synchronous.
179
173
  'packages/sootsim/cli/internal-child.ts :: fetch :: request.url': {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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;
@@ -25,10 +25,17 @@ __export(cloud_contract_exports, {
25
25
  RNX_CLOUD_COMMAND_TYPES: () => RNX_CLOUD_COMMAND_TYPES,
26
26
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES: () => RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
27
27
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES: () => RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
28
+ RNX_CLOUD_LABELS_MAX_COUNT: () => RNX_CLOUD_LABELS_MAX_COUNT,
29
+ RNX_CLOUD_LABEL_MAX_LENGTH: () => RNX_CLOUD_LABEL_MAX_LENGTH,
28
30
  RNX_CLOUD_MAX_ARTIFACT_BYTES: () => RNX_CLOUD_MAX_ARTIFACT_BYTES,
29
31
  RNX_CLOUD_TEST_FILES_MAX_BYTES: () => RNX_CLOUD_TEST_FILES_MAX_BYTES,
30
32
  RNX_CLOUD_TEST_FILES_MAX_COUNT: () => RNX_CLOUD_TEST_FILES_MAX_COUNT,
31
- isRnxCloudCommandType: () => isRnxCloudCommandType
33
+ isRnxCloudCommandType: () => isRnxCloudCommandType,
34
+ isRnxCloudSimStatus: () => isRnxCloudSimStatus,
35
+ isValidRnxCloudLabel: () => isValidRnxCloudLabel,
36
+ matchesRnxCloudLabels: () => matchesRnxCloudLabels,
37
+ parseRnxCloudLabels: () => parseRnxCloudLabels,
38
+ readRnxCloudLabels: () => readRnxCloudLabels
32
39
  });
33
40
  module.exports = __toCommonJS(cloud_contract_exports);
34
41
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -59,13 +66,60 @@ var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
59
66
  function isRnxCloudCommandType(value) {
60
67
  return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
61
68
  }
69
+ var RNX_CLOUD_LABELS_MAX_COUNT = 16;
70
+ var RNX_CLOUD_LABEL_MAX_LENGTH = 63;
71
+ var RNX_CLOUD_LABEL_CONTROL = /[\u0000-\u001f\u007f]/;
72
+ function isValidRnxCloudLabel(key, value) {
73
+ return key.length > 0 && key.length <= RNX_CLOUD_LABEL_MAX_LENGTH && value.length <= RNX_CLOUD_LABEL_MAX_LENGTH && !key.includes(",") && !key.includes("=") && !value.includes(",") && !RNX_CLOUD_LABEL_CONTROL.test(key) && !RNX_CLOUD_LABEL_CONTROL.test(value);
74
+ }
75
+ function parseRnxCloudLabels(value) {
76
+ if (value === "") return {};
77
+ const entries = value.split(",");
78
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
79
+ const labels = {};
80
+ for (const entry of entries) {
81
+ const separator = entry.indexOf("=");
82
+ if (separator < 1) return null;
83
+ const key = entry.slice(0, separator).trim();
84
+ const label = entry.slice(separator + 1).trim();
85
+ if (!isValidRnxCloudLabel(key, label)) return null;
86
+ if (Object.hasOwn(labels, key)) return null;
87
+ labels[key] = label;
88
+ }
89
+ return labels;
90
+ }
91
+ function readRnxCloudLabels(value) {
92
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
93
+ const entries = Object.entries(value);
94
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
95
+ const labels = {};
96
+ for (const [key, label] of entries) {
97
+ if (typeof label !== "string") return null;
98
+ if (!isValidRnxCloudLabel(key, label)) return null;
99
+ labels[key] = label;
100
+ }
101
+ return labels;
102
+ }
103
+ function matchesRnxCloudLabels(labels, selector) {
104
+ return Object.entries(selector).every(([key, value]) => labels[key] === value);
105
+ }
106
+ function isRnxCloudSimStatus(value) {
107
+ return value === "creating" || value === "open" || value === "hibernated" || value === "deleting" || value === "deleted";
108
+ }
62
109
  // Annotate the CommonJS export names for ESM import in node:
63
110
  0 && (module.exports = {
64
111
  RNX_CLOUD_COMMAND_TYPES,
65
112
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
66
113
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
114
+ RNX_CLOUD_LABELS_MAX_COUNT,
115
+ RNX_CLOUD_LABEL_MAX_LENGTH,
67
116
  RNX_CLOUD_MAX_ARTIFACT_BYTES,
68
117
  RNX_CLOUD_TEST_FILES_MAX_BYTES,
69
118
  RNX_CLOUD_TEST_FILES_MAX_COUNT,
70
- isRnxCloudCommandType
119
+ isRnxCloudCommandType,
120
+ isRnxCloudSimStatus,
121
+ isValidRnxCloudLabel,
122
+ matchesRnxCloudLabels,
123
+ parseRnxCloudLabels,
124
+ readRnxCloudLabels
71
125
  });
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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;
@@ -29,12 +29,59 @@ var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
29
29
  function isRnxCloudCommandType(value) {
30
30
  return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
31
31
  }
32
+ var RNX_CLOUD_LABELS_MAX_COUNT = 16;
33
+ var RNX_CLOUD_LABEL_MAX_LENGTH = 63;
34
+ var RNX_CLOUD_LABEL_CONTROL = /[\u0000-\u001f\u007f]/;
35
+ function isValidRnxCloudLabel(key, value) {
36
+ return key.length > 0 && key.length <= RNX_CLOUD_LABEL_MAX_LENGTH && value.length <= RNX_CLOUD_LABEL_MAX_LENGTH && !key.includes(",") && !key.includes("=") && !value.includes(",") && !RNX_CLOUD_LABEL_CONTROL.test(key) && !RNX_CLOUD_LABEL_CONTROL.test(value);
37
+ }
38
+ function parseRnxCloudLabels(value) {
39
+ if (value === "") return {};
40
+ const entries = value.split(",");
41
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
42
+ const labels = {};
43
+ for (const entry of entries) {
44
+ const separator = entry.indexOf("=");
45
+ if (separator < 1) return null;
46
+ const key = entry.slice(0, separator).trim();
47
+ const label = entry.slice(separator + 1).trim();
48
+ if (!isValidRnxCloudLabel(key, label)) return null;
49
+ if (Object.hasOwn(labels, key)) return null;
50
+ labels[key] = label;
51
+ }
52
+ return labels;
53
+ }
54
+ function readRnxCloudLabels(value) {
55
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
56
+ const entries = Object.entries(value);
57
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
58
+ const labels = {};
59
+ for (const [key, label] of entries) {
60
+ if (typeof label !== "string") return null;
61
+ if (!isValidRnxCloudLabel(key, label)) return null;
62
+ labels[key] = label;
63
+ }
64
+ return labels;
65
+ }
66
+ function matchesRnxCloudLabels(labels, selector) {
67
+ return Object.entries(selector).every(([key, value]) => labels[key] === value);
68
+ }
69
+ function isRnxCloudSimStatus(value) {
70
+ return value === "creating" || value === "open" || value === "hibernated" || value === "deleting" || value === "deleted";
71
+ }
32
72
  export {
33
73
  RNX_CLOUD_COMMAND_TYPES,
34
74
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
35
75
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
76
+ RNX_CLOUD_LABELS_MAX_COUNT,
77
+ RNX_CLOUD_LABEL_MAX_LENGTH,
36
78
  RNX_CLOUD_MAX_ARTIFACT_BYTES,
37
79
  RNX_CLOUD_TEST_FILES_MAX_BYTES,
38
80
  RNX_CLOUD_TEST_FILES_MAX_COUNT,
39
- isRnxCloudCommandType
81
+ isRnxCloudCommandType,
82
+ isRnxCloudSimStatus,
83
+ isValidRnxCloudLabel,
84
+ matchesRnxCloudLabels,
85
+ parseRnxCloudLabels,
86
+ readRnxCloudLabels
40
87
  };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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,6 +136,8 @@ var rnxPublicBrand = Object.freeze({
136
136
  // src/cloud.ts
137
137
  var DEFAULT_DEVICE = "iphone-16";
138
138
  var DEFAULT_WAIT_READY_MS = 2e4;
139
+ var CLOUD_MAX_RETRIES = 2;
140
+ var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit", "early_access"]);
139
141
  function isRecord(value) {
140
142
  return value !== null && typeof value === "object" && !Array.isArray(value);
141
143
  }
@@ -173,6 +175,62 @@ function responseError(status, text) {
173
175
  `RNX Cloud request failed (${status})${text.trim() ? `: ${text.trim()}` : ""}`
174
176
  );
175
177
  }
178
+ function readCloudErrorCode(text) {
179
+ let parsed;
180
+ try {
181
+ parsed = JSON.parse(text);
182
+ } catch {
183
+ return null;
184
+ }
185
+ if (!isRecord(parsed)) return null;
186
+ if (typeof parsed.error === "string") return parsed.error;
187
+ if (isRecord(parsed.error) && typeof parsed.error.code === "string") {
188
+ return parsed.error.code;
189
+ }
190
+ return null;
191
+ }
192
+ function readRetryAfterMs(response, text) {
193
+ const header = response.headers.get("retry-after");
194
+ if (header !== null) {
195
+ const seconds = Number(header.trim());
196
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
197
+ }
198
+ let parsed;
199
+ try {
200
+ parsed = JSON.parse(text);
201
+ } catch {
202
+ return null;
203
+ }
204
+ if (isRecord(parsed) && typeof parsed.retryAfterMs === "number" && Number.isFinite(parsed.retryAfterMs) && parsed.retryAfterMs >= 0) {
205
+ return parsed.retryAfterMs;
206
+ }
207
+ return null;
208
+ }
209
+ function sleep(ms) {
210
+ return new Promise((resolve) => setTimeout(resolve, ms));
211
+ }
212
+ async function fetchWithCloudRetry(url, init, options) {
213
+ let attempt = 0;
214
+ for (; ; ) {
215
+ const response = await fetch(
216
+ url,
217
+ options?.timeoutMs === void 0 ? init : { ...init, signal: AbortSignal.timeout(options.timeoutMs) }
218
+ );
219
+ if (response.ok) return response;
220
+ const retryableStatus = response.status === 429 || response.status >= 500;
221
+ if (!retryableStatus || attempt >= CLOUD_MAX_RETRIES) return response;
222
+ const text = await response.text();
223
+ const code = readCloudErrorCode(text);
224
+ const retryable = response.status >= 500 ? !(code !== null && CLOUD_NEVER_RETRY_CODES.has(code)) : code === "rate_limited";
225
+ if (!retryable) {
226
+ return new Response(text, { status: response.status, headers: response.headers });
227
+ }
228
+ if (response.status === 429) {
229
+ await sleep(readRetryAfterMs(response, text) ?? 1e3);
230
+ }
231
+ attempt += 1;
232
+ }
233
+ }
176
234
  function copyArrayBuffer(bytes) {
177
235
  const copy = new ArrayBuffer(bytes.byteLength);
178
236
  new Uint8Array(copy).set(bytes);
@@ -321,7 +379,7 @@ var RemoteSimulator = class {
321
379
  };
322
380
  let response;
323
381
  try {
324
- response = await fetch(
382
+ response = await fetchWithCloudRetry(
325
383
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`,
326
384
  {
327
385
  method: "POST",
@@ -329,9 +387,9 @@ var RemoteSimulator = class {
329
387
  authorization: `Bearer ${this.session.simulatorToken}`,
330
388
  "content-type": "application/json"
331
389
  },
332
- body: JSON.stringify(body),
333
- ...options?.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
334
- }
390
+ body: JSON.stringify(body)
391
+ },
392
+ options?.timeoutMs === void 0 ? void 0 : { timeoutMs: options.timeoutMs }
335
393
  );
336
394
  } catch (error) {
337
395
  throw new Error(
@@ -356,7 +414,7 @@ var RemoteSimulator = class {
356
414
  return parsed.result;
357
415
  }
358
416
  async confirm() {
359
- const response = await fetch(
417
+ const response = await fetchWithCloudRetry(
360
418
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/claim`,
361
419
  {
362
420
  method: "POST",
@@ -383,7 +441,7 @@ var RemoteSimulator = class {
383
441
  return claim;
384
442
  }
385
443
  async close() {
386
- const response = await fetch(
444
+ const response = await fetchWithCloudRetry(
387
445
  `${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}`,
388
446
  {
389
447
  method: "DELETE",
@@ -414,7 +472,7 @@ async function createRemoteSimulator(input) {
414
472
  const device = input.device?.trim() || DEFAULT_DEVICE;
415
473
  let response;
416
474
  try {
417
- response = await fetch(`${origin}/v1/boxes`, {
475
+ response = await fetchWithCloudRetry(`${origin}/v1/boxes`, {
418
476
  method: "POST",
419
477
  headers: {
420
478
  authorization: input.authorization,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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);
@@ -109,6 +109,8 @@ var rnxPublicBrand = Object.freeze({
109
109
  // src/cloud.ts
110
110
  var DEFAULT_DEVICE = "iphone-16";
111
111
  var DEFAULT_WAIT_READY_MS = 2e4;
112
+ var CLOUD_MAX_RETRIES = 2;
113
+ var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit", "early_access"]);
112
114
  function isRecord(value) {
113
115
  return value !== null && typeof value === "object" && !Array.isArray(value);
114
116
  }
@@ -146,6 +148,62 @@ function responseError(status, text) {
146
148
  `RNX Cloud request failed (${status})${text.trim() ? `: ${text.trim()}` : ""}`
147
149
  );
148
150
  }
151
+ function readCloudErrorCode(text) {
152
+ let parsed;
153
+ try {
154
+ parsed = JSON.parse(text);
155
+ } catch {
156
+ return null;
157
+ }
158
+ if (!isRecord(parsed)) return null;
159
+ if (typeof parsed.error === "string") return parsed.error;
160
+ if (isRecord(parsed.error) && typeof parsed.error.code === "string") {
161
+ return parsed.error.code;
162
+ }
163
+ return null;
164
+ }
165
+ function readRetryAfterMs(response, text) {
166
+ const header = response.headers.get("retry-after");
167
+ if (header !== null) {
168
+ const seconds = Number(header.trim());
169
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
170
+ }
171
+ let parsed;
172
+ try {
173
+ parsed = JSON.parse(text);
174
+ } catch {
175
+ return null;
176
+ }
177
+ if (isRecord(parsed) && typeof parsed.retryAfterMs === "number" && Number.isFinite(parsed.retryAfterMs) && parsed.retryAfterMs >= 0) {
178
+ return parsed.retryAfterMs;
179
+ }
180
+ return null;
181
+ }
182
+ function sleep(ms) {
183
+ return new Promise((resolve) => setTimeout(resolve, ms));
184
+ }
185
+ async function fetchWithCloudRetry(url, init, options) {
186
+ let attempt = 0;
187
+ for (; ; ) {
188
+ const response = await fetch(
189
+ url,
190
+ options?.timeoutMs === void 0 ? init : { ...init, signal: AbortSignal.timeout(options.timeoutMs) }
191
+ );
192
+ if (response.ok) return response;
193
+ const retryableStatus = response.status === 429 || response.status >= 500;
194
+ if (!retryableStatus || attempt >= CLOUD_MAX_RETRIES) return response;
195
+ const text = await response.text();
196
+ const code = readCloudErrorCode(text);
197
+ const retryable = response.status >= 500 ? !(code !== null && CLOUD_NEVER_RETRY_CODES.has(code)) : code === "rate_limited";
198
+ if (!retryable) {
199
+ return new Response(text, { status: response.status, headers: response.headers });
200
+ }
201
+ if (response.status === 429) {
202
+ await sleep(readRetryAfterMs(response, text) ?? 1e3);
203
+ }
204
+ attempt += 1;
205
+ }
206
+ }
149
207
  function copyArrayBuffer(bytes) {
150
208
  const copy = new ArrayBuffer(bytes.byteLength);
151
209
  new Uint8Array(copy).set(bytes);
@@ -294,7 +352,7 @@ var RemoteSimulator = class {
294
352
  };
295
353
  let response;
296
354
  try {
297
- response = await fetch(
355
+ response = await fetchWithCloudRetry(
298
356
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`,
299
357
  {
300
358
  method: "POST",
@@ -302,9 +360,9 @@ var RemoteSimulator = class {
302
360
  authorization: `Bearer ${this.session.simulatorToken}`,
303
361
  "content-type": "application/json"
304
362
  },
305
- body: JSON.stringify(body),
306
- ...options?.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
307
- }
363
+ body: JSON.stringify(body)
364
+ },
365
+ options?.timeoutMs === void 0 ? void 0 : { timeoutMs: options.timeoutMs }
308
366
  );
309
367
  } catch (error) {
310
368
  throw new Error(
@@ -329,7 +387,7 @@ var RemoteSimulator = class {
329
387
  return parsed.result;
330
388
  }
331
389
  async confirm() {
332
- const response = await fetch(
390
+ const response = await fetchWithCloudRetry(
333
391
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/claim`,
334
392
  {
335
393
  method: "POST",
@@ -356,7 +414,7 @@ var RemoteSimulator = class {
356
414
  return claim;
357
415
  }
358
416
  async close() {
359
- const response = await fetch(
417
+ const response = await fetchWithCloudRetry(
360
418
  `${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}`,
361
419
  {
362
420
  method: "DELETE",
@@ -387,7 +445,7 @@ async function createRemoteSimulator(input) {
387
445
  const device = input.device?.trim() || DEFAULT_DEVICE;
388
446
  let response;
389
447
  try {
390
- response = await fetch(`${origin}/v1/boxes`, {
448
+ response = await fetchWithCloudRetry(`${origin}/v1/boxes`, {
391
449
  method: "POST",
392
450
  headers: {
393
451
  authorization: input.authorization,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.514 | (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.513",
3
+ "version": "0.1.514",
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",
@@ -58,3 +58,123 @@ export interface RnxCloudBoxCreateReceipt {
58
58
  }
59
59
  simulator: RnxCloudCreateReceipt
60
60
  }
61
+
62
+ // what a cloud simulator reports. the stored lifecycle values are
63
+ // `creating`/`open`/`hibernated`/`deleting`; `deleted` is only ever a response,
64
+ // answered by a delete, because a deleted simulator has no row left to read.
65
+ export type RnxCloudSimStatus =
66
+ | 'creating'
67
+ | 'open'
68
+ | 'hibernated'
69
+ | 'deleting'
70
+ | 'deleted'
71
+
72
+ // one simulator as the instance list reports it: the lifecycle view plus the
73
+ // labels it was created with.
74
+ export interface RnxCloudSimView {
75
+ simId: string
76
+ status: RnxCloudSimStatus
77
+ labels: Record<string, string>
78
+ artifact: { sha256: string; bytes: number }
79
+ claim: RnxCloudClaim | null
80
+ createdAt: number
81
+ lastActiveAt: number
82
+ lastHibernatedAt?: number
83
+ storageFrom?: string
84
+ /** the idle window the creator set, or null for the service default. */
85
+ inactivityTimeoutMs: number | null
86
+ /** the instant the creator's hard timeout stops this simulator, or null. */
87
+ hardTimeoutAt: number | null
88
+ }
89
+
90
+ // one simulator as create and get report it, with the watch URL a browser
91
+ // opens to see it.
92
+ export interface RnxCloudSimInstance extends RnxCloudSimView {
93
+ /** a watch URL with its grant in the fragment, or null when none can be
94
+ * minted yet: an unconfirmed simulator is not watchable. */
95
+ streamUrl: string | null
96
+ }
97
+
98
+ // the create/reuse answer: the receipt the box path also returns, plus what
99
+ // only the instance API knows.
100
+ export interface RnxCloudSimCreateResponse extends RnxCloudCreateReceipt {
101
+ token: string
102
+ status: RnxCloudSimStatus
103
+ labels: Record<string, string>
104
+ streamUrl: string | null
105
+ }
106
+
107
+ // one grammar for the create map and the list selector: `key=value,key2=value2`.
108
+ // a value may be empty; a key may not; a repeated key is the caller's mistake
109
+ // rather than a silent last-one-wins. commas and control characters never
110
+ // belong in a key or value, and 63 characters is the ceiling for either.
111
+ export const RNX_CLOUD_LABELS_MAX_COUNT = 16
112
+ export const RNX_CLOUD_LABEL_MAX_LENGTH = 63
113
+
114
+ const RNX_CLOUD_LABEL_CONTROL = /[\u0000-\u001f\u007f]/
115
+
116
+ export function isValidRnxCloudLabel(key: string, value: string): boolean {
117
+ return (
118
+ key.length > 0 &&
119
+ key.length <= RNX_CLOUD_LABEL_MAX_LENGTH &&
120
+ value.length <= RNX_CLOUD_LABEL_MAX_LENGTH &&
121
+ !key.includes(',') &&
122
+ !key.includes('=') &&
123
+ !value.includes(',') &&
124
+ !RNX_CLOUD_LABEL_CONTROL.test(key) &&
125
+ !RNX_CLOUD_LABEL_CONTROL.test(value)
126
+ )
127
+ }
128
+
129
+ /** the wire form, or null when it is not one. `''` is the empty map. */
130
+ export function parseRnxCloudLabels(value: string): Record<string, string> | null {
131
+ if (value === '') return {}
132
+ const entries = value.split(',')
133
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null
134
+ const labels: Record<string, string> = {}
135
+ for (const entry of entries) {
136
+ const separator = entry.indexOf('=')
137
+ if (separator < 1) return null
138
+ const key = entry.slice(0, separator).trim()
139
+ const label = entry.slice(separator + 1).trim()
140
+ if (!isValidRnxCloudLabel(key, label)) return null
141
+ if (Object.hasOwn(labels, key)) return null
142
+ labels[key] = label
143
+ }
144
+ return labels
145
+ }
146
+
147
+ /** the same grammar with the map already split apart, as a json body carries
148
+ * it. anything the wire form would refuse is refused here too. */
149
+ export function readRnxCloudLabels(value: unknown): Record<string, string> | null {
150
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return null
151
+ const entries = Object.entries(value)
152
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null
153
+ const labels: Record<string, string> = {}
154
+ for (const [key, label] of entries) {
155
+ if (typeof label !== 'string') return null
156
+ if (!isValidRnxCloudLabel(key, label)) return null
157
+ labels[key] = label
158
+ }
159
+ return labels
160
+ }
161
+
162
+ /** every selector entry must be present with the same value. an empty
163
+ * selector selects everything. */
164
+ export function matchesRnxCloudLabels(
165
+ labels: Record<string, string>,
166
+ selector: Record<string, string>,
167
+ ): boolean {
168
+ return Object.entries(selector).every(([key, value]) => labels[key] === value)
169
+ }
170
+
171
+ /** the selector an instance list filters by, given the statuses it accepts. */
172
+ export function isRnxCloudSimStatus(value: string): value is RnxCloudSimStatus {
173
+ return (
174
+ value === 'creating' ||
175
+ value === 'open' ||
176
+ value === 'hibernated' ||
177
+ value === 'deleting' ||
178
+ value === 'deleted'
179
+ )
180
+ }
package/src/cloud.ts CHANGED
@@ -26,6 +26,15 @@ export type { RnxCloudClaim, RnxCloudCreateReceipt, RnxScreenCapture }
26
26
  const DEFAULT_DEVICE = 'iphone-16'
27
27
  const DEFAULT_WAIT_READY_MS = 20_000
28
28
 
29
+ // how many times a request goes back after a retryable refusal or a server
30
+ // error. the sim service passes the account's own 429 bodies through, so the
31
+ // code on the body is what decides: `rate_limited` carries a `retry-after`
32
+ // and is worth waiting for, while `concurrency_limit` refuses until the
33
+ // customer stops something and retrying only hammers a closed door.
34
+ // `early_access` and 402 never change on retry either.
35
+ const CLOUD_MAX_RETRIES = 2
36
+ const CLOUD_NEVER_RETRY_CODES = new Set(['concurrency_limit', 'early_access'])
37
+
29
38
  export type RemoteCommand = {
30
39
  type: (typeof RNX_CLOUD_COMMAND_TYPES)[number]
31
40
  [key: string]: unknown
@@ -117,6 +126,86 @@ function responseError(status: number, text: string): Error {
117
126
  )
118
127
  }
119
128
 
129
+ function readCloudErrorCode(text: string): string | null {
130
+ // the account api answers `{ error: 'code', message }` and the sim service
131
+ // `{ error: { code, message } }`; both reach this client, so both read.
132
+ let parsed: unknown
133
+ try {
134
+ parsed = JSON.parse(text)
135
+ } catch {
136
+ return null
137
+ }
138
+ if (!isRecord(parsed)) return null
139
+ if (typeof parsed.error === 'string') return parsed.error
140
+ if (isRecord(parsed.error) && typeof parsed.error.code === 'string') {
141
+ return parsed.error.code
142
+ }
143
+ return null
144
+ }
145
+
146
+ function readRetryAfterMs(response: Response, text: string): number | null {
147
+ const header = response.headers.get('retry-after')
148
+ if (header !== null) {
149
+ const seconds = Number(header.trim())
150
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000
151
+ }
152
+ let parsed: unknown
153
+ try {
154
+ parsed = JSON.parse(text)
155
+ } catch {
156
+ return null
157
+ }
158
+ if (
159
+ isRecord(parsed) &&
160
+ typeof parsed.retryAfterMs === 'number' &&
161
+ Number.isFinite(parsed.retryAfterMs) &&
162
+ parsed.retryAfterMs >= 0
163
+ ) {
164
+ return parsed.retryAfterMs
165
+ }
166
+ return null
167
+ }
168
+
169
+ function sleep(ms: number): Promise<void> {
170
+ return new Promise((resolve) => setTimeout(resolve, ms))
171
+ }
172
+
173
+ // one fetch with the cloud retry policy: `rate_limited` waits out its
174
+ // `retry-after` and 5xx goes again at once, each at most twice, and nothing
175
+ // else is retried. the body is buffered only on a retryable status, so every
176
+ // other refusal reaches the caller on its original response.
177
+ async function fetchWithCloudRetry(
178
+ url: string,
179
+ init: RequestInit,
180
+ options?: { timeoutMs?: number },
181
+ ): Promise<Response> {
182
+ let attempt = 0
183
+ for (;;) {
184
+ const response = await fetch(
185
+ url,
186
+ options?.timeoutMs === undefined
187
+ ? init
188
+ : { ...init, signal: AbortSignal.timeout(options.timeoutMs) },
189
+ )
190
+ if (response.ok) return response
191
+ const retryableStatus = response.status === 429 || response.status >= 500
192
+ if (!retryableStatus || attempt >= CLOUD_MAX_RETRIES) return response
193
+ const text = await response.text()
194
+ const code = readCloudErrorCode(text)
195
+ const retryable =
196
+ response.status >= 500
197
+ ? !(code !== null && CLOUD_NEVER_RETRY_CODES.has(code))
198
+ : code === 'rate_limited'
199
+ if (!retryable) {
200
+ return new Response(text, { status: response.status, headers: response.headers })
201
+ }
202
+ if (response.status === 429) {
203
+ await sleep(readRetryAfterMs(response, text) ?? 1000)
204
+ }
205
+ attempt += 1
206
+ }
207
+ }
208
+
120
209
  function copyArrayBuffer(bytes: Uint8Array): ArrayBuffer {
121
210
  const copy = new ArrayBuffer(bytes.byteLength)
122
211
  new Uint8Array(copy).set(bytes)
@@ -325,7 +414,7 @@ export class RemoteSimulator {
325
414
  }
326
415
  let response: Response
327
416
  try {
328
- response = await fetch(
417
+ response = await fetchWithCloudRetry(
329
418
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`,
330
419
  {
331
420
  method: 'POST',
@@ -334,10 +423,8 @@ export class RemoteSimulator {
334
423
  'content-type': 'application/json',
335
424
  },
336
425
  body: JSON.stringify(body),
337
- ...(options?.timeoutMs
338
- ? { signal: AbortSignal.timeout(options.timeoutMs) }
339
- : {}),
340
426
  },
427
+ options?.timeoutMs === undefined ? undefined : { timeoutMs: options.timeoutMs },
341
428
  )
342
429
  } catch (error) {
343
430
  throw new Error(
@@ -365,7 +452,7 @@ export class RemoteSimulator {
365
452
  }
366
453
 
367
454
  async confirm(): Promise<RnxCloudClaim> {
368
- const response = await fetch(
455
+ const response = await fetchWithCloudRetry(
369
456
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/claim`,
370
457
  {
371
458
  method: 'POST',
@@ -393,7 +480,7 @@ export class RemoteSimulator {
393
480
  }
394
481
 
395
482
  async close(): Promise<void> {
396
- const response = await fetch(
483
+ const response = await fetchWithCloudRetry(
397
484
  `${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}`,
398
485
  {
399
486
  method: 'DELETE',
@@ -427,7 +514,7 @@ export async function createRemoteSimulator(
427
514
  const device = input.device?.trim() || DEFAULT_DEVICE
428
515
  let response: Response
429
516
  try {
430
- response = await fetch(`${origin}/v1/boxes`, {
517
+ response = await fetchWithCloudRetry(`${origin}/v1/boxes`, {
431
518
  method: 'POST',
432
519
  headers: {
433
520
  authorization: input.authorization,