| 1 | // The app calls install once in its classic worker, alongside aviso's handlers. |
| 2 | // Keeping the fallback in the worker avoids caching a login redirect or a page |
| 3 | // rendered for the account that happened to install it. |
| 4 | (function (root) { |
| 5 | "use strict"; |
| 6 | const fallback = `<!doctype html><html lang="en"><meta charset="utf-8"> |
| 7 | <meta name="viewport" content="width=device-width,initial-scale=1"> |
| 8 | <title>You're offline</title><style>body{font:1.1rem system-ui;margin:4rem auto;padding:0 1.5rem;max-width:32rem;line-height:1.6}a{color:inherit}</style> |
| 9 | <main><h1>You're offline</h1><p>Connect to the internet, then try again.</p><a href="">Try again</a></main></html>`; |
| 10 | let installed = false; |
| 11 | |
| 12 | function install({ offlineHTML = fallback } = {}) { |
| 13 | if (installed) throw new Error("pwa: install may only be called once per worker"); |
| 14 | if (typeof offlineHTML !== "string" || !offlineHTML.trim()) { |
| 15 | throw new Error("pwa: offlineHTML must be a non-empty public HTML document"); |
| 16 | } |
| 17 | installed = true; |
| 18 | const scope = new URL(root.registration.scope); |
| 19 | function inScope(raw) { |
| 20 | const url = new URL(raw); |
| 21 | return url.origin === scope.origin && url.pathname.startsWith(scope.pathname); |
| 22 | } |
| 23 | |
| 24 | root.addEventListener("fetch", (event) => { |
| 25 | const request = event.request; |
| 26 | if (request.method !== "GET" || request.mode !== "navigate" || !inScope(request.url)) return; |
| 27 | // Never turn HTTP errors into offline successes, and never persist the |
| 28 | // network response: it may contain a signed-in person's data. |
| 29 | event.respondWith(root.fetch(request, { cache: "no-store" }).catch(() => new Response(offlineHTML, { |
| 30 | status: 503, |
| 31 | headers: { |
| 32 | "Content-Type": "text/html; charset=utf-8", |
| 33 | "Cache-Control": "no-store", |
| 34 | "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", |
| 35 | }, |
| 36 | }))); |
| 37 | }); |
| 38 | |
| 39 | root.addEventListener("message", (event) => { |
| 40 | if (event.data?.type !== "rastrillo:pwa:activate" || !event.source?.url || !inScope(event.source.url)) return; |
| 41 | event.waitUntil(root.skipWaiting()); |
| 42 | }); |
| 43 | // No unconditional skipWaiting or clients.claim: an older tab may still |
| 44 | // have unsaved edits. The app chooses when all tabs can accept an update. |
| 45 | } |
| 46 | |
| 47 | root.RastrilloPWA = { install }; |
| 48 | })(self); |
| 49 | |