import { type Component, createEffect, onCleanup } from 'solid-js'; import { useRegisterSW } from 'virtual:pwa-register/solid'; import { toast } from 'solid-sonner'; // We export a function so other parts of the app can manually trigger an update let manualUpdateFn: (() => void) | undefined; let updateAndReloadFn: (() => void) | undefined; export const forceServiceWorkerUpdate = () => { if (manualUpdateFn) { manualUpdateFn(); } else { console.warn("PWA Updater not initialized yet"); } }; export const updateAndReloadApp = () => { if (updateAndReloadFn) { updateAndReloadFn(); } else { console.warn("PWA Updater not initialized yet; reloading directly."); window.location.reload(); } }; export const PwaUpdater: Component = () => { let registration: ServiceWorkerRegistration | undefined; const { needRefresh: [needRefresh], updateServiceWorker, } = useRegisterSW({ onRegistered(r: ServiceWorkerRegistration | undefined) { console.log('SW Registered:', r); registration = r; // Periodically check for updates (every 1 hour) if (r) { setInterval(() => { console.log('Periodic SW update check...'); r.update(); }, 60 * 60 * 1000); } }, onRegisterError(error: Error | any) { console.error('SW registration error', error); }, }); createEffect(() => { // Expose function globally manualUpdateFn = () => { if (needRefresh()) { console.log('User manually forced an update. Reloading now...'); updateServiceWorker(true); } else if (registration) { console.log('User manually asked to check for updates...'); registration.update(); } }; updateAndReloadFn = () => { if (needRefresh()) { console.log('Update ready. Reloading now...'); updateServiceWorker(true); return; } if (registration) { console.log('Checking for updates before reload...'); registration.update(); } const start = Date.now(); const timeoutMs = 5000; const poll = window.setInterval(() => { if (needRefresh()) { console.log('Update found. Reloading with new SW...'); window.clearInterval(poll); updateServiceWorker(true); } else if (Date.now() - start > timeoutMs) { console.log('No update detected quickly. Reloading now...'); window.clearInterval(poll); toast.info("No update found"); } }, 250); }; }); createEffect(() => { // Check for updates when the app regains focus/visibility const handleVisibilityFocus = () => { if (document.visibilityState === 'visible' && registration) { console.log('App became visible, checking for SW updates...'); registration.update(); } }; document.addEventListener('visibilitychange', handleVisibilityFocus); window.addEventListener('focus', handleVisibilityFocus); onCleanup(() => { document.removeEventListener('visibilitychange', handleVisibilityFocus); window.removeEventListener('focus', handleVisibilityFocus); }); }); createEffect(() => { if (needRefresh()) { console.log('New PWA version available. Monitoring for idle/hidden state to update...'); let idleTimeout: number | undefined; const triggerUpdate = () => { console.log('Triggering PWA update...'); updateServiceWorker(true); }; const resetIdle = () => { if (idleTimeout) window.clearTimeout(idleTimeout); // 10 minutes idle idleTimeout = window.setTimeout(triggerUpdate, 10 * 60 * 1000); }; const handleVisibility = () => { if (document.visibilityState === 'hidden') { triggerUpdate(); } }; // Start idle timer resetIdle(); // Listen for user activity const events = ['mousedown', 'mousemove', 'keydown', 'scroll', 'touchstart']; events.forEach(e => window.addEventListener(e, resetIdle, { passive: true })); document.addEventListener('visibilitychange', handleVisibility); onCleanup(() => { if (idleTimeout) window.clearTimeout(idleTimeout); events.forEach(e => window.removeEventListener(e, resetIdle)); document.removeEventListener('visibilitychange', handleVisibility); }); } }); return null; };