diff --git a/changelog.d/relay-link-regional.md b/changelog.d/relay-link-regional.md new file mode 100644 index 0000000000..2feb35c3a1 --- /dev/null +++ b/changelog.d/relay-link-regional.md @@ -0,0 +1,3 @@ +### Fixed +- A pairing link without a code (pairing by comparing digits) now names the PC's regional relay, as + its QR code does, so the phone connects through the nearby relay from the start. diff --git a/docs/relay.md b/docs/relay.md index 88c555d2ea..9f7c51692a 100644 --- a/docs/relay.md +++ b/docs/relay.md @@ -624,8 +624,9 @@ them; the list is `REGIONAL_RELAYS` in [`shared/relay-regions.ts`](../shared/rel A PC with `OVIS_RELAY_URL` set uses that relay alone, unless it names one of Ovis's regional relays: that one is used with no measuring, and beside `relay.ovis.sh`, as below. - **It stays on both.** The PC keeps its link to `relay.ovis.sh` and opens a second one, with the - same key and proof, to the regional relay. New pairing QR codes name the regional relay while its - link is online (the global one otherwise), and the relay status is the global link's. Each link + same key and proof, to the regional relay. New pairing QR codes and pairing links without a code + name the regional relay while its link is online (the global one otherwise), and the relay status + is the global link's. Each link has its own session limits. - **It tells phones paired before.** A phone that offers `relay-hints` is told in `AUTH_OK` the relay new offers name, then `relay.ovis.sh`, and saves the first for that PC; the app uses it from diff --git a/server/relay/service.contract.test.ts b/server/relay/service.contract.test.ts index 407185a623..8c2f21574f 100644 --- a/server/relay/service.contract.test.ts +++ b/server/relay/service.contract.test.ts @@ -702,7 +702,8 @@ async function regionalFixture({ rtt = { global: 130, regional: 13 } as { global const base = `http://127.0.0.1:${app.port}`; const post = (path: string) => fetch(`${base}/api/relay${path}`, { method: "POST", headers: { origin: base, "x-herdr-update": "1" } }); return { dir, relays, synced, lost, probed, signals, service, post, - async offer() { return decodeOffer(((await (await post("/offer")).json()) as { url: string }).url)!; } }; + async offer() { return decodeOffer(((await (await post("/offer")).json()) as { url: string }).url)!; }, + async link() { return decodeOffer(((await (await post("/link")).json()) as { url: string }).url)!; } }; } it("advertises a regional relay that answers much faster, stays on the global one too, and keeps the choice", async () => { @@ -759,6 +760,24 @@ it("keeps no measurement that finishes after the relay was turned off", async () expect(f.service.status()).toMatchObject({ phase: "off", relay: "wss://global.test" }); }); +it("names the relay new offers name in a pairing link without a code too", async () => { + // Given a PC that chose a regional relay + const f = await regionalFixture(); + expect((await f.post("/start")).status).toBe(200); + await bounded(Promise.all([f.synced.global.promise, f.synced.regional.promise])); + // Then a link for comparing digits names the regional relay, as a QR code does + expect((await f.link()).relay).toBe("wss://regional.test"); + // And the global relay while the regional link is down + await f.relays.regional.stop(); + await bounded(f.lost.regional.promise); + expect((await f.link()).relay).toBe("wss://global.test"); + // And a PC that chose no regional relay names the global one + const plain = await regionalFixture({ rtt: { global: 100, regional: 60 } }); + expect((await plain.post("/start")).status).toBe(200); + await bounded(plain.synced.global.promise); + expect((await plain.link()).relay).toBe("wss://global.test"); +}); + it("names the global relay in new offers while the regional link is down, and reports the global link's state", async () => { // Given a PC on both relays const f = await regionalFixture(); diff --git a/server/relay/service.ts b/server/relay/service.ts index a647763bcd..9378b0f4b0 100644 --- a/server/relay/service.ts +++ b/server/relay/service.ts @@ -262,7 +262,7 @@ export class RelayService { pairingLink(): { url: string } { if (!this.enabled || !this.identity || !this.services || this.blocked) throw new Error("Start the relay before pairing."); this.approvals.openWindow(); - return { url: encodeOffer({ v: 1, server_id: this.identity.server_id, server_public_key: this.identity.public_key, relay: this.relay }, this.app) }; + return { url: encodeOffer({ v: 1, server_id: this.identity.server_id, server_public_key: this.identity.public_key, relay: this.advertised }, this.app) }; } async resetIdentity(): Promise {