minimal streamplace frontend
at main 85 lines 2.5 kB view raw
1import "@atcute/atproto"; 2import { type DidDocument, getPdsEndpoint, isAtprotoDid } from "@atcute/identity"; 3import { 4 AtprotoWebDidDocumentResolver, 5 CompositeDidDocumentResolver, 6 CompositeHandleResolver, 7 DohJsonHandleResolver, 8 PlcDidDocumentResolver, 9 WellKnownHandleResolver, 10} from "@atcute/identity-resolver"; 11 12export const didDocumentResolver = new CompositeDidDocumentResolver({ 13 methods: { 14 plc: new PlcDidDocumentResolver(), 15 web: new AtprotoWebDidDocumentResolver(), 16 }, 17}); 18 19export const handleResolver = new CompositeHandleResolver({ 20 strategy: "dns-first", 21 methods: { 22 dns: new DohJsonHandleResolver({ dohUrl: "https://cloudflare-dns.com/dns-query" }), 23 http: new WellKnownHandleResolver(), 24 }, 25}); 26 27export const resolveHandle = async (handle: string): Promise<string> => { 28 return await handleResolver.resolve(handle as `${string}.${string}`); 29}; 30 31export const resolveDidDoc = async (did: string): Promise<DidDocument> => { 32 if (!isAtprotoDid(did)) { 33 throw new Error("Not a valid DID identifier"); 34 } 35 return await didDocumentResolver.resolve(did); 36}; 37 38const didPDSCache: Record<string, Promise<string>> = {}; 39 40export const getPDS = (did: string): Promise<string> => { 41 if (did in didPDSCache) return didPDSCache[did]; 42 43 if (!isAtprotoDid(did)) { 44 return Promise.reject(new Error("Not a valid DID identifier")); 45 } 46 47 didPDSCache[did] = (async () => { 48 const doc = await didDocumentResolver.resolve(did); 49 const pds = getPdsEndpoint(doc); 50 if (!pds) { 51 delete didPDSCache[did]; 52 throw new Error("No PDS found"); 53 } 54 return pds; 55 })(); 56 57 return didPDSCache[did]; 58}; 59 60export interface LiveUser { 61 did: string; 62 handle: string; 63 title: string; 64 viewerCount: number; 65 thumbRef?: string; 66} 67 68// eslint-disable-next-line @typescript-eslint/no-explicit-any 69function mapStream(stream: any): LiveUser { 70 return { 71 did: stream.author?.did || "", 72 handle: stream.author?.handle || "unknown", 73 title: stream.record?.title || "Untitled stream", 74 viewerCount: stream.viewerCount?.count ?? 0, 75 thumbRef: stream.record?.thumb?.ref?.$link, 76 }; 77} 78 79export const fetchLiveUsers = async (): Promise<LiveUser[]> => { 80 const res = await fetch("https://stream.place/xrpc/place.stream.live.getLiveUsers"); 81 if (!res.ok) throw new Error("Failed to fetch live users"); 82 const data = await res.json(); 83 const streams = data.streams || []; 84 return streams.map(mapStream); 85};