dev vouch dev on at. thats about it atvouch.dev
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

frontend: use getRoutes xrpc instead of microcosm

Luna bfacddcd bf93de6e

+26 -85
+1 -1
frontend/public/client-metadata.json
··· 3 3 "client_name": "atvouch", 4 4 "client_uri": "http://127.0.0.1:3051", 5 5 "redirect_uris": ["http://127.0.0.1:3051/"], 6 - "scope": "atproto repo?collection=dev.atvouch.graph.vouch rpc:dev.atvouch.graph.getCurrentUserVouches?aud=* rpc:dev.atvouch.graph.getRemoteVouches?aud=*", 6 + "scope": "atproto repo?collection=dev.atvouch.graph.vouch rpc:dev.atvouch.graph.getCurrentUserVouches?aud=* rpc:dev.atvouch.graph.getRemoteVouches?aud=* rpc:dev.atvouch.graph.getRoutes?aud=*", 7 7 "grant_types": ["authorization_code", "refresh_token"], 8 8 "response_types": ["code"], 9 9 "token_endpoint_auth_method": "none",
+24 -83
frontend/src/api.ts
··· 7 7 8 8 // Slingshot: DID resolution 9 9 const SLINGSHOT = "https://slingshot.microcosm.blue"; 10 - // Microcosm: reverse graph queries 11 - const MICROCOSM = "https://constellation.microcosm.blue"; 12 - 13 10 // Create an authenticated XRPC client that proxies through the user's PDS 14 11 function appviewProxyRpc(agent: OAuthUserAgent): XRPC { 15 12 const rpc = new XRPC({ handler: agent }); ··· 81 78 return data; 82 79 } 83 80 84 - // Fetch who vouches for an arbitrary DID (via Microcosm reverse graph) 85 - export async function fetchVouchers(targetDID: string): Promise<string[]> { 86 - const params = new URLSearchParams({ 87 - target: targetDID, 88 - collection: "dev.atvouch.graph.vouch", 89 - path: ".subject", 90 - }); 91 - const resp = await fetch(`${MICROCOSM}/links/distinct-dids?${params}`, { 92 - headers: { Accept: "application/json" }, 93 - }); 94 - if (!resp.ok) throw new Error(`Microcosm error: ${resp.status}`); 95 - const data: { linking_dids: string[] } = await resp.json(); 96 - return data.linking_dids; 97 - } 98 - 99 81 // --- Session & repo operations (via user's PDS) --- 100 82 101 83 export interface SessionInfo { ··· 170 152 }); 171 153 } 172 154 173 - // List all DIDs the user has vouched for (via appview proxy) 174 - async function listMyVouches(agent: OAuthUserAgent): Promise<string[]> { 175 - const rpc = appviewProxyRpc(agent); 176 - const resp = await rpc.get("dev.atvouch.graph.getCurrentUserVouches" as any, {}); 177 - const data = resp.data as unknown as { vouches: AppviewVouchView[] }; 178 - return data.vouches.map((v) => v.targetDid); 179 - } 180 - 181 155 // --- Vouch listing (via appview proxy) --- 182 156 183 157 export interface VouchEntry { ··· 241 215 handleMap: Record<string, string>; 242 216 } 243 217 218 + interface AppviewRoutesResponse { 219 + target: string; 220 + directVouch: boolean; 221 + routes: { path: string[] }[]; 222 + } 223 + 244 224 export async function checkVouchPaths(agent: OAuthUserAgent, handle: string): Promise<CheckResult> { 245 225 const targetDID = await resolveHandle(handle); 246 - const myDID = agent.sub; 247 - const myVouches = await listMyVouches(agent); 248 226 249 - // Direct vouch check 250 - if (myVouches.includes(targetDID)) { 251 - return { targetDID, directVouch: true, paths: [], handleMap: {} }; 252 - } 227 + const rpc = appviewProxyRpc(agent); 228 + const resp = await rpc.get("dev.atvouch.graph.getRoutes" as any, { 229 + params: { target: targetDID }, 230 + }); 231 + const data = resp.data as unknown as AppviewRoutesResponse; 253 232 254 - // Build reverse graph from target via Microcosm (up to 3 levels) 255 - const reverseGraph: Record<string, Set<string>> = {}; 233 + const paths = data.routes.map((r) => r.path); 256 234 257 - // Level 1: who vouches for target 258 - const level1 = await fetchVouchers(targetDID); 259 - reverseGraph[targetDID] = new Set(level1); 260 - 261 - // Level 2: who vouches for each level-1 voucher 262 - const level2DIDs: string[] = []; 263 - for (const did of level1) { 264 - const vouchers = await fetchVouchers(did); 265 - reverseGraph[did] = new Set(vouchers); 266 - level2DIDs.push(...vouchers); 267 - } 268 - 269 - // Level 3: who vouches for each level-2 voucher 270 - for (const did of level2DIDs) { 271 - if (reverseGraph[did]) continue; 272 - const vouchers = await fetchVouchers(did); 273 - reverseGraph[did] = new Set(vouchers); 274 - } 275 - 276 - // Find paths 277 - const myVouchSet = new Set(myVouches); 278 - const paths: string[][] = []; 279 - 280 - // Depth 2: me -> X -> target 281 - for (const voucher of reverseGraph[targetDID] ?? []) { 282 - if (myVouchSet.has(voucher)) { 283 - paths.push([myDID, voucher, targetDID]); 284 - } 285 - } 286 - 287 - // Depth 3: me -> X -> Y -> target 288 - for (const yDID of reverseGraph[targetDID] ?? []) { 289 - for (const xDID of reverseGraph[yDID] ?? []) { 290 - if (myVouchSet.has(xDID)) { 291 - paths.push([myDID, xDID, yDID, targetDID]); 292 - } 293 - } 294 - } 295 - 296 - // Resolve DIDs to handles 235 + // Resolve DIDs to handles for display 297 236 const handleMap: Record<string, string> = {}; 298 237 if (paths.length > 0) { 299 238 const uniqueDIDs = new Set(paths.flat()); 300 239 handleMap[targetDID] = handle; 301 240 302 - for (const did of uniqueDIDs) { 303 - if (handleMap[did]) continue; 304 - try { 305 - handleMap[did] = await resolveDidToHandle(did); 306 - } catch { 307 - handleMap[did] = did; 308 - } 309 - } 241 + await Promise.all( 242 + [...uniqueDIDs].map(async (did) => { 243 + if (handleMap[did]) return; 244 + try { 245 + handleMap[did] = await resolveDidToHandle(did); 246 + } catch { 247 + handleMap[did] = did; 248 + } 249 + }), 250 + ); 310 251 } 311 252 312 - return { targetDID, directVouch: false, paths, handleMap }; 253 + return { targetDID, directVouch: data.directVouch, paths, handleMap }; 313 254 }
+1 -1
frontend/src/auth.ts
··· 17 17 18 18 const STORAGE_KEY = "atvouch_did"; 19 19 const AUTH_VERSION_KEY = "authVersion"; 20 - const AUTH_VERSION = 1; 20 + const AUTH_VERSION = 2; 21 21 22 22 export function initOAuth() { 23 23 configureOAuth({