rnxsim 0.1.514 → 0.1.515
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 +18 -8
- package/cli/commands/inspect/get-layout.ts +2 -1
- 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 +64 -1
- package/dist-lib/bridge-contract-input.mjs +63 -1
- package/dist-lib/bridge-contract.cjs +73 -3
- package/dist-lib/bridge-contract.mjs +69 -2
- 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 +63 -22
- package/dist-lib/cloud-contract.mjs +57 -21
- package/dist-lib/cloud.cjs +56 -20
- package/dist-lib/cloud.mjs +56 -20
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +45 -20
- 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 +62 -24
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/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 +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +62 -24
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite.cjs +1 -1
- package/package.json +2 -1
- package/src/bridge-contract-input.ts +74 -0
- package/src/bridge-contract.ts +103 -0
- package/src/cloud-contract.ts +71 -19
- package/src/cloud.ts +10 -0
package/dist-lib/render-mode.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.515 | (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.
|
|
1
|
+
/*! rnx v0.1.515 | (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.
|
|
1
|
+
/*! rnx v0.1.515 | (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.
|
|
1
|
+
/*! rnx v0.1.515 | (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);
|
package/dist-lib/skills.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.515 | (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;
|
|
@@ -27513,35 +27513,66 @@ var init_metro_fingerprint = __esm({
|
|
|
27513
27513
|
});
|
|
27514
27514
|
|
|
27515
27515
|
// src/cloud-contract.ts
|
|
27516
|
+
function scopeTableKeys() {
|
|
27517
|
+
return Object.keys(RNX_CLOUD_COMMAND_SCOPES).filter(
|
|
27518
|
+
(key) => Object.prototype.hasOwnProperty.call(RNX_CLOUD_COMMAND_SCOPES, key)
|
|
27519
|
+
);
|
|
27520
|
+
}
|
|
27516
27521
|
function isRnxCloudCommandType(value) {
|
|
27517
27522
|
return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
|
|
27518
27523
|
}
|
|
27519
|
-
|
|
27524
|
+
function rnxCloudCommandScope(type) {
|
|
27525
|
+
return RNX_CLOUD_COMMAND_SCOPES[type];
|
|
27526
|
+
}
|
|
27527
|
+
function rnxCloudScopesAllowCommand(scopes, type) {
|
|
27528
|
+
return scopes.includes(RNX_CLOUD_COMMAND_SCOPES[type]);
|
|
27529
|
+
}
|
|
27530
|
+
var RNX_CLOUD_MAX_ARTIFACT_BYTES, RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES, RNX_CLOUD_COMMAND_SCOPES, RNX_CLOUD_COMMAND_TYPES, RNX_CLOUD_COMMAND_TYPE_SET, RNX_CLOUD_TOKEN_SCOPES, RNX_CLOUD_ALL_COMMAND_SCOPES;
|
|
27520
27531
|
var init_cloud_contract = __esm({
|
|
27521
27532
|
"src/cloud-contract.ts"() {
|
|
27522
27533
|
"use strict";
|
|
27523
27534
|
RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
|
|
27524
27535
|
RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
|
|
27525
|
-
|
|
27526
|
-
|
|
27527
|
-
"
|
|
27528
|
-
"
|
|
27529
|
-
"
|
|
27530
|
-
"
|
|
27536
|
+
RNX_CLOUD_COMMAND_SCOPES = {
|
|
27537
|
+
state: "read",
|
|
27538
|
+
tree: "read",
|
|
27539
|
+
query: "read",
|
|
27540
|
+
resolve: "read",
|
|
27541
|
+
screenshot: "read",
|
|
27542
|
+
capture: "read",
|
|
27543
|
+
captureRegions: "read",
|
|
27544
|
+
memory: "read",
|
|
27545
|
+
diagnostics: "read",
|
|
27546
|
+
storageSnapshot: "read",
|
|
27547
|
+
perform: "drive",
|
|
27548
|
+
waitFor: "drive",
|
|
27549
|
+
tap: "drive",
|
|
27550
|
+
longPress: "drive",
|
|
27551
|
+
settle: "drive",
|
|
27552
|
+
openUrl: "drive",
|
|
27553
|
+
setAppearance: "drive",
|
|
27554
|
+
keyboard: "drive",
|
|
27555
|
+
focus: "drive",
|
|
27556
|
+
flowStatus: "drive",
|
|
27557
|
+
reset: "reset",
|
|
27558
|
+
camera: "media",
|
|
27559
|
+
// string evaluation and arbitrary bridge calls are maestro's boundary: the
|
|
27560
|
+
// customer sdk never carries this scope, and the service grants it only to
|
|
27561
|
+
// the account api key caller. close rides along, since ending the page ends
|
|
27562
|
+
// the sim and belongs to the sim's owner rather than any driver.
|
|
27563
|
+
evaluate: "debug",
|
|
27564
|
+
call: "debug",
|
|
27565
|
+
close: "debug"
|
|
27566
|
+
};
|
|
27567
|
+
RNX_CLOUD_COMMAND_TYPES = Object.freeze(scopeTableKeys());
|
|
27568
|
+
RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
|
|
27569
|
+
RNX_CLOUD_TOKEN_SCOPES = Object.freeze([
|
|
27570
|
+
"read",
|
|
27571
|
+
"drive",
|
|
27531
27572
|
"reset",
|
|
27532
|
-
"
|
|
27533
|
-
"capture",
|
|
27534
|
-
"captureRegions",
|
|
27535
|
-
"settle",
|
|
27536
|
-
"setAppearance",
|
|
27537
|
-
"openUrl",
|
|
27538
|
-
"tree",
|
|
27539
|
-
"tap",
|
|
27540
|
-
"longPress",
|
|
27541
|
-
"memory",
|
|
27542
|
-
"diagnostics"
|
|
27573
|
+
"media"
|
|
27543
27574
|
]);
|
|
27544
|
-
|
|
27575
|
+
RNX_CLOUD_ALL_COMMAND_SCOPES = Object.freeze(["read", "drive", "reset", "media", "debug"]);
|
|
27545
27576
|
}
|
|
27546
27577
|
});
|
|
27547
27578
|
|
|
@@ -28270,10 +28301,17 @@ function cloudCommand(command) {
|
|
|
28270
28301
|
for (const [key, value] of Object.entries(command)) {
|
|
28271
28302
|
if (key !== "id" && key !== "simId") clean[key] = value;
|
|
28272
28303
|
}
|
|
28273
|
-
if (isRnxCloudCommandType(command.type))
|
|
28274
|
-
|
|
28275
|
-
|
|
28276
|
-
|
|
28304
|
+
if (!isRnxCloudCommandType(command.type)) {
|
|
28305
|
+
throw new Error(
|
|
28306
|
+
`a remote simulator does not support bridge command type ${command.type}`
|
|
28307
|
+
);
|
|
28308
|
+
}
|
|
28309
|
+
if (!rnxCloudScopesAllowCommand(RNX_CLOUD_TOKEN_SCOPES, command.type)) {
|
|
28310
|
+
throw new Error(
|
|
28311
|
+
`a remote simulator command ${command.type} requires the ${rnxCloudCommandScope(command.type)} scope`
|
|
28312
|
+
);
|
|
28313
|
+
}
|
|
28314
|
+
return clean;
|
|
28277
28315
|
}
|
|
28278
28316
|
function createCloudBridgeForParsed(parsed) {
|
|
28279
28317
|
const session = resolveCloudSessionForParsed(parsed);
|
package/dist-lib/swift.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.515 | (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.
|
|
1
|
+
/*! rnx v0.1.515 | (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.
|
|
3
|
+
"version": "0.1.515",
|
|
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",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"./bridge-contract-input": {
|
|
93
93
|
"types": "./src/bridge-contract-input.ts",
|
|
94
94
|
"source": "./src/bridge-contract-input.ts",
|
|
95
|
+
"bun": "./src/bridge-contract-input.ts",
|
|
95
96
|
"import": "./dist-lib/bridge-contract-input.mjs",
|
|
96
97
|
"default": "./dist-lib/bridge-contract-input.cjs"
|
|
97
98
|
},
|
|
@@ -198,6 +198,46 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
|
|
|
198
198
|
return { type, key: stringValue(step.key, `${path}.key`) }
|
|
199
199
|
case 'dismissKeyboard':
|
|
200
200
|
return { type }
|
|
201
|
+
case 'setElementValue': {
|
|
202
|
+
const submit = optionalBoolean(step, 'submit', path)
|
|
203
|
+
return {
|
|
204
|
+
type,
|
|
205
|
+
id: stringValue(step.id, `${path}.id`),
|
|
206
|
+
text: stringValue(step.text, `${path}.text`),
|
|
207
|
+
...(submit === undefined ? {} : { submit }),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
case 'toggleKeyboard':
|
|
211
|
+
return { type }
|
|
212
|
+
case 'openUrl': {
|
|
213
|
+
const url = stringValue(step.url, `${path}.url`)
|
|
214
|
+
if (url.length === 0) validationFailure(`${path}.url`, 'must be non-empty')
|
|
215
|
+
return { type, url }
|
|
216
|
+
}
|
|
217
|
+
case 'keyDown':
|
|
218
|
+
case 'keyUp':
|
|
219
|
+
return { type, key: stringValue(step.key, `${path}.key`) }
|
|
220
|
+
case 'setOrientation': {
|
|
221
|
+
const orientation = stringValue(step.orientation, `${path}.orientation`)
|
|
222
|
+
if (orientation !== 'portrait' && orientation !== 'landscape') {
|
|
223
|
+
validationFailure(`${path}.orientation`, 'must be portrait or landscape')
|
|
224
|
+
}
|
|
225
|
+
return { type, orientation }
|
|
226
|
+
}
|
|
227
|
+
case 'deviceInfo':
|
|
228
|
+
return { type }
|
|
229
|
+
case 'buttonDown':
|
|
230
|
+
case 'buttonUp': {
|
|
231
|
+
const button =
|
|
232
|
+
step.button === undefined ? undefined : stringValue(step.button, `${path}.button`)
|
|
233
|
+
return { type, ...(button === undefined ? {} : { button }) }
|
|
234
|
+
}
|
|
235
|
+
case 'incrementElement':
|
|
236
|
+
case 'decrementElement':
|
|
237
|
+
return { type, id: stringValue(step.id, `${path}.id`) }
|
|
238
|
+
case 'discoverStoreKitConfig':
|
|
239
|
+
case 'clearStoreKitConfig':
|
|
240
|
+
return { type }
|
|
201
241
|
case 'wait':
|
|
202
242
|
return { type, ms: numberValue(step.ms, `${path}.ms`) }
|
|
203
243
|
case 'waitFor': {
|
|
@@ -233,6 +273,40 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
|
|
|
233
273
|
}
|
|
234
274
|
}
|
|
235
275
|
|
|
276
|
+
// an unclosed touchDown leaves a pointer stuck down, and the batch abort path
|
|
277
|
+
// would cancel it after the caller was told the batch failed. every batch is
|
|
278
|
+
// self-contained (pointers never survive it), so a touchDown without its
|
|
279
|
+
// touchUp or touchCancel in the same batch is rejected before anything runs.
|
|
280
|
+
export function findUnbalancedTouchDown(
|
|
281
|
+
steps: readonly SimPerformStep[],
|
|
282
|
+
): { index: number; pointerId: number } | null {
|
|
283
|
+
const open = new Map<number, number>()
|
|
284
|
+
const pointerIdOf = (step: SimPerformStep): number | null => {
|
|
285
|
+
if (
|
|
286
|
+
step.type !== 'touchDown' &&
|
|
287
|
+
step.type !== 'touchMove' &&
|
|
288
|
+
step.type !== 'touchUp' &&
|
|
289
|
+
step.type !== 'touchCancel'
|
|
290
|
+
) {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
const pointerId = step.pointerId ?? 999
|
|
294
|
+
return Number.isSafeInteger(pointerId) ? pointerId : null
|
|
295
|
+
}
|
|
296
|
+
for (const [index, step] of steps.entries()) {
|
|
297
|
+
if (step.type === 'touchDown') {
|
|
298
|
+
const pointerId = pointerIdOf(step) ?? 999
|
|
299
|
+
if (!open.has(pointerId)) open.set(pointerId, index)
|
|
300
|
+
} else if (step.type === 'touchUp' || step.type === 'touchCancel') {
|
|
301
|
+
const pointerId = pointerIdOf(step)
|
|
302
|
+
if (pointerId !== null) open.delete(pointerId)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const next = open.entries().next()
|
|
306
|
+
if (next.done) return null
|
|
307
|
+
return { index: next.value[1], pointerId: next.value[0] }
|
|
308
|
+
}
|
|
309
|
+
|
|
236
310
|
export function parseStateOptions(value: unknown): SimStateOptions {
|
|
237
311
|
const input = recordValue(value, 'state')
|
|
238
312
|
const route = optionalBoolean(input, 'route', 'state')
|
package/src/bridge-contract.ts
CHANGED
|
@@ -163,6 +163,24 @@ export type PerformStep =
|
|
|
163
163
|
condition?: 'present' | 'mounted' | 'absent'
|
|
164
164
|
timeoutMs?: number
|
|
165
165
|
}
|
|
166
|
+
// limrun parity steps. each maps onto an engine primitive and reports
|
|
167
|
+
// per step; the refused ones below parse but never run.
|
|
168
|
+
| { type: 'setElementValue'; id: string; text: string; submit?: boolean }
|
|
169
|
+
| { type: 'toggleKeyboard' }
|
|
170
|
+
| { type: 'openUrl'; url: string }
|
|
171
|
+
| { type: 'keyDown'; key: string }
|
|
172
|
+
| { type: 'keyUp'; key: string }
|
|
173
|
+
| { type: 'setOrientation'; orientation: 'portrait' | 'landscape' }
|
|
174
|
+
| { type: 'deviceInfo' }
|
|
175
|
+
// refused with a named reason, never emulated: no hardware-button model,
|
|
176
|
+
// no storekit simulation, no stepper primitive, and key/orientation state
|
|
177
|
+
// the engine does not hold.
|
|
178
|
+
| { type: 'buttonDown'; button?: string }
|
|
179
|
+
| { type: 'buttonUp'; button?: string }
|
|
180
|
+
| { type: 'incrementElement'; id: string }
|
|
181
|
+
| { type: 'decrementElement'; id: string }
|
|
182
|
+
| { type: 'discoverStoreKitConfig' }
|
|
183
|
+
| { type: 'clearStoreKitConfig' }
|
|
166
184
|
|
|
167
185
|
export interface PerformOptions {
|
|
168
186
|
// stop the batch at the first failing step. default true — a gesture whose
|
|
@@ -179,6 +197,91 @@ export interface PerformOptions {
|
|
|
179
197
|
timeoutMs?: number
|
|
180
198
|
}
|
|
181
199
|
|
|
200
|
+
// observed device identity for the `deviceInfo` step: the live device spec
|
|
201
|
+
// in both runtimes, so every field is measured, never a default.
|
|
202
|
+
export interface PerformDeviceInfo {
|
|
203
|
+
platform: string
|
|
204
|
+
model?: string
|
|
205
|
+
width: number
|
|
206
|
+
height: number
|
|
207
|
+
scale: number
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// steps that parse but never run. one owner for the refusal vocabulary so
|
|
211
|
+
// the browser page and the cpu runtime refuse with the same named reason and
|
|
212
|
+
// neither plane emulates what the engine cannot do.
|
|
213
|
+
export const REFUSED_PERFORM_STEP_TYPES = [
|
|
214
|
+
'keyDown',
|
|
215
|
+
'keyUp',
|
|
216
|
+
'setOrientation',
|
|
217
|
+
'buttonDown',
|
|
218
|
+
'buttonUp',
|
|
219
|
+
'incrementElement',
|
|
220
|
+
'decrementElement',
|
|
221
|
+
'discoverStoreKitConfig',
|
|
222
|
+
'clearStoreKitConfig',
|
|
223
|
+
] as const
|
|
224
|
+
|
|
225
|
+
export type RefusedPerformStepType = (typeof REFUSED_PERFORM_STEP_TYPES)[number]
|
|
226
|
+
|
|
227
|
+
export function refusedPerformStepReason(type: RefusedPerformStepType): string {
|
|
228
|
+
switch (type) {
|
|
229
|
+
case 'keyDown':
|
|
230
|
+
case 'keyUp':
|
|
231
|
+
return `${type} is not supported: keys dispatch atomically; use the key step`
|
|
232
|
+
case 'setOrientation':
|
|
233
|
+
return 'setOrientation is not supported: the simulator viewport is fixed at boot'
|
|
234
|
+
case 'buttonDown':
|
|
235
|
+
case 'buttonUp':
|
|
236
|
+
return `${type} is not supported: the simulator has no hardware buttons`
|
|
237
|
+
case 'incrementElement':
|
|
238
|
+
case 'decrementElement':
|
|
239
|
+
return `${type} is not supported: steppers have no engine primitive; drive them with tapId`
|
|
240
|
+
case 'discoverStoreKitConfig':
|
|
241
|
+
case 'clearStoreKitConfig':
|
|
242
|
+
return `${type} is not supported: storekit has no simulator model`
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// returns the refusal for a refused step, null when the step is runnable.
|
|
247
|
+
// every variant is listed so adding a step forces its classification here;
|
|
248
|
+
// the executors mirror the refused cases in their own switches.
|
|
249
|
+
export function performStepRefusal(type: PerformStep['type']): string | null {
|
|
250
|
+
switch (type) {
|
|
251
|
+
case 'keyDown':
|
|
252
|
+
case 'keyUp':
|
|
253
|
+
case 'setOrientation':
|
|
254
|
+
case 'buttonDown':
|
|
255
|
+
case 'buttonUp':
|
|
256
|
+
case 'incrementElement':
|
|
257
|
+
case 'decrementElement':
|
|
258
|
+
case 'discoverStoreKitConfig':
|
|
259
|
+
case 'clearStoreKitConfig':
|
|
260
|
+
return refusedPerformStepReason(type)
|
|
261
|
+
case 'touchDown':
|
|
262
|
+
case 'touchMove':
|
|
263
|
+
case 'touchUp':
|
|
264
|
+
case 'touchCancel':
|
|
265
|
+
case 'tap':
|
|
266
|
+
case 'tapId':
|
|
267
|
+
case 'doubleTap':
|
|
268
|
+
case 'longPress':
|
|
269
|
+
case 'drag':
|
|
270
|
+
case 'pinch':
|
|
271
|
+
case 'scroll':
|
|
272
|
+
case 'type':
|
|
273
|
+
case 'key':
|
|
274
|
+
case 'dismissKeyboard':
|
|
275
|
+
case 'setElementValue':
|
|
276
|
+
case 'toggleKeyboard':
|
|
277
|
+
case 'openUrl':
|
|
278
|
+
case 'deviceInfo':
|
|
279
|
+
case 'wait':
|
|
280
|
+
case 'waitFor':
|
|
281
|
+
return null
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
182
285
|
export interface PerformStepResult {
|
|
183
286
|
index: number
|
|
184
287
|
type: PerformStep['type']
|
package/src/cloud-contract.ts
CHANGED
|
@@ -6,25 +6,51 @@ export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
|
|
|
6
6
|
export const RNX_CLOUD_TEST_FILES_MAX_COUNT = 256
|
|
7
7
|
export const RNX_CLOUD_TEST_FILES_MAX_BYTES = 500_000
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
|
|
9
|
+
export type RnxCloudCommandScope = 'read' | 'drive' | 'reset' | 'media' | 'debug'
|
|
10
|
+
|
|
11
|
+
// one scope table for both remote planes: every bridge verb names the scope
|
|
12
|
+
// that may send it, so the nano service and the box relay admit the same
|
|
13
|
+
// vocabulary and differ only by caller scope. reads observe, drive moves,
|
|
14
|
+
// reset wipes, media injects camera fixtures, and debug evaluates.
|
|
15
|
+
export const RNX_CLOUD_COMMAND_SCOPES = {
|
|
16
|
+
state: 'read',
|
|
17
|
+
tree: 'read',
|
|
18
|
+
query: 'read',
|
|
19
|
+
resolve: 'read',
|
|
20
|
+
screenshot: 'read',
|
|
21
|
+
capture: 'read',
|
|
22
|
+
captureRegions: 'read',
|
|
23
|
+
memory: 'read',
|
|
24
|
+
diagnostics: 'read',
|
|
25
|
+
storageSnapshot: 'read',
|
|
26
|
+
perform: 'drive',
|
|
27
|
+
waitFor: 'drive',
|
|
28
|
+
tap: 'drive',
|
|
29
|
+
longPress: 'drive',
|
|
30
|
+
settle: 'drive',
|
|
31
|
+
openUrl: 'drive',
|
|
32
|
+
setAppearance: 'drive',
|
|
33
|
+
keyboard: 'drive',
|
|
34
|
+
focus: 'drive',
|
|
35
|
+
flowStatus: 'drive',
|
|
36
|
+
reset: 'reset',
|
|
37
|
+
camera: 'media',
|
|
38
|
+
// string evaluation and arbitrary bridge calls are maestro's boundary: the
|
|
39
|
+
// customer sdk never carries this scope, and the service grants it only to
|
|
40
|
+
// the account api key caller. close rides along, since ending the page ends
|
|
41
|
+
// the sim and belongs to the sim's owner rather than any driver.
|
|
42
|
+
evaluate: 'debug',
|
|
43
|
+
call: 'debug',
|
|
44
|
+
close: 'debug',
|
|
45
|
+
} satisfies Record<BridgeCommandType, RnxCloudCommandScope>
|
|
46
|
+
|
|
47
|
+
function scopeTableKeys(): BridgeCommandType[] {
|
|
48
|
+
return Object.keys(RNX_CLOUD_COMMAND_SCOPES).filter((key): key is BridgeCommandType =>
|
|
49
|
+
Object.prototype.hasOwnProperty.call(RNX_CLOUD_COMMAND_SCOPES, key),
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const RNX_CLOUD_COMMAND_TYPES = Object.freeze(scopeTableKeys())
|
|
28
54
|
|
|
29
55
|
const RNX_CLOUD_COMMAND_TYPE_SET = new Set<string>(RNX_CLOUD_COMMAND_TYPES)
|
|
30
56
|
|
|
@@ -34,6 +60,32 @@ export function isRnxCloudCommandType(
|
|
|
34
60
|
return RNX_CLOUD_COMMAND_TYPE_SET.has(value)
|
|
35
61
|
}
|
|
36
62
|
|
|
63
|
+
export function rnxCloudCommandScope(type: BridgeCommandType): RnxCloudCommandScope {
|
|
64
|
+
return RNX_CLOUD_COMMAND_SCOPES[type]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// what the full per-simulator token and the box token carry until scoped
|
|
68
|
+
// tokens exist: every scope except debug.
|
|
69
|
+
export const RNX_CLOUD_TOKEN_SCOPES: readonly RnxCloudCommandScope[] = Object.freeze([
|
|
70
|
+
'read',
|
|
71
|
+
'drive',
|
|
72
|
+
'reset',
|
|
73
|
+
'media',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
// every scope, granted only to the account api key caller through the account
|
|
77
|
+
// service. the only credential the services accept for it today is the shared
|
|
78
|
+
// service key, which only the account service holds.
|
|
79
|
+
export const RNX_CLOUD_ALL_COMMAND_SCOPES: readonly RnxCloudCommandScope[] =
|
|
80
|
+
Object.freeze(['read', 'drive', 'reset', 'media', 'debug'])
|
|
81
|
+
|
|
82
|
+
export function rnxCloudScopesAllowCommand(
|
|
83
|
+
scopes: readonly RnxCloudCommandScope[],
|
|
84
|
+
type: BridgeCommandType,
|
|
85
|
+
): boolean {
|
|
86
|
+
return scopes.includes(RNX_CLOUD_COMMAND_SCOPES[type])
|
|
87
|
+
}
|
|
88
|
+
|
|
37
89
|
export interface RnxCloudClaim {
|
|
38
90
|
id: string
|
|
39
91
|
expiresAt: number
|
package/src/cloud.ts
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
isRnxCloudCommandType,
|
|
15
15
|
RNX_CLOUD_COMMAND_TYPES,
|
|
16
16
|
RNX_CLOUD_MAX_ARTIFACT_BYTES,
|
|
17
|
+
RNX_CLOUD_TOKEN_SCOPES,
|
|
18
|
+
rnxCloudCommandScope,
|
|
19
|
+
rnxCloudScopesAllowCommand,
|
|
17
20
|
type RnxCloudBoxCreateReceipt,
|
|
18
21
|
type RnxCloudClaim,
|
|
19
22
|
type RnxCloudCreateReceipt,
|
|
@@ -338,6 +341,13 @@ function cloudCommand(command: { type: string; [key: string]: unknown }): Remote
|
|
|
338
341
|
if (!isRnxCloudCommandType(command.type)) {
|
|
339
342
|
throw new Error(`RNX Cloud does not support bridge command type ${command.type}`)
|
|
340
343
|
}
|
|
344
|
+
// the customer sdk never carries debug: evaluate/call/close belong to the
|
|
345
|
+
// account api key caller. refusing here names the scope before a request.
|
|
346
|
+
if (!rnxCloudScopesAllowCommand(RNX_CLOUD_TOKEN_SCOPES, command.type)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`RNX Cloud command ${command.type} requires the ${rnxCloudCommandScope(command.type)} scope`,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
341
351
|
return { ...clean, type: command.type }
|
|
342
352
|
}
|
|
343
353
|
|