serve-sim 0.1.48 → 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 +39 -8
- package/dist/middleware.js +82 -54
- package/dist/serve-sim.js +109 -80
- package/package.json +1 -1
- package/src/middleware.ts +383 -81
- package/src/state.ts +75 -1
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
|
|
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
|
-
###
|
|
301
|
+
### Forward WebSocket upgrades
|
|
278
302
|
|
|
279
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|