forked from
pds.ls/pdsls
this repo has no description
1import "@atcute/atproto";
2import {
3 type DidDocument,
4 getLabelerEndpoint,
5 getPdsEndpoint,
6 isAtprotoDid,
7} from "@atcute/identity";
8import {
9 AtprotoWebDidDocumentResolver,
10 CompositeDidDocumentResolver,
11 CompositeHandleResolver,
12 DohJsonHandleResolver,
13 PlcDidDocumentResolver,
14 WellKnownHandleResolver,
15 XrpcHandleResolver,
16} from "@atcute/identity-resolver";
17import { Did, Handle } from "@atcute/lexicons";
18import { isHandle } from "@atcute/lexicons/syntax";
19import { createStore } from "solid-js/store";
20import { setPDS } from "../components/navbar";
21
22const didDocumentResolver = new CompositeDidDocumentResolver({
23 methods: {
24 plc: new PlcDidDocumentResolver({
25 apiUrl: localStorage.plcDirectory ?? "https://plc.directory",
26 }),
27 web: new AtprotoWebDidDocumentResolver(),
28 },
29});
30
31const handleResolver = new XrpcHandleResolver({
32 serviceUrl: "https://public.api.bsky.app",
33});
34
35const didPDSCache: Record<string, string> = {};
36const [labelerCache, setLabelerCache] = createStore<Record<string, string>>({});
37const didDocCache: Record<string, DidDocument> = {};
38const getPDS = async (did: string) => {
39 if (did in didPDSCache) return didPDSCache[did];
40
41 if (!isAtprotoDid(did)) {
42 throw new Error("Not a valid DID identifier");
43 }
44
45 const doc = await didDocumentResolver.resolve(did);
46 didDocCache[did] = doc;
47
48 const pds = getPdsEndpoint(doc);
49 const labeler = getLabelerEndpoint(doc);
50
51 if (labeler) {
52 setLabelerCache(did, labeler);
53 }
54
55 if (!pds) {
56 throw new Error("No PDS found");
57 }
58
59 return (didPDSCache[did] = pds);
60};
61
62const resolveHandle = async (handle: Handle) => {
63 if (!isHandle(handle)) {
64 throw new Error("Not a valid handle");
65 }
66
67 return await handleResolver.resolve(handle);
68};
69
70const resolveDidDoc = async (did: Did) => {
71 if (!isAtprotoDid(did)) {
72 throw new Error("Not a valid DID identifier");
73 }
74 return await didDocumentResolver.resolve(did);
75};
76
77const validateHandle = async (handle: Handle, did: Did) => {
78 if (!isHandle(handle)) return false;
79
80 const handleResolver = new CompositeHandleResolver({
81 strategy: "dns-first",
82 methods: {
83 dns: new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }),
84 http: new WellKnownHandleResolver(),
85 },
86 });
87
88 let resolvedDid: string;
89 try {
90 resolvedDid = await handleResolver.resolve(handle);
91 } catch (err) {
92 console.error(err);
93 return false;
94 }
95 if (resolvedDid !== did) return false;
96 return true;
97};
98
99const resolvePDS = async (did: string) => {
100 setPDS(undefined);
101 const pds = await getPDS(did);
102 if (!pds) throw new Error("No PDS found");
103 setPDS(pds.replace("https://", "").replace("http://", ""));
104 return pds;
105};
106
107interface LinkData {
108 links: {
109 [key: string]: {
110 [key: string]: {
111 records: number;
112 distinct_dids: number;
113 };
114 };
115 };
116}
117
118const getConstellation = async (
119 endpoint: string,
120 target: string,
121 collection?: string,
122 path?: string,
123 cursor?: string,
124 limit?: number,
125) => {
126 const url = new URL("https://constellation.microcosm.blue");
127 url.pathname = endpoint;
128 url.searchParams.set("target", target);
129 if (collection) {
130 if (!path) throw new Error("collection and path must either both be set or neither");
131 url.searchParams.set("collection", collection);
132 url.searchParams.set("path", path);
133 } else {
134 if (path) throw new Error("collection and path must either both be set or neither");
135 }
136 if (limit) url.searchParams.set("limit", `${limit}`);
137 if (cursor) url.searchParams.set("cursor", `${cursor}`);
138 const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
139 if (!res.ok) throw new Error("failed to fetch from constellation");
140 return await res.json();
141};
142
143const getAllBacklinks = (target: string) => getConstellation("/links/all", target);
144
145const getRecordBacklinks = (
146 target: string,
147 collection: string,
148 path: string,
149 cursor?: string,
150 limit?: number,
151) => getConstellation("/links", target, collection, path, cursor, limit || 100);
152
153const getDidBacklinks = (
154 target: string,
155 collection: string,
156 path: string,
157 cursor?: string,
158 limit?: number,
159) => getConstellation("/links/distinct-dids", target, collection, path, cursor, limit || 100);
160
161export {
162 didDocCache,
163 getAllBacklinks,
164 getDidBacklinks,
165 getPDS,
166 getRecordBacklinks,
167 labelerCache,
168 resolveDidDoc,
169 resolveHandle,
170 resolvePDS,
171 validateHandle,
172 type LinkData,
173};