// // Service worker // (◡ ‿ ◡ ✿) // // This worker is responsible for caching the application // so it can be used offline and acts as a proxy that // allows for example, authentication through headers // when using audio elements. // /// const KEY = /* eslint-disable no-undef *//* @ts-ignore */ `diffuse-${BUILD_TIMESTAMP}` const EXCLUDE = [ "_headers" , "_redirects" , "CORS" ] const GOOGLE_DRIVE = "https://www.googleapis.com/drive/" // 🙈 const isNativeWrapper = location.host === "localhost:44999" || location.host === "127.0.0.1:44999" let googleDriveToken // 📣 self.addEventListener("activate", () => { // Remove all caches except the one with the currently used `KEY` caches.keys().then(keys => { keys.forEach(k => { if (k !== KEY) caches.delete(k) }) }) }) self.addEventListener("install", event => { if (isNativeWrapper) { return globalThis.skipWaiting() } const href = self.location.href.replace("service-worker.js", "") const promise = fetch("tree.json") .then(response => response.json()) .then(tree => { const filteredTree = tree .filter(t => !EXCLUDE.find(u => u === t)) .filter(u => u.endsWith(".jpg")) const whatToCache = [ href, `${href.replace(/\/+$/, "")}/about/` ].concat(filteredTree) return caches.open(KEY).then(c => Promise.all(whatToCache.map(x => c.add(x)))) }) event.waitUntil(promise) }) self.addEventListener("fetch", fetchEvent => { const event = fetchEvent as FetchEvent const isInternal = !!event.request.url.match(new RegExp("^" + self.location.origin)) // Ping if (event.request.url.includes("?ping=1")) { event.respondWith( (async () => { const serverIsOnline = await network(event).then(_ => true).catch(_ => false) return new Response(JSON.stringify(serverIsOnline), { headers: { "Content-Type": "application/json" } }) })() ) // When doing a request with basic authentication in the url, put it in the headers instead } else if (event.request.url.includes("basic_auth=")) { const url = new URL(event.request.url) const token = url.searchParams.get("basic_auth") event.respondWith(newRequestWithAuth( event.request, url.toString(), "Basic " + token )) // When doing a request with access token in the url, put it in the headers instead } else if (event.request.url.includes("bearer_token=")) { const url = new URL(event.request.url) const token = url.searchParams.get("bearer_token") if (url.href.startsWith(GOOGLE_DRIVE)) googleDriveToken = token url.searchParams.delete("bearer_token") url.search = "?" + url.searchParams.toString() event.respondWith(newRequestWithAuth( event.request, url.toString(), "Bearer " + token )) // Use cache if internal request and not using native app } else if (isInternal) { event.respondWith( isNativeWrapper ? network(event) : cacheThenNetwork(event) ) } else if (event.request.url && event.request.url.startsWith(GOOGLE_DRIVE) && event.request.url.includes("alt=media")) { // For some reason Safari starts using the non bearer-token URL while playing audio event.respondWith( googleDriveToken ? newRequestWithAuth( event.request, event.request.url.toString(), "Bearer " + googleDriveToken ) : network(event) ) } }) function cacheThenNetwork(event) { const url = new URL(event.request.url) url.search = "" return caches .open(KEY) .then(cache => cache.match(url)) .then(match => match || fetch(url)) } function network(event) { return fetch(event.request.url) } addEventListener("message", event => { if (event.data === "skipWaiting") { globalThis.skipWaiting() } }) // ⚗️ function newRequestWithAuth(request: Request, newUrl: string, authToken: string): Promise { const newHeaders = new Headers(request.headers) newHeaders.append("authorization", authToken) const newRequest = new Request(request, { headers: newHeaders }) const makeFetch = () => fetch(newRequest).then(async r => { if (r.ok) { return r } else { return r.text().then(text => { throw new Error(text) }) } }) return makeFetch() }