# Service worker

## Hvad det er

En service worker er et script, som et websted registrerer hos browseren, og som
browseren derefter kører for sig, adskilt fra alle sider;
netværksforespørgslerne fra de sider, scriptet styrer, går igennem det. Service
workers er en slags web worker: hver af dem kører i sin egen tråd, uden dokument
og uden vindue. Browseren starter en, når der er noget, den skal tage sig af, og
må stoppe den igen, så snart der ikke er mere at gøre, så alt, hvad den holder i
en variabel, kan være væk ved næste hændelse.

En side installerer en med ét kald, og specifikationen stiller tre betingelser
for det. Siden skal være en sikker kontekst, hvilket i praksis betyder HTTPS,
med `localhost` og loopback-adresserne tilladt under udvikling. Scriptet skal
have samme origin som siden, så det kan ikke ligge på et CDN, men det må gerne
importere kode derfra. Og registreringen dækker et område, et såkaldt scope: som
standard den mappe, scriptet ligger i, og intet bredere, medmindre serveren
sender en `Service-Worker-Allowed`-header med scriptet.

```js
// på siden
navigator.serviceWorker.register("/sw.js");

// i /sw.js
async function answer(request) {
    const copy = await caches.match(request);
    return copy ?? fetch(request);
}

self.addEventListener("fetch", (event) => {
    event.respondWith(answer(event.request));
});
```

Denne worker svarer på hver forespørgsel med sin egen kopi, når den har en, og
ellers fra netværket. Det er livscyklussens opgave at lægge kopierne der. Når
scriptet er hentet, udløser browseren `install`, tidspunktet til at hente og
gemme det, workeren får brug for; bliver et promise, der er givet til
`event.waitUntil()`, afvist, mislykkes hele installationen. Derefter kommer
`activate`, tidspunktet til at rydde op efter en ældre version, og først da
modtager workeren `fetch`-hændelser. En side kobles til sin worker, når den
indlæses, så den side, der registrerede den allerførste worker, kører videre
uden, indtil den genindlæses, medmindre workeren kalder `clients.claim()`.

Kopierne ligger i Cache Storage, som samme specifikation definerer: navngivne
cacher med par af forespørgsler og svar, som workeren selv fylder, læser og
sletter. De er adskilt fra browserens HTTP-cache. En post udløber aldrig,
opdateres aldrig af sig selv og overlever en opdatering af workerens script, så
den bliver liggende, indtil workeren sletter den, eller browseren rydder
webstedets lager.

## Hvorfor det betyder noget

Det er den eneste del af webplatformen, der lader et websted fungere helt uden
netværk. En service worker afgør forespørgsel for forespørgsel, i kode,
webstedet selv har skrevet, om svaret skal komme fra netværket, fra dens egne
kopier eller fra begge, og det er det, der gør en installeret webapp brugbar på
et skib eller i en kælder. Push-beskeder når også frem til den, når ingen af
webstedets sider er åbne.

De fleste workers koger ned til at vælge en strategi for hver slags
forespørgsel, og Workbox, et bibliotek til at skrive dem, navngiver fem. Cache
først svarer fra kopien og går kun til netværket, når kopien mangler, og egner
sig til filer, der aldrig ændrer sig under samme navn. Netværk først spørger
netværket og falder tilbage på kopien og passer til sider.
Stale-while-revalidate svarer fra kopien og opdaterer den i baggrunden. Kun
netværk og kun cache er de to yderpunkter.

Det er også kode, der står mellem hver side i sit scope og netværket, på
læserens enhed, og den overlever besøget: en fejl i den bliver, indtil et rettet
script afløser den. Specifikationen er bygget op om den risiko. Hver navigation
får browseren til at se efter et nyt script, og andre hændelser gør det, når der
er gået et døgn siden sidste tjek. Som standard bliver hovedscriptet
genvalideret hos serveren i stedet for at blive taget fra HTTP-cachen, så en
lang `max-age` på worker-filen kan ikke holde en defekt version på plads;
importerede scripts får samme behandling, når der er gået et døgn, eller altid,
når `updateViaCache` er `none`. Browseren sammenligner det hentede script med
det installerede byte for byte, og kun en forskel sætter en opdatering i gang.
Et shift-reload springer workeren over for den ene navigation, og derfor kan en
fejl se rettet ud på en udviklers maskine.

Reglen om at vente er den anden halvdel af det design. En ny version installeres
ved siden af den gamle og venter derefter, ubrugt, indtil ingen åben side
længere afhænger af den gamle, så to versioner aldrig deles om at betjene én
side. Kalder workeren `skipWaiting()`, tager den over med det samme, og prisen
er, at ny kode kører under sider, som den gamle har indlæst.

Lager er den sidste omkostning. Cache Storage tæller med i den plads, webstedet
optager på læserens enhed, og efter Storage Standard er den plads som standard
best-effort: browseren må rydde den, når enheden mangler plads, medmindre
webstedet har bedt om vedvarende lager og fået det.

## Sådan ser det ud i praksis

Behovet er tydeligst, hvor netværket er svagest.
[Den progressive webapp til søfarende og feltmedarbejdere](/da/portfolio/delivered-full-progressive-web-app-support-installable-and-74/)
betjener folk, der ofte er offline eller på en dårlig forbindelse. Dens service
worker er sat op via next-pwa-pluginet, og Workbox' runtime-caching bruger cache
først til det, der er det samme fra forespørgsel til forespørgsel, altså
skrifttyper, billeder, lyd, video og CDN-filer, side om side med HTTP-headere
til cachestyring. Fordi service workers kun for alvor fungerer over HTTPS, blev
offline- og installationsadfærden testet over HTTPS på rigtige enheder, og
gentagne indlæsninger kommer fra cachen i stedet for fra netværket.

Dette websted vælger det modsatte og siger det. [Cookiesiden](/da/cookies/)
oplyser, at webstedet hverken har en service worker, IndexedDB eller Cache
Storage: intet fra det kører i baggrunden, før læseren åbner det, eller efter at
læseren lukker det, og der ligger ingen offlinekopi af det på læserens enhed.
Det eneste script, det leverer, omkring en kilobyte af egen kode, filtrerer
listerne på de sider, der har en.

[Installationssiden](/da/install/) drager konsekvensen for alle, der lægger
webstedet på en hjemmeskærm: offlineunderstøttelse kræver en service worker, et
script, der holder sin egen kopi af webstedet på enheden, så den installerede
app kræver forbindelse.

En worker registreres fra script, og skabelonen for sidens `head` begrænser,
hvor script overhovedet må køre: dens Content-Security-Policy siger `script-src
'none'` på hver side uden listefilteret og `'self'` på dem med det. Dens
kommentar noterer et build, hvor 550 af 793 dokumenter angav `'none'` og 243
angav `'self'`, præcis det antal, der har scriptet.

## Hvor det går galt

**Den gamle version bliver ved med at svare efter en udrulning.** Den nye worker
installeres og venter derefter, så længe en åben fane stadig bruger den gamle,
så en læser, der aldrig lukker den fane, bliver ved med at køre den gamle kode.
Kald `skipWaiting()`, når den nye version er sikker under sider, som den gamle
har indlæst, eller vis en besked om, at en opdatering er klar, og lad siden bede
den ventende worker om at kalde den.

**Sider bliver hængende i en gammel version i ugevis.** En cache-først-strategi
brugt på HTML besvarer hver navigation fra Cache Storage, hvis poster aldrig
udløber og ser bort fra serverens cache-headere. Brug kun cache først til filer,
hvis navn skifter, når indholdet gør, og netværk først til sider.

**Installationen mislykkes, og den gamle worker bliver.** Én adresse på
installationstrinnets liste svarer med en fejl, så `cache.addAll()` afviser, det
promise, der er givet til `waitUntil()`, afvises med det, og den nye version
kasseres. Byg listen ud fra filer, der findes, og hent valgfrie filer senere i
stedet for under installationen.

**Workeren overser det meste af webstedet.** Et script, der serveres fra
`/js/sw.js`, har `/js/` som både sit standard-scope og sit bredeste scope, så
sider uden for den mappe indlæses uden det. Servér scriptet fra webstedets rod,
eller send `Service-Worker-Allowed: /` med det.

**Lageret på enheden vokser med hver udgivelse.** Cacher overlever en opdatering
af workerens script, så hver version, der åbner en cache under et nyt navn,
efterlader den forrige. Sæt et versionsnummer i hvert cachenavn, og slet i
`activate`-handleren hver cache, som den aktuelle version ikke bruger.

## Læs mere

- [Service Workers](https://www.w3.org/TR/service-workers/) — registrering,
  scope, opdateringstjekket, livscyklussen og Cache Storage.
- [Secure Contexts](https://www.w3.org/TR/secure-contexts/) — hvilke origins der
  regnes for potentielt troværdige, `localhost` og loopback-adresserne
  medregnet.
- [Fetch Standard](https://fetch.spec.whatwg.org/) — de `Request`- og
  `Response`-objekter, en `fetch`-handler modtager og returnerer, og hvordan en
  forespørgsel gives videre til en service worker.
- [Storage Standard](https://storage.spec.whatwg.org/) — de storage buckets,
  Cache Storage ligger i, kvoteoverslag og hvordan et websted beder om
  vedvarende lager.
- [Workbox: workbox-strategies](https://developer.chrome.com/docs/workbox/modules/workbox-strategies)
  — de fem caching-strategier og den slags forespørgsel, hver af dem passer til.

<https://engineer.company/da/glossary/service-worker/>
