// Browser prompt and worker objects stay outside Alpine's reactive state. export function setupPWA({installAvailable, updateAvailable, installed, failed}) { let prompt; let registration; const cleanup = []; const listen = (target, name, handler) => { target.addEventListener(name, handler); cleanup.push(() => target.removeEventListener(name, handler)); }; const standalone = () => window.matchMedia('(display-mode: standalone)').matches || !!navigator.standalone; installed(standalone()); listen(window, 'beforeinstallprompt', event => { event.preventDefault(); prompt = event; installAvailable(true); }); listen(window, 'appinstalled', () => { prompt = undefined; installAvailable(false); installed(true); }); let destroyed = false; if ('serviceWorker' in navigator) { let controlled = !!navigator.serviceWorker.controller; listen(navigator.serviceWorker, 'controllerchange', () => { if (controlled) window.location.reload(); controlled = true; }); navigator.serviceWorker.register('./sw.js', {scope: './', type: 'module', updateViaCache: 'none'}) .then(value => { if (destroyed) return; registration = value; const showUpdate = () => updateAvailable(!!registration.waiting); showUpdate(); listen(registration, 'updatefound', () => { if (registration.installing) listen(registration.installing, 'statechange', showUpdate); }); }).catch(() => { if (!destroyed) failed(); }); } return { async install() { if (!prompt) return; const pending = prompt; prompt = undefined; installAvailable(false); try { await pending.prompt(); await pending.userChoice; } catch { /* Manual installation guidance remains available. */ } }, update() { registration?.waiting?.postMessage('ACTIVATE_UPDATE'); }, destroy() { destroyed = true; cleanup.forEach(remove => remove()); }, }; }