// "Last seen" backfill for buddies we have no live data for (the offline group). // // A repo's latest commit `rev` is a TID that encodes the microsecond timestamp // of their most recent write of ANY kind — post, reply, like, follow, even a // delete. So it's the truest "last did something" signal, and it catches pure // lurkers that getAuthorFeed would miss. The relay answers getLatestCommit for // any DID in ONE CORS-enabled call, so no per-PDS resolution is needed. const RELAY = 'https://bsky.network'; const B32 = '234567abcdefghijklmnopqrstuvwxyz'; // TID base32-sortable alphabet // TID (13 chars) -> milliseconds. Top 53 bits are micros since epoch; low 10 // bits are a clock id, so shift them off. export function tidToMs(tid) { if (!tid || tid.length < 10) return 0; let n = 0n; for (const c of tid) { const i = B32.indexOf(c); if (i < 0) return 0; n = n * 32n + BigInt(i); } return Number(n >> 10n) / 1000; } // Last-write time (ms) for one DID, or 0 if unknown/error. export async function fetchLastSeen(did) { try { const r = await fetch( `${RELAY}/xrpc/com.atproto.sync.getLatestCommit?did=${encodeURIComponent(did)}`, { headers: { accept: 'application/json' } }); if (!r.ok) return 0; const { rev } = await r.json(); return tidToMs(rev); } catch { return 0; } } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // Backfill a list of DIDs politely: a small worker pool with a stagger between // requests so we trickle rather than burst. Calls onResult(did, ms) per success; // stops early if signal.aborted (e.g. on sign-off). export async function backfillLastSeen(dids, { onResult, signal, concurrency = 4, staggerMs = 100 } = {}) { let i = 0; async function worker() { while (i < dids.length) { if (signal?.aborted) return; const did = dids[i++]; const ms = await fetchLastSeen(did); if (signal?.aborted) return; if (ms) onResult(did, ms); await sleep(staggerMs); } } await Promise.all(Array.from({ length: Math.min(concurrency, dids.length) }, worker)); }