forked from pdsls.dev/pdsls
atproto explorer
at main 4.7 kB view raw
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} from "@atcute/identity-resolver"; 16import { DohJsonLexiconAuthorityResolver } from "@atcute/lexicon-resolver"; 17import { Did, Handle } from "@atcute/lexicons"; 18import { isHandle, Nsid } 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 CompositeHandleResolver({ 32 strategy: "dns-first", 33 methods: { 34 dns: new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }), 35 http: new WellKnownHandleResolver(), 36 }, 37}); 38 39const authorityResolver = new DohJsonLexiconAuthorityResolver({ 40 dohUrl: "https://mozilla.cloudflare-dns.com/dns-query", 41}); 42 43const didPDSCache: Record<string, string> = {}; 44const [labelerCache, setLabelerCache] = createStore<Record<string, string>>({}); 45const didDocCache: Record<string, DidDocument> = {}; 46const getPDS = async (did: string) => { 47 if (did in didPDSCache) return didPDSCache[did]; 48 49 if (!isAtprotoDid(did)) { 50 throw new Error("Not a valid DID identifier"); 51 } 52 53 const doc = await didDocumentResolver.resolve(did); 54 didDocCache[did] = doc; 55 56 const pds = getPdsEndpoint(doc); 57 const labeler = getLabelerEndpoint(doc); 58 59 if (labeler) { 60 setLabelerCache(did, labeler); 61 } 62 63 if (!pds) { 64 throw new Error("No PDS found"); 65 } 66 67 return (didPDSCache[did] = pds); 68}; 69 70const resolveHandle = async (handle: Handle) => { 71 if (!isHandle(handle)) { 72 throw new Error("Not a valid handle"); 73 } 74 75 return await handleResolver.resolve(handle); 76}; 77 78const resolveDidDoc = async (did: Did) => { 79 if (!isAtprotoDid(did)) { 80 throw new Error("Not a valid DID identifier"); 81 } 82 return await didDocumentResolver.resolve(did); 83}; 84 85const validateHandle = async (handle: Handle, did: Did) => { 86 if (!isHandle(handle)) return false; 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 107const resolveLexiconAuthority = async (nsid: Nsid) => { 108 return await authorityResolver.resolve(nsid); 109}; 110 111interface LinkData { 112 links: { 113 [key: string]: { 114 [key: string]: { 115 records: number; 116 distinct_dids: number; 117 }; 118 }; 119 }; 120} 121 122const getConstellation = async ( 123 endpoint: string, 124 target: string, 125 collection?: string, 126 path?: string, 127 cursor?: string, 128 limit?: number, 129) => { 130 const url = new URL("https://constellation.microcosm.blue"); 131 url.pathname = endpoint; 132 url.searchParams.set("target", target); 133 if (collection) { 134 if (!path) throw new Error("collection and path must either both be set or neither"); 135 url.searchParams.set("collection", collection); 136 url.searchParams.set("path", path); 137 } else { 138 if (path) throw new Error("collection and path must either both be set or neither"); 139 } 140 if (limit) url.searchParams.set("limit", `${limit}`); 141 if (cursor) url.searchParams.set("cursor", `${cursor}`); 142 const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); 143 if (!res.ok) throw new Error("failed to fetch from constellation"); 144 return await res.json(); 145}; 146 147const getAllBacklinks = (target: string) => getConstellation("/links/all", target); 148 149const getRecordBacklinks = ( 150 target: string, 151 collection: string, 152 path: string, 153 cursor?: string, 154 limit?: number, 155) => getConstellation("/links", target, collection, path, cursor, limit || 100); 156 157const getDidBacklinks = ( 158 target: string, 159 collection: string, 160 path: string, 161 cursor?: string, 162 limit?: number, 163) => getConstellation("/links/distinct-dids", target, collection, path, cursor, limit || 100); 164 165export { 166 didDocCache, 167 getAllBacklinks, 168 getDidBacklinks, 169 getPDS, 170 getRecordBacklinks, 171 labelerCache, 172 resolveDidDoc, 173 resolveHandle, 174 resolveLexiconAuthority, 175 resolvePDS, 176 validateHandle, 177 type LinkData, 178};