Repository navigation
fix(relay): name the PC in the pairing link without a code - #2868
Conversation
The code-less pairing link (POST /api/relay/link) built its offer without `name`, so a phone that paired from it listed the PC by its raw scope URL (https://app.ovis.sh/s/<id>/), as #2599 item 2 described for the coded offer before #2759. Both offers now take the name from one helper. Part of #2599. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
Both relay pairing links now hold the PC's name (the QR offer since #2759, the code-less link with this PR); docs/relay.md listed their contents without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: Codex (gpt-6.1-terra, xhigh, read-only) at 110e0aeMEDIUM: the code-less link now carries the host name, while
LOW (not changed): a phone that paired from a code-less link before this change keeps its unnamed entry. Opening a newer, named link reuses the saved device record ( Test note (not changed): on a host whose CodeRabbit skipped this PR, so this review stands in for it. The review ran at 110e0ae; the only commit since is the docs change f583576. |
# Conflicts: # server/relay/service.ts
Merge noteMerged origin/main ( Validation at CodeRabbit did not review this PR; the Codex review above stands in for it. Its open question for the maintainer is unchanged: whether the code-less link should carry the host name at all (#2599 item 2 asks for it). No review threads are open. |
Deploying ovis with
|
| Latest commit: |
5e54321
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://5dfc1948.ovis.pages.dev |
| Branch Preview URL: | https://lane-issue-2599.ovis.pages.dev |
|
Local merge gate (owner-approved): origin/main merged, |
Part of #2599 (item 1, the Android half). Items 2 and 3 already landed: #2759 and #2868 put the PC's name in both pairing offers, and #2605 moved the file path out of app.ovis.sh requests. ## Problem A relay pairing link (`https://app.ovis.sh/s/<server_id>/#offer=...`) tapped outside the app, in a message or read from the PC's QR code by the phone's camera, opens in the browser as the hosted web app. The Android app never sees it, because its manifest declares no `VIEW` filter for `https://app.ovis.sh/s/` and app.ovis.sh serves no `/.well-known/assetlinks.json`. ## Change - **Hosted shell** (`scripts/shell-build.ts`): `dist-shell/` now carries `/.well-known/assetlinks.json` and `/.well-known/apple-app-site-association`, sent as `application/json` (`_headers`). The asset links name `sh.ovis.app` with two certificates: Google Play's app signing key (copied from Play Console, App integrity, which signs every copy Play installs) and the upload key (read from the certificate in the uploaded 1.0 (4) bundle, which signs a release build installed by hand). The Apple file names `5987KT43TJ.sh.ovis.app` for `/s/*` only. The front page `/` stays the web app's. - **Android manifest**: an `autoVerify` intent-filter for `https://app.ovis.sh` with path prefix `/s/`. - **`MainActivity`**: only a link to a PC's scope (`/s/<32 hex>/`, port 443 or none) is taken (`PCStore.appLink`); any other page of app.ovis.sh leaves the open page alone. A link to pair from (the scope alone or with its `#offer=`) is taken out of its intent, kept in memory (`PCStore.link`) and shows the connect screen, both at a cold start and while a PC is open (`onNewIntent` starts the activity again, as Change PC does). A view of a PC (a file's `#read=`, a pane's `#pane=`, an older `?pane=`) opens that PC at that view when the app has it saved, as the browser did, and otherwise leaves the open page alone. A link the system hands back when the activity is restored or started from Recents is not opened again. - **`HerdrShellPlugin.takeLink`**: hands the pending link out once, and never in an activity that shows a PC: a PC's page has the bridge too and must not take another PC's pairing link. - **Connect screen** (`app/web/connect.js`): on Android it takes the link once (`HerdrShell.takeLink`), fills the address field with it and opens it through the same `open()` as a scanned QR code, so `relayLink()` checks the offer the same way and shows the same errors. The link is opened without another tap, as a scanned QR code is: tapping it is the user's action, and the hosted web app already pairs from the same link in the browser. ## What needs a new native build, and what does not - No native build: the two `.well-known` files. They go live when a release deploys the hosted shell (`release.yml` deploys `dist-shell` to Pages; Wrangler uploads `.well-known/`, its ignore list covers only `_worker.js`, `_redirects`, `_headers`, `_routes.json`, `functions` and VCS folders). - Android build: the intent-filter, `MainActivity`, `HerdrShellPlugin.takeLink` and `connect.js` ship in the app's bundle, so they reach users with the next Play build. Android verifies the link at install, so the order of the two does not matter: until both are out, such links keep opening in the browser. - iOS (not in this PR): the Associated Domains capability (`applinks:app.ovis.sh`) has to be turned on for the App ID in the Apple developer account first; adding the entitlement before that would break the next iOS build's signing. After that, the entitlement and a handler in `SceneDelegate`/`HerdrBridgeViewController` follow this same path. The `apple-app-site-association` file served here is what that step needs, so #2599 stays open for it. ## Validation - `bun test scripts/shell-build.test.ts`: the new tests failed on `origin/main` (`Export named 'ANDROID_APP_FINGERPRINTS' not found`) and pass now (17 pass). They check both files against the Android manifest's filter and `app/capacitor.config.json`'s id. - `./gradlew :app:testDebugUnitTest` (JDK 21): `PCStoreTest` 19 pass, 0 fail. The first version of the new link test failed (1 of 18) with the host check removed. - `./gradlew :app:assembleDebug`, then the APK on an Android 15 (API 35) emulator. `pm get-app-links sh.ovis.app` lists `app.ovis.sh` (state `none`: the live app.ovis.sh has no asset links yet, and a debug key is not in them). The domain was then allowed with `pm set-app-links-user-selection`, which stands in for verification, and links were sent with `am start -a VIEW -c BROWSABLE -d <link>` and no package: - A. App not running, link with an offer named "Lane QA PC": the app started cold on `https://app.ovis.sh/s/0123…cdef/` (read from the WebView's DevTools), and the connect screen later listed it as "Lane QA PC (app.ovis.sh)". - B. That PC open, link whose offer names another PC: the app went to the connect screen with the link in the address field and "This pairing link is damaged or names another PC" (screenshot below). - C. Same link sent with `FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY` (as Recents does): not opened, the app stayed on the PC it had open. - D. Control for C, the link without the flag: opened that second PC with its offer. - After the review round, on the rebuilt APK: G. a file link (`#read=%2Fhome%2Fa.md&machine=local`) of the saved PC, sent from the connect screen, opened that PC at `…/#read=%2Fhome%2Fa.md&machine=local`. H. a pane link of a PC the app does not have, sent while G was open, left G's page as it was. A and B behaved as before. - `bun run check fast`: all steps ok on 8ee5bae, the review fixes with origin/main merged in (unit: 12621 pass, 0 fail, 50 skip). - Skipped: browser regressions (no client component changed), a real pairing through a live relay (the offers above name no running PC; pairing itself is unchanged and already covered for scanned links), iOS (not changed). | B: running app, bad link (the PC from A listed under its offer's name) | A: cold start, the app opened the PC's page on app.ovis.sh | |---|---| |  |  | ## Review round (omo + Codex Terra xhigh at 73d8e8a) - Fixed, HIGH (Codex): a PC's page could call `takeLink` and race the connect screen for another PC's pairing link. `takeLink` now answers only in an activity that shows no PC. - Fixed, MEDIUM (omo, Codex): a file or pane link of the hosted shell was taken and failed on the connect screen as "not a PC", closing the open PC. Such a view now opens a saved PC at that view, and is otherwise left alone. - Fixed, MEDIUM (Codex): a malformed `/s/...` link closed the open page before the connect screen rejected it. Only `/s/<32 hex>/` links are taken now. A pairing link with a damaged offer still goes to the connect screen and shows its error, as a scanned one does. - Fixed, LOW (omo): `:443` is accepted like no port. - Not changed, LOW (omo): the Caddy mirror sends no JSON type for `/.well-known/*`; only app.ovis.sh is in the app's link, the mirror is never asked. - Not changed, LOW (omo): another app on the phone can send the app a pairing link, as it can open one in the browser today; the pairing then asks for what the offer would ask for in the browser.
Part of #2599 (item 2, for the pairing link added after #2759).
Problem
#2759 made
RelayService.offer()(POST /api/relay/offer, the QR code) carry the PC'sname, so the phone app lists a relay PC under its name instead of its scope URL. The code-less pairing link added later in #2696 (RelayService.pairingLink(),POST /api/relay/link, Copy a link without a code) still builds its offer withoutname. A phone that pairs from that link saves the PC ashttps://app.ovis.sh/s/<32 hex>/again, which is the gap #2599 item 2 describes.Fix
Both offers take the name from one helper,
offerName()inserver/relay/service.ts:os.hostname()(the name Ovis lists the PC under, as inserver/machines.ts), cut to the 64 charactersRelayOfferallows, left out when empty. The name rides in the#offer=fragment, so it never reaches app.ovis.sh or the relay. The link holds no code either way. Itsnameis no more secret than the one the QR offer already carries.The code-less link has only shipped in betas (
v0.4.23-beta.6), so this PR extends its unreleasedAddedfragment (changelog.d/relay-pair-by-comparing-digits.md) instead of adding aFixedline for a feature the next release introduces.State of the other #2599 items on main (cbcb961)
app.ovis.sh/s/links in the native apps) is still open. It needs an Android App Link (autoVerifyintent-filter plus/.well-known/assetlinks.jsonwith the Play signing key's SHA-256) and an iOS Universal Link (Associated Domains plusapple-app-site-associationwith the Team ID). That takes signing details and an Apple capability change from the maintainer, so this PR does not close the issue.readerHref()goes throughpageAddress(), which puts the query in the fragment in relay mode.Validation
names the PC in the pairing link without a code as Ovis lists it(server/relay/service.contract.test.ts) failed on main (Expected: "<hostname>",Received: undefined) and passes with the fix.bun test server/relay/service.contract.test.ts server/relay/pair-sas.contract.test.ts server/dispatch.test.ts: 67 pass, 0 fail.bun run check fastran on the code and test change: all steps ok (unit: 11427 pass, 0 fail, 45 skip). The changelog fragment was edited afterwards and checked withbun test scripts/changelog-fold.test.ts(12 pass).bun run check full, browser and native-app runs. The change is server-side only, in an offer the existing client paths (relayLink()inapp/web/shell.js,src/lib/relay/shell.tsx) already readnamefrom.🤖 Generated with Claude Code