Files

61 lines
2.2 KiB
JavaScript

// 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());
},
};
}