'use client'; import { createContext, useCallback, useContext, useEffect, useRef, useState, type ReactNode, } from 'react'; import { subscribeBroadcast } from '@/lib/queue/broadcast'; import { runSync } from '@/lib/queue/sync'; import { db } from '@/lib/queue/db'; interface SyncState { pendingCount: number; deadLetterCount: number; } const SyncCtx = createContext({ pendingCount: 0, deadLetterCount: 0 }); export const useSyncState = () => useContext(SyncCtx); export function SyncProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ pendingCount: 0, deadLetterCount: 0 }); const timerRef = useRef | null>(null); const refreshCounts = useCallback(async () => { const [p, d] = await Promise.all([db.pending.count(), db.deadLetters.count()]); setState({ pendingCount: p, deadLetterCount: d }); }, []); const sync = useCallback(async () => { await runSync(); await refreshCounts(); }, [refreshCounts]); useEffect(() => { refreshCounts(); const unsub = subscribeBroadcast(async () => refreshCounts()); const onOnline = () => sync(); const onVisible = () => { if (document.visibilityState === 'visible' && navigator.onLine) sync(); }; window.addEventListener('online', onOnline); document.addEventListener('visibilitychange', onVisible); // Poll every 10s as a fallback timerRef.current = setInterval(() => { if (navigator.onLine) sync(); }, 10_000); // Register Background Sync if SW + API available if ('serviceWorker' in navigator && 'SyncManager' in window) { navigator.serviceWorker.ready // eslint-disable-next-line @typescript-eslint/no-explicit-any .then((reg) => (reg as any).sync.register('mai-call-sync')) .catch(() => {/* not supported or permission denied */}); } // Initial sync attempt if (navigator.onLine) sync(); return () => { unsub(); window.removeEventListener('online', onOnline); document.removeEventListener('visibilitychange', onVisible); if (timerRef.current) clearInterval(timerRef.current); }; }, [sync, refreshCounts]); return {children}; }