Skip to content

[build-tools] Start expo-device-hub with a session token and the website origins - #4578

Merged
krystofwoldrich merged 4 commits into
mainfrom
krystofwoldrich/expo-device-hub-website-flags
Oct 15, 2026
Merged

krystofwoldrich merged 4 commits into
mainfrom
krystofwoldrich/expo-device-hub-website-flags

Conversation

@krystofwoldrich

@krystofwoldrich krystofwoldrich commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Why

The website streams Android simulator sessions with @expo/hub-client from its own origin (expo.dev, staging.expo.dev, deploy previews, and local *.expo.test servers). iOS sessions already allow this: the worker starts serve-sim with --require-token and --cors-origin / --frame-ancestor for the website origins, and reads its session token from serve-sim's state file.

Android sessions start expo-device-hub without these flags. So the browser blocks most requests from the website, and the Hub runs on the public ngrok tunnel without a token.

expo/expo-device-hub#292 (on expo/expo-device-hub#265) gives the Hub --cors-origin with the same values as serve-sim, including https://*.expo.dev, and expo/expo-device-hub#294 makes the CLI record its session token in a state file, as serve-sim does.

How

In packages/build-tools/src/steps/utils/deviceSessionHost.ts:

  • createExpoDeviceHubArgs adds --require-token and the website origin args, in the same places as createServeSimArgs.
  • websiteOriginServeSimArgs is renamed websiteOriginArgs, because both servers now take it. The values are unchanged.
  • After /readyz, the host reads the Android session token from ${tmpdir}/expo-device-hub/server-<port>.json (readExpoDeviceHubPreviewTokenAsync), as it reads serve-sim's from ${tmpdir}/serve-sim/server-<udid>.json. Without a token, the session stops with the same error as on iOS, now naming the server.
  • The host passes that directory to the Hub as EXPO_DEVICE_HUB_STATE_DIR. Otherwise the Hub would resolve it from the step's env, and a step env that sets its own TMPDIR or EXPO_DEVICE_HUB_STATE_DIR would put the token where the host does not read it.
  • The token goes into the preview remote config, as on iOS, so the website sends it.
  • The token is added to the host's secrets once it is read, as on iOS, so the streamed host output redacts it. The Hub's startup link (/?token=…) is printed before that, and the ?token= rule in processOutput.ts redacts it.

Unchanged: the recording stop route (/_eas/android-recording/stop) has its own control token, and the Hub leaves it outside the session gate. The Android session preview captures through adb, not the Hub.

Release order: done. The default package is expo-device-hub@latest, now 0.17.0. It has every change this needs: the state file (expo/expo-device-hub#294) and /healthz (expo/expo-device-hub#307) since 0.16.0, and --cors-origin (expo/expo-device-hub#292, on expo/expo-device-hub#265) since 0.17.0. A session that pins a packageVersion older than 0.17.0 fails at startup, because that Hub does not know --cors-origin.

Test Plan

Run by Claude Code on 2026-10-09:

  • packages/build-tools: yarn test passed (1862 passed, 8 skipped). New tests: the Android host starts expo-device-hub with --require-token and the website origins and reports the token from the state file; it stops when no token was recorded; createExpoDeviceHubArgs appends the website args after the TURN args. The two new host tests failed against the previous deviceSessionHost.ts.
  • yarn typecheck, yarn lint, and yarn fmt:check passed.
  • Against a local build of those expo-device-hub changes: expo-device-hub --require-token --cors-origin 'https://*.expo.test' wrote server-3400.json (mode 0600) with the token, allowed https://universe-3001.expo.test, refused https://expo.test and other origins, and removed the file on SIGTERM.
  • Not run: an EAS Android session with this worker change.

Run by Claude Code on 2026-10-15, after the rebase onto main:

  • The rebase kept main's new secrets.push(previewToken) and moved it after the shared token check, so it covers Android too. The Android secrets test now also checks that the Hub's token is added to the array passed at spawn; it failed with the push limited to iOS. main's new test for a host that exits during the session now writes the Hub state file, as the other Android mocks do.
  • The changelog entry is now a changeset (.changeset/expo-device-hub-website-flags.md, minor), because main generates CHANGELOG.md from changesets.
  • packages/build-tools: yarn test passed (174 suites, 2136 passed, 8 skipped). An earlier run on the same code reported 8 suites that failed to load, with no failed tests; the rerun passed.
  • yarn typecheck, yarn lint, and yarn fmt:check passed.

Run by Claude Code on 2026-10-15, after the state directory fix (d30aecd):

  • Two new tests start the Android host with a step env that sets EXPO_DEVICE_HUB_STATE_DIR or TMPDIR to another directory; the spawn mock resolves the state directory from its env, as the Hub does. Both failed with the "wrote no session token" error without the fix, and pass with it.
  • packages/build-tools: yarn test passed (174 suites, 2138 passed, 8 skipped). yarn typecheck, yarn lint, and yarn fmt:check passed.

Authorship: Claude Code (Claude Opus 5.5) wrote this change and description at Krystof Woldrich's direction.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.46%. Comparing base (f97e6ec) to head (d30aecd).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4578      +/-   ##
==========================================
+ Coverage   74.44%   74.46%   +0.03%     
==========================================
  Files        1029     1029              
  Lines       50370    50390      +20     
  Branches    10884    10891       +7     
==========================================
+ Hits        37492    37519      +27     
+ Misses      11887    11881       -6     
+ Partials      991      990       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gwdp gwdp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes at Gabe's direction: the Android share-page health-check dependency must ship before enabling --require-token here. See the inline P1 comment.

The P2 token-log issue is covered by #4525: its process-output collector redacts the Hub startup token before returning diagnostics. Please ship that before or together with this change, and add the Android preview token to its mutable secrets list when combining the changes.

Codex reviewed 17fbdfa in an isolated checkout. 110 focused worker tests and 48 CLI utility tests passed. The Hub auth probe returned 401 for /healthz; the website failure was source-traced against Universe main at 5e80ae8fda. Website tests were blocked by the installed mise version; no hosted Android/browser E2E ran. Gabe discussed the findings and directed this review; this does not claim human inspection of the final diff.

— Codex

Comment thread packages/build-tools/src/steps/utils/deviceSessionHost.ts
Comment thread packages/build-tools/src/steps/utils/deviceSessionHost.ts
@douglowder

Copy link
Copy Markdown
Contributor

eas-cli now uses changesets for changelogs (#4580). The required "Check for a changeset" check fails on PRs that edit CHANGELOG.md.

To update this PR:

  1. Revert the changes to CHANGELOG.md.
  2. Run yarn changeset, select the changed packages and the bump type, and write the summary.
  3. Commit the new file in .changeset/.

The release adds the PR link and the author to the entry. If this PR needs no changelog entry, revert the CHANGELOG.md changes and add the "no changelog" label.

krystofwoldrich and others added 3 commits October 15, 2026 11:34
…ite origins

Android sessions now start expo-device-hub with --require-token, --cors-origin,
and --frame-ancestor, as iOS sessions start serve-sim, and read its session
token from the Hub's state file. Needs an expo-device-hub release with
expo/expo-device-hub#265.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host output

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@krystofwoldrich
krystofwoldrich force-pushed the krystofwoldrich/expo-device-hub-website-flags branch from e0ad8de to bec4c19 Compare October 15, 2026 09:37
… its token from

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

✅ Thank you for adding a changeset!

@krystofwoldrich
krystofwoldrich merged commit 4400b93 into main Oct 15, 2026
10 checks passed
@krystofwoldrich
krystofwoldrich deleted the krystofwoldrich/expo-device-hub-website-flags branch October 15, 2026 13:37
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.

4 participants