4.2 KiB, 1 run
created by r2519314175:1421, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | /* pwa.js — what makes Daimond an app on a phone rather than a bookmark. |
| 2 | * |
| 3 | * Three small jobs, none of which changes anything a user sees in a browser tab: |
| 4 | * |
| 5 | * 1. Register the shell worker (`sw.js`). See that file for the caching rule |
| 6 | * and why a product that refuses stale code is allowed a cache at all. |
| 7 | * 2. Hand the worker the build id `js/updater.js` has just read, so the two |
| 8 | * share one notion of which build is live rather than each forming their own. |
| 9 | * 3. Keep `<meta name="theme-color">` on the palette the user chose. In |
| 10 | * standalone mode this is the colour the platform paints its own furniture |
| 11 | * with, so a static value would leave a light palette wearing a near-black |
| 12 | * status bar. |
| 13 | * |
| 14 | * Registration is deliberately quiet: a browser that has no service workers, a |
| 15 | * page opened over plain http, a user who has switched them off -- each is a |
| 16 | * normal way to run Daimond, and none of them is worth a message. |
| 17 | */ |
| 18 | (function () { |
| 19 | 'use strict'; |
| 20 | |
| 21 | /// The one flag `sw.js` takes, forwarded from the page's own URL. |
| 22 | /// |
| 23 | /// It can only switch the cache ON, and production has it on regardless, so |
| 24 | /// the worst it can do is make a dev server behave like production -- which |
| 25 | /// is exactly what dev/verify_pwa.mjs needs in order to prove the caching |
| 26 | /// rules against a real browser. See sw.js for why the cache is off on a |
| 27 | /// loopback host to begin with. |
| 28 | function scriptUrl() { |
| 29 | var forced = false; |
| 30 | try { forced = new URLSearchParams(location.search).get('cache') === 'on'; } catch (e) {} |
| 31 | return forced ? 'sw.js?cache=on' : 'sw.js'; |
| 32 | } |
| 33 | |
| 34 | function register() { |
| 35 | if (!('serviceWorker' in navigator)) return; |
| 36 | // A worker needs a secure context. `file://` and plain http on a LAN |
| 37 | // address are both ordinary ways to open this, and neither is an error. |
| 38 | if (!window.isSecureContext) return; |
| 39 | navigator.serviceWorker.register(scriptUrl(), { scope: './' }) |
| 40 | .catch(function () { /* no worker, no cache; the app is unaffected. */ }); |
| 41 | } |
| 42 | |
| 43 | /// Tell the worker which build the server is on. |
| 44 | /// |
| 45 | /// `js/updater.js` reads build.json no-store, at boot and on every poll, and |
| 46 | /// calls this with what it found. The worker acts on it immediately: a |
| 47 | /// different id empties the shell cache there and then, rather than at the |
| 48 | /// next navigation. |
| 49 | function tellBuild(build) { |
| 50 | if (!build || !('serviceWorker' in navigator)) return; |
| 51 | var c = navigator.serviceWorker.controller; |
| 52 | if (!c) return; |
| 53 | try { c.postMessage({ type: 'daimond-build', build: build }); } catch (e) {} |
| 54 | } |
| 55 | |
| 56 | /// The platform's own furniture, painted the colour of the app's ground. |
| 57 | /// |
| 58 | /// Read from the live stylesheet rather than listed here, so the eleven |
| 59 | /// palettes need no second table to fall out of step with. |
| 60 | function paint() { |
| 61 | var meta = document.querySelector('meta[name="theme-color"]'); |
| 62 | if (!meta) return; |
| 63 | var bg = getComputedStyle(document.documentElement) |
| 64 | .getPropertyValue('--bg-primary').trim(); |
| 65 | if (bg) meta.setAttribute('content', bg); |
| 66 | } |
| 67 | |
| 68 | function init() { |
| 69 | register(); |
| 70 | paint(); |
| 71 | // The palette is set by an attribute on <html> -- by the pre-paint script |
| 72 | // in index.html, and again by DaimondTheme whenever the user picks one. |
| 73 | // Watching the attribute catches both, and anything added later, without |
| 74 | // a second place having to remember to call this. |
| 75 | try { |
| 76 | new MutationObserver(paint).observe(document.documentElement, |
| 77 | { attributes: true, attributeFilter: ['data-theme'] }); |
| 78 | } catch (e) { /* no observer: the boot colour stands. */ } |
| 79 | } |
| 80 | |
| 81 | if (document.readyState === 'loading') { |
| 82 | document.addEventListener('DOMContentLoaded', init); |
| 83 | } else { |
| 84 | init(); |
| 85 | } |
| 86 | |
| 87 | window.DaimondPWA = { |
| 88 | tellBuild: tellBuild, |
| 89 | /// What the worker believes, asked of the worker itself. Null when there |
| 90 | /// is none. Used by dev/verify_pwa.mjs, and worth having on a phone. |
| 91 | state: function () { |
| 92 | return new Promise(function (res) { |
| 93 | var c = navigator.serviceWorker && navigator.serviceWorker.controller; |
| 94 | if (!c) { res(null); return; } |
| 95 | var ch = new MessageChannel(); |
| 96 | var done = false; |
| 97 | ch.port1.onmessage = function (e) { done = true; res(e.data); }; |
| 98 | setTimeout(function () { if (!done) res(null); }, 2000); |
| 99 | try { c.postMessage({ type: 'daimond-sw-state' }, [ch.port2]); } |
| 100 | catch (e) { res(null); } |
| 101 | }); |
| 102 | }, |
| 103 | }; |
| 104 | })(); |