runcloud 0.1.107 → 0.1.108

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.
@@ -99,7 +99,7 @@ export function consumeSimulatorLogSse(raw) {
99
99
  function writeSimulatorLogEntry(entry, json) {
100
100
  console.log(json ? JSON.stringify(entry) : entry.message);
101
101
  }
102
- function parseLabels(labels) {
102
+ function parseTags(labels) {
103
103
  const out = {};
104
104
  for (const raw of labels ?? []) {
105
105
  const idx = raw.indexOf('=');
@@ -307,7 +307,7 @@ async function createSimulatorSession(platform, opts) {
307
307
  model: opts.model,
308
308
  region: opts.region,
309
309
  displayName: opts.displayName,
310
- labels: parseLabels(opts.label),
310
+ tags: parseTags(opts.label),
311
311
  installAssets,
312
312
  inactivityTimeout: opts.inactivityTimeout,
313
313
  hardTimeout: opts.hardTimeout,
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.1.107';
1
+ export const CLI_VERSION = '0.1.108';
@@ -71,9 +71,14 @@ function escapeHtml(value) {
71
71
  .replaceAll('>', '>');
72
72
  }
73
73
 
74
+ function scriptData(value) {
75
+ return JSON.stringify(value).replaceAll('<', '\\u003c');
76
+ }
77
+
74
78
  function embedUrl(value) {
75
79
  const url = new URL(value);
76
80
  url.searchParams.set('embed', '1');
81
+ url.searchParams.set('loadingGuard', '1');
77
82
  return url.toString();
78
83
  }
79
84
 
@@ -130,6 +135,7 @@ export function mosaicViewerHtml(sessions, deadline = Date.now() + DEMO_LEASE_MS
130
135
  const deadline = ${deadline};
131
136
  const tiles = [...document.querySelectorAll('.tile')];
132
137
  const frames = [...document.querySelectorAll('iframe')];
138
+ const frameOrigins = ${scriptData(sessions.map((session) => new URL(session.url).origin))};
133
139
  const ready = new Set();
134
140
  const count = document.querySelector('#ready-count');
135
141
  const timer = document.querySelector('#timer');
@@ -141,7 +147,12 @@ export function mosaicViewerHtml(sessions, deadline = Date.now() + DEMO_LEASE_MS
141
147
  setInterval(tick, 250);
142
148
  window.addEventListener('message', (event) => {
143
149
  const index = frames.findIndex((frame) => frame.contentWindow === event.source);
144
- if (index < 0 || !event.data || typeof event.data !== 'object') return;
150
+ if (
151
+ index < 0
152
+ || event.origin !== frameOrigins[index]
153
+ || !event.data
154
+ || typeof event.data !== 'object'
155
+ ) return;
145
156
  if (event.data.type === 'ios-simulator:status' && event.data.streaming === true) {
146
157
  ready.add(index);
147
158
  tiles[index].dataset.ready = 'true';
@@ -35,8 +35,11 @@ test('renders eight signed embeds without printing them into the viewer URL', as
35
35
  }));
36
36
  const html = mosaicViewerHtml(sessions, 601_000);
37
37
  assert.equal((html.match(/<iframe /g) || []).length, 8);
38
- assert.match(html, /embed=1/);
39
- assert.doesNotMatch(html, /loadingGuard=1/);
38
+ assert.equal((html.match(/embed=1/g) || []).length, 8);
39
+ assert.equal((html.match(/loadingGuard=1/g) || []).length, 8);
40
+ assert.match(html, /const frameOrigins = \["https:\/\/sim\.example"/);
41
+ assert.match(html, /frame\.contentWindow === event\.source/);
42
+ assert.match(html, /event\.origin !== frameOrigins\[index\]/);
40
43
  assert.match(html, /const deadline = 601000/);
41
44
 
42
45
  const viewer = await startMosaicViewer(sessions, 601_000);
@@ -87,6 +87,7 @@ function scriptData(value) {
87
87
  function embedUrl(value) {
88
88
  const url = new URL(value);
89
89
  url.searchParams.set('embed', '1');
90
+ url.searchParams.set('loadingGuard', '1');
90
91
  return url.toString();
91
92
  }
92
93
 
@@ -193,6 +194,7 @@ export function cameraViewerHtml(sessions, duration, roomId, now = Date.now()) {
193
194
  const deadline = ${deadline};
194
195
  const devices = [...document.querySelectorAll('.device')];
195
196
  const frames = [...document.querySelectorAll('iframe')];
197
+ const frameOrigins = ${scriptData(sessions.map((session) => new URL(session.url).origin))};
196
198
  const ready = new Set();
197
199
  const count = document.querySelector('#ready-count');
198
200
  const timer = document.querySelector('#timer');
@@ -287,7 +289,12 @@ export function cameraViewerHtml(sessions, duration, roomId, now = Date.now()) {
287
289
  setInterval(tick, 250);
288
290
  window.addEventListener('message', (event) => {
289
291
  const index = frames.findIndex((frame) => frame.contentWindow === event.source);
290
- if (index < 0 || !event.data || typeof event.data !== 'object') return;
292
+ if (
293
+ index < 0
294
+ || event.origin !== frameOrigins[index]
295
+ || !event.data
296
+ || typeof event.data !== 'object'
297
+ ) return;
291
298
  if (event.data.type === 'ios-simulator:status' && event.data.streaming === true) {
292
299
  ready.add(index);
293
300
  devices[index].dataset.ready = 'true';
@@ -67,8 +67,11 @@ test('renders three signed embeds while keeping credentials out of the local URL
67
67
  const demoSessions = sessions();
68
68
  const html = cameraViewerHtml(demoSessions, 60, 'room-12345678', 1_000);
69
69
  assert.equal((html.match(/<iframe /g) || []).length, 3);
70
- assert.match(html, /embed=1/);
71
- assert.doesNotMatch(html, /loadingGuard=1/);
70
+ assert.equal((html.match(/embed=1/g) || []).length, 3);
71
+ assert.equal((html.match(/loadingGuard=1/g) || []).length, 3);
72
+ assert.match(html, /const frameOrigins = \["https:\/\/sim\.example"/);
73
+ assert.match(html, /frame\.contentWindow === event\.source/);
74
+ assert.match(html, /event\.origin !== frameOrigins\[index\]/);
72
75
  assert.match(html, /Connect webcam/);
73
76
  assert.match(html, /com\.apple\.mobilesafari/);
74
77
  assert.match(html, /audio: false/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.107",
3
+ "version": "0.1.108",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -134,7 +134,7 @@ import { Client } from "@run-cloud/sdk";
134
134
  const cloud = new Client();
135
135
  const session = await cloud.ios.create({
136
136
  displayName: "Agent smoke",
137
- labels: { owner: "agent" },
137
+ tags: { owner: "agent" },
138
138
  inactivityTimeout: "60s",
139
139
  hardTimeout: "10m",
140
140
  codec: "auto",
@@ -159,7 +159,7 @@ The mobile SDK surface is:
159
159
  `delete`
160
160
  - `cloud.assets`: `upload`, `list`, `delete`
161
161
 
162
- Create options include `model`, `region`, `displayName`, `labels`,
162
+ Create options include `model`, `region`, `displayName`, `tags`,
163
163
  `installAssets`, `inactivityTimeout`, `hardTimeout`, and `codec`.
164
164
 
165
165
  Do not invent SDK methods for scripted taps, typing, recording, app lifecycle,
@@ -180,10 +180,13 @@ Delete uploaded assets when they are no longer needed.
180
180
 
181
181
  ## Embed a Session
182
182
 
183
+ - In React, use `RemoteControl` from `@runcloud/ui` with the signed session URL.
183
184
  - Add `embed=1` to the signed session URL for the clean iframe UI.
184
185
  - Add `loadingGuard=1` when the iframe should block interaction until streaming
185
186
  and app launch are ready.
186
- - Verify `event.source` is the expected iframe before processing messages.
187
+ - For raw iframes, verify both `event.source` and the exact signed-URL origin
188
+ before processing messages, and use that exact origin as the command
189
+ `postMessage` target.
187
190
  - Handle `ios-simulator:status`, `ios-simulator:auth-error`,
188
191
  `ios-simulator:session-ended`, and
189
192
  `ios-simulator:session-restart-requested`.