/** * The app shell for offline mode (#523): tells the service worker * (public/sw.js) to keep this build's pages and files while any account * keeps mail on this device, and to drop them when none does. */ import { apiFetch, withBasePath } from '@/lib/browser-navigation'; import { SW_SCOPE } from '@/lib/app-update'; /** The app's pages kept for a start without the network. */ const SHELL_PAGES = ['/', '/mail', '/settings']; async function shellFiles(): Promise { try { const res = await apiFetch('/api/offline/shell', { cache: 'no-store' }); if (!res.ok) return []; const data = (await res.json()) as { files?: unknown }; return Array.isArray(data.files) ? data.files.filter((f): f is string => typeof f === 'string') : []; } catch { return []; } } async function activeWorker(): Promise { if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return null; try { const registration = await navigator.serviceWorker.getRegistration(SW_SCOPE); return registration?.active ?? navigator.serviceWorker.controller ?? null; } catch { return null; } } export async function tellServiceWorker(on: boolean): Promise { const worker = await activeWorker(); if (!worker) return; const urls = on ? [...SHELL_PAGES.map((p) => withBasePath(p)), ...(await shellFiles())] : []; worker.postMessage({ type: 'offline-shell', on, urls }); } /** The service worker's copies of the shell, from the page (also when no worker runs). */ export async function deleteShellCaches(): Promise { if (typeof caches === 'undefined') return; try { const keys = await caches.keys(); await Promise.all(keys.filter((key) => key.startsWith('bulwark-shell-')).map((key) => caches.delete(key))); } catch { /* no Cache API */ } }