atproto explorer

add schema tab to any record

handle.invalid 00e9a156 110e2301

verified
+45 -19
+12 -3
src/utils/api.ts
··· 13 13 PlcDidDocumentResolver, 14 14 WellKnownHandleResolver, 15 15 } from "@atcute/identity-resolver"; 16 - import { DohJsonLexiconAuthorityResolver } from "@atcute/lexicon-resolver"; 16 + import { DohJsonLexiconAuthorityResolver, LexiconSchemaResolver } from "@atcute/lexicon-resolver"; 17 17 import { Did, Handle } from "@atcute/lexicons"; 18 - import { isHandle, Nsid } from "@atcute/lexicons/syntax"; 18 + import { AtprotoDid, isHandle, Nsid } from "@atcute/lexicons/syntax"; 19 19 import { createStore } from "solid-js/store"; 20 20 import { setPDS } from "../components/navbar"; 21 21 22 22 const didDocumentResolver = new CompositeDidDocumentResolver({ 23 23 methods: { 24 24 plc: new PlcDidDocumentResolver({ 25 - apiUrl: localStorage.plcDirectory ?? "https://plc.directory", 25 + apiUrl: localStorage.getItem("plcDirectory") ?? "https://plc.directory", 26 26 }), 27 27 web: new AtprotoWebDidDocumentResolver(), 28 28 }, ··· 40 40 dohUrl: "https://mozilla.cloudflare-dns.com/dns-query", 41 41 }); 42 42 43 + const schemaResolver = new LexiconSchemaResolver({ 44 + didDocumentResolver: didDocumentResolver, 45 + }); 46 + 43 47 const didPDSCache: Record<string, string> = {}; 44 48 const [labelerCache, setLabelerCache] = createStore<Record<string, string>>({}); 45 49 const didDocCache: Record<string, DidDocument> = {}; ··· 106 110 107 111 const resolveLexiconAuthority = async (nsid: Nsid) => { 108 112 return await authorityResolver.resolve(nsid); 113 + }; 114 + 115 + const resolveLexiconSchema = async (authority: AtprotoDid, nsid: Nsid) => { 116 + return await schemaResolver.resolve(authority, nsid); 109 117 }; 110 118 111 119 interface LinkData { ··· 186 194 resolveDidDoc, 187 195 resolveHandle, 188 196 resolveLexiconAuthority, 197 + resolveLexiconSchema, 189 198 resolvePDS, 190 199 validateHandle, 191 200 type LinkData,
+33 -16
src/views/record.tsx
··· 1 1 import { Client, CredentialManager } from "@atcute/client"; 2 2 import { lexiconDoc } from "@atcute/lexicon-doc"; 3 + import { ResolvedSchema } from "@atcute/lexicon-resolver"; 3 4 import { ActorIdentifier, is, Nsid, ResourceUri } from "@atcute/lexicons"; 4 5 import { A, useLocation, useNavigate, useParams } from "@solidjs/router"; 5 6 import { createResource, createSignal, ErrorBoundary, Show, Suspense } from "solid-js"; ··· 14 15 import { pds } from "../components/navbar.jsx"; 15 16 import Tooltip from "../components/tooltip.jsx"; 16 17 import { setNotif } from "../layout.jsx"; 17 - import { didDocCache, resolveLexiconAuthority, resolvePDS } from "../utils/api.js"; 18 + import { 19 + didDocCache, 20 + resolveLexiconAuthority, 21 + resolveLexiconSchema, 22 + resolvePDS, 23 + } from "../utils/api.js"; 18 24 import { AtUri, uriTemplates } from "../utils/templates.js"; 19 25 import { lexicons } from "../utils/types/lexicons.js"; 20 26 import { verifyRecord } from "../utils/verify.js"; ··· 31 37 const [lexiconUri, setLexiconUri] = createSignal<string>(); 32 38 const [validRecord, setValidRecord] = createSignal<boolean | undefined>(undefined); 33 39 const [validSchema, setValidSchema] = createSignal<boolean | undefined>(undefined); 40 + const [schema, setSchema] = createSignal<ResolvedSchema>(); 41 + const [lexiconNotFound, setLexiconNotFound] = createSignal<boolean>(); 34 42 const did = params.repo; 35 43 let rpc: Client; 36 44 ··· 72 80 if (is(lexicons[params.collection], record.value)) setValidSchema(true); 73 81 else setValidSchema(false); 74 82 } else if (params.collection === "com.atproto.lexicon.schema") { 83 + setLexiconNotFound(false); 75 84 try { 76 85 lexiconDoc.parse(record.value, { mode: "passthrough" }); 77 86 setValidSchema(true); ··· 101 110 102 111 const resolveLexicon = async (nsid: Nsid) => { 103 112 try { 104 - const res = await resolveLexiconAuthority(nsid); 105 - setLexiconUri(`at://${res}/com.atproto.lexicon.schema/${nsid}`); 106 - } catch {} 113 + const authority = await resolveLexiconAuthority(nsid); 114 + setLexiconUri(`at://${authority}/com.atproto.lexicon.schema/${nsid}`); 115 + if (params.collection !== "com.atproto.lexicon.schema") { 116 + const schema = await resolveLexiconSchema(authority, nsid); 117 + setSchema(schema); 118 + setLexiconNotFound(false); 119 + } 120 + } catch { 121 + setLexiconNotFound(true); 122 + } 107 123 }; 108 124 109 125 const deleteRecord = async () => { ··· 166 182 <div class="dark:shadow-dark-700 dark:bg-dark-300 mb-3 flex w-full justify-between rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm shadow-xs dark:border-neutral-700"> 167 183 <div class="flex gap-3"> 168 184 <RecordTab tab="record" label="Record" /> 169 - <Show when={params.collection === "com.atproto.lexicon.schema"}> 170 - <RecordTab tab="schema" label="Schema" /> 171 - </Show> 185 + <RecordTab tab="schema" label="Schema" /> 172 186 <RecordTab tab="backlinks" label="Backlinks" /> 173 187 <RecordTab tab="info" label="Info" error /> 174 188 </div> ··· 237 251 <JSONValue data={record()?.value as any} repo={record()!.uri.split("/")[2]} /> 238 252 </div> 239 253 </Show> 240 - <Show 241 - when={ 242 - (location.hash === "#schema" || location.hash.startsWith("#schema:")) && 243 - params.collection === "com.atproto.lexicon.schema" 244 - } 245 - > 246 - <ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}> 247 - <LexiconSchemaView schema={record()?.value as any} /> 248 - </ErrorBoundary> 254 + <Show when={location.hash === "#schema" || location.hash.startsWith("#schema:")}> 255 + <Show when={lexiconNotFound() === true}> 256 + <span class="w-full px-2 text-sm">Lexicon schema could not be resolved.</span> 257 + </Show> 258 + <Show when={lexiconNotFound() === undefined}> 259 + <span class="w-full px-2 text-sm">Resolving lexicon schema...</span> 260 + </Show> 261 + <Show when={schema() || params.collection === "com.atproto.lexicon.schema"}> 262 + <ErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}> 263 + <LexiconSchemaView schema={schema()?.rawSchema ?? (record()?.value as any)} /> 264 + </ErrorBoundary> 265 + </Show> 249 266 </Show> 250 267 <Show when={location.hash === "#backlinks"}> 251 268 <ErrorBoundary fallback={(err) => <div class="break-words">Error: {err.message}</div>}>