Ein Feld, ein Knopf, grosse Flaeche. Was ohne Netz erfasst wird, liegt in IndexedDB, bis es hinausgeht — nicht in localStorage: Ein Eintrag, der beim Aufraeumen des Speichers verschwindet, ist eine verlorene Aufgabe. Erst anstellen, dann senden. Andersherum ginge der Eintrag verloren, wenn der Browser mitten im Senden geschlossen wird. Jede Erfassung traegt eine vom Geraet vergebene Kennung, damit eine doppelt zugestellte Nachricht keine zweite Aufgabe ergibt. Der Dienstarbeiter haelt genau diese eine Seite offline bereit und sonst nichts. Ein halb veralteter Planer waere schlimmer als gar keiner — ein Eingabefeld dagegen veraltet nicht, und ohne die Seite naehme die Warteschlange im Geraet niemandem etwas ab. Dazu das Manifest, damit sich der Planer als Anwendung ablegen laesst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
3.2 KiB
JavaScript
108 lines
3.2 KiB
JavaScript
/*
|
|
* Service Worker des Planers.
|
|
*
|
|
* Zwei Aufgaben: Push-Nachrichten anzeigen, und die Seite "Erfassen" auch
|
|
* ohne Netz bereithalten.
|
|
*
|
|
* Zwischengespeichert wird **genau diese eine Seite** und sonst nichts. Ein
|
|
* halb veralteter Planer wäre schlimmer als gar keiner — ein Eingabefeld
|
|
* dagegen veraltet nicht, und ohne die Seite nützte die Warteschlange im
|
|
* Gerät niemandem: Man käme gar nicht erst hin.
|
|
*/
|
|
|
|
const LAGER = 'planer-erfassen-v1'
|
|
const OFFLINE_SEITE = '/erfassen'
|
|
|
|
self.addEventListener('install', (ereignis) => {
|
|
ereignis.waitUntil(
|
|
caches
|
|
.open(LAGER)
|
|
.then((lager) => lager.add(new Request(OFFLINE_SEITE, { cache: 'reload' })))
|
|
.catch(() => undefined),
|
|
)
|
|
// Sofort übernehmen, nicht auf das Schließen aller Tabs warten.
|
|
self.skipWaiting()
|
|
})
|
|
|
|
self.addEventListener('activate', (ereignis) => {
|
|
ereignis.waitUntil(
|
|
caches
|
|
.keys()
|
|
.then((namen) =>
|
|
Promise.all(namen.filter((n) => n !== LAGER).map((n) => caches.delete(n))),
|
|
)
|
|
.then(() => self.clients.claim()),
|
|
)
|
|
})
|
|
|
|
self.addEventListener('fetch', (ereignis) => {
|
|
const anfrage = ereignis.request
|
|
if (anfrage.method !== 'GET') return
|
|
|
|
const adresse = new URL(anfrage.url)
|
|
if (adresse.origin !== self.location.origin) return
|
|
if (adresse.pathname !== OFFLINE_SEITE) return
|
|
|
|
// Zuerst das Netz: Wer online ist, bekommt die frische Seite. Erst wenn das
|
|
// scheitert, kommt die aufgehobene Fassung.
|
|
ereignis.respondWith(
|
|
fetch(anfrage)
|
|
.then((antwort) => {
|
|
if (antwort.ok) {
|
|
const kopie = antwort.clone()
|
|
void caches.open(LAGER).then((lager) => lager.put(OFFLINE_SEITE, kopie))
|
|
}
|
|
return antwort
|
|
})
|
|
.catch(() =>
|
|
caches
|
|
.match(OFFLINE_SEITE)
|
|
.then(
|
|
(aufgehoben) =>
|
|
aufgehoben ??
|
|
new Response('Offline und nichts aufgehoben.', {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
}),
|
|
),
|
|
),
|
|
)
|
|
})
|
|
|
|
self.addEventListener('push', (ereignis) => {
|
|
let daten = { titel: 'Planer', text: '', pfad: '/' }
|
|
try {
|
|
if (ereignis.data) daten = { ...daten, ...ereignis.data.json() }
|
|
} catch {
|
|
// Kaputte Nutzlast: lieber eine karge Meldung als gar keine.
|
|
}
|
|
|
|
ereignis.waitUntil(
|
|
self.registration.showNotification(daten.titel, {
|
|
body: daten.text,
|
|
lang: 'de',
|
|
// Gleiche Anlässe überschreiben einander, statt sich zu stapeln.
|
|
tag: daten.anlass || 'planer',
|
|
data: { pfad: daten.pfad },
|
|
}),
|
|
)
|
|
})
|
|
|
|
self.addEventListener('notificationclick', (ereignis) => {
|
|
ereignis.notification.close()
|
|
const pfad = (ereignis.notification.data && ereignis.notification.data.pfad) || '/'
|
|
|
|
ereignis.waitUntil(
|
|
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((fenster) => {
|
|
// Ist der Planer schon offen, dorthin wechseln statt ein zweites
|
|
// Fenster aufzumachen.
|
|
for (const f of fenster) {
|
|
if (f.url.includes(self.location.origin)) {
|
|
return f.focus().then(() => f.navigate(pfad))
|
|
}
|
|
}
|
|
return self.clients.openWindow(pfad)
|
|
}),
|
|
)
|
|
})
|