atproto user agency toolkit for individuals and groups
1/**
2 * DID resolution for Node.js
3 */
4
5import {
6 CompositeDidDocumentResolver,
7 PlcDidDocumentResolver,
8 WebDidDocumentResolver,
9} from "@atcute/identity-resolver";
10import type { DidDocument } from "@atcute/identity";
11import type { Did } from "@atcute/lexicons/syntax";
12import type { DidCache } from "./did-cache.js";
13
14const PLC_DIRECTORY = "https://plc.directory";
15const TIMEOUT_MS = 3000;
16
17export interface DidResolverOpts {
18 plcUrl?: string;
19 timeout?: number;
20 didCache?: DidCache;
21}
22
23export type { DidDocument };
24
25export class DidResolver {
26 private resolver: CompositeDidDocumentResolver<"plc" | "web">;
27 private timeout: number;
28 private cache?: DidCache;
29
30 constructor(opts: DidResolverOpts = {}) {
31 this.timeout = opts.timeout ?? TIMEOUT_MS;
32 this.cache = opts.didCache;
33
34 this.resolver = new CompositeDidDocumentResolver({
35 methods: {
36 plc: new PlcDidDocumentResolver({
37 apiUrl: opts.plcUrl ?? PLC_DIRECTORY,
38 }),
39 web: new WebDidDocumentResolver(),
40 },
41 });
42 }
43
44 async resolve(did: string): Promise<DidDocument | null> {
45 if (this.cache) {
46 const cached = await this.cache.checkCache(did);
47 if (cached && !cached.expired) {
48 if (cached.stale) {
49 this.cache.refreshCache(
50 did,
51 () => this.resolveNoCache(did),
52 cached,
53 );
54 }
55 return cached.doc;
56 }
57 }
58
59 const doc = await this.resolveNoCache(did);
60
61 if (doc && this.cache) {
62 await this.cache.cacheDid(did, doc);
63 } else if (!doc && this.cache) {
64 await this.cache.clearEntry(did);
65 }
66
67 return doc;
68 }
69
70 private async resolveNoCache(did: string): Promise<DidDocument | null> {
71 const controller = new AbortController();
72 const timeoutId = setTimeout(() => controller.abort(), this.timeout);
73
74 try {
75 const doc = await this.resolver.resolve(did as Did<"plc" | "web">, {
76 signal: controller.signal,
77 });
78 if (doc.id !== did) {
79 return null;
80 }
81 return doc;
82 } catch {
83 return null;
84 } finally {
85 clearTimeout(timeoutId);
86 }
87 }
88}