sw.js RawHistoryFolder
1// sw.js: the service worker of the installed app, served at /-/sw.js with
2// Service-Worker-Allowed: / (see serviceworker.go).
3//
4// Browsers want an installed app to have a service worker with a fetch handler
5// (Chrome on Android mints a WebAPK for such sites, and drops an empty handler
6// as if there were none). A forge has nothing to cache: pages are rendered per
7// caller (the owner sees private repositories), diffs and logs are live. So
8// this worker stores nothing. A page navigation goes to the network as it would
9// without the worker; only when the network is unreachable is the visitor shown
10// a short offline page instead of the browser's error. Every other request
11// (assets, the event stream, Git, POSTs) is left to the browser: the handler
12// does not answer it.
13//
14// The browser fetches this file again on each navigation (at least once a day)
15// and installs the new version when a single byte differs; edit it to ship a
16// new version. The worker takes over at once (skipWaiting, claim): it holds no
17// state.
18'use strict';
19
20const OFFLINE = `<!doctype html>
21<html lang="en"><head><meta charset="utf-8">
22<meta name="viewport" content="width=device-width, initial-scale=1">
23<meta name="color-scheme" content="light dark">
24<title>ermite is unreachable</title></head>
25<body style="font: 16px/1.5 system-ui, sans-serif; max-width: 32rem; margin: 20vh auto; padding: 0 1rem">
26<h1 style="font-size: 1.25rem">ermite is unreachable</h1>
27<p>The server does not answer. Check the connection, then reload.</p>
28</body></html>`;
29
30self.addEventListener('install', () => self.skipWaiting());
31
32self.addEventListener('activate', (event) => {
33 // Navigation preload starts the page request while the worker boots, so the
34 // worker adds no latency to a navigation.
35 event.waitUntil(Promise.all([
36 self.registration.navigationPreload?.enable(),
37 self.clients.claim(),
38 ]));
39});
40
41self.addEventListener('fetch', (event) => {
42 const req = event.request;
43 if (req.mode !== 'navigate' || req.method !== 'GET') return;
44 event.respondWith((async () => {
45 try {
46 return (await event.preloadResponse) || (await fetch(req));
47 } catch {
48 return new Response(OFFLINE, {
49 status: 503,
50 headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' },
51 });
52 }
53 })());
54});