Files
bitvid/sw.min.js

172 lines
5.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(() => {
"use strict";
let cancelled = false;
// Handle messages from clients
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
if (event.data && event.data.type === "CLEAR_CACHES") {
caches
.keys()
.then((cacheNames) =>
Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)))
);
}
});
// Immediately install and skip waiting
self.addEventListener("install", (event) => {
self.skipWaiting();
});
// Claim clients on activation and clear caches
self.addEventListener("activate", (event) => {
event.waitUntil(
Promise.all([
clients.claim(),
self.skipWaiting(),
caches
.keys()
.then((cacheNames) =>
Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)))
),
])
);
});
// Handle fetch events
self.addEventListener("fetch", (event) => {
const requestURL = event.request.url;
// Only handle WebTorrent streaming requests
if (!requestURL.includes("/webtorrent/")) {
return;
}
// Create a promise to handle the request
const responsePromise = (async () => {
// 1) Keepalive check
if (requestURL.includes("/webtorrent/keepalive/")) {
return new Response();
}
// 2) Cancel check
if (requestURL.includes("/webtorrent/cancel/")) {
return new Response(
new ReadableStream({
cancel() {
cancelled = true;
},
})
);
}
// 3) Streaming requests
// We define an async function and immediately invoke it with `event`
return (async function ({ request }) {
const { url, method, headers, destination } = request;
// 3a) Find open window clients
const windowClients = await clients.matchAll({
type: "window",
includeUncontrolled: true,
});
// 3b) We send a message to each client with a MessageChannel
const [clientResponse, port] = await new Promise((resolve) => {
for (const client of windowClients) {
const channel = new MessageChannel();
channel.port1.onmessage = ({ data }) => {
resolve([data, channel.port1]);
};
client.postMessage(
{
url,
method,
headers: Object.fromEntries(headers.entries()),
scope: self.registration.scope,
destination,
type: "webtorrent",
},
[channel.port2]
);
}
});
// 3c) Setup a small helper to close the port
let timeoutId = null;
const closeChannel = () => {
port.postMessage(false);
clearTimeout(timeoutId);
port.onmessage = null;
};
// 3d) Build a Headers object that prevents caching
const responseHeaders = new Headers(clientResponse.headers);
responseHeaders.set(
"Cache-Control",
"no-cache, no-store, must-revalidate, max-age=0"
);
responseHeaders.set("Pragma", "no-cache");
responseHeaders.set("Expires", "0");
// 3e) If the response is not a streaming request, return it directly
if (clientResponse.body !== "STREAM") {
closeChannel();
return new Response(clientResponse.body, {
status: clientResponse.status,
statusText: clientResponse.statusText,
headers: responseHeaders,
});
}
// 3f) Otherwise, create a streaming response
return new Response(
new ReadableStream({
pull(controller) {
return new Promise((resolvePull) => {
port.onmessage = ({ data }) => {
if (data) {
controller.enqueue(data);
} else {
closeChannel();
controller.close();
}
resolvePull();
};
// If not cancelled, autoclose after 5s of no data
// *** Increase the timeout to avoid frequent CPU wake-ups ***
if (!cancelled && destination !== "document") {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
closeChannel();
resolvePull();
}, 30000); // 30 seconds
}
// Request next chunk from client
port.postMessage(true);
});
},
cancel() {
closeChannel();
},
}),
{
status: clientResponse.status,
statusText: clientResponse.statusText,
headers: responseHeaders,
}
);
})(event);
})();
// respondWith the promise if it exists
event.respondWith(responsePromise);
});
})();