import { Client } from "@atcute/client"; import { CompositeDidDocumentResolver, CompositeHandleResolver, DohJsonHandleResolver, PlcDidDocumentResolver, AtprotoWebDidDocumentResolver, WellKnownHandleResolver, } from "@atcute/identity-resolver"; import { configureOAuth, defaultIdentityResolver, OAuthUserAgent, } from "@atcute/oauth-browser-client"; export const didDocumentResolver = new CompositeDidDocumentResolver({ methods: { plc: new PlcDidDocumentResolver(), web: new AtprotoWebDidDocumentResolver(), }, }); export const handleResolver = new CompositeHandleResolver({ strategy: "dns-first", methods: { dns: new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }), http: new WellKnownHandleResolver(), }, }); const OAUTH_CLIENT_ID = import.meta.env.VITE_OAUTH_CLIENT_ID; const OAUTH_REDIRECT_URI = import.meta.env.VITE_OAUTH_REDIRECT_URI; if (typeof window !== "undefined") { configureOAuth({ metadata: { client_id: OAUTH_CLIENT_ID, redirect_uri: OAUTH_REDIRECT_URI, }, identityResolver: defaultIdentityResolver({ handleResolver, didDocumentResolver, }), }); } // module-level state export let agent: OAuthUserAgent | null = null; export let currentDid: string | null = null; export const setAgent = (a: OAuthUserAgent | null) => { agent = a; }; export const setCurrentDid = (did: string | null) => { currentDid = did; }; export const getAgent = () => agent; export const getCurrentDid = () => currentDid; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const getRpc = (): any => { if (!agent) throw new Error("not logged in"); return new Client({ handler: agent }); }; export const getPdsUrl = (): string | null => { if (!agent) return null; // eslint-disable-next-line @typescript-eslint/no-explicit-any const session = (agent as any).session; return session?.info?.aud || null; }; // handle resolution const handleCache = new Map(); export const resolveHandle = async (did: string): Promise => { if (handleCache.has(did)) return handleCache.get(did)!; try { const res = await fetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${did}`); if (res.ok) { const data = await res.json(); if (data.handle) { handleCache.set(did, data.handle); return data.handle; } } } catch { /* ignore */ } return did; }; // PDS URL resolution from DID document const pdsCache = new Map(); export const resolvePdsUrl = async (did: string): Promise => { if (pdsCache.has(did)) return pdsCache.get(did)!; try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const doc = await didDocumentResolver.resolve(did as any); const pds = doc.service?.find(s => s.id === "#atproto_pds"); if (pds && typeof pds.serviceEndpoint === "string") { pdsCache.set(did, pds.serviceEndpoint); return pds.serviceEndpoint; } } catch { /* ignore */ } return "https://bsky.social"; }; // public API fetch (no auth required) export const publicFetch = async (pdsUrl: string, method: string, params: Record) => { const url = new URL(`/xrpc/${method}`, pdsUrl); Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); const res = await fetch(url.toString()); if (!res.ok) return null; return res.json(); };