Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
smoldata.json
160 changes: 82 additions & 78 deletions sw.js
Original file line number Diff line number Diff line change
@@ -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);
}
})
)
}
}