import { openDB } from "idb"; import { useCallback, useEffect, useState } from "react"; import { chatApi } from "../api/chat"; const DB_NAME = "nexus-offline"; const STORE = "message_queue"; interface QueueEntry { conversationId: string; content: string; queuedAt: number; } async function getDb() { return openDB(DB_NAME, 1, { upgrade(db) { db.createObjectStore(STORE, { autoIncrement: true }); }, }); } /** * Offline message queue backed by IndexedDB. * Enqueues messages when offline; auto-flushes when the online event fires. */ export function useOfflineQueue(): { enqueue: (conversationId: string, content: string) => Promise; flush: () => Promise; queuedCount: number; } { const [queuedCount, setQueuedCount] = useState(0); // On mount, read current queue count from IndexedDB useEffect(() => { let cancelled = false; getDb() .then((db) => db.count(STORE)) .then((count) => { if (!cancelled) setQueuedCount(count); }) .catch(() => { // IndexedDB unavailable (SSR or private browsing edge case) — ignore }); return () => { cancelled = true; }; }, []); const flush = useCallback(async () => { let db; try { db = await getDb(); } catch { return; } const allKeys = await db.getAllKeys(STORE); for (const key of allKeys) { const entry = (await db.get(STORE, key)) as QueueEntry | undefined; if (!entry) continue; try { await chatApi.postMessage(entry.conversationId, { role: "user", content: entry.content, }); await db.delete(STORE, key); setQueuedCount((c) => Math.max(0, c - 1)); } catch { // Stop flushing on first failure — retry next time the online event fires break; } } }, []); const enqueue = useCallback( async (conversationId: string, content: string) => { let db; try { db = await getDb(); } catch { return; } await db.add(STORE, { conversationId, content, queuedAt: Date.now() }); setQueuedCount((c) => c + 1); }, [], ); // Auto-flush when reconnected useEffect(() => { window.addEventListener("online", flush); return () => window.removeEventListener("online", flush); }, [flush]); return { enqueue, flush, queuedCount }; }