2025-07-30 00:21:30 +02:00
|
|
|
// This file must have worker types, but not DOM types.
|
|
|
|
// The global should be that of a service worker.
|
|
|
|
|
|
|
|
// This fixes `self`'s type.
|
2025-08-01 00:44:25 +02:00
|
|
|
import { SWGenerated } from "./SWGenerated"
|
|
|
|
|
2025-07-30 00:21:30 +02:00
|
|
|
declare var self: ServiceWorkerGlobalScope
|
|
|
|
export {}
|
|
|
|
|
2025-08-01 00:44:25 +02:00
|
|
|
const selfDomain = self.location.hostname
|
2025-07-31 12:30:09 +02:00
|
|
|
|
|
|
|
function jsonResponse(object: object | []): Response {
|
|
|
|
return new Response(JSON.stringify(object), {
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2025-07-30 00:21:30 +02:00
|
|
|
|
2025-08-01 00:44:25 +02:00
|
|
|
function respondFromCache(event: FetchEvent) {
|
|
|
|
event.respondWith(
|
|
|
|
caches.open(SWGenerated.vNumber).then(async cache => {
|
|
|
|
const cached = await cache.match(event.request)
|
|
|
|
if (!cached) {
|
|
|
|
const response = await fetch(event.request)
|
|
|
|
cache.put(event.request, response.clone())
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
return cached
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2025-07-31 12:30:09 +02:00
|
|
|
|
2025-08-01 00:44:25 +02:00
|
|
|
async function listCachedRequests(): Promise<string[]> {
|
|
|
|
const cache = await caches.open(SWGenerated.vNumber)
|
|
|
|
const requests = await cache.keys()
|
|
|
|
return requests.map(req => req.url)
|
|
|
|
}
|
|
|
|
|
|
|
|
class Router {
|
|
|
|
private readonly _endpoints: Record<string, (event: FetchEvent) => void>
|
|
|
|
private readonly _subpaths: Record<string, (event: FetchEvent, rest: string) => void>
|
|
|
|
private readonly _fallback: undefined | ((event: FetchEvent, rest: string) => void)
|
|
|
|
|
|
|
|
constructor(endpoints: Record<string, (event: FetchEvent) => void>,
|
|
|
|
subpaths: Record<string, (event: FetchEvent, rest: string) => void>,
|
|
|
|
fallback?: undefined | ((event: FetchEvent, rest: string) => void)
|
|
|
|
) {
|
|
|
|
this._endpoints = endpoints
|
|
|
|
this._subpaths = subpaths
|
|
|
|
this._fallback = fallback
|
2025-07-31 12:30:09 +02:00
|
|
|
}
|
2025-08-01 00:44:25 +02:00
|
|
|
|
|
|
|
public route(event: FetchEvent, rest?: string) {
|
|
|
|
const url = new URL(event.request.url)
|
|
|
|
rest ??= url.pathname.split("/service-worker/")[1]
|
|
|
|
console.log(">>> routing", rest)
|
|
|
|
if (rest.indexOf("/") > 0) {
|
|
|
|
const nextSegment = rest.split("/")[0]
|
|
|
|
if (this._subpaths[nextSegment]) {
|
|
|
|
return this._subpaths[nextSegment](event, rest.substring(nextSegment.length + 1))
|
|
|
|
}
|
|
|
|
} else if (this._endpoints[rest] !== undefined) {
|
|
|
|
return this._endpoints[rest](event)
|
2025-07-31 12:30:09 +02:00
|
|
|
}
|
2025-08-01 00:44:25 +02:00
|
|
|
if (this._fallback) {
|
|
|
|
return this._fallback(event, rest)
|
2025-07-30 00:21:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-01 00:44:25 +02:00
|
|
|
|
|
|
|
const allOffline = new Router({
|
|
|
|
"status.json": (event) => {
|
|
|
|
event.respondWith(
|
|
|
|
listCachedRequests().then(cached =>
|
|
|
|
jsonResponse(
|
|
|
|
{
|
|
|
|
status: "ok", cached,
|
|
|
|
}
|
|
|
|
))
|
|
|
|
)
|
|
|
|
}
|
2025-08-01 14:54:30 +02:00
|
|
|
}, {})
|
|
|
|
|
2025-08-01 00:44:25 +02:00
|
|
|
|
2025-07-30 00:21:30 +02:00
|
|
|
self.addEventListener("fetch", (event) => {
|
|
|
|
const url = event.request.url
|
2025-08-01 00:44:25 +02:00
|
|
|
if (url.endsWith("/service-worker.js")) {
|
|
|
|
return // Installation of a new version, we don't interfere
|
|
|
|
}
|
|
|
|
console.log("Intercepting event", event.request.url)
|
|
|
|
if (url.indexOf("/service-worker/") >= 0) {
|
|
|
|
allOffline.route(event)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const urlObj = new URL(url)
|
|
|
|
if (urlObj.hostname === selfDomain && selfDomain !== "localhost" && selfDomain !== "127.0.0.1") {
|
|
|
|
respondFromCache(event)
|
|
|
|
return
|
2025-07-30 00:21:30 +02:00
|
|
|
}
|
|
|
|
})
|
2025-07-31 12:30:09 +02:00
|
|
|
|
|
|
|
self.addEventListener("install", () => self.skipWaiting())
|
2025-08-01 00:44:25 +02:00
|
|
|
self.addEventListener("activate", event => {
|
|
|
|
event.waitUntil(self.clients.claim())
|
|
|
|
|
|
|
|
// Delete the old caches (of an older version number
|
|
|
|
event.waitUntil(
|
|
|
|
caches.keys().then((keys) =>
|
|
|
|
Promise.all(
|
|
|
|
keys.map(async (key) => {
|
|
|
|
if (key !== SWGenerated.vNumber) {
|
|
|
|
await caches.delete(key)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
))
|
|
|
|
|
|
|
|
})
|