From 0d49f944188bf4fd27e88134c697ac753102395c Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 12 Jun 2026 16:47:56 +0200 Subject: [PATCH] fix(collab): switched collab links to dot-joined room secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed the link grammar from `#` / `host[:port]/r/#` to dot-joined `.` in `formatCollabLink`/`parseCollabLink` (`packages/coding-agent/src/collab/protocol.ts`) and the collab-web mirror (`packages/collab-web/src/lib/link.ts`): RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS Foundation behind terminal click-to-open) percent-encoded the second `#`. - Kept legacy `#`-joined links parseable via `BARE_LINK_RE` and added lenient `%23` → `#` decoding for mangled deep links. - Updated the `ConnectScreen` placeholder, `app.tsx` deep-link comment, `DEFAULT_RELAY_URL` doc in `packages/wire`, `docs/collab.md` examples, and both package CHANGELOGs. - Extended `crypto.test.ts` and `link.test.ts` with dot-joined, legacy-hash, and `%23`-mangled link coverage. --- docs/collab.md | 16 ++++---- packages/coding-agent/CHANGELOG.md | 4 ++ packages/coding-agent/src/collab/protocol.ts | 41 +++++++++++-------- .../coding-agent/test/collab/crypto.test.ts | 32 ++++++++++++--- packages/collab-web/CHANGELOG.md | 4 ++ packages/collab-web/README.md | 4 +- packages/collab-web/src/app.tsx | 2 +- .../src/components/shell/ConnectScreen.tsx | 2 +- packages/collab-web/src/lib/link.ts | 41 +++++++++++-------- packages/collab-web/test/link.test.ts | 24 ++++++++++- packages/wire/src/index.ts | 2 +- 11 files changed, 121 insertions(+), 51 deletions(-) diff --git a/docs/collab.md b/docs/collab.md index aa4168f54..edcc9127f 100644 --- a/docs/collab.md +++ b/docs/collab.md @@ -14,14 +14,14 @@ prints ``` Collab session started! - • Join from another terminal: omp join "mgAYTZwEnpRQtca0CTgn-Q#gdJUbTovD94ofDaa8YvhY0-ty16w4fn8PgB6PLnoA30" - • or any web browser: my.omp.sh/#mgAYTZwEnpRQtca0CTgn-Q#gdJUbTovD94ofDaa8YvhY0-ty16w4fn8PgB6PLnoA30 + • Join from another terminal: omp join "mgAYTZwEnpRQtca0CTgn-Q.gdJUbTovD94ofDaa8YvhY0-ty16w4fn8PgB6PLnoA30" + • or any web browser: my.omp.sh/#mgAYTZwEnpRQtca0CTgn-Q.gdJUbTovD94ofDaa8YvhY0-ty16w4fn8PgB6PLnoA30 ``` The browser line is click-to-join (an OSC 8 hyperlink to the full `https://` deep link): the relay serves the web guest client at `/`, and the room id + key ride in the URL fragment. From another omp (any directory, any machine), either form works: ``` -/join my.omp.sh/#mgAYTZwEnpRQtca0CTgn-Q#gdJU… +/join my.omp.sh/#mgAYTZwEnpRQtca0CTgn-Q.gdJU… ``` The guest's previous session is restored on `/leave` (or when the host stops). @@ -42,17 +42,17 @@ The guest's previous session is restored on `/leave` (or when the host stops). ``` https://host[:port]/# → browser deep link (printed by /collab; /join accepts it too) -# → default relay (my.omp.sh) -host[:port]/r/# → custom relay, wss:// inferred -ws://localhost:7475/r/# → plain ws, allowed for localhost only +. → default relay (my.omp.sh) +host[:port]/r/. → custom relay, wss:// inferred +ws://localhost:7475/r/. → plain ws, allowed for localhost only ``` -The trailing fragment (`#`) is the room secret, base64url-encoded, in one of two strengths: +The trailing `.` part is the room secret, base64url-encoded, in one of two strengths: - **Full link** — 48 bytes: the 32-byte AES-256-GCM room key followed by a 16-byte write token. Grants prompting, interrupting, and subagent control. - **View-only link** — the bare 32-byte key, no write token. Grants live read access only. Pre-token links parse as view-only. -In the browser deep link, everything after the first `#` — room id and key — is a URL fragment: it never appears in any HTTP request, and neither secret is ever sent to the relay. +The room secret is dot-joined rather than `#`-joined: RFC 3986 forbids a raw `#` inside a URL fragment, so strict URL stacks (macOS Foundation behind terminal click-to-open) percent-encode a second `#` to `%23` and break the link. Parsers leniently accept the legacy `#` form and the mangled `%23` form. In the browser deep link, everything after the `#` — room id and key — is a URL fragment: it never appears in any HTTP request, and neither secret is ever sent to the relay. ## End-to-end encryption diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d759352f6..147feaa9a 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Collab links now dot-join the room secret (`.`, `host/r/.`) instead of using a second `#`: RFC 3986 forbids a raw `#` inside a URL fragment, so macOS Foundation (behind terminal click-to-open) percent-encoded the browser deep link's second `#` to `%23` and the web client rejected the session. `/join`, `omp join`, and the web client still accept legacy `#`-joined links and leniently decode `%23`-mangled ones + ## [15.12.1] - 2026-06-12 ### Added diff --git a/packages/coding-agent/src/collab/protocol.ts b/packages/coding-agent/src/collab/protocol.ts index f4d388013..fd46c3b02 100644 --- a/packages/coding-agent/src/collab/protocol.ts +++ b/packages/coding-agent/src/collab/protocol.ts @@ -108,11 +108,11 @@ export function rewriteEnvelopePeer(data: Uint8Array, peerId: number): void { } // ═══════════════════════════════════════════════════════════════════════════ -// Link format: wss:///r/# +// Link format: wss:///r/. // ═══════════════════════════════════════════════════════════════════════════ -const ROOM_PATH_RE = /^\/r\/([A-Za-z0-9_-]{10,64})$/; -const BARE_LINK_RE = /^([A-Za-z0-9_-]{10,64})#([A-Za-z0-9_-]+)$/; +const ROOM_PATH_RE = /^\/r\/([A-Za-z0-9_-]{10,64})(?:\.([A-Za-z0-9_-]+))?$/; +const BARE_LINK_RE = /^([A-Za-z0-9_-]{10,64})[#.]([A-Za-z0-9_-]+)$/; const B64URL_RE = /^[A-Za-z0-9_-]+$/; const LOCAL_HOSTNAMES: Record = { localhost: true, "127.0.0.1": true, "::1": true, "[::1]": true }; @@ -152,11 +152,17 @@ function normalizeRelayOrigin(relayUrl: string): { origin: string } | { error: s /** * Render the shareable link. Compact forms: the default relay collapses to - * `#`, other wss relays drop the scheme (`host[:port]/r/…`); + * `.`, other wss relays drop the scheme (`host[:port]/r/…`); * only localhost ws:// links keep their full URL so parsing cannot * mis-infer wss. * - * Full links append the write token to the key in the fragment + * The room secret is dot-joined (`.`) rather than `#`-joined: + * RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS + * Foundation behind terminal click-to-open) percent-encode a second `#` to + * `%23` and break the link. Parsers still accept the legacy `#` form and the + * mangled `%23` form. + * + * Full links append the write token to the key * (`base64url(key ∥ writeToken)`); read-only (view) links carry the bare * 32-byte key, which is also the pre-token link format. */ @@ -165,11 +171,11 @@ export function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Arr if ("error" in normalized) throw new Error(normalized.error); const secret = writeToken ? Buffer.concat([key, writeToken]) : Buffer.from(key); const keyText = secret.toString("base64url"); - if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}#${keyText}`; + if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}.${keyText}`; const compact = normalized.origin.startsWith("wss://") ? normalized.origin.slice("wss://".length) : normalized.origin; - return `${compact}/r/${roomId}#${keyText}`; + return `${compact}/r/${roomId}.${keyText}`; } /** @@ -193,10 +199,12 @@ export function formatCollabWebLink( } export function parseCollabLink(link: string): ParsedCollabLink | { error: string } { - let text = link.trim(); - // Bare `#` → default relay. + // Lenient input: terminals that open OSC 8 links through strict URL stacks + // (macOS Foundation) percent-encode the legacy second `#` to `%23`. + let text = link.trim().replace(/%23/gi, "#"); + // Bare `.` (legacy `#`) → default relay. const bare = BARE_LINK_RE.exec(text); - if (bare) text = `${DEFAULT_RELAY_URL}/r/${bare[1]}#${bare[2]}`; + if (bare) text = `${DEFAULT_RELAY_URL}/r/${bare[1]}.${bare[2]}`; // Scheme-less `host[:port]/r/…` → wss. else if (!text.includes("://")) text = `wss://${text}`; let url: URL; @@ -210,17 +218,18 @@ export function parseCollabLink(link: string): ParsedCollabLink | { error: strin const match = ROOM_PATH_RE.exec(url.pathname); if (!match) { // Web deep link: `http(s):///#` — the fragment holds - // the whole link (which itself contains another `#`). URL.hash spans to - // the end of the string, so recurse on it; the inner fragment is just - // the key (no `#`), which bounds the recursion. + // the whole link, so recurse on it. The recursion terminates because + // the inner text is a strict suffix of the input. const inner = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash; - if (inner.includes("#")) return parseCollabLink(inner); + if (inner) return parseCollabLink(inner); return { error: "Collab link must contain a /r/ path" }; } const roomId = match[1]!; - const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash; + // Key rides dot-joined in the path (`/r/.`); legacy links + // carry it in the fragment (`/r/#`). + const fragment = match[2] ?? (url.hash.startsWith("#") ? url.hash.slice(1) : url.hash); if (!fragment) { - return { error: "Collab link is missing the # fragment" }; + return { error: "Collab link is missing the part" }; } const secret = B64URL_RE.test(fragment) ? new Uint8Array(Buffer.from(fragment, "base64url")) : null; if (!secret || (secret.byteLength !== ROOM_KEY_BYTES && secret.byteLength !== ROOM_KEY_BYTES + WRITE_TOKEN_BYTES)) { diff --git a/packages/coding-agent/test/collab/crypto.test.ts b/packages/coding-agent/test/collab/crypto.test.ts index 41c41839d..68f5bd181 100644 --- a/packages/coding-agent/test/collab/crypto.test.ts +++ b/packages/coding-agent/test/collab/crypto.test.ts @@ -44,9 +44,9 @@ describe("collab link format", () => { const key = generateRoomKey(); const roomId = generateRoomId(); - it("collapses the default relay to a bare roomId#key link", () => { + it("collapses the default relay to a bare roomId.key link", () => { const link = formatCollabLink(DEFAULT_RELAY_URL, roomId, key); - expect(link).toBe(`${roomId}#${Buffer.from(key).toString("base64url")}`); + expect(link).toBe(`${roomId}.${Buffer.from(key).toString("base64url")}`); const parsed = parseCollabLink(link); if ("error" in parsed) throw new Error(parsed.error); expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${roomId}`); @@ -82,8 +82,30 @@ describe("collab link format", () => { }); it("rejects keys that are not 32 base64url bytes", () => { - expect("error" in parseCollabLink(`${roomId}#dG9vc2hvcnQ`)).toBe(true); - expect("error" in parseCollabLink(`${roomId}#not+base64url/`)).toBe(true); + expect("error" in parseCollabLink(`${roomId}.dG9vc2hvcnQ`)).toBe(true); + expect("error" in parseCollabLink(`${roomId}.not+base64url/`)).toBe(true); + }); + + it("accepts legacy #-separated links", () => { + const keyText = Buffer.from(key).toString("base64url"); + for (const legacy of [ + `${roomId}#${keyText}`, + `relay.example.com:8443/r/${roomId}#${keyText}`, + `https://my.omp.sh/#${roomId}#${keyText}`, + ]) { + const parsed = parseCollabLink(legacy); + if ("error" in parsed) throw new Error(`${legacy}: ${parsed.error}`); + expect(parsed.roomId).toBe(roomId); + expect(Buffer.from(parsed.key)).toEqual(Buffer.from(key)); + } + }); + + it("accepts %23-mangled legacy deep links (macOS Foundation re-encoding)", () => { + const keyText = Buffer.from(key).toString("base64url"); + const parsed = parseCollabLink(`https://my.omp.sh/#${roomId}%23${keyText}`); + if ("error" in parsed) throw new Error(parsed.error); + expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${roomId}`); + expect(Buffer.from(parsed.key)).toEqual(Buffer.from(key)); }); it("renders web deep links that parse back to the same room", () => { @@ -140,7 +162,7 @@ describe("collab link format", () => { expect(url.origin).toBe("https://my.omp.sh"); expect(url.pathname).toBe("/"); expect(url.search).toBe(""); - expect(url.hash).toBe(`#${roomId}#${Buffer.from(key).toString("base64url")}`); + expect(url.hash).toBe(`#${roomId}.${Buffer.from(key).toString("base64url")}`); }); }); diff --git a/packages/collab-web/CHANGELOG.md b/packages/collab-web/CHANGELOG.md index a875381bb..621c9e485 100644 --- a/packages/collab-web/CHANGELOG.md +++ b/packages/collab-web/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Link parsing accepts the new dot-joined room secret (`.`, `/r/.`) and leniently decodes `%23`-mangled legacy deep links (macOS Foundation percent-encodes a second `#` when terminals open clicked links), which previously failed to connect + ## [15.12.0] - 2026-06-12 ### Added diff --git a/packages/collab-web/README.md b/packages/collab-web/README.md index fddaff609..e098b93a8 100644 --- a/packages/collab-web/README.md +++ b/packages/collab-web/README.md @@ -12,7 +12,7 @@ bun run dev bun run mock-host ``` -Host a session from any omp instance (`/collab`, or `/collab ws://localhost:7466` to use the mock relay), then paste the printed link into the connect screen. Deep links work too: `http://localhost:3000/##` auto-connects on load. +Host a session from any omp instance (`/collab`, or `/collab ws://localhost:7466` to use the mock relay), then paste the printed link into the connect screen. Deep links work too: `http://localhost:3000/#.` auto-connects on load. ## Build & deploy @@ -23,7 +23,7 @@ bun run build # static site in dist/ `dist/` is a fully static SPA — host it anywhere. JS/CSS bundles are content-hashed; favicons, `manifest.webmanifest`, `robots.txt`, `sitemap.xml`, and `og-image.png` come from `public/` and are emitted at the site root under stable names (canonical URL: `https://my.omp.sh/`). Two runtime requirements: - **Secure context**: room keys are unwrapped with WebCrypto (`crypto.subtle`), which browsers expose only on `https://` or `localhost`. -- **Relay reachability**: the client connects straight to the relay over WebSocket (`wss://` for anything that isn't localhost). The default relay is `wss://my.omp.sh`; bare `#` links resolve against it. +- **Relay reachability**: the client connects straight to the relay over WebSocket (`wss://` for anything that isn't localhost). The default relay is `wss://my.omp.sh`; bare `.` links resolve against it (legacy `#` and `%23`-mangled links still parse). The room key never leaves the URL fragment — it is not sent to the relay or any server. diff --git a/packages/collab-web/src/app.tsx b/packages/collab-web/src/app.tsx index c99644661..5b1432820 100644 --- a/packages/collab-web/src/app.tsx +++ b/packages/collab-web/src/app.tsx @@ -28,7 +28,7 @@ function storedName(): string { } } -/** Deep link = everything after the FIRST `#` (the link itself contains another `#`). */ +/** Deep link = everything after the FIRST `#` (legacy links carry a second `#` inside the fragment). */ function hashLink(): string | null { const href = window.location.href; const i = href.indexOf("#"); diff --git a/packages/collab-web/src/components/shell/ConnectScreen.tsx b/packages/collab-web/src/components/shell/ConnectScreen.tsx index 0e23961a4..8e3e1fdf2 100644 --- a/packages/collab-web/src/components/shell/ConnectScreen.tsx +++ b/packages/collab-web/src/components/shell/ConnectScreen.tsx @@ -38,7 +38,7 @@ export function ConnectScreen({ defaultName, error, onConnect }: ConnectScreenPr type="text" value={link} onChange={e => setLink(e.target.value)} - placeholder="ws://host:port/r/room#key" + placeholder="ws://host:port/r/room.key" spellCheck={false} autoComplete="off" autoFocus diff --git a/packages/collab-web/src/lib/link.ts b/packages/collab-web/src/lib/link.ts index 2f47980a0..2c3ccd447 100644 --- a/packages/collab-web/src/lib/link.ts +++ b/packages/collab-web/src/lib/link.ts @@ -3,7 +3,7 @@ * link/envelope half of `@oh-my-pi/pi-coding-agent/src/collab/protocol.ts`; * base64url goes through atob/btoa instead of Buffer). * - * Link format: `wss:///r/#` + * Link format: `wss:///r/.` * Wire envelope: `[4B uint32 BE peerId][sealed payload]` — the guest always * sends peerId 0; the relay rewrites it to the sender's id. */ @@ -21,8 +21,8 @@ export { COLLAB_PROTO } from "@oh-my-pi/pi-wire"; export type { ParsedCollabLink }; export { DEFAULT_RELAY_URL, ENVELOPE_HEADER_LENGTH, ROOM_ID_BYTES }; -const ROOM_PATH_RE = /^\/r\/([A-Za-z0-9_-]{10,64})$/; -const BARE_LINK_RE = /^([A-Za-z0-9_-]{10,64})#([A-Za-z0-9_-]+)$/; +const ROOM_PATH_RE = /^\/r\/([A-Za-z0-9_-]{10,64})(?:\.([A-Za-z0-9_-]+))?$/; +const BARE_LINK_RE = /^([A-Za-z0-9_-]{10,64})[#.]([A-Za-z0-9_-]+)$/; const B64URL_RE = /^[A-Za-z0-9_-]+$/; const LOCAL_HOSTNAMES: Record = { localhost: true, "127.0.0.1": true, "::1": true, "[::1]": true }; @@ -113,10 +113,16 @@ function normalizeRelayOrigin(relayUrl: string): { origin: string } | { error: s /** * Render the shareable link. Compact forms: the default relay collapses to - * `#`; custom wss relays drop the scheme (`host[:port]/r/…`); + * `.`; custom wss relays drop the scheme (`host[:port]/r/…`); * plain-ws localhost relays keep the full `ws://` URL. * - * Full links append the write token to the key in the fragment + * The room secret is dot-joined (`.`) rather than `#`-joined: + * RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS + * Foundation behind terminal click-to-open) percent-encode a second `#` to + * `%23` and break the link. Parsers still accept the legacy `#` form and the + * mangled `%23` form. + * + * Full links append the write token to the key * (`base64url(key ∥ writeToken)`); read-only (view) links carry the bare key. */ export function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array): string { @@ -129,18 +135,20 @@ export function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Arr secret.set(writeToken, key.byteLength); } const keyText = encodeBase64Url(secret); - if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}#${keyText}`; + if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}.${keyText}`; const compact = normalized.origin.startsWith("wss://") ? normalized.origin.slice("wss://".length) : normalized.origin; - return `${compact}/r/${roomId}#${keyText}`; + return `${compact}/r/${roomId}.${keyText}`; } export function parseCollabLink(link: string): ParsedCollabLink | { error: string } { - let text = link.trim(); - // Bare `#` → default relay. + // Lenient input: terminals that open OSC 8 links through strict URL stacks + // (macOS Foundation) percent-encode the legacy second `#` to `%23`. + let text = link.trim().replace(/%23/gi, "#"); + // Bare `.` (legacy `#`) → default relay. const bare = BARE_LINK_RE.exec(text); - if (bare) text = `${DEFAULT_RELAY_URL}/r/${bare[1]}#${bare[2]}`; + if (bare) text = `${DEFAULT_RELAY_URL}/r/${bare[1]}.${bare[2]}`; // Scheme-less `host[:port]/r/…` → wss. else if (!text.includes("://")) text = `wss://${text}`; let url: URL; @@ -154,17 +162,18 @@ export function parseCollabLink(link: string): ParsedCollabLink | { error: strin const match = ROOM_PATH_RE.exec(url.pathname); if (!match) { // Web deep link: `http(s):///#` — the fragment holds - // the whole link (which itself contains another `#`). URL.hash spans to - // the end of the string, so recurse on it; the inner fragment is just - // the key (no `#`), which bounds the recursion. + // the whole link, so recurse on it. The recursion terminates because + // the inner text is a strict suffix of the input. const inner = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash; - if (inner.includes("#")) return parseCollabLink(inner); + if (inner) return parseCollabLink(inner); return { error: "Collab link must contain a /r/ path" }; } const roomId = match[1] as string; - const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash; + // Key rides dot-joined in the path (`/r/.`); legacy links + // carry it in the fragment (`/r/#`). + const fragment = match[2] ?? (url.hash.startsWith("#") ? url.hash.slice(1) : url.hash); if (!fragment) { - return { error: "Collab link is missing the # fragment" }; + return { error: "Collab link is missing the part" }; } const secret = decodeBase64Url(fragment); if (!secret || (secret.byteLength !== ROOM_KEY_BYTES && secret.byteLength !== ROOM_KEY_BYTES + WRITE_TOKEN_BYTES)) { diff --git a/packages/collab-web/test/link.test.ts b/packages/collab-web/test/link.test.ts index 5314e678c..cfe4c9fa0 100644 --- a/packages/collab-web/test/link.test.ts +++ b/packages/collab-web/test/link.test.ts @@ -15,7 +15,15 @@ const KEY_TEXT = encodeBase64Url(KEY); const ROOM = "AbCdEf123456_-Xy"; describe("collab link parsing", () => { - it("parses a bare roomId#key link against the default relay", () => { + it("parses a bare roomId.key link against the default relay", () => { + const parsed = parseCollabLink(`${ROOM}.${KEY_TEXT}`); + if ("error" in parsed) throw new Error(parsed.error); + expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${ROOM}`); + expect(parsed.roomId).toBe(ROOM); + expect(parsed.key).toEqual(KEY); + }); + + it("parses a legacy bare roomId#key link against the default relay", () => { const parsed = parseCollabLink(`${ROOM}#${KEY_TEXT}`); if ("error" in parsed) throw new Error(parsed.error); expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${ROOM}`); @@ -74,6 +82,20 @@ describe("collab link parsing", () => { expect(local.wsUrl).toBe(`ws://localhost:7466/r/${ROOM}`); }); + it("parses dot-joined web deep links (https:///#.)", () => { + const parsed = parseCollabLink(`https://my.omp.sh/#${ROOM}.${KEY_TEXT}`); + if ("error" in parsed) throw new Error(parsed.error); + expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${ROOM}`); + expect(parsed.key).toEqual(KEY); + }); + + it("accepts %23-mangled legacy deep links (macOS Foundation re-encoding)", () => { + const parsed = parseCollabLink(`https://my.omp.sh/#${ROOM}%23${KEY_TEXT}`); + if ("error" in parsed) throw new Error(parsed.error); + expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${ROOM}`); + expect(parsed.key).toEqual(KEY); + }); + it("round-trips format → parse for default, custom, and localhost relays", () => { const roomId = generateRoomId(); for (const relay of [DEFAULT_RELAY_URL, "wss://relay.example.com:8443", "ws://127.0.0.1:7466"]) { diff --git a/packages/wire/src/index.ts b/packages/wire/src/index.ts index 6cb6e03fb..e9dc5a479 100644 --- a/packages/wire/src/index.ts +++ b/packages/wire/src/index.ts @@ -363,7 +363,7 @@ export const ROOM_KEY_BYTES = 32; */ export const WRITE_TOKEN_BYTES = 16; -/** Default public relay; bare `#` links resolve against it. */ +/** Default public relay; bare `.` links resolve against it. */ export const DEFAULT_RELAY_URL = "wss://my.omp.sh"; /** Default share viewer/upload base; `/share` links resolve against `/#`. */