/** * DID resolution for Node.js */ import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver, } from "@atcute/identity-resolver"; import type { DidDocument } from "@atcute/identity"; import type { Did } from "@atcute/lexicons/syntax"; import type { DidCache } from "./did-cache.js"; const PLC_DIRECTORY = "https://plc.directory"; const TIMEOUT_MS = 3000; export interface DidResolverOpts { plcUrl?: string; timeout?: number; didCache?: DidCache; } export type { DidDocument }; export class DidResolver { private resolver: CompositeDidDocumentResolver<"plc" | "web">; private timeout: number; private cache?: DidCache; constructor(opts: DidResolverOpts = {}) { this.timeout = opts.timeout ?? TIMEOUT_MS; this.cache = opts.didCache; this.resolver = new CompositeDidDocumentResolver({ methods: { plc: new PlcDidDocumentResolver({ apiUrl: opts.plcUrl ?? PLC_DIRECTORY, }), web: new WebDidDocumentResolver(), }, }); } async resolve(did: string): Promise { if (this.cache) { const cached = await this.cache.checkCache(did); if (cached && !cached.expired) { if (cached.stale) { this.cache.refreshCache( did, () => this.resolveNoCache(did), cached, ); } return cached.doc; } } const doc = await this.resolveNoCache(did); if (doc && this.cache) { await this.cache.cacheDid(did, doc); } else if (!doc && this.cache) { await this.cache.clearEntry(did); } return doc; } private async resolveNoCache(did: string): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); try { const doc = await this.resolver.resolve(did as Did<"plc" | "web">, { signal: controller.signal, }); if (doc.id !== did) { return null; } return doc; } catch { return null; } finally { clearTimeout(timeoutId); } } }