A music player that connects to your cloud/distributed storage.
at main 182 lines 4.4 kB view raw
1// 2// Service worker 3// (◡ ‿ ◡ ✿) 4// 5// This worker is responsible for caching the application 6// so it can be used offline and acts as a proxy that 7// allows for example, authentication through headers 8// when using audio elements. 9// 10/// <reference lib="webworker" /> 11 12 13const KEY = 14 /* eslint-disable no-undef *//* @ts-ignore */ 15 `diffuse-${BUILD_TIMESTAMP}` 16 17 18const EXCLUDE = 19 [ "_headers" 20 , "_redirects" 21 , "CORS" 22 ] 23 24 25const GOOGLE_DRIVE = "https://www.googleapis.com/drive/" 26 27 28 29// 🙈 30 31 32const isNativeWrapper = location.host === "localhost:44999" || location.host === "127.0.0.1:44999" 33let googleDriveToken 34 35 36 37// 📣 38 39 40self.addEventListener("activate", () => { 41 // Remove all caches except the one with the currently used `KEY` 42 caches.keys().then(keys => { 43 keys.forEach(k => { 44 if (k !== KEY) caches.delete(k) 45 }) 46 }) 47}) 48 49 50self.addEventListener("install", event => { 51 if (isNativeWrapper) { 52 return globalThis.skipWaiting() 53 } 54 55 const href = self.location.href.replace("service-worker.js", "") 56 const promise = fetch("tree.json") 57 .then(response => response.json()) 58 .then(tree => { 59 const filteredTree = tree 60 .filter(t => !EXCLUDE.find(u => u === t)) 61 .filter(u => u.endsWith(".jpg")) 62 const whatToCache = [ href, `${href.replace(/\/+$/, "")}/about/` ].concat(filteredTree) 63 return caches.open(KEY).then(c => Promise.all(whatToCache.map(x => c.add(x)))) 64 }) 65 66 event.waitUntil(promise) 67}) 68 69 70self.addEventListener("fetch", fetchEvent => { 71 const event = fetchEvent as FetchEvent 72 73 const isInternal = 74 !!event.request.url.match(new RegExp("^" + self.location.origin)) 75 76 // Ping 77 if (event.request.url.includes("?ping=1")) { 78 event.respondWith( 79 (async () => { 80 const serverIsOnline = await network(event).then(_ => true).catch(_ => false) 81 return new Response(JSON.stringify(serverIsOnline), { 82 headers: { "Content-Type": "application/json" } 83 }) 84 })() 85 ) 86 87 // When doing a request with basic authentication in the url, put it in the headers instead 88 } else if (event.request.url.includes("basic_auth=")) { 89 const url = new URL(event.request.url) 90 const token = url.searchParams.get("basic_auth") 91 92 event.respondWith(newRequestWithAuth( 93 event.request, 94 url.toString(), 95 "Basic " + token 96 )) 97 98 // When doing a request with access token in the url, put it in the headers instead 99 } else if (event.request.url.includes("bearer_token=")) { 100 const url = new URL(event.request.url) 101 const token = url.searchParams.get("bearer_token") 102 103 if (url.href.startsWith(GOOGLE_DRIVE)) googleDriveToken = token 104 105 url.searchParams.delete("bearer_token") 106 url.search = "?" + url.searchParams.toString() 107 108 event.respondWith(newRequestWithAuth( 109 event.request, 110 url.toString(), 111 "Bearer " + token 112 )) 113 114 // Use cache if internal request and not using native app 115 } else if (isInternal) { 116 event.respondWith( 117 isNativeWrapper 118 ? network(event) 119 : cacheThenNetwork(event) 120 ) 121 122 } else if (event.request.url && event.request.url.startsWith(GOOGLE_DRIVE) && event.request.url.includes("alt=media")) { 123 // For some reason Safari starts using the non bearer-token URL while playing audio 124 event.respondWith( 125 googleDriveToken 126 ? newRequestWithAuth( 127 event.request, 128 event.request.url.toString(), 129 "Bearer " + googleDriveToken 130 ) 131 : network(event) 132 ) 133 134 } 135}) 136 137 138function cacheThenNetwork(event) { 139 const url = new URL(event.request.url) 140 url.search = "" 141 142 return caches 143 .open(KEY) 144 .then(cache => cache.match(url)) 145 .then(match => match || fetch(url)) 146} 147 148 149function network(event) { 150 return fetch(event.request.url) 151} 152 153 154addEventListener("message", event => { 155 if (event.data === "skipWaiting") { 156 globalThis.skipWaiting() 157 } 158}) 159 160 161 162// ⚗️ 163 164 165function newRequestWithAuth(request: Request, newUrl: string, authToken: string): Promise<Response> { 166 const newHeaders = new Headers(request.headers) 167 newHeaders.append("authorization", authToken) 168 169 const newRequest = new Request(request, { headers: newHeaders }) 170 171 const makeFetch = () => fetch(newRequest).then(async r => { 172 if (r.ok) { 173 return r 174 } else { 175 return r.text().then(text => { 176 throw new Error(text) 177 }) 178 } 179 }) 180 181 return makeFetch() 182}