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