Files
weerscherm/public/sw.js
T
theoandClaude Opus 5 c0715b7cc9 feat: assen bij de luchtdrukgrafiek
De lijn had geen schaal, dus je kon niet zien hoeveel hPa de uitslag was
of wanneer een dip viel. Nu links de hoogste en laagste waarde van het
venster in hele hPa, uitgelijnd op boven- en onderkant van de lijn, en
eronder vijf tijdlabels (0/25/50/75/100% van het venster) met "nu"
rechts, ondersteund door drie lichte verticale hulplijnen. Posities
komen uit de pure functie axisMarks(), met tests.

De losse tekst "1009 – 1015 hPa in 24 u" in de kopregel vervalt: de
y-as zegt hetzelfde en de x-as maakt het venster duidelijk. De extra
labelrij past door de marge onder de grote temperatuur 8px te krappen;
er blijft 5px speling boven en onder de hero. Servicewerker naar v8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 11:30:11 +02:00

91 lines
2.7 KiB
JavaScript

/* Service worker: app-shell cache-first, API network-first met timeout en fallback op de laatste response. */
const VERSION = 'weerscherm-v8';
const SHELL = [
'./',
'./index.html',
'./styles.css',
'./app.js',
'./format.js',
'./icons.js',
'./manifest.webmanifest',
'./icons/icon.svg',
'./icons/icon-180.png',
'./icons/icon-192.png',
'./icons/icon-512.png',
];
const API_PREFIX = '/api/';
const DATA_PATH = '/api/data.php';
const API_TIMEOUT_MS = 8000;
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(VERSION).then((cache) => cache.addAll(SHELL)).then(() => self.skipWaiting()),
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then((keys) => Promise.all(keys.filter((k) => k !== VERSION).map((k) => caches.delete(k))))
.then(() => self.clients.claim()),
);
});
self.addEventListener('fetch', (event) => {
const req = event.request;
if (req.method !== 'GET') return;
const url = new URL(req.url);
if (url.origin !== self.location.origin) return;
if (url.pathname.includes(API_PREFIX)) {
event.respondWith(networkFirst(req));
} else {
event.respondWith(cacheFirst(req));
}
});
async function networkFirst(req) {
const cache = await caches.open(VERSION);
const url = new URL(req.url);
// Sleutel zonder querystring: het radarbeeld krijgt elke paar minuten een andere ?t=, en zonder
// normaliseren zou de cache vollopen met varianten en zou offline nooit een treffer geven.
const key = url.origin + url.pathname;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
try {
const res = await fetch(req, { signal: controller.signal, cache: 'no-store' });
if (res.ok) {
cache.put(key, res.clone());
}
return res;
} catch (err) {
const cached = await cache.match(key);
if (cached) {
const headers = new Headers(cached.headers);
headers.set('X-From-Cache', '1');
return new Response(await cached.arrayBuffer(), { status: 200, statusText: 'OK', headers });
}
if (url.pathname.endsWith(DATA_PATH)) {
return new Response(JSON.stringify({ errors: ['offline'] }), {
status: 503,
headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' },
});
}
return new Response('offline', { status: 503, headers: { 'Cache-Control': 'no-store' } });
} finally {
clearTimeout(timer);
}
}
async function cacheFirst(req) {
const cache = await caches.open(VERSION);
const cached = await cache.match(req, { ignoreSearch: true });
if (cached) return cached;
const res = await fetch(req);
if (res.ok) {
cache.put(req, res.clone());
}
return res;
}