forked from MapComplete/MapComplete
147 lines
4.1 KiB
TypeScript
147 lines
4.1 KiB
TypeScript
// This file must have worker types, but not DOM types.
|
|
// The global should be that of a service worker.
|
|
|
|
// This fixes `self`'s type.
|
|
import { SWGenerated } from "./SWGenerated"
|
|
|
|
declare var self: ServiceWorkerGlobalScope
|
|
export {}
|
|
|
|
const selfDomain = self.location.hostname
|
|
|
|
function jsonResponse(object: object | []): Response {
|
|
return new Response(JSON.stringify(object), {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
}
|
|
|
|
const cacheKey = SWGenerated.vNumber + "-" + SWGenerated.vNumber
|
|
|
|
function cleanOldCaches() {
|
|
caches.keys().then((keys) => {
|
|
return Promise.all(
|
|
keys.map((k) => {
|
|
if (k !== cacheKey) {
|
|
return caches.delete(k)
|
|
}
|
|
})
|
|
)
|
|
})
|
|
}
|
|
|
|
function getCache() {
|
|
return caches.open(cacheKey)
|
|
}
|
|
|
|
function respondFromCache(event: FetchEvent) {
|
|
event.respondWith(
|
|
getCache().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
|
|
})
|
|
)
|
|
}
|
|
|
|
async function listCachedRequests(): Promise<string[]> {
|
|
const cache = await getCache()
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
if (this._fallback) {
|
|
return this._fallback(event, rest)
|
|
}
|
|
}
|
|
}
|
|
|
|
const allOffline = new Router(
|
|
{
|
|
"status.json": (event) => {
|
|
event.respondWith(
|
|
listCachedRequests().then((cached) =>
|
|
jsonResponse({
|
|
status: "ok",
|
|
cached,
|
|
})
|
|
)
|
|
)
|
|
},
|
|
},
|
|
{}
|
|
)
|
|
|
|
self.addEventListener("fetch", (event) => {
|
|
const url = event.request.url
|
|
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
|
|
}
|
|
})
|
|
|
|
self.addEventListener("install", async () => {
|
|
await self.skipWaiting()
|
|
cleanOldCaches()
|
|
})
|
|
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)
|
|
}
|
|
})
|
|
)
|
|
)
|
|
)
|
|
})
|