// Session-level avatar cache. Avatars are small (<100KB), referenced from // many places (chat list, bubbles, dialogs), and rarely change — so keeping // one blob-URL per remote URL avoids repeated decodes + network 304s across // remounts. OPFS is optional; the in-memory map is enough for daily use. const blobUrls = new Map(); // remote URL → blob URL const inflight = new Map>(); async function fetchAsBlobUrl(url: string): Promise { try { const res = await fetch(url, { cache: 'force-cache' }); if (!res.ok) return null; const blob = await res.blob(); return URL.createObjectURL(blob); } catch { return null; } } // Returns a cached blob URL for the avatar. Falls back to the original URL // if caching fails so rendering never breaks. export function useCachedAvatarUrl(url: string | null | undefined): string | null | undefined { if (!url) return url; return blobUrls.get(url) ?? url; } // Eagerly populate the cache for a list of URLs (e.g. conversation members) // so subsequent renders hit the Map directly. export function warmAvatarCache(urls: Array): void { for (const url of urls) { if (!url) continue; if (blobUrls.has(url)) continue; if (inflight.has(url)) continue; const p = fetchAsBlobUrl(url).then((blobUrl) => { if (blobUrl) blobUrls.set(url, blobUrl); inflight.delete(url); return blobUrl; }); inflight.set(url, p); } }