Files
sapien/public/sw.js
2026-05-19 12:13:04 -05:00

109 lines
3.7 KiB
JavaScript

const CACHE_NAME = 'l484-pwa-v14'
const APP_SHELL = [
'/',
'/manifest.webmanifest',
]
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)))
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(
(async () => {
const keys = await caches.keys()
await Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
await self.clients.claim()
})(),
)
})
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
if (!['http:', 'https:'].includes(url.protocol)) return
if (url.pathname.startsWith('/api/')) return
if (event.request.mode === 'navigate') {
if (['/admin', '/edit'].includes(url.pathname)) {
event.respondWith(fetch(event.request).catch(() => Response.error()))
return
}
event.respondWith(
fetch(event.request)
.then((response) => {
if (response.ok && response.type === 'basic') {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put('/', clone))
}
return response
})
.catch(async () => (await caches.match('/')) || (await caches.match(event.request)) || Response.error()),
)
return
}
if (event.request.method !== 'GET') return
event.respondWith(
caches.match(event.request).then((cached) =>
cached || fetch(event.request).then((response) => {
if (event.request.method === 'GET' && response.ok && response.type === 'basic') {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone))
}
return response
}).catch(() => Response.error()),
),
)
})
self.addEventListener('pushsubscriptionchange', (event) => {
event.waitUntil((async () => {
const response = await fetch('/api/notifications/vapid-public-key', { cache: 'no-store' })
const { publicKey } = response.ok ? await response.json() : {}
if (!publicKey) return
const padding = '='.repeat((4 - publicKey.length % 4) % 4)
const base64 = (publicKey + padding).replace(/-/g, '+').replace(/_/g, '/')
const rawData = atob(base64)
const applicationServerKey = new Uint8Array([...rawData].map((char) => char.charCodeAt(0)))
const subscription = await self.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey })
await fetch('/api/notifications/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ subscription: subscription.toJSON() }),
})
})())
})
self.addEventListener('push', (event) => {
let data = {}
try {
data = event.data ? event.data.json() : {}
} catch {
data = { title: 'L484', message: event.data?.text() || 'New L484 update.' }
}
const title = data.title || 'L484'
const options = {
body: data.message || data.body || 'New L484 update.',
icon: data.icon || '/images/small-logo.svg',
badge: '/images/small-logo.svg',
tag: data.tag || 'l484-update',
data: { url: data.data?.url || '/' },
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
const urlToOpen = event.notification.data?.url || '/'
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
const existing = clientList.find((client) => new URL(client.url).pathname === urlToOpen && 'focus' in client)
if (existing) return existing.focus()
if (clients.openWindow) return clients.openWindow(urlToOpen)
return null
}),
)
})