// node --test javascript/src/run-module/host-call.test.mjs (no network) import assert from "node:assert/strict"; import { test } from "node:test"; import { hostCall, json, sessionXrpc, sseEvents } from "./host-call.js"; const SUB = "com.atproto.sync.subscribeRepos"; // review 3a L5 / 3b #10: the pages built aud as did:web: or dropped the port; every host now // mints through serviceAud. test("a page's host call (canHeader false): aud keeps the port as %3A, the page's own pseudo-calls, the rest refused", async () => { const minted = []; const fetched = []; const urls = []; class FakeSocket { constructor(url) { urls.push(url); this.readyState = 1; } close() {} } const xrpc = async (nsid, { params }) => (minted.push(params), { token: "jwt1" }); const ctx = { http: { fetch: async (url, init) => (fetched.push([url, init.headers]), new Response("{}")) } }; const own = []; const call = hostCall({ ctx, xrpc, canHeader: false, WebSocket: FakeSocket, calls: { "com.fedfork.exec.webrtc.connect": (r) => (own.push(r.subscribe === true), "mine") } }); await call(SUB, { subscribe: true, service: "https://svc.example:8443", serviceAuth: { param: "auth" } }); await call("com.example.q", { service: "https://svc.example:8443", params: { a: "1" } }); assert.deepEqual(minted, [{ aud: "did:web:svc.example%3A8443", lxm: SUB }, { aud: "did:web:svc.example%3A8443", lxm: "com.example.q" }]); assert.deepEqual(urls, [`wss://svc.example:8443/xrpc/${SUB}?auth=jwt1`]); assert.deepEqual(fetched, [["https://svc.example:8443/xrpc/com.example.q?a=1", { authorization: "Bearer jwt1" }]]); await assert.rejects(call(SUB, { subscribe: true, service: "https://svc.example", serviceAuth: {} }), /browsers cannot set WebSocket headers/); assert.equal(await call("com.fedfork.exec.webrtc.connect", { input: {} }), "mine"); assert.equal(await call("com.fedfork.exec.webrtc.connect", { subscribe: true }), "mine"); assert.deepEqual(own, [false, true]); // a transport this host has none of: the same text as rust/host's transport::unavailable for (const request of [{ input: {} }, { subscribe: true }]) await assert.rejects(call("com.fedfork.exec.iroh.connect", request), { message: "com.fedfork.exec.iroh.connect: not available on this host (built without iroh)" }); await assert.rejects(call("com.fedfork.exec.didkey.sign", { input: {} }), { message: "com.fedfork.exec.didkey.sign is not available on this host" }); await assert.rejects(call("com.fedfork.exec.attest", { subscribe: true }), /cannot be subscribed to on this host/); await assert.rejects(hostCall({ ctx, signIn: "sign in" })("com.atproto.repo.createRecord", { input: {} }), /needs a session \(sign in\)/); }); test("an event-stream answer is read whole into { events } (Rust json_response the same)", async () => { const body = 'event: meta\ndata: {"jobUri":"at://x"}\n\n: comment\n\ndata: one\ndata: two\n\n'; const response = new Response(body, { headers: { "content-type": "text/event-stream; charset=utf-8" } }); assert.deepEqual(await json("dev.cocore.inference.dispatch", response), { events: [{ event: "meta", data: { jobUri: "at://x" } }, { event: "message", data: "one\ntwo" }] }); assert.deepEqual(sseEvents("data: x\r\n\r\nevent: e\r\ndata: 1\r\n\r\n"), [{ event: "message", data: "x" }, { event: "e", data: 1 }]); }); test("signed out, a proxied query goes straight to the did#id's endpoint with no credentials; a did#id service too", async () => { const fetched = []; const doc = { service: [{ id: "#atproto_pds", serviceEndpoint: "https://pds.example" }, { id: "#svc", serviceEndpoint: "https://api.example" }] }; const ctx = { http: { fetch: async (url, init) => (fetched.push([url, init?.headers]), Response.json(url.endsWith("did.json") ? doc : { ok: url })) } }; const call = hostCall({ ctx }); assert.deepEqual(await call("com.example.q", { proxy: "did:web:api.example#svc", params: { a: "1" } }), { ok: "https://api.example/xrpc/com.example.q?a=1" }); assert.deepEqual(await call("com.example.q", { service: "did:web:api.example#svc" }), { ok: "https://api.example/xrpc/com.example.q" }); assert.deepEqual(await call("com.example.q", { service: "did:web:api.example" }), { ok: "https://pds.example/xrpc/com.example.q" }); assert.ok(fetched.every(([, headers]) => headers === undefined), "no credentials"); await assert.rejects(call("com.example.p", { proxy: "did:web:api.example#svc", input: {} }), /com.example.p needs a session/); await assert.rejects(call("com.example.q", { proxy: "did:web:api.example#nope" }), { message: "did:web:api.example has no #nope service" }); // signed in, the proxy still goes through the session const sent = []; await hostCall({ ctx, xrpc: async (nsid, init) => (sent.push(init.headers), {}) })("com.example.q", { proxy: "did:web:api.example#svc" }); assert.deepEqual(sent, [{ "atproto-proxy": "did:web:api.example#svc" }]); }); test("bytes: a { $bytes } input goes as those bytes with its encoding; a binary answer comes back as { $bytes } (Rust the same)", async () => { const sent = []; const agent = { fetchHandler: async (path, init) => (sent.push([path, init]), new Response(Uint8Array.of(0, 1, 2, 255), { headers: { "content-type": "application/x-git-packed-objects" } })) }; const call = hostCall({ ctx: { http: { fetch } }, xrpc: sessionXrpc(agent) }); assert.deepEqual(await call("com.atproto.repo.uploadBlob", { input: { $bytes: "AAEC/w" }, encoding: "application/x-git-packed-objects" }), { $bytes: "AAEC/w" }); await call("com.atproto.repo.uploadBlob", { input: { $bytes: "AAEC/w==" } }); assert.deepEqual(sent.map(([path, init]) => [path, init.method, init.headers["content-type"], [...init.body]]), [ ["/xrpc/com.atproto.repo.uploadBlob", "POST", "application/x-git-packed-objects", [0, 1, 2, 255]], ["/xrpc/com.atproto.repo.uploadBlob", "POST", "application/octet-stream", [0, 1, 2, 255]], ]); // JSON and text answers are read as before; a JSON input that merely has $bytes beside other keys stays JSON assert.deepEqual(await json("x", new Response("{}")), {}); assert.deepEqual(await json("x", Response.json({ a: 1 })), { a: 1 }); await sessionXrpc({ fetchHandler: async (path, init) => (sent.push([path, init]), Response.json({})) })("a.b.c", { input: { $bytes: "AA", x: 1 } }); assert.deepEqual([sent.at(-1)[1].headers["content-type"], sent.at(-1)[1].body], ["application/json", '{"$bytes":"AA","x":1}']); }); test("a did#id call with endpoints: a fetcher's scheme first, else https, with service auth aud did#id; none reachable is an error (Rust TransportCall/HostCall the same)", async () => { const minted = []; const sent = []; const xrpc = async (nsid, { params }) => (minted.push([nsid, params]), { token: "jwt1" }); const ctx = { http: { fetch: async (url, init) => (sent.push(["https", url, init.method, init.headers.authorization, init.body]), Response.json({ via: "https" })) } }; const fetchers = { "iroh:": async ({ url, method, headers, body }) => (sent.push(["iroh", url, method, headers.authorization, body]), { status: 200, headers: { "content-type": "application/json" }, body: '{"via":"iroh"}' }) }; const aud = "did:plc:bidder#pdr_temp_market"; const endpoints = [{ kind: "https", address: "https://bidder.example" }, { kind: "iroh", address: "endpointabc234" }]; const nsid = "com.publicdomainrelay.temp.market.submitRfp"; assert.deepEqual(await hostCall({ ctx, xrpc, fetchers })(nsid, { service: aud, endpoints, input: { a: 1 } }), { via: "iroh" }); assert.deepEqual(await hostCall({ ctx, xrpc })(nsid, { service: aud, endpoints, input: { a: 1 } }), { via: "https" }); assert.deepEqual(minted, [["com.atproto.server.getServiceAuth", { aud, lxm: nsid }], ["com.atproto.server.getServiceAuth", { aud, lxm: nsid }]]); assert.deepEqual(sent, [ ["iroh", `iroh://endpointabc234/xrpc/${nsid}`, "POST", "Bearer jwt1", '{"a":1}'], ["https", `https://bidder.example/xrpc/${nsid}`, "POST", "Bearer jwt1", '{"a":1}'], ]); await assert.rejects(hostCall({ ctx, xrpc })(nsid, { service: aud, endpoints: [{ kind: "iroh", address: "endpointabc234" }, { kind: "https", address: "http://plain.example" }], input: {} }), { message: `${nsid}: this host reaches none of ${aud}'s endpoints` }); await assert.rejects(hostCall({ ctx, fetchers })(nsid, { service: aud, endpoints, input: {} }), /needs a session/); });