122 lines
3.2 KiB
JavaScript
122 lines
3.2 KiB
JavaScript
const CACHE_PREFIX = "who-need-help-static-"
|
|
const CACHE = `${CACHE_PREFIX}v6`
|
|
const OFFLINE_URL = "/offline.html"
|
|
const SHELL = [
|
|
OFFLINE_URL,
|
|
"/manifest.webmanifest",
|
|
"/assets/css/app.css",
|
|
"/assets/js/app.js",
|
|
"/images/logo.svg",
|
|
"/images/pwa-192.png",
|
|
"/images/pwa-512.png",
|
|
"/images/pwa-maskable-512.png"
|
|
]
|
|
|
|
self.addEventListener("install", event => {
|
|
event.waitUntil(
|
|
caches
|
|
.open(CACHE)
|
|
.then(cache => cache.addAll(SHELL))
|
|
.then(() => self.skipWaiting())
|
|
)
|
|
})
|
|
|
|
self.addEventListener("activate", event => {
|
|
event.waitUntil(
|
|
caches
|
|
.keys()
|
|
.then(keys =>
|
|
Promise.all(
|
|
keys
|
|
.filter(key => key.startsWith(CACHE_PREFIX) && key !== CACHE)
|
|
.map(key => caches.delete(key))
|
|
)
|
|
)
|
|
.then(() => self.clients.claim())
|
|
)
|
|
})
|
|
|
|
self.addEventListener("fetch", event => {
|
|
const url = new URL(event.request.url)
|
|
const sameOrigin = url.origin === self.location.origin
|
|
const isStatic =
|
|
sameOrigin &&
|
|
(url.pathname.startsWith("/assets/") || url.pathname.startsWith("/images/"))
|
|
|
|
if (event.request.method !== "GET" || !sameOrigin) return
|
|
|
|
if (event.request.mode === "navigate") {
|
|
event.respondWith(
|
|
fetch(event.request).catch(async () => {
|
|
const fallback = await caches.match(OFFLINE_URL)
|
|
return fallback || Response.error()
|
|
})
|
|
)
|
|
return
|
|
}
|
|
|
|
if (isStatic) {
|
|
event.respondWith(cacheFirst(event))
|
|
}
|
|
})
|
|
|
|
self.addEventListener("push", event => {
|
|
let payload = {}
|
|
|
|
try {
|
|
payload = event.data?.json() || {}
|
|
} catch (_error) {
|
|
payload = {title: "Who Need Help", body: "Open the app to view an update."}
|
|
}
|
|
|
|
const title = String(payload.title || "Who Need Help").slice(0, 120)
|
|
const body = String(payload.body || "Open the app to view an update.").slice(0, 240)
|
|
const path = validPath(payload.path) ? payload.path : "/notifications"
|
|
|
|
event.waitUntil(self.registration.showNotification(title, {
|
|
body,
|
|
icon: "/images/pwa-192.png",
|
|
badge: "/images/favicon-48.png",
|
|
tag: String(payload.tag || "who-need-help-update").slice(0, 64),
|
|
data: {path}
|
|
}))
|
|
})
|
|
|
|
self.addEventListener("notificationclick", event => {
|
|
event.notification.close()
|
|
const path = validPath(event.notification.data?.path)
|
|
? event.notification.data.path
|
|
: "/notifications"
|
|
|
|
event.waitUntil((async () => {
|
|
const target = new URL(path, self.location.origin).href
|
|
const windows = await self.clients.matchAll({type: "window", includeUncontrolled: true})
|
|
const existing = windows.find(client => new URL(client.url).origin === self.location.origin)
|
|
|
|
if (existing) {
|
|
await existing.navigate(target)
|
|
return existing.focus()
|
|
}
|
|
|
|
return self.clients.openWindow(target)
|
|
})())
|
|
})
|
|
|
|
function validPath(value) {
|
|
return typeof value === "string" && /^\/(?!\/)[A-Za-z0-9_/?=&.#%-]*$/.test(value)
|
|
}
|
|
|
|
async function cacheFirst(event) {
|
|
const cached = await caches.match(event.request)
|
|
if (cached) return cached
|
|
|
|
const response = await fetch(event.request)
|
|
|
|
if (response.ok) {
|
|
const copy = response.clone()
|
|
event.waitUntil(caches.open(CACHE).then(cache => cache.put(event.request, copy)))
|
|
}
|
|
|
|
return response
|
|
}
|