Files
werkbaum/frontend/public/sw.js
T
mhoennigandClaude Fable 5 19053eba1d feat: Als PWA installierbar — Manifest, Offline-Worker (network-first), Dateihandling (D73)
Manifest mit Icons aus der Marke und Standalone-Fenster; ein bewusst dummer
Service Worker beantwortet nur die App-Navigation network-first und hält die
zuletzt gesehene Fassung für den Offline-Fall — die Update-Prüfung (D45)
bleibt dadurch unverändert wahr, der skipWaiting-Lebenszyklus entfällt. Die
installierte App registriert sich für .werkbaum-Dateien (file_handlers +
launchQueue -> adoptFile). Beide Deploy-Wege kopieren die App-Hülle mit;
.htaccess liefert .webmanifest mit MIME-Typ aus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:03:22 +02:00

61 lines
2.3 KiB
JavaScript

/* Werkbaum — Service Worker (D73).
*
* Bewusst ein dummer Offline-Mantel, kein App-Verwalter: Navigationen gehen
* NETWORK-FIRST (der Server ist die Quelle der Wahrheit, wie ohne Worker),
* der Cache hält nur die zuletzt gesehene Fassung der einen self-contained
* Datei (D19) für den Offline-Fall bereit. Drei Folgen, alle Absicht:
*
* - Die Update-Prüfung (D45) bleibt wahr: ihr fetch() ist keine Navigation
* und läuft unangefasst ans Netz; „Jetzt laden" ist eine Navigation und
* bekommt network-first die frische Fassung. Kein skipWaiting-Tanz,
* keine zweite Update-Logik.
* - ?sourceUrl=- und Pad-Abrufe (D23/D31) werden nie abgefangen — der
* Worker fasst ausschließlich die Navigation zur App-Wurzel an; llms.md,
* llms.txt und alles Fremde gehen unverändert durch.
* - Diese Datei ändert sich praktisch nie: Die App kommt vom Server, nicht
* aus dem Worker — es gibt keine Versionsnummer, die hier gepflegt werden
* müsste.
*/
const CACHE = 'werkbaum-shell';
self.addEventListener('install', e => {
/* Die Shell sofort vorhalten, damit Offline schon nach dem ersten Besuch
funktioniert (die erste Navigation lief noch ohne Worker). {cache:
'reload'} umgeht den HTTP-Cache — vorgehalten wird, was der Server
JETZT sagt. Scheitert das (Installation offline), bleibt der Cache
leer und die nächste erfolgreiche Navigation füllt ihn. */
e.waitUntil(
caches.open(CACHE)
.then(c => fetch('./', { cache: 'reload' })
.then(r => { if (r.ok) return c.put('./', r); }))
.catch(() => {})
);
self.skipWaiting();
});
self.addEventListener('activate', e => {
e.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', e => {
/* Nur die Navigation zur App selbst; alles andere geht unangefasst durch. */
if (e.request.mode !== 'navigate') return;
const path = new URL(e.request.url).pathname;
const scopePath = new URL(self.registration.scope).pathname;
if (path !== scopePath && path !== scopePath + 'index.html') return;
e.respondWith((async () => {
try {
const r = await fetch(e.request);
if (r.ok) {
const copy = r.clone();
e.waitUntil(caches.open(CACHE).then(c => c.put('./', copy)));
}
return r;
} catch (_) {
const m = await caches.match('./');
return m || Response.error();
}
})());
});