/* * Offline copy of the public calculator (field staff often have weak signal). * - The calculator page: network first (fresh parameters), stored copy when * offline or slow. Responses marked no-store (admin views) are never kept. * - Versioned assets (assets/...?v=): stored copy first. * - calc.php, admin/ and everything else: network only, never stored. */ 'use strict'; var CACHE = 'serubi-v1'; var PAGE_TIMEOUT_MS = 4000; var SCOPE = self.registration.scope; // e.g. https://host/ or http://localhost/calcserubi-prod/ var PAGE_KEY = SCOPE; // one stored copy of the calculator, whatever the query string self.addEventListener('install', function () { self.skipWaiting(); }); self.addEventListener('activate', function (event) { event.waitUntil(caches.keys().then(function (keys) { return Promise.all(keys.filter(function (k) { return k !== CACHE; }).map(function (k) { return caches.delete(k); })); }).then(function () { return self.clients.claim(); })); }); function isCalculatorPage(url) { var rel = url.href.slice(SCOPE.length).split('?')[0]; return rel === '' || rel === 'index.php'; } function storable(res) { return res && res.ok && res.type === 'basic' && !/no-store/i.test(res.headers.get('Cache-Control') || ''); } function pageNetworkFirst(request) { return caches.open(CACHE).then(function (cache) { var network = fetch(request).then(function (res) { if (storable(res)) cache.put(PAGE_KEY, res.clone()); return res; }); network.catch(function () { /* handled below */ }); var timeout = new Promise(function (resolve) { setTimeout(resolve, PAGE_TIMEOUT_MS); }); // slow network: answer from the stored copy after the timeout, if there is one return Promise.race([network, timeout.then(function () { return cache.match(PAGE_KEY).then(function (c) { return c || network; }); })]) .catch(function () { return cache.match(PAGE_KEY).then(function (c) { if (c) return c; throw new Error('offline'); }); }); }); } function assetCacheFirst(request) { return caches.open(CACHE).then(function (cache) { return cache.match(request).then(function (hit) { if (hit) return hit; return fetch(request).then(function (res) { if (storable(res)) { // keep only the newest version (?v=) of each asset var path = new URL(request.url).pathname; cache.keys().then(function (keys) { keys.forEach(function (k) { if (new URL(k.url).pathname === path && k.url !== request.url) cache.delete(k); }); }); cache.put(request, res.clone()); } return res; }); }); }); } self.addEventListener('fetch', function (event) { var req = event.request; if (req.method !== 'GET') return; var url = new URL(req.url); if (url.origin !== self.location.origin || req.url.indexOf(SCOPE) !== 0) return; if (req.mode === 'navigate' && isCalculatorPage(url)) { event.respondWith(pageNetworkFirst(req)); } else if (url.pathname.indexOf('/assets/') !== -1) { event.respondWith(assetCacheFirst(req)); } // anything else (calc.php, admin/, ...) goes straight to the network });