Skip to content

feat: support Expo DevTools plugin context.upgrade(hooks) WebSocket upgrades - #26

Draft
krystofwoldrich wants to merge 4 commits into
expofrom
krystof/devtools-plugin-upgrade-context
Draft

feat: support Expo DevTools plugin context.upgrade(hooks) WebSocket upgrades#26
krystofwoldrich wants to merge 4 commits into
expofrom
krystof/devtools-plugin-upgrade-context

Conversation

@krystofwoldrich

Copy link
Copy Markdown

Why

Expo CLI's DevTools plugin request handlers now receive a per-request context whose upgrade(hooks) commits WebSocket handshakes on dynamic paths (expo/expo@a6ca932ee5a). Until now, only serve-sim's static /exec-ws route could work under Expo CLI (via exact-path webSocketHandlers mounts in expo-device-hub); the dynamic /helper/<device>/ws (HID input) and /devtools/page/<id> (WebKit DevTools proxy) routes required a host that forwards raw Node upgrade events, which Expo CLI doesn't do.

What

simMiddleware's fetch handler now takes an optional second context argument. For WebSocket Upgrade requests it routes through a hook-based twin of handleUpgrade and commits the handshake by returning context.upgrade(hooks):

  • New src/upgrade-context.ts — structural mirror of the CLI contract (no @expo/cli dependency), with three adapters onto the existing socket consumers:
    • execSocketFromUpgradeExecWebSocket for the exec/control channel (handles pre-open close(), e.g. the origin check, by closing the peer once it appears)
    • hidSocketFromUpgradeHidSocket, attached to the DeviceSession from onopen so the synchronously-seeded config frame has a live peer
    • bridgeUpgradeToWebSocket → relay to the loopback-only inspect-webkit upstream, queueing frames until the upstream opens and preserving text/binary frame types across the hop
  • src/middleware.ts — routing: devtools proxy → helper HID (plain Response 404/400 rejects the handshake) → exec-ws → undefined declines. The host's upgrade() exists on plain HTTP requests but throws, so the middleware gates on the Upgrade header before calling it.
  • src/runtime-utils.tsWebMiddleware signature carries the optional context (backwards compatible; existing hosts pass one argument).
  • README — documents the plugin-mount mode.

Existing transports are untouched: raw-socket handleUpgrade (standalone serve-sim) and handleWebSocket (static webSocketHandlers mounts) work as before; the protocol logic stays single-sourced in exec-ws.ts / device-session.ts.

Follow-up (separate repo)

expo-device-hub's server entry still calls middleware(request) with one argument; forwarding the context (handler(request, context)middleware(rewritten, context)) lights this up and makes proxyHelpers: true viable for single-port remote viewing.

Test plan

  • New src/__tests__/upgrade-context.test.ts (8 tests) drives the middleware with a fake context mirroring the CLI semantics: exec auth + command round-trip, bad-token close, cross-origin close-before-open, unknown-path decline without committing, helper-404 reject, plain-HTTP passthrough (asserting upgrade() is never called — it throws in the CLI for HTTP), HID adapter framing both directions, and a live DevTools bridge against a Bun WebSocket upstream asserting queued frames survive and text frames stay text.
  • bun run typecheck, bun run lint, and the full bun test suite (315 pass) are green.

🤖 Generated with Claude Code

krystofwoldrich and others added 4 commits July 10, 2026 12:15
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This makes vendoring the license asier for expo-device-hub
## Summary

- replace the GitHub Actions simulator, lint, and typecheck workflows with EAS Workflow custom jobs
- install dependencies with `eas/install_node_modules`
- start the iOS Simulator with `eas/start_ios_simulator`
- preserve serialized tests and the one-time simulator reboot retry

## Why

The repository's CI checks should run on EAS infrastructure. This also lets the simulator tests use the built-in EAS simulator lifecycle instead of maintaining runner-specific simulator discovery logic.

## Impact

Pull requests run lint and typecheck on parallel EAS Linux workers. Changes under `packages/serve-sim/**` or to the simulator workflow also run the build and simulator-backed test suite on an EAS macOS worker. Superseded runs on the same branch remain canceled.

EAS project: https://expo.dev/accounts/expo/projects/serve-sim

## Validation

- validated both EAS workflow files against the current EAS Workflows JSON schema
- `bun run lint`
- `bun run typecheck`
- EAS `Checks / Lint` and `Checks / Typecheck` jobs
- `git diff --check`
…pgrades

Expo CLI's DevTools plugin request handlers receive a per-request context
whose upgrade(hooks) commits WebSocket handshakes on dynamic paths
(expo/expo@a6ca932ee5a). simMiddleware now accepts that context as an
optional second argument, so all three of its WebSocket routes work when
mounted as a DevTools plugin — /exec-ws plus the dynamic
/helper/<device>/ws and /devtools/page/<id> proxy paths that static
exact-path webSocketHandlers mounts can't express.

New src/upgrade-context.ts mirrors the CLI contract structurally (no
@expo/cli dependency) and adapts hook-based peers onto the existing
socket consumers: ExecWebSocket for the exec channel, HidSocket for HID
input (attached from onopen, once the peer can take the seeded config
frame), and a frame-type-preserving relay to the inspect-webkit DevTools
upstream. The host's upgrade() throws for plain HTTP requests, so the
middleware gates on the Upgrade header before calling it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants