'use client' /** * useAvatarCache — cache de avatares em IndexedDB. * * Chave de cache: `${jid}:${version}` (version = hash curto do avatarUrl no banco). * Quando a versão muda (novo avatar no banco), a chave muda e o cache invalida * automaticamente — sem TTL fixo, sem stale data visível ao usuário. * * Armazenamento: dataURL base64 do JPEG/WebP (compatível com ). * Limpeza: entradas com mais de 30 dias são removidas na abertura do DB. */ const DB_NAME = 'nw-avatars' const STORE_NAME = 'blobs' const DB_VERSION = 1 const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000 // 30 dias interface CacheEntry { key: string // `${jid}:${version}` dataUrl: string savedAt: number } let dbPromise: Promise | null = null function openDB(): Promise { if (dbPromise) return dbPromise dbPromise = new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, DB_VERSION) req.onupgradeneeded = () => { const db = req.result if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: 'key' }) } } req.onsuccess = () => { const db = req.result pruneOldEntries(db) resolve(db) } req.onerror = () => { dbPromise = null; reject(req.error) } }) return dbPromise } function pruneOldEntries(db: IDBDatabase): void { const tx = db.transaction(STORE_NAME, 'readwrite') const store = tx.objectStore(STORE_NAME) const cutoff = Date.now() - MAX_AGE_MS const req = store.openCursor() req.onsuccess = () => { const cursor = req.result if (!cursor) return if ((cursor.value as CacheEntry).savedAt < cutoff) cursor.delete() cursor.continue() } } export async function getAvatarFromCache(jid: string, version: string): Promise { if (typeof window === 'undefined') return null try { const db = await openDB() const key = `${jid}:${version}` return new Promise((resolve) => { const tx = db.transaction(STORE_NAME, 'readonly') const req = tx.objectStore(STORE_NAME).get(key) req.onsuccess = () => resolve((req.result as CacheEntry | undefined)?.dataUrl ?? null) req.onerror = () => resolve(null) }) } catch { return null } } export async function setAvatarInCache(jid: string, version: string, dataUrl: string): Promise { if (typeof window === 'undefined') return try { const db = await openDB() const key = `${jid}:${version}` await new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, 'readwrite') const entry: CacheEntry = { key, dataUrl, savedAt: Date.now() } const req = tx.objectStore(STORE_NAME).put(entry) req.onsuccess = () => resolve() req.onerror = () => reject(req.error) }) } catch { /* quota exceeded ou IDB indisponível — ignora silenciosamente */ } }