// node --test web/ingress/ingress-cubit.test.mjs (after `cargo wasm`) // The page cubit over the real ingress module, the real didkey and subscribeXrpc, with a fake relay // behind the WebSocket class: registers, serves, gets dropped (1011), re-registers, serves again. import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { registerHooks } from "node:module"; import { test } from "node:test"; registerHooks({ resolve(specifier, context, next) { const lib = specifier.startsWith("atlibs/") && new URL(`../../../atlibs/javascript/src/${specifier.slice("atlibs/".length)}`, import.meta.url).href; return next(lib || specifier, context); }, }); const { IngressCubit } = await import("./ingress-cubit.js"); const { load } = await import("../../../atlibs/rust/src/harness.mjs"); const WASM = new URL("../../target/wasm32-unknown-unknown/release/ingress.wasm", import.meta.url); const { run } = await load(WASM); const manifest = JSON.parse(await readFile(new URL("../../modules/ingress/module.json", import.meta.url), "utf8")); const T = "com.fedproxy.temp.xrpc.subscribe"; const nonce = () => { const bytes = crypto.getRandomValues(new Uint8Array(64)); bytes[0] = 0; // not a JWS character, so didkey.sign takes it return Buffer.from(bytes).toString("base64"); }; // The relay side of each subscribe socket. The first connection is dropped with 1011 after it // answers one request; the second answers one request and the test stops the run. function relayWorld() { const sockets = []; const responses = []; class FakeRelay { readyState = 0; constructor(url) { this.url = new URL(url); this.n = sockets.push(this); setTimeout(() => { this.readyState = 1; this.onopen?.(); this.push({ $type: `${T}#registered`, subdomain: this.url.searchParams.get("did").replaceAll(":", "-").toLowerCase() }); this.push({ $type: `${T}#request`, requestId: `hello-${this.n}`, method: "GET", path: "/xrpc/com.example.ingress.hello", params: { name: `socket${this.n}` }, headers: {} }); }, 5); } push(frame) { this.onmessage?.({ data: JSON.stringify(frame) }); } send(text) { const frame = JSON.parse(text); responses.push({ socket: this.n, ...frame }); if (frame.requestId === "hello-1") setTimeout(() => this.close(1011, "subscriber unresponsive for 90s"), 5); } close(code = 1005, reason = "") { if (this.readyState > 1) return; this.readyState = 3; this.onclose?.({ code, reason }); } } return { FakeRelay, sockets, responses }; } test("the browser host keeps its did:key in secret settings and re-registers after the relay drops it", async () => { const settings = new Map(); const fetched = []; const ctx = { settings: { get: async (k) => settings.get(k) ?? null, set: async (k, v) => void settings.set(k, v) }, wasm: { run: async ({ entry, input }) => run(entry, input) }, http: { fetch: async (url, init) => { fetched.push(url); assert.equal(url, "https://xrpc.fedproxy.com/xrpc/com.fedproxy.temp.xrpc.getRegistrationNonce"); assert.match(init.headers.authorization, /^Bearer /); return new Response(JSON.stringify({ nonce: nonce() })); }, }, }; const { FakeRelay, sockets, responses } = relayWorld(); const module = Promise.resolve({ module: new Uint8Array(await readFile(WASM)), calls: manifest.calls, didkey: manifest.didkey }); const id = "http://localhost/modules/ingress.wasm"; const cubit = new IngressCubit({ ctx, module, id, WebSocket: FakeRelay }); await cubit.init(); assert.equal(cubit.state.status, "idle"); assert.match(cubit.state.did, /^did:key:zDn/); assert.equal(cubit.state.url, `https://${cubit.state.did.replaceAll(":", "-").toLowerCase()}.xrpc.fedproxy.com/`); assert.ok(settings.get("didkeys")[id].d, "the private JWK is in the didkeys setting"); const again = new IngressCubit({ ctx, module, id, WebSocket: FakeRelay }); await again.init(); assert.equal(again.state.did, cubit.state.did, "same identity, same did"); const running = cubit.start({ lifetimeMs: "30000" }); const until = async (check) => { for (let i = 0; i < 400 && !check(); i++) await new Promise((r) => setTimeout(r, 10)); assert.ok(check()); }; await until(() => responses.some((r) => r.requestId === "hello-2")); cubit.stop(); const ran = await running; assert.equal(ran.error, "aborted"); assert.equal(cubit.state.status, "done"); assert.deepEqual(responses.map((r) => [r.socket, r.requestId, r.status, r.body.message]), [ [1, "hello-1", 200, "hello socket1"], [2, "hello-2", 200, "hello socket2"], ]); assert.equal(sockets.length, 2); assert.equal(fetched.length, 2, "a fresh nonce per registration"); assert.equal(new URL(sockets[1].url).searchParams.get("did"), cubit.state.did); assert.ok(sockets[1].url.searchParams.get("service_auth"), "service-auth rides in the query string"); assert.equal(cubit.state.drops, 1); assert.ok(cubit.state.log.some((line) => /relay closed: .*1011/.test(line)), cubit.state.log.join("\n")); assert.ok(cubit.state.log.some((line) => /timer reconnect/.test(line))); }); test("a module with no didkey services is refused before any key is made", async () => { const ctx = { settings: { get: async () => null, set: async () => {} }, wasm: { run: async ({ entry, input }) => run(entry, input) }, http: { fetch: async () => new Response("{}") } }; const cubit = new IngressCubit({ ctx, module: Promise.resolve({ module: new Uint8Array(), calls: [], didkey: { services: [] } }), id: "x" }); await cubit.init(); assert.equal(cubit.state.status, "error"); assert.match(cubit.state.error, /didkey services/); }); // review 3a L6: a link can name any module record; &serve starts one only after the user confirms. test("&serve starts a module named by at:// only when confirmed; the local module at once", async () => { const ctx = { settings: { get: async () => null, set: async () => {} } }; const module = Promise.resolve({ module: new Uint8Array(), calls: [], didkey: { services: ["https://xrpc.fedproxy.com"] } }); const asked = []; const confirm = (answer) => (message) => (asked.push(message), answer); const remote = new IngressCubit({ ctx, module, id: "at://did:plc:attacker/com.fedfork.exec.wasm.module/x" }); assert.equal(await remote.mayAutoServe(confirm(false)), false); assert.equal(await remote.mayAutoServe(confirm(true)), true); assert.match(asked[0], /at:\/\/did:plc:attacker\/com\.fedfork\.exec\.wasm\.module\/x[\s\S]*https:\/\/xrpc\.fedproxy\.com/); asked.length = 0; assert.equal(await new IngressCubit({ ctx, module, id: "http://localhost/modules/ingress.wasm" }).mayAutoServe(confirm(false)), true); assert.deepEqual(asked, []); // ?module=/: the loaded record's at:// URI is the identity, so it still asks const uri = "at://did:plc:attacker/com.fedfork.exec.wasm.module/x"; const byHandle = new IngressCubit({ ctx, module: module.then((m) => ({ ...m, id: uri })), id: "attacker.example/x" }); assert.equal(await byHandle.mayAutoServe(confirm(false)), false); assert.match(asked[0], /Serve at:\/\/did:plc:attacker\//); });