// node --test web/webrtc/webrtc-cubit.test.mjs (after `cargo wasm`) // The cubit with the real requester module and runModule; the bidder's PDS, age and the // peer connection are stubbed (Node has no RTCPeerConnection). import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import { registerHooks } from "node:module"; import { mock, test } from "node:test"; import { load } from "../../../atlibs/rust/src/harness.mjs"; registerHooks({ resolve(specifier, context, next) { // atlibs/webrtc/ is the atlibs-webrtc sibling checkout's js/; the rest of atlibs/ is core's javascript/src const lib = specifier.startsWith("atlibs/webrtc/") ? new URL(`../../../atlibs-webrtc/js/${specifier.slice("atlibs/webrtc/".length)}`, import.meta.url).href : specifier.startsWith("atlibs/") && new URL(`../../../atlibs/javascript/src/${specifier.slice("atlibs/".length)}`, import.meta.url).href; return next(lib || specifier, context); }, }); const { WebrtcCubit } = await import("./webrtc-cubit.js"); const { checkFingerprint, guestOf, parseReceipt } = await import("atlibs/webrtc/index.js"); const { run } = await load(new URL("../../target/wasm32-unknown-unknown/release/requester.wasm", import.meta.url)); // What runModule and pdsOf use of a PluginContext. const context = (fetch) => ({ wasm: { run: async ({ entry, input }) => run(entry, input) }, http: { fetch } }); const T = "com.publicdomainrelay.temp"; const HAT9 = "did:plc:4vcig7s43z3nyqrnepmcfzqt"; const PDS = "https://pds.example"; const receipt = { uri: `at://${HAT9}/${T}.market.receipt/receipt-1`, cid: "bafyrc" }; const FP = "sha-256 AB:CD:EF"; const spec = JSON.parse(await readFile(new URL("../../modules/requester/module.json", import.meta.url), "utf8")); const module = { module: new Uint8Array(await readFile(new URL("../../target/wasm32-unknown-unknown/release/requester.wasm", import.meta.url))), calls: spec.calls, }; // "age" that seals by wrapping: sealed to r opens only with the identity whose recipient is r. const age = { generateX25519Identity: async () => "AGE-SECRET-KEY-ME", identityToRecipient: async (id) => id.replace("AGE-SECRET-KEY-", "age1"), Encrypter: class { addRecipient(r) { this.r = r } async encrypt(text) { return JSON.stringify({ to: this.r, text }) } }, Decrypter: class { addIdentity(id) { this.r = id.replace("AGE-SECRET-KEY-", "age1") } async decrypt(bytes, _) { const b = JSON.parse(bytes); if (b.to !== this.r) throw new Error("no identity matched"); return b.text } }, armor: { encode: (s) => `-----BEGIN AGE ENCRYPTED FILE-----\n${s}`, decode: (s) => s.split("\n").slice(1).join("\n") }, }; const sealed = (to, body) => age.armor.encode(JSON.stringify({ to, text: JSON.stringify(body) })); function peer() { const pc = new EventTarget(); Object.assign(pc, { iceGatheringState: "new", createDataChannel: (label, init) => (pc.dc = { label, init, readyState: "open", sent: [], send: (b) => pc.dc.sent.push(new TextDecoder().decode(b)) }), createOffer: async () => ({ type: "offer", sdp: "v=0 offer" }), // gather: "complete" (default), or a list of candidate events the test fires itself setLocalDescription: async (d) => { pc.localDescription = d; if (pc.gather !== "manual") pc.iceGatheringState = "complete", pc.dispatchEvent(new Event("icegatheringstatechange")); }, setRemoteDescription: async (d) => { pc.remoteDescription = d }, close() {}, }); return pc; } // The bidder's repo: each payload at the key the bidder gives it (kcp bidder syncs.go): a report at // -payload-, key onNetwork:: | hostKeys:; a // guest event the n-th of its stream, key guest:::::. const hex = (key) => createHash("sha256").update(key).digest("hex").slice(0, 32); function bidder(payloads) { const records = new Map(); const counts = new Map(); for (const value of Object.values(payloads)) { const c = value.$collection; const nsid = `${T}.compute.events.${c}`; let key; if (c === "vm.onNetwork") key = ["onnetwork", value.tag ? `onNetwork:${value.tag}:${receipt.uri}` : `onNetwork:${receipt.uri}`]; else if (c === "vm.hostKeys") key = ["hostkeys", `hostKeys:${receipt.uri}`]; else { const stream = `guest:${nsid}:${value.tag}:${value.kind}:`; const n = counts.get(stream) ?? 0; counts.set(stream, n + 1); key = ["guest", `${stream}${n}:${receipt.uri}`]; } records.set(`at://${HAT9}/${nsid}/${key[0]}-payload-${hex(key[1])}`, value); } return async (url) => { url = new URL(url); if (url.hostname === "plc.directory") return Response.json({ service: [{ id: "#atproto_pds", serviceEndpoint: PDS }] }); const p = Object.fromEntries(url.searchParams); assert.ok(!url.pathname.endsWith("listRecords"), "never listRecords"); const value = url.pathname.endsWith("getRecord") && records.get(`at://${p.repo}/${p.collection}/${p.rkey}`); if (value) return Response.json({ value }); return Response.json({ error: "RecordNotFound" }, { status: 400 }); }; } test("find, offer, answer, send: a sealed offer to the guest key and the answer that opens", async () => { const repo = { k: { $collection: "guest.publicKey", kind: "age-x25519", tag: "shell", key: "age1GUEST", createdAt: "1" }, n: { $collection: "vm.onNetwork", kind: "webrtc", tag: "shell", address: FP, createdAt: "1" }, ssh: { $collection: "vm.onNetwork", kind: "webrtc", tag: "ssh", address: "sha-256 00", createdAt: "2" }, }; const ctx = context(bidder(repo)); let pc; const cubit = new WebrtcCubit({ age, module: Promise.resolve(module), ctx, makePeer: (config) => ((pc = peer()), (pc.config = config), pc), sleep: async () => {} }); await cubit.find(JSON.stringify(receipt), "shell"); assert.equal(cubit.state.error, null); assert.deepEqual(cubit.state.guest, { key: "age1GUEST", fingerprint: FP }); await cubit.offer(); assert.deepEqual(pc.config, { iceServers: [{ urls: "stun:stun.cloudflare.com:3478" }] }); assert.equal(pc.dc.label, "shell"); assert.equal(cubit.state.phase, "offered"); assert.deepEqual(cubit.state.signal.receipt, receipt); assert.deepEqual(JSON.parse(JSON.parse(age.armor.decode(cubit.state.signal.offer)).text), { sdp: "v=0 offer", replyTo: "age1ME" }); // An answer for someone else, then ours. repo.a1 = { $collection: "guest.signalWebRTC", kind: "answer", tag: "shell", ciphertext: sealed("age1OTHER", { sdp: "other" }), createdAt: "3" }; repo.a2 = { $collection: "guest.signalWebRTC", kind: "answer", tag: "shell", ciphertext: sealed("age1ME", { sdp: `v=0\r\na=fingerprint:${FP.toLowerCase()}\r\n` }), createdAt: "4" }; ctx.http.fetch = bidder(repo); await cubit.answer(); assert.equal(cubit.state.error, null); assert.equal(cubit.state.phase, "connecting"); assert.match(pc.remoteDescription.sdp, /a=fingerprint/); pc.dc.onopen(); assert.equal(cubit.state.phase, "open"); cubit.send("echo hi"); assert.deepEqual(pc.dc.sent, ["echo hi\n"]); pc.dc.onmessage({ data: new TextEncoder().encode("hi\n").buffer }); assert.equal(cubit.state.output, "hi\n"); }); // The prod round of 2026-11-10 21:06Z: hat-9 published vm.onNetwork for shell only; a find for ssh // must still see it (the guest's one DTLS certificate), so the watch reads the default tags too. test("find for a tag whose vm.onNetwork is missing takes another tag's fingerprint", async () => { const ctx = context(bidder({ k: { $collection: "guest.publicKey", kind: "age-x25519", tag: "ssh", key: "age1GUEST", createdAt: "1" }, n: { $collection: "vm.onNetwork", kind: "webrtc", tag: "shell", address: FP, createdAt: "1" }, })); const cubit = new WebrtcCubit({ age, module: Promise.resolve(module), ctx, makePeer: peer }); await cubit.find(JSON.stringify(receipt), "ssh"); assert.equal(cubit.state.error, null); assert.deepEqual(cubit.state.guest, { key: "age1GUEST", fingerprint: FP }); }); test("find reports a guest that has not published its key or fingerprint", async () => { const ctx = context(bidder({})); const cubit = new WebrtcCubit({ age, module: Promise.resolve(module), ctx, makePeer: peer }); await cubit.find(JSON.stringify(receipt), "shell"); assert.equal(cubit.state.phase, "error"); assert.match(cubit.state.error, /guest.publicKey .*kind webrtc/); // contract's mcp URLs go to the webrtc: fetcher, which signals as the session. assert.deepEqual(Object.keys(cubit.fetchers), ["webrtc:"]); await assert.rejects(cubit.fetchers["webrtc:"]({ url: "webrtc:///x/shell/mcp" }), /needs a signed-in session/); }); test("fingerprint check and receipt parsing", () => { checkFingerprint("a=fingerprint:SHA-256 ab:cd:ef\r\n", FP); assert.throws(() => checkFingerprint("v=0\r\n", FP), /no a=fingerprint/); assert.throws(() => checkFingerprint("a=fingerprint:sha-256 00\r\n", FP), /is not the guest's/); assert.deepEqual(parseReceipt(JSON.stringify(receipt)), { ...receipt, bidder: HAT9 }); assert.throws(() => parseReceipt('{"uri":"at://x"}'), /strong ref/); // hat-9 published vm.onNetwork for the first tag only; the agent's one fingerprint serves all. const onNetwork = { kind: "vm.onNetwork", value: { kind: "webrtc", tag: "ssh", address: FP } }; assert.equal(guestOf([onNetwork], "shell").fingerprint, FP); assert.equal(guestOf([onNetwork, { ...onNetwork, value: { ...onNetwork.value, tag: "shell", address: "sha-256 01" } }], "shell").fingerprint, "sha-256 01"); }); // Signed in: the OAuth agent is stubbed at fetchHandler, the curve is a fake (attest.test.mjs // covers real signing); checks the host contract routes: attest, repo writes, https service. // The page's forms give text; the module reads numbers and booleans (hold_ms as text was no hold). test("form text goes as each field's module.json type; lists and other text as typed", async () => { const inputs = []; const ctx = { ...context(bidder({})), wasm: { run: async ({ input }) => (inputs.push(input), Promise.reject(new Error("stop"))) } }; const cubit = new WebrtcCubit({ age, module: Promise.resolve({ ...module, actions: spec.actions }), ctx, makePeer: peer }); await assert.rejects(cubit.run("contract", { hold_ms: "60000", release: "false", shell: "true", cpus: "2", bidders: "https://a https://b", tags: '["x"]', user_data: "7" })); const { hold_ms, release, shell, cpus, bidders, tags, user_data } = inputs[0]; assert.deepEqual({ hold_ms, release, shell, cpus, bidders, tags, user_data }, { hold_ms: 60000, release: false, shell: true, cpus: 2, bidders: "https://a https://b", tags: ["x"], user_data: "7" }); }); test("signed in: run plans through attest, the session's PDS, and service-auth to an https bidder", async () => { const alice = "did:plc:lpfuqerea3deuoyrn7ojser4"; const xrpc = []; const agent = { did: alice, com: { atproto: { server: { getSession: async () => ({ success: true, data: { handle: "alice.test" } }) } } }, async fetchHandler(path, init) { const [nsid, query] = path.replace("/xrpc/", "").split("?"); const input = init.body && JSON.parse(init.body); xrpc.push({ nsid, query, input, headers: init.headers }); if (nsid === "com.atproto.server.getServiceAuth") return Response.json({ token: "svc-jwt" }); if (nsid === "com.atproto.repo.createRecord") return Response.json({ uri: `at://${alice}/${input.collection}/r${xrpc.length}`, cid: `bafy${xrpc.length}` }); if (nsid === "com.atproto.repo.deleteRecord") return Response.json({}); return new Response("{}", { status: 400 }); }, }; const bidderCalls = []; const fetch = async (url, init) => (bidderCalls.push({ url, init }), Response.json({ ok: true, name: "bid-1" })); const store = new Map(); const ctx = { ...context(fetch), settings: { get: async (k) => store.get(k), set: async (k, v) => store.set(k, v) } }; const secp256k1 = { utils: { randomSecretKey: () => new Uint8Array(32).fill(1) }, getPublicKey: () => new Uint8Array(33).fill(2), sign: () => new Uint8Array(64) }; const cubit = new WebrtcCubit({ client: Promise.resolve({ init: async () => ({ session: {} }) }), makeAgent: () => agent, secp256k1, age, module: Promise.resolve(module), ctx, makePeer: peer }); await cubit.init(); assert.equal(cubit.state.session, "signed-in"); const bound = await cubit.run("bind_key"); assert.equal(xrpc[0].input.collection, `${T}.badgeBlueKeys`); assert.match(xrpc[0].input.record.keyId, /^did:key:z/); assert.ok(store.get("attestKeys")[alice]); assert.deepEqual(cubit.state.results.bind_key, bound); const endpoint = "https://bidder.example"; await cubit.run("request", { user_data: "#cloud-config\n", bidders: [endpoint] }); const rfp = xrpc.find((c) => c.input?.collection === `${T}.market.rfp`).input.record; assert.equal(rfp.signatures[0].issuer, alice); assert.deepEqual(xrpc.find((c) => c.nsid === "com.atproto.server.getServiceAuth").query, `aud=did%3Aweb%3Abidder.example&lxm=${T}.market.submitRfp`); assert.equal(bidderCalls[0].url, `${endpoint}/xrpc/${T}.market.submitRfp`); assert.equal(bidderCalls[0].init.headers.authorization, "Bearer svc-jwt"); await assert.rejects(cubit.deleteRecords([`at://${HAT9}/${T}.market.rfp/x`]), /not in your repo/); assert.deepEqual(await cubit.deleteRecords([`at://${alice}/${T}.market.rfp/x`]), [`at://${alice}/${T}.market.rfp/x`]); assert.deepEqual(xrpc.at(-1).input, { repo: alice, collection: `${T}.market.rfp`, rkey: "x" }); }); // The requester's shell entry plans com.fedfork.exec.webrtc.connect; the page's call() runs // find/offer/signal/answer and resolves once the channel opens. Other pseudo-calls never reach the PDS. test("signed in: shell -> webrtc.connect opens the channel; iroh.connect is refused here", async () => { const alice = "did:plc:lpfuqerea3deuoyrn7ojser4"; const repo = { k: { $collection: "guest.publicKey", kind: "age-x25519", tag: "ssh", key: "age1GUEST", createdAt: "1" }, n: { $collection: "vm.onNetwork", kind: "webrtc", tag: "ssh", address: FP, createdAt: "1" }, }; const store = new Map(); const ctx = { ...context(bidder(repo)), settings: { get: async (k) => store.get(k), set: async (k, v) => store.set(k, v) } }; const written = []; const agent = { did: alice, com: { atproto: { server: { getSession: async () => ({ success: true, data: { handle: "alice.test" } }) } } }, async fetchHandler(path, init) { const nsid = path.replace("/xrpc/", "").split("?")[0]; const input = init.body && JSON.parse(init.body); written.push(nsid === "com.atproto.repo.createRecord" ? input.collection : nsid); if (input?.collection === `${T}.compute.events.guest.signalWebRTC`) { // The guest answers once the offer is published. repo.a = { $collection: "guest.signalWebRTC", kind: "answer", tag: "ssh", ciphertext: sealed("age1ME", { sdp: `a=fingerprint:${FP}\r\n` }), createdAt: "2" }; ctx.http.fetch = bidder(repo); } return Response.json({ uri: `at://${alice}/${input?.collection}/r${written.length}`, cid: `bafy${written.length}` }); }, }; const secp256k1 = { utils: { randomSecretKey: () => new Uint8Array(32).fill(1) }, getPublicKey: () => new Uint8Array(33).fill(2), sign: () => new Uint8Array(64) }; let pc; const makePeer = () => { pc = peer(); pc.setRemoteDescription = async (d) => { pc.remoteDescription = d; queueMicrotask(() => pc.dc.onopen()) }; return pc; }; const cubit = new WebrtcCubit({ client: Promise.resolve({ init: async () => ({ session: {} }) }), makeAgent: () => agent, secp256k1, age, module: Promise.resolve(module), ctx, makePeer, sleep: async () => {} }); await cubit.init(); const result = await cubit.run("shell", { receipt: JSON.stringify(receipt) }); assert.deepEqual(result, { via: "webrtc", result: { tag: "ssh", fingerprint: FP } }); assert.equal(cubit.state.phase, "open"); assert.equal(pc.dc.label, "ssh"); assert.deepEqual(written, [`${T}.compute.events.guest.signalWebRTC`, `${T}.market.event`]); await assert.rejects(cubit.run("shell", { receipt: JSON.stringify(receipt), command: "id" }), /only over tag ssh, with an ssh client/); repo.i = { $collection: "vm.onNetwork", address: "iroh://endpointabc", createdAt: "3" }; repo.h = { $collection: "vm.hostKeys", keys: [{ type: "ssh-ed25519", publicKey: "AAAA" }], createdAt: "3" }; ctx.http.fetch = bidder(repo); const before = written.length; await assert.rejects(cubit.run("shell", { receipt: JSON.stringify(receipt), via: "iroh", command: "id" }), /com\.fedfork\.exec\.iroh\.connect: not available on this host \(built without iroh\)/); assert.equal(written.length, before); }); // Offer creation waits only as long as gathering needs: complete, the null candidate, 2 s after // the first srflx, or the 20 s cap. Fake timers; the peer fires candidates by hand. test("offer stops gathering on complete, the null candidate, 2 s after the first srflx, or 20 s", async (t) => { mock.timers.enable({ apis: ["setTimeout"] }); t.after(() => mock.timers.reset()); const repo = { k: { $collection: "guest.publicKey", kind: "age-x25519", tag: "shell", key: "age1GUEST", createdAt: "1" }, n: { $collection: "vm.onNetwork", kind: "webrtc", tag: "shell", address: FP, createdAt: "1" }, }; const candidate = (type) => Object.assign(new Event("icecandidate"), { candidate: type && { candidate: `candidate:1 1 udp 1 192.0.2.1 5000 typ ${type} generation 0` } }); const flush = () => new Promise((r) => setImmediate(r)); async function offering(fire) { let pc; const cubit = new WebrtcCubit({ age, module: Promise.resolve(module), ctx: context(bidder(repo)), makePeer: () => ((pc = peer()), (pc.gather = "manual"), pc) }); await cubit.find(JSON.stringify(receipt), "shell"); const offered = cubit.offer(); while (!pc?.localDescription) await flush(); await fire(pc); await flush(); return { cubit, offered }; } let { cubit, offered } = await offering((pc) => pc.dispatchEvent(candidate(null))); await offered; assert.equal(cubit.state.phase, "offered", "null candidate"); ({ cubit, offered } = await offering((pc) => { pc.dispatchEvent(candidate("host")); pc.dispatchEvent(candidate("srflx")); pc.dispatchEvent(candidate("srflx")) })); mock.timers.tick(1999); await flush(); assert.equal(cubit.state.phase, "offering", "still gathering 1999 ms after srflx"); mock.timers.tick(1); await offered; assert.equal(cubit.state.phase, "offered", "2 s after the first srflx"); ({ cubit, offered } = await offering((pc) => pc.dispatchEvent(candidate("host")))); mock.timers.tick(19999); await flush(); assert.equal(cubit.state.phase, "offering", "host candidates only: still gathering"); mock.timers.tick(1); await offered; assert.equal(cubit.state.phase, "offered", "20 s cap"); }); // A subscribe to webrtc.connect is a channel session; messages in, sends out. test("signed in: a subscribe to webrtc.connect streams the datachannel both ways", async () => { const alice = "did:plc:lpfuqerea3deuoyrn7ojser4"; const repo = { k: { $collection: "guest.publicKey", kind: "age-x25519", tag: "tty", key: "age1GUEST", createdAt: "1" }, n: { $collection: "vm.onNetwork", kind: "webrtc", tag: "tty", address: FP, createdAt: "1" }, }; let pc; const ctx = { http: { fetch: bidder(repo) }, settings: (() => { const store = new Map(); return { get: async (k) => store.get(k), set: async (k, v) => void store.set(k, v) }; })(), // The handler stands in for a module: subscribe, echo the first message back, finish. wasm: { run: async ({ entry, input }) => { // The requester runs every other entry (find, signal); "tty" is the test's own handler. if (entry !== "tty") return run(entry, input); const { event, receipt: r } = input; if (event.type === "start") return { calls: [{ id: "tty", nsid: "com.fedfork.exec.webrtc.connect", params: { receipt: r, tag: "tty" }, subscribe: true }] }; if (event.type === "message") return { send: [{ id: "tty", data: `echo ${event.data}` }], result: event.data, done: true }; return { result: event }; }, }, }; const agent = { did: alice, com: { atproto: { server: { getSession: async () => ({ success: true, data: { handle: "alice.test" } }) } } }, async fetchHandler(path, init) { const input = init.body && JSON.parse(init.body); if (input?.collection === `${T}.compute.events.guest.signalWebRTC`) { repo.a = { $collection: "guest.signalWebRTC", kind: "answer", tag: "tty", ciphertext: sealed("age1ME", { sdp: `a=fingerprint:${FP}\r\n` }), createdAt: "2" }; ctx.http.fetch = bidder(repo); } return Response.json({ uri: `at://${alice}/${input?.collection}/r1`, cid: "bafy1" }); }, }; const makePeer = () => { pc = peer(); pc.setRemoteDescription = async (d) => { pc.remoteDescription = d; queueMicrotask(() => pc.dc.onopen()); setTimeout(() => pc.dc.onmessage({ data: new TextEncoder().encode("login: ").buffer }), 5); }; return pc; }; const secp256k1 = { utils: { randomSecretKey: () => new Uint8Array(32).fill(1) }, getPublicKey: () => new Uint8Array(33).fill(2), sign: () => new Uint8Array(64) }; const cubit = new WebrtcCubit({ client: Promise.resolve({ init: async () => ({ session: {} }) }), makeAgent: () => agent, secp256k1, age, module: Promise.resolve(module), ctx, makePeer, sleep: async () => {} }); await cubit.init(); assert.equal(await cubit.run("tty", { receipt: JSON.stringify(receipt) }), "login: "); assert.deepEqual(pc.dc.sent, ["echo login: "]); assert.equal(cubit.state.phase, "open", "closing the subscription leaves the channel open"); }); // A peer connection failing before the channel opens is the NAT case; after it opened (live: // the contract released the VM) it is the guest going away, so the page closes, no NAT error. test("a failed peer connection is a NAT error before open and a close after", async () => { const repo = { k: { $collection: "guest.publicKey", kind: "age-x25519", tag: "shell", key: "age1GUEST", createdAt: "1" }, n: { $collection: "vm.onNetwork", kind: "webrtc", tag: "shell", address: FP, createdAt: "1" }, }; const fail = (pc) => ((pc.connectionState = "failed"), (pc.iceConnectionState = "disconnected"), pc.dispatchEvent(new Event("connectionstatechange"))); for (const opened of [false, true]) { let pc; const cubit = new WebrtcCubit({ age, module: Promise.resolve(module), ctx: context(bidder(repo)), makePeer: () => (pc = peer()) }); await cubit.find(JSON.stringify(receipt), "shell"); await cubit.offer(); if (opened) pc.dc.onopen(); fail(pc); if (opened) assert.deepEqual([cubit.state.phase, cubit.state.error], ["closed", null]); else assert.match(cubit.state.error, /symmetric NAT/); } });