import { ClientResponseError } from "@atcute/client"; import * as v from "@atcute/lexicons/validations"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { hostForServiceDid, mintServiceAuth } from "$lib/auth/agent"; import { buildUrl, toResponseError } from "$lib/api/_request"; import { CREATE_DELEGATE_NSID, LIST_DELEGATES_ENDPOINT, LIST_DELEGATES_NSID, listDelegatesSchema } from "$lib/api/tranquil"; export { CREATE_DELEGATE_NSID, LIST_DELEGATES_NSID }; // The gate mints its own response, distinct from Tranquil's createAccount output. const createdDelegateSchema = v.object({ controllerDid: v.didString(), did: v.didString(), handle: v.handleString() }); export type CreatedDelegate = v.InferInput; export const GATE_CREATE_DELEGATE_NSID = "sh.tangled.delegation.createAccount"; export interface DelegationService { serviceUrl: string; serviceDid?: string; fetch?: typeof globalThis.fetch; } export const provisionDelegate = async ( agent: OAuthUserAgent, handle: string, service: DelegationService ): Promise => { if (!service.serviceUrl || !service.serviceDid) throw new Error("Organization creation is not configured."); let token: string; try { token = await mintServiceAuth(agent, { aud: service.serviceDid, lxm: CREATE_DELEGATE_NSID }); } catch (cause) { if (cause instanceof ClientResponseError && cause.status === 403) { throw new Error("Sign in again to grant Tangled permission to create organizations.", { cause }); } throw cause; } const response = await (service.fetch ?? globalThis.fetch)( buildUrl(service.serviceUrl, GATE_CREATE_DELEGATE_NSID), { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ handle }) } ); if (!response.ok) { const error = await toResponseError(response); const messages: Record = { VerifiedEmailRequired: "Verify an email address in Settings → Emails before creating an organization.", // Tranquil enforces the per-controller delegate cap and reports it as // InvalidDelegation; the gate relays Tranquil's error code and message. InvalidDelegation: "You have reached the limit on organizations you can create.", UpstreamUnavailable: "Organization creation is temporarily unavailable. Please try again later." }; throw new Error(messages[error.error] ?? error.message); } const account = v.parse(createdDelegateSchema, await response.json()); if (account.controllerDid !== agent.sub) throw new Error("The organization was assigned to an unexpected owner."); return account; }; // Remote controllers mint this token on their own PDS and list on Tranquil. export const listDelegatedAccounts = async ( agent: OAuthUserAgent, serviceDid: string, fetch: typeof globalThis.fetch = globalThis.fetch ) => { const host = hostForServiceDid(serviceDid); if (!host) throw new Error("Organization accounts are not configured."); const token = await mintServiceAuth(agent, { aud: serviceDid, lxm: LIST_DELEGATES_NSID }); const response = await fetch(buildUrl(`https://${host}`, LIST_DELEGATES_ENDPOINT), { headers: { authorization: `Bearer ${token}` } }); if (!response.ok) throw await toResponseError(response); return v.parse(listDelegatesSchema, await response.json()).accounts; };