/*! trentpower.fr · authored source */ /* trentpower.fr service-worker registration Role: registers /sw.js for deterministic offline navigation, and owns the tp-app Trusted Types policy. the page CSP enforces require-trusted-types-for 'script'; trusted-types tp-app; so the one TrustedScriptURL sink on the whole site — navigator.serviceWorker.register — must route through a policy. Source: edited here as sw-register.template.js; compiled to sw-register.js by generate_site.py (minified, no substitution). Constraints: - no fetch, no inline handlers, no third-party scripts - enhancement only: offline support; the site works without it */ (function () { 'use strict'; // tp-app trusted types policy. the only script-URL sink on the site // is the service-worker registration below, so createScriptURL is a // single exact-match gate — no prefix allowlist, no sanitiser. function _safeScriptURL(s) { if (s !== '/sw.js') { throw new Error('tp-app: script URL not allowed: ' + s); } return s; } var ttPolicy = (typeof window !== 'undefined' && window.trustedTypes && typeof window.trustedTypes.createPolicy === 'function') ? window.trustedTypes.createPolicy('tp-app', { createScriptURL: _safeScriptURL }) : null; function trustedScriptURL(value) { return ttPolicy ? ttPolicy.createScriptURL(value) : value; } // tp-sw-meta · device-local timestamps for the /local/ diagnostics // page. sw.js postMessages { type: 'tp-sw-installed' | 'tp-sw-activated', at } // after the precache settles and after clients.claim() runs; we // capture both into a single localStorage key so /local/ can show // when the offline cache was first installed and when the current // SW edition activated. lastCheckedAt is updated by /local/ itself // every time it queries the registration. function _recordSwMeta(field, when) { try { var raw = localStorage.getItem('tp-sw-meta'); var meta = raw ? JSON.parse(raw) : {}; meta[field] = new Date(when || Date.now()).toISOString(); localStorage.setItem('tp-sw-meta', JSON.stringify(meta)); } catch (_) { /* storage disabled — silent skip */ } } if ('serviceWorker' in navigator) { try { navigator.serviceWorker.addEventListener('message', function (ev) { if (!ev || !ev.data) return; if (ev.data.type === 'tp-sw-installed') _recordSwMeta('installedAt', ev.data.at); else if (ev.data.type === 'tp-sw-activated') _recordSwMeta('activatedAt', ev.data.at); }); } catch (_) {} } // defer registration until after `load` so it stays off the critical // render path. sw.js calls skipWaiting() + clients.claim(), so a // deploy reaches return visitors on the next navigation without a // manual reg.update() nudge. all errors are silent in production; // `?debug-sw=1` opts into operator-side diagnostic logging. if ('serviceWorker' in navigator && location.protocol === 'https:') { var swDebug = location.search.indexOf('debug-sw=1') !== -1; window.addEventListener('load', function () { try { navigator.serviceWorker.register(trustedScriptURL('/sw.js'), { scope: '/' }) .then(function (reg) { if (swDebug) { try { console.info('[tp] service worker registered', reg && reg.scope); } catch (_) {} } }) .catch(function (err) { if (swDebug) { try { console.warn('[tp] service worker registration skipped', err); } catch (_) {} } }); } catch (err) { if (swDebug) { try { console.warn('[tp] service worker registration unavailable', err); } catch (_) {} } } }, { once: true }); } })();