Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/www/js/pwa.js

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})();