···7/**
8 * Registers a service worker that makes the page available offline.
9 *
10- * All resources fetched by the page are cached as they load.
11 * While online, requests always go to the network (the cache is bypassed),
12 * and successful responses are stored for later. While offline, the cache
13 * is used as a fallback.
···7/**
8 * Registers a service worker that makes the page available offline.
9 *
10+ * All resources, except audio & video, fetched by the page are cached as they load.
11 * While online, requests always go to the network (the cache is bypassed),
12 * and successful responses are stored for later. While offline, the cache
13 * is used as a fallback.
···23import { create as createCid } from "./common/cid.js";
400005/** Media content types to ignore */
6const MEDIA_CONTENT_TYPE = /^(audio|video)\//;
7···48});
4950////////////////////////////////////////////
51-// CACHE (content-addressed)
52////////////////////////////////////////////
5354/**
···73}
7475/**
00000000000000076 * Computes the CID of `response`'s body and writes it into the two-level cache.
77 * The same content is stored only once, regardless of how many URLs reference it.
78 *
00079 * @param {Request} request
80 * @param {Response} response - a clone; its body is fully consumed here
81 */
82async function store(request, response) {
83- const bytes = new Uint8Array(await response.clone().arrayBuffer());
84- const cid = await createCid(RAW_CODEC, bytes);
000085 const cidKey = cidUrl(cid);
8687 const caches = await openCaches();
88- const minRequest = new Request(request.url);
8990 // Only store the content if we haven't seen this CID before
91 if (!(await caches.content.match(cidKey))) {
92 await caches.content.put(new Request(cidKey), response);
93 }
9495- if (!(await caches.index.match(minRequest))) {
96- // Update the URL → CID map
97- await caches.index.put(
98- new Request(request.url),
99- new Response(cid, { headers: { "content-type": "text/plain" } }),
100- );
101- }
102}
103104/**
···111 const caches = await openCaches();
112113 const indexEntry = await caches.index.match(request);
114- if (!indexEntry) console.log("⚔️", request.url);
115-116 if (!indexEntry) return undefined;
117118 const cid = await indexEntry.text();
119- console.log("REQ", request.url, cid);
120 return caches.content.match(cidUrl(cid));
121}
122···137 * @returns {Promise<Response>}
138 */
139async function handleFetch(request) {
140- // When we know we're offline, skip the network entirely.
141 if (navigator.onLine) {
142 try {
143 return await fetchAndStore(request);
···23import { create as createCid } from "./common/cid.js";
45+const fileTreePromise = import("./file-tree.json", { with: { type: "json" } })
6+ .then((m) => m.default)
7+ .catch(() => null);
8+9/** Media content types to ignore */
10const MEDIA_CONTENT_TYPE = /^(audio|video)\//;
11···52});
5354////////////////////////////////////////////
55+// CONTENT-ADDRESSED CACHE
56////////////////////////////////////////////
5758/**
···77}
7879/**
80+ * Looks up a pathname in the pre-built file tree and returns its CID, or
81+ * `undefined` if the entry is absent or the tree failed to load.
82+ *
83+ * @param {string} pathname - e.g. "/components/foo.js"
84+ * @returns {Promise<string | undefined>}
85+ */
86+async function cidFromTree(pathname) {
87+ /** @type {Record<string, string> | null} */
88+ const tree = await fileTreePromise;
89+ if (!tree) return undefined;
90+ const key = pathname.replace(/^\//, "");
91+ return tree[key];
92+}
93+94+/**
95 * Computes the CID of `response`'s body and writes it into the two-level cache.
96 * The same content is stored only once, regardless of how many URLs reference it.
97 *
98+ * Uses the pre-built file tree CID when available; falls back to hashing the
99+ * response body when the entry is missing from the tree.
100+ *
101 * @param {Request} request
102 * @param {Response} response - a clone; its body is fully consumed here
103 */
104async function store(request, response) {
105+ const { pathname } = new URL(request.url);
106+ const cid = await cidFromTree(pathname) ??
107+ await createCid(
108+ RAW_CODEC,
109+ new Uint8Array(await response.clone().arrayBuffer()),
110+ );
111 const cidKey = cidUrl(cid);
112113 const caches = await openCaches();
0114115 // Only store the content if we haven't seen this CID before
116 if (!(await caches.content.match(cidKey))) {
117 await caches.content.put(new Request(cidKey), response);
118 }
119120+ // Update the URL → CID map
121+ await caches.index.put(
122+ new Request(request.url),
123+ new Response(cid, { headers: { "content-type": "text/plain" } }),
124+ );
00125}
126127/**
···134 const caches = await openCaches();
135136 const indexEntry = await caches.index.match(request);
00137 if (!indexEntry) return undefined;
138139 const cid = await indexEntry.text();
0140 return caches.content.match(cidUrl(cid));
141}
142···157 * @returns {Promise<Response>}
158 */
159async function handleFetch(request) {
0160 if (navigator.onLine) {
161 try {
162 return await fetchAndStore(request);