From c0abe41be8b4d392160aa584fd4c1390f6e00a1a Mon Sep 17 00:00:00 2001 From: grml Date: Sun, 22 Feb 2026 00:39:44 +0800 Subject: [PATCH 1/2] chore: added gitignore file - prevents contributors from committing the file --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2d15a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +smoldata.json \ No newline at end of file From 779d3345076037d9c173f2542e660b2ff98d8bf2 Mon Sep 17 00:00:00 2001 From: grml Date: Sun, 22 Feb 2026 00:41:32 +0800 Subject: [PATCH 2/2] fix(sw): return monitored response to enable progress tracking progressMonitor() was being called but its return value was discarded. The original networkResponse was cached and returned instead, meaning the monitored ReadableStream was never consumed and progress events were never sent to the client. Changes: - Capture and return monitoredResponse instead of networkResponse - Cache the monitored response so progress works on cache hits too - Refactor progressMonitor to async/await for cleaner flow - Handle null client gracefully (stream data without progress events) --- sw.js | 160 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 82 insertions(+), 78 deletions(-) diff --git a/sw.js b/sw.js index 7f7fa49..a60a39a 100644 --- a/sw.js +++ b/sw.js @@ -1,101 +1,105 @@ const SW_VERSION = '1.1.3'; self.addEventListener('install', () => { - self.skipWaiting(); + self.skipWaiting(); }); self.addEventListener("fetch", (event) => { - const filename = new URL(event.request.url).pathname.split("/").at(-1); - if (filename == "clearHtml") - return event.respondWith((async () => { - await caches.delete("html"); - return new Response("cleared", { - status: 200, - headers: { "Content-Type": "text/plain" }, - }); + const filename = new URL(event.request.url).pathname.split("/").at(-1); + if (filename == "clearHtml") + return event.respondWith((async () => { + await caches.delete("html"); + return new Response("cleared", { + status: 200, + headers: { "Content-Type": "text/plain" }, + }); + })()); + if (filename == "swVer") + return event.respondWith((() => new Response(SW_VERSION, { + status: 200, + headers: { "Content-Type": "text/plain" }, + }))()); + if (!["", "index.html", "app.webmanifest", "favicon.ico", "favicon-48.png", "favicon-256.png", "smoldata.json"].includes(filename)) + return; + event.respondWith((async () => { + const request = event.request; + const cachedResponse = await caches.match(request); + if (cachedResponse) + return cachedResponse; + const isSmolData = filename == "smoldata.json"; + // todo: delete after verifying the new one downloaded + if (isSmolData) + await caches.delete("smoldata"); + const cache = await caches.open(isSmolData ? "smoldata" : "html"); + try { + const networkResponse = await fetch(request); + if (isSmolData) { + const monitoredResponse = progressMonitor(event.clientId, networkResponse.clone()); + await cache.put(request, monitoredResponse.clone()); + return monitoredResponse; // Return the monitored response, not the original + } else { + await cache.put(request, networkResponse.clone()); + return networkResponse; + } + } catch (error) { + return new Response("Network error happened", { + status: 408, + headers: { "Content-Type": "text/plain" }, + }); + } })()); - if (filename == "swVer") - return event.respondWith((()=>new Response(SW_VERSION, { - status: 200, - headers: { "Content-Type": "text/plain" }, - }))()); - if (!["", "index.html", "app.webmanifest", "favicon.ico", "favicon-48.png", "favicon-256.png", "smoldata.json"].includes(filename)) - return; - event.respondWith((async () => { - const request = event.request; - const cachedResponse = await caches.match(request); - if (cachedResponse) - return cachedResponse; - const isSmolData = filename == "smoldata.json"; - // todo: delete after verifying the new one downloaded - if (isSmolData) - await caches.delete("smoldata"); - const cache = await caches.open(isSmolData ? "smoldata" : "html"); - try { - const networkResponse = await fetch(request); - if (isSmolData) - progressMonitor(event.clientId, networkResponse.clone()); - await cache.put(request, networkResponse.clone()); - return networkResponse; - } catch (error) { - return new Response("Network error happened", { - status: 408, - headers: { "Content-Type": "text/plain" }, - }); - } - })()); }); // based on https://github.com/anthumchris/fetch-progress-indicators/blob/master/sw-basic/sw-simple.js function progressMonitor(clientId, response) { - if (!response.body) { - console.warn("ReadableStream is not yet supported in this browser. See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream") - return response; - } - if (!response.ok) { - // HTTP error code response - return response; - } + if (!response.body) { + console.warn("ReadableStream is not yet supported in this browser. See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream") + return response; + } + if (!response.ok) { + return response; + } - let loaded = 0; - const reader = response.body.getReader(); + let loaded = 0; + const reader = response.body.getReader(); - return new Response( - new ReadableStream({ - start(controller) { - // get client to post message. Awaiting resolution first read() progress - // is sent for progress indicator accuracy - let client; - clients.get(clientId).then(c => { - client = c; - read(); - }); + return new Response( + new ReadableStream({ + async start(controller) { + try { + const client = await clients.get(clientId); + if (!client) { + console.warn(`Client ${clientId} not found, streaming without progress`); + } + await read(client, controller); + } catch (error) { + console.error('Error in start()', error); + controller.error(error); + } + }, + cancel(reason) { + console.log('cancel()', reason); + } + }) + ); - function read() { - reader.read().then(({done, value}) => { + async function read(client, controller) { + try { + const { done, value } = await reader.read(); if (done) { - controller.close(); - return; + controller.close(); + return; } - controller.enqueue(value); loaded += value.byteLength; - client.postMessage({event:"downloadProgress",data:loaded}) - read(); - }) - .catch(error => { - // error only typically occurs if network fails mid-download + if (client) { + client.postMessage({ event: "downloadProgress", data: loaded }); + } + await read(client, controller); + } catch (error) { console.error('error in read()', error); controller.error(error); - }); } - }, - - // Firefox excutes this on page stop, Chrome does not - cancel(reason) { - console.log('cancel()', reason); - } - }) - ) + } } \ No newline at end of file