feat(pwa): split pre-cache and dynamic cache in service worker

This commit is contained in:
Julien Oculi 2024-07-18 14:18:26 +02:00
parent 18f911b79b
commit 4a917971d0

View file

@ -31,19 +31,21 @@ const swStorage = {
} }
async function getPreCachedUrls(): Promise<string[]> { async function getPreCachedUrls(): Promise<string[]> {
const raw = await swStorage.getItem<string[]>('$sw.cache.urls') const raw = await swStorage.getItem<string[]>('$sw.pre-cache.urls')
if (raw === null) { if (raw === null) {
await openCache() await openPreCache()
return getPreCachedUrls() return getPreCachedUrls()
} }
return raw return raw
} }
async function getCache(): Promise<Cache> { async function getPreCache(): Promise<Cache> {
const version = await swStorage.getItem<string>('$sw.cache.version') const version = await swStorage.getItem<string>('$sw.pre-cache.version')
// Get cache from server
if (version === null) { if (version === null) {
await openCache() await openPreCache()
return getCache() return getPreCache()
} }
return caches.open(version) return caches.open(version)
} }
@ -52,7 +54,7 @@ const IS_SW = 'onpushsubscriptionchange' in self
if (IS_SW) { if (IS_SW) {
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
// Assign global cache and pre-cached-urls // Assign global cache and pre-cached-urls
event.waitUntil(openCache()) event.waitUntil(openPreCache())
}) })
self.addEventListener('activate', () => { self.addEventListener('activate', () => {
@ -85,18 +87,22 @@ if (IS_SW) {
async function fetchHandler(event: FetchEvent) { async function fetchHandler(event: FetchEvent) {
const url = new URL(event.request.url) const url = new URL(event.request.url)
const method = event.request.method const method = event.request.method
const preCachedUrls = await getPreCachedUrls() const preCachedUrls = await getPreCachedUrls()
const cache = await getCache()
// Cache will be updated on each request if version change
updateCache()
// Cache first for "pre-cached-urls" // Cache first for "pre-cached-urls"
if (preCachedUrls.includes(url.pathname) && method === 'GET') { if (preCachedUrls.includes(url.pathname) && method === 'GET') {
const cached = await cache.match(event.request) ?? const preCache = await getPreCache()
// Cache request update on each request if version change
updatePreCache()
// TODO handle search params
const cached = await preCache.match(event.request) ??
await fetch(event.request).then((response) => await fetch(event.request).then((response) =>
response.ok ? response : null response.ok ? response : null
).catch(() => null) ?? ).catch(() => null) ??
await cache.match(event.request, { ignoreSearch: true }) await preCache.match(event.request, { ignoreSearch: true })
if (cached === undefined) { if (cached === undefined) {
throw new Error(`no cache available for pre-cached-url "${url}"`) throw new Error(`no cache available for pre-cached-url "${url}"`)
@ -119,39 +125,41 @@ async function fetchHandler(event: FetchEvent) {
cache.put(event.request, response.clone()) cache.put(event.request, response.clone())
return response return response
}) })
.catch((cause) => { async function getDynCache(): Promise<Cache> {
// Try serve cache if no network const version = await swStorage.getItem<number>('$sw.dyn-cache.version')
if (cached === undefined) { // Create cache
throw new Error(`no cache available for "${url}"`, { cause }) if (version === null) {
} await swStorage.setItem<number>('$sw.dyn-cache.version', now + lifeTime)
return cached return getDynCache()
})
if (cached === undefined) {
return response
}
return cached
} }
// Clean outdated cache
if (version < Date.now()) {
await swStorage.setItem<number>('$sw.dyn-cache.version', now + lifeTime)
await caches.delete(version.toString())
return getDynCache()
}
return cache
}
// Network only // Network only
return fetch(event.request) return fetch(event.request)
} }
async function updateCache() { const serverVersion = await getServerPreCacheVersion()
const serverVersion = await getServerCacheVersion() const clientVersion = await swStorage.getItem<string>('$sw.pre-cache.version')
const clientVersion = await swStorage.getItem<string>('$sw.cache.version')
if (clientVersion === null) return if (clientVersion === null) return
if (serverVersion === undefined) return if (serverVersion === undefined) return
if (clientVersion === serverVersion) return if (clientVersion === serverVersion) return
// Open new pre-cache // Open new pre-cache
await openCache() await openPreCache()
// Delete old pre-cache // Delete old pre-cache
caches.delete(clientVersion) caches.delete(clientVersion)
} }
async function getServerCacheVersion() { async function getServerPreCacheVersion(): Promise<string | undefined> {
const response = await fetch('/api/serviceworker/precache').then( const response = await fetch('/api/serviceworker/precache').then(
(response) => response.json() as Promise<ApiPayload<PrecacheResponse>>, (response) => response.json() as Promise<ApiPayload<PrecacheResponse>>,
) )
@ -161,7 +169,7 @@ async function getServerCacheVersion() {
} }
} }
async function openCache() { async function openPreCache() {
const response = await fetch('/api/serviceworker/precache').then( const response = await fetch('/api/serviceworker/precache').then(
(response) => response.json() as Promise<ApiPayload<PrecacheResponse>>, (response) => response.json() as Promise<ApiPayload<PrecacheResponse>>,
) )
@ -183,8 +191,8 @@ async function openCache() {
} }
await Promise.allSettled(addList) await Promise.allSettled(addList)
await swStorage.setItem('$sw.cache.version', version) await swStorage.setItem('$sw.pre-cache.version', version)
await swStorage.setItem('$sw.cache.urls', preCachedUrls) await swStorage.setItem('$sw.pre-cache.urls', preCachedUrls)
return { cache, version, preCachedUrls } return { cache, version, preCachedUrls }
} }