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 () => {
34 cancelled = true;
35 };
36 }
37
38 const isDid = normalizedInput.startsWith("did:");
39 const normalizedHandle = !isDid
40 ? normalizedInput.toLowerCase()
41 : undefined;
42 const cached = isDid
43 ? didCache.getByDid(normalizedInput)
44 : didCache.getByHandle(normalizedHandle);
45
46 const initialDid = cached?.did ?? (isDid ? normalizedInput : undefined);
47 const initialHandle =
48 cached?.handle ?? (!isDid ? normalizedHandle : undefined);
49
50 setError(undefined);
51 setDid(initialDid);
52 setHandle(initialHandle);
53
54 const needsHandleResolution = !isDid && !cached?.did;
55 const needsDocResolution =
56 isDid && (!cached?.doc || cached.handle === undefined);
57
58 if (!needsHandleResolution && !needsDocResolution) {
59 setLoading(false);
60 return () => {
61 cancelled = true;
62 };
63 }
64
65 setLoading(true);
66
67 (async () => {
68 try {
69 let snapshot = cached;
70 if (!isDid && normalizedHandle && needsHandleResolution) {
71 snapshot = await didCache.ensureHandle(
72 resolver,
73 normalizedHandle,
74 );
75 }
76
77 if (isDid) {
78 snapshot = await didCache.ensureDidDoc(
79 resolver,
80 normalizedInput,
81 );
82 }
83
84 if (!cancelled) {
85 const resolvedDid =
86 snapshot?.did ?? (isDid ? normalizedInput : undefined);
87 const resolvedHandle =
88 snapshot?.handle ??
89 (!isDid ? normalizedHandle : undefined);
90 setDid(resolvedDid);
91 setHandle(resolvedHandle);
92 setError(undefined);
93 }
94 } catch (e) {
95 if (!cancelled) {
96 setError(e as Error);
97 }
98 } finally {
99 if (!cancelled) setLoading(false);
100 }
101 })();
102
103 return () => {
104 cancelled = true;
105 };
106 }, [normalizedInput, resolver, didCache]);
107
108 return { did, handle, error, loading };
109}