@run-cloud/sdk 0.19.0 → 0.21.0
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/README.md +134 -0
- package/dist/compat/remotion-lambda.d.ts +81 -0
- package/dist/compat/remotion-lambda.js +65 -0
- package/dist/compat/remotion-lambda.js.map +1 -0
- package/dist/index.d.ts +84 -2
- package/dist/index.js +245 -3
- package/dist/index.js.map +1 -1
- package/dist/remotion.d.ts +138 -0
- package/dist/remotion.js +990 -0
- package/dist/remotion.js.map +1 -0
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { RemotionClient } from "./remotion.js";
|
|
4
|
+
export * from "./remotion.js";
|
|
3
5
|
export const SIMULATOR_KEYS = [
|
|
4
6
|
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
|
|
5
7
|
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
|
@@ -74,7 +76,18 @@ export const SIMULATOR_SCREENSHOT_ERROR_CODES = [
|
|
|
74
76
|
"screenshot_invalid_response",
|
|
75
77
|
"screenshot_failed",
|
|
76
78
|
];
|
|
79
|
+
export const SIMULATOR_ACCESSIBILITY_ERROR_CODES = [
|
|
80
|
+
"active_session_not_found",
|
|
81
|
+
"simulator_capacity_unavailable",
|
|
82
|
+
"simulator_session_ended",
|
|
83
|
+
"accessibility_unavailable",
|
|
84
|
+
"accessibility_cancelled",
|
|
85
|
+
"accessibility_timeout",
|
|
86
|
+
"accessibility_transport_error",
|
|
87
|
+
"accessibility_invalid_response",
|
|
88
|
+
];
|
|
77
89
|
export const DEFAULT_SIMULATOR_INTERACTION_TIMEOUT_MS = 15_000;
|
|
90
|
+
export const DEFAULT_SIMULATOR_ACCESSIBILITY_TIMEOUT_MS = 20_000;
|
|
78
91
|
const SIMULATOR_RESPONSE_MARGIN_MS = 250;
|
|
79
92
|
const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
80
93
|
function assertNormalizedPoint(point, name) {
|
|
@@ -235,6 +248,188 @@ export function validateSimulatorInteraction(interaction, timeoutMs = DEFAULT_SI
|
|
|
235
248
|
return;
|
|
236
249
|
}
|
|
237
250
|
}
|
|
251
|
+
const simulatorAccessibilityRoleSet = new Set([
|
|
252
|
+
"application", "window", "dialog", "alert", "group", "scroll-view", "list",
|
|
253
|
+
"list-item", "text", "heading", "button", "link", "image", "text-field",
|
|
254
|
+
"checkbox", "switch", "radio-button", "slider", "progress-indicator", "tab",
|
|
255
|
+
"tab-bar", "toolbar", "menu", "menu-item", "web-view", "keyboard", "other",
|
|
256
|
+
]);
|
|
257
|
+
function accessibilityRecord(value, field) {
|
|
258
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
259
|
+
throw new TypeError(`${field} must be an object`);
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function accessibilityString(value, field) {
|
|
264
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 16_384) {
|
|
265
|
+
throw new TypeError(`${field} must be a non-empty bounded string`);
|
|
266
|
+
}
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
function accessibilityNullableString(value, field) {
|
|
270
|
+
if (value === null)
|
|
271
|
+
return null;
|
|
272
|
+
if (typeof value !== "string" || value.length > 16_384) {
|
|
273
|
+
throw new TypeError(`${field} must be null or a bounded string`);
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
function accessibilityBoolean(value, field) {
|
|
278
|
+
if (typeof value !== "boolean")
|
|
279
|
+
throw new TypeError(`${field} must be a boolean`);
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
function accessibilityNullableBoolean(value, field) {
|
|
283
|
+
if (value !== null && typeof value !== "boolean") {
|
|
284
|
+
throw new TypeError(`${field} must be a boolean or null`);
|
|
285
|
+
}
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
function accessibilityNumber(value, field, nonNegative = false) {
|
|
289
|
+
if (typeof value !== "number" || !Number.isFinite(value) || (nonNegative && value < 0)) {
|
|
290
|
+
throw new TypeError(`${field} must be a${nonNegative ? " non-negative" : ""} finite number`);
|
|
291
|
+
}
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
function parseAccessibilityBounds(value, field) {
|
|
295
|
+
if (value === null)
|
|
296
|
+
return null;
|
|
297
|
+
const input = accessibilityRecord(value, field);
|
|
298
|
+
return {
|
|
299
|
+
x: accessibilityNumber(input.x, `${field}.x`),
|
|
300
|
+
y: accessibilityNumber(input.y, `${field}.y`),
|
|
301
|
+
width: accessibilityNumber(input.width, `${field}.width`, true),
|
|
302
|
+
height: accessibilityNumber(input.height, `${field}.height`, true),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function parseAccessibilityStates(value, field) {
|
|
306
|
+
const input = accessibilityRecord(value, field);
|
|
307
|
+
if (input.checked !== null && typeof input.checked !== "boolean" && input.checked !== "mixed") {
|
|
308
|
+
throw new TypeError(`${field}.checked must be a boolean, mixed, or null`);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
enabled: accessibilityBoolean(input.enabled, `${field}.enabled`),
|
|
312
|
+
focused: accessibilityNullableBoolean(input.focused, `${field}.focused`),
|
|
313
|
+
selected: accessibilityNullableBoolean(input.selected, `${field}.selected`),
|
|
314
|
+
checked: input.checked,
|
|
315
|
+
expanded: accessibilityNullableBoolean(input.expanded, `${field}.expanded`),
|
|
316
|
+
focusable: accessibilityNullableBoolean(input.focusable, `${field}.focusable`),
|
|
317
|
+
clickable: accessibilityNullableBoolean(input.clickable, `${field}.clickable`),
|
|
318
|
+
scrollable: accessibilityNullableBoolean(input.scrollable, `${field}.scrollable`),
|
|
319
|
+
editable: accessibilityBoolean(input.editable, `${field}.editable`),
|
|
320
|
+
secure: accessibilityBoolean(input.secure, `${field}.secure`),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function parseAccessibilityNative(value, platform, field) {
|
|
324
|
+
const input = accessibilityRecord(value, field);
|
|
325
|
+
if (input.platform !== platform)
|
|
326
|
+
throw new TypeError(`${field}.platform must match the snapshot`);
|
|
327
|
+
if (platform === "ios") {
|
|
328
|
+
return {
|
|
329
|
+
platform,
|
|
330
|
+
type: accessibilityNullableString(input.type, `${field}.type`),
|
|
331
|
+
roleDescription: accessibilityNullableString(input.roleDescription, `${field}.roleDescription`),
|
|
332
|
+
subrole: accessibilityNullableString(input.subrole, `${field}.subrole`),
|
|
333
|
+
title: accessibilityNullableString(input.title, `${field}.title`),
|
|
334
|
+
help: accessibilityNullableString(input.help, `${field}.help`),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
platform,
|
|
339
|
+
className: accessibilityNullableString(input.className, `${field}.className`),
|
|
340
|
+
packageName: accessibilityNullableString(input.packageName, `${field}.packageName`),
|
|
341
|
+
resourceId: accessibilityNullableString(input.resourceId, `${field}.resourceId`),
|
|
342
|
+
contentDescription: accessibilityNullableString(input.contentDescription, `${field}.contentDescription`),
|
|
343
|
+
checkable: accessibilityBoolean(input.checkable, `${field}.checkable`),
|
|
344
|
+
longClickable: accessibilityBoolean(input.longClickable, `${field}.longClickable`),
|
|
345
|
+
password: accessibilityBoolean(input.password, `${field}.password`),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function parseAccessibilityNode(value, expectedPath, depth, context) {
|
|
349
|
+
if (depth > 80 || context.count >= 500) {
|
|
350
|
+
throw new TypeError("accessibility tree exceeds its declared bounds");
|
|
351
|
+
}
|
|
352
|
+
const field = `accessibility node ${expectedPath}`;
|
|
353
|
+
const input = accessibilityRecord(value, field);
|
|
354
|
+
const id = accessibilityString(input.id, `${field}.id`);
|
|
355
|
+
const path = accessibilityString(input.path, `${field}.path`);
|
|
356
|
+
if (path !== expectedPath)
|
|
357
|
+
throw new TypeError(`${field}.path must be ${expectedPath}`);
|
|
358
|
+
if (context.ids.has(id))
|
|
359
|
+
throw new TypeError(`duplicate accessibility node id: ${id}`);
|
|
360
|
+
context.ids.add(id);
|
|
361
|
+
context.count += 1;
|
|
362
|
+
if (typeof input.role !== "string"
|
|
363
|
+
|| !simulatorAccessibilityRoleSet.has(input.role))
|
|
364
|
+
throw new TypeError(`${field}.role is not supported`);
|
|
365
|
+
const role = input.role;
|
|
366
|
+
const states = parseAccessibilityStates(input.states, `${field}.states`);
|
|
367
|
+
const native = parseAccessibilityNative(input.native, context.platform, `${field}.native`);
|
|
368
|
+
if (!Array.isArray(input.children))
|
|
369
|
+
throw new TypeError(`${field}.children must be an array`);
|
|
370
|
+
const secure = states.secure
|
|
371
|
+
|| (native.platform === "android" && native.password)
|
|
372
|
+
|| (native.platform === "ios" && /(?:secure|password)/iu.test([
|
|
373
|
+
native.type,
|
|
374
|
+
native.roleDescription,
|
|
375
|
+
native.subrole,
|
|
376
|
+
].filter(Boolean).join(" ")));
|
|
377
|
+
return {
|
|
378
|
+
id,
|
|
379
|
+
path,
|
|
380
|
+
role,
|
|
381
|
+
label: accessibilityNullableString(input.label, `${field}.label`),
|
|
382
|
+
value: secure ? null : accessibilityNullableString(input.value, `${field}.value`),
|
|
383
|
+
identifier: accessibilityNullableString(input.identifier, `${field}.identifier`),
|
|
384
|
+
bounds: parseAccessibilityBounds(input.bounds, `${field}.bounds`),
|
|
385
|
+
states: { ...states, secure },
|
|
386
|
+
native,
|
|
387
|
+
children: input.children.map((child, index) => (parseAccessibilityNode(child, `${expectedPath}.${index}`, depth + 1, context))),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/** Validate an API response and remove any secure value defensively. */
|
|
391
|
+
export function validateSimulatorAccessibilitySnapshot(value, expectedPlatform) {
|
|
392
|
+
const input = accessibilityRecord(value, "accessibility snapshot");
|
|
393
|
+
if (input.schemaVersion !== 1)
|
|
394
|
+
throw new TypeError("accessibility schemaVersion must be 1");
|
|
395
|
+
if (input.platform !== expectedPlatform)
|
|
396
|
+
throw new TypeError("accessibility platform mismatch");
|
|
397
|
+
const capturedAt = accessibilityString(input.capturedAt, "accessibility capturedAt");
|
|
398
|
+
if (!Number.isFinite(Date.parse(capturedAt))) {
|
|
399
|
+
throw new TypeError("accessibility capturedAt must be an ISO date-time string");
|
|
400
|
+
}
|
|
401
|
+
const screenInput = accessibilityRecord(input.screen, "accessibility screen");
|
|
402
|
+
const unit = expectedPlatform === "ios" ? "points" : "pixels";
|
|
403
|
+
if (screenInput.unit !== unit)
|
|
404
|
+
throw new TypeError(`accessibility screen.unit must be ${unit}`);
|
|
405
|
+
const nodeCount = accessibilityNumber(input.nodeCount, "accessibility nodeCount", true);
|
|
406
|
+
if (!Number.isInteger(nodeCount) || nodeCount > 500) {
|
|
407
|
+
throw new TypeError("accessibility nodeCount must be an integer from 0 to 500");
|
|
408
|
+
}
|
|
409
|
+
if (!Array.isArray(input.roots))
|
|
410
|
+
throw new TypeError("accessibility roots must be an array");
|
|
411
|
+
const context = {
|
|
412
|
+
platform: expectedPlatform,
|
|
413
|
+
ids: new Set(),
|
|
414
|
+
count: 0,
|
|
415
|
+
};
|
|
416
|
+
const roots = input.roots.map((root, index) => (parseAccessibilityNode(root, String(index), 0, context)));
|
|
417
|
+
if (context.count !== nodeCount)
|
|
418
|
+
throw new TypeError("accessibility nodeCount does not match tree");
|
|
419
|
+
return {
|
|
420
|
+
schemaVersion: 1,
|
|
421
|
+
platform: expectedPlatform,
|
|
422
|
+
capturedAt,
|
|
423
|
+
screen: {
|
|
424
|
+
width: accessibilityNumber(screenInput.width, "accessibility screen.width", true),
|
|
425
|
+
height: accessibilityNumber(screenInput.height, "accessibility screen.height", true),
|
|
426
|
+
unit,
|
|
427
|
+
},
|
|
428
|
+
nodeCount,
|
|
429
|
+
truncated: accessibilityBoolean(input.truncated, "accessibility truncated"),
|
|
430
|
+
roots,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
238
433
|
function requestAbort(options) {
|
|
239
434
|
const { signal, timeoutMs } = options;
|
|
240
435
|
if (timeoutMs === undefined && !signal) {
|
|
@@ -372,6 +567,7 @@ function optionalFiniteNumber(value) {
|
|
|
372
567
|
}
|
|
373
568
|
const simulatorInteractionErrorCodeSet = new Set(SIMULATOR_INTERACTION_ERROR_CODES);
|
|
374
569
|
const simulatorScreenshotErrorCodeSet = new Set(SIMULATOR_SCREENSHOT_ERROR_CODES);
|
|
570
|
+
const simulatorAccessibilityErrorCodeSet = new Set(SIMULATOR_ACCESSIBILITY_ERROR_CODES);
|
|
375
571
|
function normalizedInteractionErrorCode(value) {
|
|
376
572
|
if (typeof value === "string" && simulatorInteractionErrorCodeSet.has(value)) {
|
|
377
573
|
return value;
|
|
@@ -402,6 +598,11 @@ function normalizedScreenshotErrorCode(value) {
|
|
|
402
598
|
? value
|
|
403
599
|
: "screenshot_failed";
|
|
404
600
|
}
|
|
601
|
+
function normalizedAccessibilityErrorCode(value) {
|
|
602
|
+
return typeof value === "string" && simulatorAccessibilityErrorCodeSet.has(value)
|
|
603
|
+
? value
|
|
604
|
+
: "accessibility_unavailable";
|
|
605
|
+
}
|
|
405
606
|
async function parseApiError(response) {
|
|
406
607
|
const text = await response.text();
|
|
407
608
|
try {
|
|
@@ -428,9 +629,11 @@ async function parseApiError(response) {
|
|
|
428
629
|
const rawCode = nested?.code ?? parsed.code;
|
|
429
630
|
const code = action === "screenshot"
|
|
430
631
|
? normalizedScreenshotErrorCode(rawCode)
|
|
431
|
-
: action
|
|
432
|
-
?
|
|
433
|
-
:
|
|
632
|
+
: action === "accessibility"
|
|
633
|
+
? normalizedAccessibilityErrorCode(rawCode)
|
|
634
|
+
: action !== "open-url" && (action !== undefined || interactionStatus !== undefined)
|
|
635
|
+
? normalizedInteractionErrorCode(rawCode)
|
|
636
|
+
: optionalString(rawCode);
|
|
434
637
|
return {
|
|
435
638
|
detail,
|
|
436
639
|
code,
|
|
@@ -498,6 +701,7 @@ export class Client {
|
|
|
498
701
|
secrets;
|
|
499
702
|
ci;
|
|
500
703
|
images;
|
|
704
|
+
remotion;
|
|
501
705
|
apiKey;
|
|
502
706
|
apiUrl;
|
|
503
707
|
fetchImpl;
|
|
@@ -523,6 +727,7 @@ export class Client {
|
|
|
523
727
|
this.secrets = new SecretsClient(this);
|
|
524
728
|
this.ci = new CiClient(this);
|
|
525
729
|
this.images = new ImageClient(this);
|
|
730
|
+
this.remotion = new RemotionClient(this.sandboxes);
|
|
526
731
|
}
|
|
527
732
|
async usage(options = {}) {
|
|
528
733
|
const query = options.orgId ? `?orgId=${encodeURIComponent(options.orgId)}` : "";
|
|
@@ -946,6 +1151,39 @@ export class SimulatorClient {
|
|
|
946
1151
|
async setRenderDebug(id, option, enabled, options = {}) {
|
|
947
1152
|
return await this.interact(id, { action: "setRenderDebug", option, enabled }, options);
|
|
948
1153
|
}
|
|
1154
|
+
async accessibilityTree(id, options = {}) {
|
|
1155
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SIMULATOR_ACCESSIBILITY_TIMEOUT_MS;
|
|
1156
|
+
assertIntegerRange(timeoutMs, 100, 60_000, "timeoutMs");
|
|
1157
|
+
const context = {
|
|
1158
|
+
sessionId: id,
|
|
1159
|
+
platform: this.platform,
|
|
1160
|
+
action: "accessibility",
|
|
1161
|
+
};
|
|
1162
|
+
const value = await this.client.request("GET", `/run-cloud/${this.platform}/${encodeURIComponent(id)}/accessibility`, undefined, undefined, {
|
|
1163
|
+
signal: options.signal,
|
|
1164
|
+
timeoutMs: timeoutMs + SIMULATOR_RESPONSE_MARGIN_MS,
|
|
1165
|
+
timeoutMessage: `simulator accessibility capture did not complete within ${timeoutMs} ms`,
|
|
1166
|
+
timeoutCode: "accessibility_timeout",
|
|
1167
|
+
transportErrorCode: "accessibility_transport_error",
|
|
1168
|
+
transportErrorMessage: "simulator accessibility could not reach the run.cloud API",
|
|
1169
|
+
timeoutContext: context,
|
|
1170
|
+
errorContext: {
|
|
1171
|
+
...context,
|
|
1172
|
+
code: "accessibility_unavailable",
|
|
1173
|
+
},
|
|
1174
|
+
});
|
|
1175
|
+
try {
|
|
1176
|
+
return validateSimulatorAccessibilitySnapshot(value, this.platform);
|
|
1177
|
+
}
|
|
1178
|
+
catch (error) {
|
|
1179
|
+
throw new RunCloudError(502, "simulator accessibility returned an invalid tree", {
|
|
1180
|
+
...context,
|
|
1181
|
+
code: "accessibility_invalid_response",
|
|
1182
|
+
retryable: true,
|
|
1183
|
+
details: { reason: error instanceof Error ? error.message : "unknown" },
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
949
1187
|
async screenshot(id, options = {}) {
|
|
950
1188
|
const timeoutMs = options.timeoutMs ?? DEFAULT_SIMULATOR_INTERACTION_TIMEOUT_MS;
|
|
951
1189
|
const requestId = options.requestId ?? `screenshot_${randomUUID()}`;
|
|
@@ -1197,6 +1435,10 @@ export class SimulatorsClient {
|
|
|
1197
1435
|
const { platform = "ios", ...requestOptions } = options;
|
|
1198
1436
|
return await new SimulatorClient(this.client, platform).screenshot(id, requestOptions);
|
|
1199
1437
|
}
|
|
1438
|
+
async accessibilityTree(id, options = {}) {
|
|
1439
|
+
const { platform = "ios", ...requestOptions } = options;
|
|
1440
|
+
return await new SimulatorClient(this.client, platform).accessibilityTree(id, requestOptions);
|
|
1441
|
+
}
|
|
1200
1442
|
async startRecording(id, options = {}) {
|
|
1201
1443
|
const { platform = "ios", ...requestOptions } = options;
|
|
1202
1444
|
return await new SimulatorClient(this.client, platform).startRecording(id, requestOptions);
|