serve-sim 0.1.48-beta.130.1 → 0.1.49-beta.131.1

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 CHANGED
@@ -107,6 +107,10 @@ serve-sim fold <deg> [-d udid] Set a Duo hinge angle (0–180)
107
107
  serve-sim repair-input [-d udid] Repair Device Hub input (restarts apps)
108
108
  serve-sim memory-warning [-d udid] Simulate a memory warning
109
109
  serve-sim event-log [-d udid] Show recent simulator events
110
+ serve-sim exec <command...> [-d udid] Run a host shell command through a server
111
+ started with --allow-exec. One argument
112
+ is a shell line (exec "xcrun simctl list");
113
+ several are quoted as an argv
110
114
 
111
115
  serve-sim camera <bundle-id> [-d udid] [source-options]
112
116
  Inject a synthetic camera feed and (re)launch the app
@@ -122,6 +126,9 @@ serve-sim camera --stop-webcam [-d udid]
122
126
  Options:
123
127
  -p, --port <port> Starting port (preview default: 3200; helper default: 3100)
124
128
  -d, --detach Spawn helper and exit (daemon mode)
129
+ --allow-exec Enable the host shell (/exec, {command} frames on
130
+ /exec-ws, `serve-sim exec`) and .ipa installs from
131
+ the preview. Off by default
125
132
  -q, --quiet JSON-only output
126
133
  --no-preview Skip the web UI; stream in foreground only
127
134
  --panes <panes> Initially open preview panes: devices, tools, devtools,
@@ -157,6 +164,7 @@ serve-sim --list # show running streams
157
164
  serve-sim --kill # stop all helpers
158
165
  serve-sim --panes devices,tools --fit # start with selected panes open and fit the simulator
159
166
  serve-sim --theme dark # start the simulator in Dark Mode
167
+ serve-sim --allow-exec # enable `serve-sim exec` and .ipa drag-to-install
160
168
 
161
169
  # Type text into the focused field
162
170
  serve-sim type "Hello, world!"
@@ -184,6 +192,22 @@ serve-sim camera --stop-webcam
184
192
 
185
193
  Multiple booted simulators are supported — pass several device names, or leave it empty to attach to all of them.
186
194
 
195
+ ### Remote access (`--auth-token`)
196
+
197
+ Requests that don't come straight from this machine — LAN clients, tunnels, reverse proxies — are refused unless the server is started with `--auth-token <token>` (or `SERVE_SIM_AUTH_TOKEN`). CLI and scripted clients send `Authorization: Bearer <token>`. To share the preview with a browser, send the link with the token in the URL fragment:
198
+
199
+ ```
200
+ http://<host>:3200/#token=<token>
201
+ ```
202
+
203
+ Browsers never send the fragment to the server, so the token stays out of request lines and server/proxy logs. The page that loads first is a small auth bootstrap (it contains no secrets): it reads the fragment, removes it from the address bar and history, and POSTs it in a header to the same-origin `/auth` endpoint, which sets an HttpOnly, `SameSite=Lax` cookie and the page reloads. `/auth` only accepts same-origin `POST`s, compares in constant time, and locks out after 10 wrong tokens a minute. A `?token=` query parameter is never accepted.
204
+
205
+ ### Host shell (`--allow-exec`)
206
+
207
+ The host shell — `POST /exec`, `{command}` frames on the `/exec-ws` control channel, and `serve-sim exec` — runs commands on the host, so it is off unless the server is started with `--allow-exec`; otherwise `/exec` answers `403 {"error":"exec disabled; start with --allow-exec"}` and `{command}` frames get `exec disabled`. Installing an app (dropping an `.ipa` on the preview) runs arbitrary code on the host too, so it also requires `--allow-exec` (`installApp requires --allow-exec`). The other preview tools (screenshots, camera, permissions, location, media drops, reveal in Finder) don't need it: they use fixed, validated host actions with no shell, available to loopback same-origin pages. Even when enabled, `/exec` is loopback-only and requires a per-session token that is never sent in any HTTP response: the server writes it to an owner-only file under `$TMPDIR/serve-sim/exec-tokens/` (removed on stop), and `serve-sim exec` reads it from there. A raw TCP forward of the preview port (`ssh -R`, socat) looks like a local client, so only enable `--allow-exec` on a port nobody else can reach. Note that `installApp` needs no exec token: with `--allow-exec` it only requires a same-origin loopback page, so anyone who can reach an `--allow-exec` port through a raw `ssh -R` or socat forward can install (and so run) arbitrary apps on the host.
208
+
209
+ `--detach` and `--no-preview` reuse a running server only when it was started with the same `--auth-token` (or with none on both sides) and the same `--allow-exec` setting (on or off on both sides); otherwise they exit with an error — stop the old server with `serve-sim -k <udid>` first.
210
+
187
211
  ### Camera
188
212
 
189
213
  `serve-sim camera <bundle-id>` replaces the simulator's camera feed for a single app. A small host-side helper writes BGRA frames into a POSIX shared-memory region; an injected dylib (`DYLD_INSERT_LIBRARIES`) swizzles AVFoundation inside the simulator process so the app reads from that region instead of the simulator's stub camera.
@@ -272,23 +296,30 @@ app.use(simMiddleware({ basePath: "/.sim" }));
272
296
  // → state JSON at /.sim/api
273
297
  ```
274
298
 
275
- The middleware reads the helper's state from `$TMPDIR/serve-sim/` and points the browser at the helper's stream, interaction WebSocket, and WebKit DevTools endpoints. By default those URLs target the helper's own port directly (CORS is wide-open on the helper), so a plain `app.use(...)` mount works without touching your server's WebSocket handling.
299
+ Host-shell features are off unless you pass `allowExec: true`. The middleware reads the helper's state from `$TMPDIR/serve-sim/` and hands the browser same-origin `/.sim/helper/<device>` and `/.sim/devtools` URLs for the stream, interaction WebSocket, and WebKit DevTools; the middleware proxies them to the helper. The helper itself sends no CORS headers and only accepts same-origin WebSockets, so an embedded page can't talk to the helper's port directly.
276
300
 
277
- ### Single-port / remote proxying
301
+ ### Forward WebSocket upgrades
278
302
 
279
- To expose the preview to remote viewers behind a single port (the way standalone `serve-sim` does), pass `proxyHelpers: true`. The browser then reaches the stream, control socket, and DevTools through same-origin `/.sim/helper/<device>` and `/.sim/devtools` URLs, so the per-device helper port and inspect-webkit bridge can stay local to the host. This routes WebSockets through the middleware, so you must forward your server's `upgrade` events to `handleUpgrade`:
303
+ Simulator input, the control channel, and DevTools are WebSockets, so forward your server's `upgrade` events under the mount path to `handleUpgrade`. `handleUpgrade` closes any socket it doesn't own, so leave every other upgrade (HMR, your app's own sockets) to your server's existing handlers:
280
304
 
281
305
  ```ts
282
- const middleware = simMiddleware({ basePath: "/.sim", proxyHelpers: true });
306
+ const basePath = "/.sim";
307
+ const middleware = simMiddleware({ basePath });
283
308
  app.use(middleware);
284
309
 
285
310
  const server = app.listen(3000);
286
- server.on("upgrade", (req, socket, head) =>
287
- middleware.handleUpgrade(req, socket, head),
288
- );
311
+ server.on("upgrade", (req, socket, head) => {
312
+ const { pathname } = new URL(req.url ?? "/", "http://localhost");
313
+ if (pathname === basePath || pathname.startsWith(`${basePath}/`)) {
314
+ middleware.handleUpgrade(req, socket, head);
315
+ }
316
+ // Anything else belongs to the host server's other upgrade listeners.
317
+ });
289
318
  ```
290
319
 
291
- If you enable `proxyHelpers` but don't wire `upgrade`, the page still loads video over HTTP but loses simulator input and DevTools (their sockets never reach the proxy). When terminating TLS at a reverse proxy, forward `X-Forwarded-Proto` so the helper URLs use `https`/`wss` and avoid mixed-content blocks.
320
+ Without that, the page still loads video over HTTP but loses simulator input and DevTools (their sockets never reach the proxy). The same setup exposes the preview to remote viewers behind a single port (the way standalone `serve-sim` does), so the per-device helper port and inspect-webkit bridge can stay local to the host. When terminating TLS at a reverse proxy, forward `X-Forwarded-Proto` so the helper URLs use `https`/`wss` and avoid mixed-content blocks.
321
+
322
+ Pass `proxyHelpers: false` to hand the page the helper's direct URLs instead; that only works when the page is served from the helper's own origin.
292
323
 
293
324
  ## How it works
294
325