Files
RawMark/web/src/service-worker.js
T

73 lines
2.9 KiB
JavaScript

const CACHE = 'rawmark-shell-__VERSION__'
const FILES = __PRECACHE__
const paths = new Set(FILES)
globalThis.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE)
// Download in batches instead of requesting hundreds of syntax packages and fonts concurrently.
for (let i = 0; i < FILES.length; i += 16) {
await cache.addAll(FILES.slice(i, i + 16).map(url => new Request(url, { cache: 'reload' })))
}
})())
})
function clientUsesCurrentShell(client) {
return new Promise((resolve) => {
const channel = new MessageChannel()
const timer = setTimeout(() => {
channel.port1.close(); resolve(false)
}, 2000)
channel.port1.onmessage = (event) => {
clearTimeout(timer)
channel.port1.close()
resolve(paths.has(event.data))
}
client.postMessage('SHELL_ENTRY', [channel.port2])
})
}
async function cleanCaches() {
// Installing versions use independent caches too; old workers must not delete them as obsolete resources.
if (globalThis.registration.installing || globalThis.registration.waiting) return
const clients = await globalThis.clients.matchAll({ type: 'window', includeUncontrolled: true })
if (!(await Promise.all(clients.map(clientUsesCurrentShell))).every(Boolean)) return
for (const name of await caches.keys()) {
if (globalThis.registration.installing || globalThis.registration.waiting) return
if (name.startsWith('rawmark-shell-') && name !== CACHE) await caches.delete(name)
}
}
globalThis.addEventListener('activate', (event) => {
event.waitUntil(cleanCaches())
})
// Activation changes every page's controller, but only the page requesting the update refreshes itself.
globalThis.addEventListener('message', (event) => {
if (event.data === 'ACTIVATE') event.waitUntil(globalThis.skipWaiting())
if (event.data === 'CLEANUP') event.waitUntil(cleanCaches())
})
globalThis.addEventListener('fetch', (event) => {
const { request } = event
const url = new URL(request.url)
if (request.method !== 'GET' || url.origin !== globalThis.location.origin) return
// Match only build assets and application entry points; never cache APIs, note images, or plugins.
const entry = request.mode === 'navigate' && (url.pathname === '/' || url.pathname === '/index.html')
if (!entry && !paths.has(url.pathname) && !url.pathname.startsWith('/assets/')) return
event.respondWith((async () => {
const cache = await caches.open(CACHE)
const cached = await cache.match(entry ? '/index.html' : url.pathname)
if (cached) return cached
// Unrefreshed old tabs load old syntax packages and fonts by their original hashes.
if (url.pathname.startsWith('/assets/')) {
for (const name of await caches.keys()) {
if (!name.startsWith('rawmark-shell-')) continue
const previous = await (await caches.open(name)).match(url.pathname)
if (previous) return previous
}
}
return fetch(request)
})())
})