Something went wrong. Try again.
Something went wrong. Try again.
3.0 kB · 89 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990import { PLC_DIRECTORY } from "./constants.ts";
export interface DidDocument { id: string; alsoKnownAs?: string[]; service?: { id: string; type: string; serviceEndpoint: string }[];}
export function isDid(s: string): boolean { return s.startsWith("did:");}
export function normalizeHandle(h: string): string { return h.replace(/^@/, "").toLowerCase();}
export async function resolveHandleToDid(handle: string): Promise<string> { const h = normalizeHandle(handle);
// Try .well-known first (works for tangled.sh handles). try { const resp = await fetch(`https://${h}/.well-known/atproto-did`, { signal: AbortSignal.timeout(5000), }); if (resp.ok) { const did = (await resp.text()).trim(); if (isDid(did)) return did; } } catch { // fall through to DNS }
// DNS TXT fallback via Cloudflare DoH (works in Bun without native DNS deps). const dnsResp = await fetch( `https://cloudflare-dns.com/dns-query?name=_atproto.${h}&type=TXT`, { headers: { accept: "application/dns-json" } }, ); if (!dnsResp.ok) throw new Error(`could not resolve handle ${h}`); const dns = (await dnsResp.json()) as { Answer?: { data: string }[] }; for (const ans of dns.Answer ?? []) { const txt = ans.data.replace(/^"|"$/g, ""); if (txt.startsWith("did=")) return txt.slice(4); } throw new Error(`could not resolve handle ${h}`);}
export async function resolveDidToDocument(did: string): Promise<DidDocument> { if (did.startsWith("did:plc:")) { const resp = await fetch(`${PLC_DIRECTORY}/${did}`); if (!resp.ok) throw new Error(`PLC lookup failed for ${did}: ${resp.status}`); return (await resp.json()) as DidDocument; } if (did.startsWith("did:web:")) { const host = did.slice("did:web:".length).replace(/:/g, "/"); const resp = await fetch(`https://${host}/.well-known/did.json`); if (!resp.ok) throw new Error(`did:web lookup failed for ${did}: ${resp.status}`); return (await resp.json()) as DidDocument; } throw new Error(`unsupported DID method: ${did}`);}
export function pdsFromDocument(doc: DidDocument): string { const pds = doc.service?.find((s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer"); if (!pds) throw new Error(`no PDS service in DID document for ${doc.id}`); return pds.serviceEndpoint;}
export function handleFromDocument(doc: DidDocument): string | null { const aka = doc.alsoKnownAs?.find((a) => a.startsWith("at://")); return aka ? aka.slice("at://".length) : null;}
export interface ResolvedIdentity { did: string; handle: string | null; pds: string; doc: DidDocument;}
export async function resolveIdentity(handleOrDid: string): Promise<ResolvedIdentity> { const did = isDid(handleOrDid) ? handleOrDid : await resolveHandleToDid(handleOrDid); const doc = await resolveDidToDocument(did); return { did, handle: handleFromDocument(doc), pds: pdsFromDocument(doc), doc, };}