import { XrpcError, type PdsClient } from "./pds.ts"; /** * Calls an XRPC endpoint on a knot or spindle. Mints a fresh service-auth JWT * scoped to (aud, lxm) for each call — knots/spindles verify the JWT against * the user's signing key from their DID document. */ export async function callServiceXrpc(opts: { pds: PdsClient; serviceHost: string; serviceDid: string; nsid: string; method: "GET" | "POST"; body?: unknown; query?: Record; }): Promise { const { token } = await opts.pds.getServiceAuth(opts.serviceDid, opts.nsid); const search = new URLSearchParams(); for (const [k, v] of Object.entries(opts.query ?? {})) { if (v !== undefined) search.set(k, v); } const qs = search.toString(); const url = `https://${opts.serviceHost}/xrpc/${opts.nsid}${qs ? `?${qs}` : ""}`; const init: RequestInit = { method: opts.method, headers: { authorization: `Bearer ${token}` }, }; if (opts.body !== undefined) { (init.headers as Record)["content-type"] = "application/json"; init.body = JSON.stringify(opts.body); } const resp = await fetch(url, init); if (!resp.ok) { const body = (await resp.json().catch(() => ({}))) as { error?: string; message?: string }; throw new XrpcError( resp.status, body.error, body.message ?? `${opts.nsid} failed on ${opts.serviceHost} (${resp.status})`, ); } // Some procedures return empty body on success. const text = await resp.text(); if (!text) return undefined as T; return JSON.parse(text) as T; } export function knotDid(host: string): string { return `did:web:${host}`; } export function spindleDid(host: string): string { return `did:web:${host}`; }