Files
oh-my-pi/packages/coding-agent/test/collab/crypto.test.ts
T
can1357 0d49f94418 fix(collab): switched collab links to dot-joined room secrets
- Changed the link grammar from `<roomId>#<key>` / `host[:port]/r/<roomId>#<key>` to dot-joined `<roomId>.<key>` 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.
2026-06-12 16:47:56 +02:00

190 lines
7.6 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import {
generateRoomKey,
generateWriteToken,
importRoomKey,
open,
seal,
} from "@oh-my-pi/pi-coding-agent/collab/crypto";
import {
type CollabFrame,
DEFAULT_RELAY_URL,
formatCollabLink,
formatCollabWebLink,
generateRoomId,
packEnvelope,
parseCollabLink,
rewriteEnvelopePeer,
unpackEnvelope,
} from "@oh-my-pi/pi-coding-agent/collab/protocol";
describe("collab crypto", () => {
it("round-trips a frame through seal/open", async () => {
const key = await importRoomKey(generateRoomKey());
const frame: CollabFrame = { t: "prompt", text: "check bun.lock — and ünïcode 🚀" };
const sealed = await seal(key, frame);
expect(await open(key, sealed)).toEqual(frame);
});
it("rejects tampered ciphertext", async () => {
const key = await importRoomKey(generateRoomKey());
const sealed = await seal(key, { t: "abort" });
sealed[sealed.length - 1]! ^= 0xff;
expect(open(key, sealed)).rejects.toThrow();
});
it("rejects frames sealed with a different key", async () => {
const sealed = await seal(await importRoomKey(generateRoomKey()), { t: "abort" });
const otherKey = await importRoomKey(generateRoomKey());
expect(open(otherKey, sealed)).rejects.toThrow();
});
});
describe("collab link format", () => {
const key = generateRoomKey();
const roomId = generateRoomId();
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")}`);
const parsed = parseCollabLink(link);
if ("error" in parsed) throw new Error(parsed.error);
expect(parsed.wsUrl).toBe(`${DEFAULT_RELAY_URL}/r/${roomId}`);
expect(parsed.roomId).toBe(roomId);
expect(parsed.key).toEqual(key);
});
it("drops the wss scheme for custom relays and infers it on parse", () => {
const link = formatCollabLink("wss://relay.example.com:8443", roomId, key);
expect(link.startsWith("relay.example.com:8443/r/")).toBe(true);
const parsed = parseCollabLink(link);
if ("error" in parsed) throw new Error(parsed.error);
expect(parsed.wsUrl).toBe(`wss://relay.example.com:8443/r/${roomId}`);
});
it("keeps full ws:// URLs for localhost relays", () => {
const link = formatCollabLink("ws://localhost:7475", roomId, key);
expect(link.startsWith("ws://localhost:7475/r/")).toBe(true);
const parsed = parseCollabLink(link);
if ("error" in parsed) throw new Error(parsed.error);
expect(parsed.wsUrl).toBe(`ws://localhost:7475/r/${roomId}`);
});
it("rewrites https relay URLs to wss", () => {
const parsed = parseCollabLink(`https://relay.example.com/r/${roomId}#${Buffer.from(key).toString("base64url")}`);
if ("error" in parsed) throw new Error(parsed.error);
expect(parsed.wsUrl).toBe(`wss://relay.example.com/r/${roomId}`);
});
it("rejects plain ws:// for non-localhost hosts", () => {
const parsed = parseCollabLink(`ws://relay.example.com/r/${roomId}#${Buffer.from(key).toString("base64url")}`);
expect("error" in parsed && parsed.error.includes("wss://")).toBe(true);
});
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);
});
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", () => {
for (const relay of [DEFAULT_RELAY_URL, "wss://relay.example.com:8443", "ws://localhost:7475"]) {
const webLink = formatCollabWebLink(relay, roomId, key);
const direct = parseCollabLink(formatCollabLink(relay, roomId, key));
const viaWeb = parseCollabLink(webLink);
if ("error" in direct) throw new Error(direct.error);
if ("error" in viaWeb) throw new Error(viaWeb.error);
expect(webLink.startsWith(relay === "ws://localhost:7475" ? "http://" : "https://")).toBe(true);
expect(webLink.includes("/#")).toBe(true);
expect(viaWeb.wsUrl).toBe(direct.wsUrl);
expect(viaWeb.roomId).toBe(roomId);
expect(Buffer.from(viaWeb.key)).toEqual(Buffer.from(key));
}
});
it("parses the scheme-less display form of web deep links", () => {
const parsed = parseCollabLink(`my.omp.sh/#${formatCollabLink(DEFAULT_RELAY_URL, roomId, key)}`);
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("embeds the write token in full links and omits it from view links", () => {
const token = generateWriteToken();
const full = parseCollabLink(formatCollabLink(DEFAULT_RELAY_URL, roomId, key, token));
if ("error" in full) throw new Error(full.error);
expect(Buffer.from(full.key)).toEqual(Buffer.from(key));
expect(Buffer.from(full.writeToken ?? new Uint8Array())).toEqual(Buffer.from(token));
const view = parseCollabLink(formatCollabLink(DEFAULT_RELAY_URL, roomId, key));
if ("error" in view) throw new Error(view.error);
expect(Buffer.from(view.key)).toEqual(Buffer.from(key));
expect(view.writeToken).toBeUndefined();
});
it("carries the write token through web deep links", () => {
const token = generateWriteToken();
const parsed = parseCollabLink(formatCollabWebLink(DEFAULT_RELAY_URL, roomId, key, token));
if ("error" in parsed) throw new Error(parsed.error);
expect(Buffer.from(parsed.key)).toEqual(Buffer.from(key));
expect(Buffer.from(parsed.writeToken ?? new Uint8Array())).toEqual(Buffer.from(token));
});
it("rejects secrets that are neither 32 nor 48 bytes", () => {
const bad = Buffer.alloc(40, 1).toString("base64url");
expect("error" in parseCollabLink(`${roomId}#${bad}`)).toBe(true);
});
it("keeps the key out of web-link path and query", () => {
const webLink = formatCollabWebLink(DEFAULT_RELAY_URL, roomId, key);
const url = new URL(webLink);
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")}`);
});
});
describe("collab wire envelope", () => {
it("round-trips peer id and payload", () => {
const payload = new Uint8Array([1, 2, 3, 250]);
const packed = packEnvelope(0xdeadbeef, payload);
const unpacked = unpackEnvelope(packed);
expect(unpacked?.peerId).toBe(0xdeadbeef);
expect(unpacked?.payload).toEqual(payload);
});
it("rewrites the peer id in place without touching the payload", () => {
const packed = packEnvelope(0, new Uint8Array([9, 8, 7]));
rewriteEnvelopePeer(packed, 42);
const unpacked = unpackEnvelope(packed);
expect(unpacked?.peerId).toBe(42);
expect(unpacked?.payload).toEqual(new Uint8Array([9, 8, 7]));
});
it("returns null for frames shorter than the header", () => {
expect(unpackEnvelope(new Uint8Array([0, 0]))).toBeNull();
});
});