Something went wrong. Try again.
forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114import { 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.ts";import { buildUrl, toResponseError } from "#lib/api/_request.ts";import { didOf, nsidOf } from "#lib/api/syntax.ts";import { sentence } from "#lib/format.ts";import { CREATE_DELEGATE_NSID, LIST_DELEGATES_ENDPOINT, LIST_DELEGATES_NSID, listDelegatesSchema} from "#lib/api/tranquil.ts";import type { Fetch } from "#lib/fetch.ts";
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<typeof createdDelegateSchema>;
export const GATE_CREATE_DELEGATE_NSID = nsidOf("org.tangled.delegation.createAccount");
export interface DelegationService { serviceUrl: string; serviceDid?: string; handleDomain?: string; fetch?: Fetch;}
const GATE_ERRORS: Record<string, string> = { VerifiedEmailRequired: "Verify an email address in Settings → Emails before creating an organization.", UpstreamUnavailable: "Organization creation is temporarily unavailable. Try again in a few minutes.", HandleNotAvailable: "This organization name is taken. Choose another one.", InvalidHandle: "This organization name isn't allowed. Choose another one."};
const gateError = (cause: ClientResponseError): string => { const known = GATE_ERRORS[cause.error]; if (known) return known; if (cause.description) return sentence(cause.description); return "Unable to create the organization. Try again.";};
export const provisionDelegate = async ( agent: OAuthUserAgent, handle: string, service: DelegationService): Promise<CreatedDelegate> => { if (!service.serviceUrl || !service.serviceDid) throw new Error("Organization creation isn't set up on this server."); let token: string; try { token = await mintServiceAuth(agent, { aud: didOf(service.serviceDid), lxm: CREATE_DELEGATE_NSID }); } catch (cause) { if (cause instanceof ClientResponseError) { throw new Error( cause.status === 401 || cause.status === 403 ? "Sign in again to grant Tangled permission to create organizations." : "Unable to confirm your account with your PDS. Try again.", { 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 cause = await toResponseError(response); throw new Error(gateError(cause), { cause }); } const account = v.parse(createdDelegateSchema, await response.json()); if (account.controllerDid !== agent.sub) throw new Error( "The organization was created under a different account than the one you chose. Sign out, sign back in, and try again." ); return account;};
// Remote controllers mint this token on their own PDS and list on Tranquil.export const listDelegatedAccounts = async ( agent: OAuthUserAgent, serviceDid: string, fetch: Fetch = globalThis.fetch) => { const host = hostForServiceDid(serviceDid); if (!host) throw new Error("Organization accounts are not configured."); const token = await mintServiceAuth(agent, { aud: didOf(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;};