- One base layout (layout.html) with the header, the nav from Registry.Nav()
(D-038), a content slot and the CSRF token; every page renders inside it and
a module supplies content only
- Embedded same-origin assets (app.css · app.js · webauthn.js · sw.js ·
manifest.webmanifest · icons) served by the reserved `static` workflow
- webauthn.js drives the three ceremonies from data-* attributes — no inline
script, so the CSP `script-src 'self'` holds (AC-3)
- manifest start_url "/" (D-011); the service worker never caches an
authenticated response or /{GUID} (A5)
79 lines
2.3 KiB
JavaScript
79 lines
2.3 KiB
JavaScript
// MyDay service worker (SPEC-009 §6, register A5).
|
|
//
|
|
// THE RULE: cache STATIC ASSETS ONLY. An authenticated response and a /{GUID}
|
|
// page are NEVER cached — and not merely "not cached": such a request is not
|
|
// intercepted by this worker at all, so it cannot reach a cache. Offline is
|
|
// explicitly out of scope.
|
|
//
|
|
// CACHEABLE_PREFIXES is kept in step with web.StaticCachePrefixes (Go) by
|
|
// TestAC5_ServiceWorkerPolicy — there is no second, drifting copy of the rule.
|
|
"use strict";
|
|
|
|
var CACHEABLE_PREFIXES = ["/static/"];
|
|
var CACHE_NAME = "myday-static-v1";
|
|
|
|
// isCacheable is the single gate: GET · same origin · a static asset.
|
|
function isCacheable(request) {
|
|
if (request.method !== "GET") {
|
|
return false;
|
|
}
|
|
var url = new URL(request.url);
|
|
if (url.origin !== self.location.origin) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < CACHEABLE_PREFIXES.length; i++) {
|
|
if (url.pathname.indexOf(CACHEABLE_PREFIXES[i]) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
self.addEventListener("install", function (event) {
|
|
// Nothing is precached (offline is out of scope) — just take over.
|
|
event.waitUntil(self.skipWaiting());
|
|
});
|
|
|
|
self.addEventListener("activate", function (event) {
|
|
event.waitUntil(
|
|
caches
|
|
.keys()
|
|
.then(function (keys) {
|
|
return Promise.all(
|
|
keys
|
|
.filter(function (key) {
|
|
return key !== CACHE_NAME;
|
|
})
|
|
.map(function (key) {
|
|
return caches.delete(key);
|
|
})
|
|
);
|
|
})
|
|
.then(function () {
|
|
return self.clients.claim();
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener("fetch", function (event) {
|
|
// Anything that is not a static asset is NOT intercepted: an authenticated
|
|
// response can never be read from — or written to — a cache (A5).
|
|
if (!isCacheable(event.request)) {
|
|
return;
|
|
}
|
|
event.respondWith(
|
|
caches.open(CACHE_NAME).then(function (cache) {
|
|
return cache.match(event.request).then(function (hit) {
|
|
if (hit) {
|
|
return hit;
|
|
}
|
|
return fetch(event.request).then(function (response) {
|
|
if (response && response.ok && response.type === "basic") {
|
|
cache.put(event.request, response.clone());
|
|
}
|
|
return response;
|
|
});
|
|
});
|
|
})
|
|
);
|
|
});
|