The pairing QR was effectively IP-first: the phone dialed the scanned LAN host and went dark when the LAN renumbered or it left home. FIPS peers on npubs; IPs are only dial hints. fanchors now leads with the paired node ITSELF (fnpub @ current host, npub-keyed so LAN contact is direct p2p over FIPS and survives DHCP), and fips.pair-info always includes the Archipelago vps2 public anchor (pairing hint only — the node's own anchor file is not modified) so the phone can rendezvous through the public mesh when away. Contract doc updated with the REQUIRED app-side changes (built on the Mac). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.7 KiB
Companion app pairing QR — integration handoff
Status: web-UI side SHIPPED (CompanionIntroOverlay.vue, 2026-07-16). This doc is the contract + requirements for the companion-app side (worked on separately, on the Mac).
What the web UI now does
The "Remote Companion" intro modal (shown once after first dashboard login, and in the public demo) gained a second screen:
- Screen 1 (existing): APK download QR (desktop) / download button, plus a new "I've installed it" button to the right of the download button.
- Screen 2 (new, slide transition): a pairing QR the companion app scans to auto-fill the server entry, with a Back button returning to screen 1. On small screens (where you can't scan your own display) an "Open in companion app" deep-link button is shown above Back, using the same URI as the QR.
The QR payload / deep link (the contract)
A single URI, also usable as an OS deep link:
archipelago://pair?v=1&url=<origin>&name=<display name>[&tok=<device token>][&pw=<password>][&fnpub=…&fip=…&fhost=…&fudp=…&ftcp=…]
Query parameters:
| param | required | meaning |
|---|---|---|
v |
yes | Payload version, currently 1. Reject/ignore unknown majors gracefully — show "please update the app". |
url |
yes | Full origin the app should connect to, scheme included: https://demo.archipelago-foundation.org, http://archipelago.local, http://192.168.1.228, etc. No trailing slash guaranteed either way — normalize. |
name |
no | Display name for the server entry. Real nodes send the configured server name, or My Archipelago when it's still the factory default. |
tok |
no | Device token minted via auth.createDeviceToken when the QR is rendered. The app logs in with {"method":"auth.login","params":{"token":"…"}} — same endpoint, same rate limiter, skips TOTP (the token was minted from an authenticated session). Long-lived until re-minted (re-showing the pair screen replaces the companion token) or revoked (auth.revokeDeviceToken). Scan → instantly connected, no typing. |
pw |
no | Login password. Only present in the public demo (shared demo password entertoexit). Real nodes never embed a password — the frontend doesn't have it. |
fnpub |
no | Node's FIPS mesh identity (bech32 npub of the daemon's seed-derived key). Presence of this param means "this node speaks FIPS — mesh with it". |
fip |
no | Node's fips0 ULA (IPv6). Once the phone is meshed, the node's UI stays reachable at http://[<fip>] from anywhere — this is the remote-access address (replaces the old WireGuard 10.44.0.1 flow). |
fhost |
no | Host the phone's embedded FIPS dials (same host url resolved to). |
fudp / ftcp |
no | Mesh transport ports on fhost (currently 2121/udp and 8443/tcp). |
fanchors |
no | Comma-joined npub@host:port/transport rendezvous anchors, capped at 4. The FIRST entry is the paired node itself (fnpub at its current LAN host — the addr is a dial hint, the npub is the identity). The remaining entries are the node's seed anchors, and the node guarantees the Archipelago public anchor (vps2, 146.59.87.168:8444/tcp) is present even if the operator trimmed their own list — so the phone can always rendezvous through the public mesh when the LAN endpoint is unreachable (away from home / NAT). |
Examples the web UI actually emits:
- Demo:
archipelago://pair?v=1&url=https%3A%2F%2Fdemo.archipelago-foundation.org&pw=entertoexit - Real node, browsed via LAN IP:
archipelago://pair?v=1&url=http%3A%2F%2F192.168.1.228 - Real node kiosk (UI runs on localhost, so it advertises the mDNS name from
system.get-hostname):archipelago://pair?v=1&url=http%3A%2F%2Farchipelago.local
Companion app requirements
- Scan entry point: the app's action is labeled "Scan Node's QR" (implemented 2026-07-17; the modal copy in CompanionIntroOverlay.vue was updated to match).
- Parse the URI (from camera scan AND from an OS deep-link intent —
register the
archipelago://scheme so the "Open in companion app" button on phones works). - On success, create/update a saved server entry:
- Server address =
urlexactly as given (respect the scheme — the demo is https, LAN nodes are typically http,.localmDNS names must work). - If
pwpresent, prefill the password and attempt auto-login; otherwise land on the password prompt for that server. - If an entry with the same origin already exists, update it rather than duplicating.
- Server address =
- Demo flow (the showcase): scanning the demo QR should take a fresh install
to a logged-in demo session in one step — url
https://demo.archipelago-foundation.org, passwordentertoexit, no manual typing. - Robustness:
- Tolerate unknown extra query params (forward compat — we may add
name,certfingerprint, etc. underv=1). - Self-signed HTTPS on
.local/LAN addresses may appear later; don't hard-fail the parse on scheme. - Bad/foreign QR → clear error, stay on the scan screen.
- Tolerate unknown extra query params (forward compat — we may add
Landed extensions (2026-07-22)
- Device token (
tok) — real nodes now pair instantly; see the param table. Minting replaces the previouscompaniontoken, so merely re-opening the pair screen invalidates a previously issued token (the phone's session/remember cookies keep working; a re-scan re-pairs). name— the app labels the entry with the node's server name ("My Archipelago" when unset).- FIPS mesh params (
fnpub/fip/fhost/fudp/ftcp/fanchors) — the companion app embeds a leaf-only FIPS node (Android/rust/archy-fips-core) behind a split-tunnel VpnService and dials the node + rendezvous anchors on scan. This replaces the WireGuard install/tunnel onboarding screens entirely; remote access = the node's fips0 ULA (fip), which the WebView falls back to automatically when the LAN address stops answering.
npub-first connectivity (2026-07-23 — REQUIRED app-side changes)
The QR used to be effectively IP-first: the app connected to url/fhost and
broke as soon as the LAN renumbered or the phone left home. FIPS peers on
npubs; IPs are only dial hints. The app must treat them that way:
- Identity =
fnpub. The saved server entry is keyed by the node's npub (fall back to origin only when the QR has no FIPS params). Re-scanning the same npub updates the entry even if every address changed. - Peer with the node itself as the first anchor (
fanchors[0]): on LAN this is direct p2p over FIPS (mDNS/known-endpoint dial via archy-fips-core v0.4+, which discovers LAN peers without any IP pinning), so it keeps working after DHCP renumbering. - Peer with the public anchors too (remaining
fanchorsentries — the Archipelago vps2 anchor is always included by the node): away from LAN the phone routes to the node's npub via the public mesh and reaches the UI athttp://[<fip>](the fips0 ULA). - Address selection order for the WebView: LAN
urlwhen it answers → ULAfipover the mesh otherwise. Never hard-fail because the scanned LAN IP stopped existing.
(Node-side counterpart shipped 2026-07-23: fips.pair-info always includes
the vps2 public anchor, and the web UI prepends the node's self-anchor to
fanchors.)
Testing checklist (app side)
- Scan demo QR from https://demo.archipelago-foundation.org → auto-connected demo session.
- Scan a real node's QR (LAN IP origin) → entry created, password prompt shown.
- Scan a kiosk node's QR (
http://<name>.local) → mDNS resolution works on the phone. - Tap "Open in companion app" on a phone browser → deep link opens the app with the same behavior.
- Re-scan same node → no duplicate entry.