declare global { interface Window { __P2PDS_TOKEN__: string; } } const TOKEN = window.__P2PDS_TOKEN__; const HEADERS = { Authorization: "Bearer " + TOKEN }; export async function apiFetch(endpoint: string, params?: Record): Promise { const url = new URL("/xrpc/" + endpoint, location.origin); if (params) { for (const [k, v] of Object.entries(params)) { url.searchParams.set(k, v); } } const res = await fetch(url.toString(), { headers: HEADERS }); return res.json(); } export async function apiPost(endpoint: string, body: unknown): Promise { const url = new URL("/xrpc/" + endpoint, location.origin); const res = await fetch(url.toString(), { method: "POST", headers: { ...HEADERS, "Content-Type": "application/json" }, body: JSON.stringify(body), }); return res.json(); } export interface SyncProgressEvent { type: string; did: string; sourceType?: string; carBytes?: number; blocksStored?: number; blocksSizeKb?: number; blobsFetched?: number; blobsTotal?: number; blobBytes?: number; durationMs?: number; error?: string; missingBlocks?: number; } /** * Connect to the SSE sync progress stream. * Returns a cleanup function to close the connection. */ export function connectSyncProgress(onEvent: (event: SyncProgressEvent) => void): () => void { const url = new URL("/xrpc/org.p2pds.app.syncProgress", location.origin); url.searchParams.set("token", TOKEN); const es = new EventSource(url.toString()); const eventTypes = [ "sync:start", "sync:car-received", "sync:blocks-stored", "sync:verified", "sync:blob-progress", "sync:complete", "sync:error", "sync-cycle:start", "sync-cycle:complete", ]; for (const type of eventTypes) { es.addEventListener(type, (e: MessageEvent) => { try { const data = JSON.parse(e.data); onEvent(data); } catch { /* ignore parse errors */ } }); } es.onerror = () => { // EventSource auto-reconnects; no action needed }; return () => es.close(); }