Make phone pairing survive real networks - #126
Conversation
A phone pairs over plain http on the LAN, which is an insecure browser context, and the app assumed secure-context APIs throughout. Every direct crypto.randomUUID call threw there (the existing fallback was a decoy that re-threw the same error), leaving a freshly paired phone in an empty shell. UUIDs now come from one shared helper that falls back to RFC 4122 v4 over getRandomValues; the eight ad-hoc clipboard call sites share one helper with an execCommand fallback; getUserMedia's error names the insecure origin when that is the cause. The pairing surfaces got the same treatment end to end: serve mode prints a pairing URL and QR on a reachable interface instead of localhost (display only; binding is unchanged); the reveal dialog shows a labelled setup link, labelled pairing code, QR, and working copy buttons on LAN origins; Add computer no longer hangs on Adding forever when its unbounded getConfig await outlives a socket swap (bounded reconnect-retrying read, plus timeboxed remote auth fetches); and the desktop Devices list says it is loading instead of rendering an empty region that reads as no devices while revoke sits disabled.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
On the nightly channel the update chip compacted the target to its base triple, so "v0.3.2 available" could mean the stable or any of the day's nightlies. The release workflow's run counter is monotonic across days, so a same-base nightly target now labels as just ".222" (shorter than the old ambiguous label), a cross-base nightly as "v0.3.3-nightly", and stables keep the exact triple. Tooltips always carry the full version string.
…/threadlines into fix/pairing-lan-gaps
|
Added a scoped second commit: the sidebar update chip now labels same-base nightly targets by run number (".222") instead of the ambiguous compacted base, with full versions in tooltips. 53/53 logic tests. |
The chip fits about eleven characters, so v0.3.3-nightly would ellipsize into exactly the part that mattered. The feed only serves the selected channel, so the chip drops the suffix (v0.3.3 on the nightly track can only be a nightly) while the popout row and tooltip keep the explicit channel.
The re-walk's remaining pairing gaps, root-caused:
- The empty no-composer shell a paired phone landed on was a cold-start
hydration lie: the app rendered "No projects yet" and the no-active-
thread state in the ~1s window before the first shell snapshot
arrived, on every entry path (the audit's owner-context anomaly was
the same window). The directly-paired path now shows a loading state
until bootstrap completes, exactly as the hosted path already did;
the sidebar's project list gets the same gate.
- Revoking a device only rewrote persistence, so an already-connected
phone kept streaming until it happened to reconnect. The websocket
now races a revocation watcher (subscribe-then-read, so no missed
signals; a failed watcher parks rather than killing a trusted
socket). Client-side, a rejected reconnect is indistinguishable from
a network drop in the browser, so after a disconnect the app asks
the session endpoint: an explicit not-authenticated answer stops the
retries and shows "Access removed. This device was disconnected from
the computer. Pair again to reconnect."; any probe failure keeps
reconnecting, so a network blink can never lock a device out.
- The manual pairing path on a direct LAN origin is the token form by
design ("Add computer" belongs to the hosted app, which has no local
backend); the form's happy and rejected paths gained the coverage
they were missing.
|
Re-walk residuals fixed on this branch: the phone's empty-shell arrival was a cold-start hydration gap (empty states rendered before the first snapshot; now gated behind a loading state on every entry path), revoked devices now lose their live socket immediately and see a clear Access removed state (with an HTTP probe distinguishing revocation from network blips — no false lockouts), and the LAN manual path was confirmed works-as-designed with new token-form coverage. Final targeted sol re-walk runs before merge. |
The boot log's pairing URL shared its derivation with the browser-open target, so a server bound to every interface still advertised localhost — a pairing link only this machine could open. The advertised URL now follows the same rule headless serve uses: explicit hosts verbatim, wildcard binds resolve a reachable interface, and the desktop shell keeps localhost since its device URLs come from the Devices dialog.
The first external IPv4 on a developer machine is often a WSL, Hyper-V, Docker, or VPN adapter whose subnet no phone can reach, so the pairing URL advertised a dead address. Virtual-looking interface names are deprioritized rather than excluded; a machine with only virtual adapters still advertises its best candidate.
Fixes all six majors from the pairing end-to-end audit (preview-6). Root cause class: phones pair over plain http (insecure context) where secure-context APIs are undefined — all prior testing on localhost could never see it.
Tests: new uuid + clipboard suites, startupAccess host cases, wsRpcClient retry path, SettingsPanels loading-state case. Gates green; web 2077 unit, SettingsPanels 36, SourceControlPanel 48, shared 260.
Sol re-walks the full phone journey on this branch before merge (checklist in the build report: real LAN arrival with composer, clipboard on http, serve QR shows LAN address, Add computer resolves, revoked-phone state).