Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { mintServiceAuth, serviceDidForHost } from "$lib/auth/agent";import { buildUrl, toResponseError } from "$lib/api/_request";import type { QueryValue, XrpcRequestInit } from "$lib/api/client";
// Authenticated client for the go appview's org.tangled.temp.* xrpc methods.// Unlike bobbin (public reads) and the pds (record writes), these are authed// with an atproto service-auth jwt: the browser mints a per-call token via the// user's pds (getServiceAuth), scoped to the method (lxm) and the appview's did// (aud). The appview verifies the issuer's signature and the aud/lxm claims.export interface AppviewContext { readonly serviceUrl: string; readonly aud: string; readonly agent: OAuthUserAgent; readonly fetch: typeof globalThis.fetch;}
export interface CreateAppviewOptions { apiUrl: string; agent: OAuthUserAgent; fetch?: typeof globalThis.fetch;}
export const createAppviewClient = ({ apiUrl, agent, fetch}: CreateAppviewOptions): AppviewContext => { const serviceUrl = apiUrl.replace(/\/+$/, ""); // aud host must match the appview's TANGLED_APPVIEW_HOST, which is the host // it's reached at; derive both from the same url so they can't drift. const aud = serviceDidForHost(new URL(serviceUrl).host); return { serviceUrl, aud, agent, fetch: fetch ?? globalThis.fetch };};
const authHeader = async (ctx: AppviewContext, nsid: string): Promise<string> => { const token = await mintServiceAuth(ctx.agent, { aud: ctx.aud, lxm: nsid }); return `Bearer ${token}`;};
export const authedGet = async <T>( ctx: AppviewContext, nsid: string, params?: Record<string, QueryValue>, init?: XrpcRequestInit): Promise<T> => { const response = await ctx.fetch(buildUrl(ctx.serviceUrl, nsid, params), { headers: { accept: "application/json", authorization: await authHeader(ctx, nsid), ...init?.headers }, signal: init?.signal }); if (!response.ok) throw await toResponseError(response); return (await response.json()) as T;};
export const authedPost = async <T>( ctx: AppviewContext, nsid: string, body: unknown, init?: XrpcRequestInit): Promise<T | null> => { const response = await ctx.fetch(buildUrl(ctx.serviceUrl, nsid), { method: "POST", headers: { "content-type": "application/json", accept: "application/json", authorization: await authHeader(ctx, nsid), ...init?.headers }, body: JSON.stringify(body ?? {}), signal: init?.signal }); if (!response.ok) throw await toResponseError(response); // most temp procedures return no body (200 with empty payload) const text = await response.text(); return text ? (JSON.parse(text) as T) : null;};