import { Client, simpleFetchHandler } from "@atcute/client"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { mintServiceAuth, serviceDidForHost } from "$lib/auth/agent"; export { ClientResponseError, isXRPCErrorPayload, ok } from "@atcute/client"; export type { XRPCErrorPayload } from "@atcute/client"; // generated lexicons register ambient types; don't import the barrel at runtime. export interface BobbinContext { readonly xrpc: Client; readonly serviceUrl: string; readonly fetch: typeof globalThis.fetch; } export interface CreateBobbinOptions { serviceUrl: string; fetch?: typeof globalThis.fetch; /** when set, every xrpc request carries a freshly minted service-auth jwt */ agent?: OAuthUserAgent; } const nsidOf = (input: RequestInfo | URL): string | null => { const href = input instanceof Request ? input.url : String(input); let pathname: string; try { pathname = new URL(href).pathname; } catch { return null; } const [, prefix, nsid, ...rest] = pathname.split("/"); return prefix === "xrpc" && nsid && rest.length === 0 ? nsid : null; }; // pocket keys stored preferences by the token's `aud` export const PREFERENCES_AUD = "did:web:tangled.org"; export const PREFERENCES_LXMS: ReadonlySet = new Set([ "com.bad-example.pocket.getPreferences", "com.bad-example.pocket.putPreferences" ]); const withServiceAuth = ( base: typeof globalThis.fetch, agent: OAuthUserAgent, origin: string ): typeof globalThis.fetch => { return async (input, init) => { const lxm = nsidOf(input); if (!lxm) return base(input, init); const aud = PREFERENCES_LXMS.has(lxm) ? PREFERENCES_AUD : serviceDidForHost(new URL(origin).host); const token = await mintServiceAuth(agent, { aud, lxm }); const headers = new Headers(init?.headers); headers.set("authorization", `Bearer ${token}`); return base(input, { ...init, headers }); }; }; export const createBobbinClient = ({ serviceUrl, fetch, agent }: CreateBobbinOptions): BobbinContext => { const origin = serviceUrl.replace(/\/+$/, ""); const baseFetch = fetch ?? globalThis.fetch; const boundFetch = agent ? withServiceAuth(baseFetch, agent, origin) : baseFetch; const xrpc = new Client({ handler: simpleFetchHandler({ service: origin, fetch: boundFetch }) }); return { xrpc, serviceUrl: origin, fetch: boundFetch }; }; export type QueryValue = string | number | boolean | readonly (string | number)[] | undefined; export interface XrpcRequestInit { signal?: AbortSignal; headers?: HeadersInit; }