A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import { useEffect, useMemo, useState } from 'react';
2import { useAtProto } from '../providers/AtProtoProvider';
3
4/**
5 * Resolves a handle to its DID, or returns the DID immediately when provided.
6 *
7 * @param handleOrDid - Bluesky handle or DID string.
8 * @returns {{ did: string | undefined; error: Error | undefined; loading: boolean }} Object containing the resolved DID, error (if any), and loading state.
9 */
10export function useDidResolution(handleOrDid: string | undefined) {
11 const { resolver, didCache } = useAtProto();
12 const [did, setDid] = useState<string | undefined>();
13 const [handle, setHandle] = useState<string | undefined>();
14 const [error, setError] = useState<Error | undefined>();
15 const [loading, setLoading] = useState(false);
16
17 const normalizedInput = useMemo(() => {
18 if (!handleOrDid) return undefined;
19 const trimmed = handleOrDid.trim();
20 return trimmed || undefined;
21 }, [handleOrDid]);
22
23 useEffect(() => {
24 let cancelled = false;
25 const reset = () => {
26 setDid(undefined);
27 setHandle(undefined);
28 setError(undefined);
29 setLoading(false);
30 };
31 if (!normalizedInput) {
32 reset();
33 return () => { cancelled = true; };
34 }
35
36 const isDid = normalizedInput.startsWith('did:');
37 const normalizedHandle = !isDid ? normalizedInput.toLowerCase() : undefined;
38 const cached = isDid
39 ? didCache.getByDid(normalizedInput)
40 : didCache.getByHandle(normalizedHandle);
41
42 const initialDid = cached?.did ?? (isDid ? normalizedInput : undefined);
43 const initialHandle = cached?.handle ?? (!isDid ? normalizedHandle : undefined);
44
45 setError(undefined);
46 setDid(initialDid);
47 setHandle(initialHandle);
48
49 const needsHandleResolution = !isDid && !cached?.did;
50 const needsDocResolution = isDid && (!cached?.doc || cached.handle === undefined);
51
52 if (!needsHandleResolution && !needsDocResolution) {
53 setLoading(false);
54 return () => { cancelled = true; };
55 }
56
57 setLoading(true);
58
59 (async () => {
60 try {
61 let snapshot = cached;
62 if (!isDid && normalizedHandle && needsHandleResolution) {
63 snapshot = await didCache.ensureHandle(resolver, normalizedHandle);
64 }
65
66 if (isDid) {
67 snapshot = await didCache.ensureDidDoc(resolver, normalizedInput);
68 }
69
70 if (!cancelled) {
71 const resolvedDid = snapshot?.did ?? (isDid ? normalizedInput : undefined);
72 const resolvedHandle = snapshot?.handle ?? (!isDid ? normalizedHandle : undefined);
73 setDid(resolvedDid);
74 setHandle(resolvedHandle);
75 setError(undefined);
76 }
77 } catch (e) {
78 if (!cancelled) {
79 setError(e as Error);
80 }
81 } finally {
82 if (!cancelled) setLoading(false);
83 }
84 })();
85
86 return () => { cancelled = true; };
87 }, [normalizedInput, resolver, didCache]);
88
89 return { did, handle, error, loading };
90}