workbox
workbox copied to clipboard
Cache is not updated in StaleWhileRevalidate
Library Affected: workbox 6.4.1
Browser & Platform: all browsers
Issue or Feature Request Description: Hi, I ran into a problem that after refreshing the page, the html cache for NavigationRoute is not updated
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');
self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(OFFLINE_CACHE_NAME)
.then((cache) => {
cache.add(FALLBACK_HTML_URL)
})
);
});
workbox.navigationPreload.enable();
const navigationHandler = async (params) => {
try {
return await new workbox.strategies.StaleWhileRevalidate({
cacheName: "navigation-and-route",
plugins: [
new workbox.broadcastUpdate.BroadcastUpdatePlugin('workbox-broadcast-update'),
new workbox.expiration.ExpirationPlugin({
maxEntries: 20,
maxAgeSeconds: 7 * 24 * 60 * 60, // 1 week
purgeOnQuotaError: true,
}),
],
}).handle(params);
} catch (error) {
return caches.match(FALLBACK_HTML_URL, {
cacheName: OFFLINE_CACHE_NAME,
});
}
};
workbox.routing.registerRoute(
new workbox.routing.NavigationRoute(navigationHandler, {
denylist: [
new RegExp('admin'),
],
})
);
workbox.precaching.cleanupOutdatedCaches();
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
workbox.core.skipWaiting();
workbox.core.clientsClaim();
`
but at the same time new workbox.broadcastUpdate.BroadcastUpdatePlugin ('workbox-broadcast-update') is triggered and a page reload notification is shown to me
And something else. I am using BroadcastUpdatePlugin to show a restart notification. In chrome, this works great, but in Safari, the broadcast continues to send a notification that the cache has been updated. For example
workbox.routing.registerRoute(
({request}) =>
request.destination === "style",
new workbox.strategies.StaleWhileRevalidate({
cacheName: style,
plugins: [
new workbox.broadcastUpdate.BroadcastUpdatePlugin('workbox-broadcast-update'),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [200]
}),
new workbox.expiration.ExpirationPlugin({
maxEntries: 15,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
purgeOnQuotaError: true,
}),
],
})
);
navigator.serviceWorker.addEventListener('message', async (event) => {
// Optional: ensure the message came from workbox-broadcast-update
console.log(event.data)
if (event.data.meta === "workbox-broadcast-update") {
//show notification for reload page
showUpdateBar();
}
});
`
Hello @CapInSpase!
Regarding your first comment, the BroadcastUpdatePlugin only runs if a cache update happened: https://github.com/GoogleChrome/workbox/blob/cdfc4cbc5d16f076d6ad51a49f19ad961c0c7482/packages/workbox-broadcast-update/src/BroadcastUpdatePlugin.ts#L58-L60
By the time you see a message broadcast while using the plugin, the cache put() should have already occurred. I am not sure how you are confirming that the cached HTML was not updated, but is it possible that you're mistaken? If not, is there a reproduction available on a live site that I could take a look at?
Regarding your second comment, while there is some specific logic in place for broadcasting update notifications for navigation request, your code snippet implies that you're seeing odd behavior when broadcasting updates about cached CSS. The code for that is fairly straightforward: https://github.com/GoogleChrome/workbox/blob/cdfc4cbc5d16f076d6ad51a49f19ad961c0c7482/packages/workbox-broadcast-update/src/BroadcastCacheUpdate.ts#L200-L203
I'm not aware of anything in Workbox that would cause that postMessage() to execute again independent of an actual cache update. You mentioned that Safari exhibits this behavior, and Chrome doesn't. What about Firefox? And again, is there a live version of the web app (along with a mechanism for triggering an update) that I can reproduce this against?
Hi @jeffposnick I'm currently experiencing this issue (alongside another one I think) and I do have a live site you can check out.
Everything seems to be working fine on every other platform except Safari.
I've tested Safari 17.1 on OS X and 16.7.2 on iOS.
The English version of the site can be found here: https://en.buses.uy
The site should display an alert prompting the users to "update" whenever a service worker update or a cache update is detected. (The alert is the same in both cases, although the callback for the "update" button differs for each case).
Code snippets:
service-worker.js
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js')
const CACHE = 'busesuy-offline-pages'
const offlineFallbackPage = 'sin-conexion'
const ignoredHosts = ['localhost']
const ignoredPaths = ['/admin', '/imgs/bg']
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
self.addEventListener('install', async (event) => {
event.waitUntil(
new Promise((resolve, reject) => {
// Delete all cached items when the SW first installs
caches.delete(CACHE).then(() => {
// Cache the offline fallback page
caches.open(CACHE).then((cache) => cache.add(offlineFallbackPage)).then(resolve).catch(reject)
}).catch(reject)
})
)
})
if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable()
}
workbox.routing.registerRoute(
(event) => {
return (
(ignoredHosts.indexOf(event.url.hostname) === -1) &&
(ignoredPaths.reduce((noMatch, path) => noMatch && !event.url.pathname.includes(path), true))
)
},
new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE,
plugins: [
new workbox.broadcastUpdate.BroadcastUpdatePlugin(),
],
})
)
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResp = await event.preloadResponse
if (preloadResp) return preloadResp
const networkResp = await fetch(event.request)
return networkResp
} catch (error) {
const cache = await caches.open(CACHE)
const cachedResp = await cache.match(offlineFallbackPage)
return cachedResp
}
})())
}
})
SW Registration
installServiceWorker() {
if (typeof navigator.serviceWorker !== 'undefined') {
// Display an "Update" alert whenever the service worker detects an update
navigator.serviceWorker.addEventListener('message', async (event) => {
if (event.data.meta !== 'workbox-broadcast-update') return
this.updateAlert(() => window.location.reload())
})
// Install the service worker
navigator.serviceWorker.register('/service-worker.js').then((registration) => {
registration.addEventListener('updatefound', () => {
const newServiceWorker = registration.installing
if (newServiceWorker) newServiceWorker.addEventListener('statechange', () => serviceWorkerChangeStateHandler(newServiceWorker))
})
// If there's a waiting/installing service worker
// Listen for state changes to reload the page once the new service worker is active
const newServiceWorker = registration.waiting
if (newServiceWorker) newServiceWorker.addEventListener('statechange', () => serviceWorkerChangeStateHandler(newServiceWorker))
serviceWorkerChangeStateHandler(newServiceWorker)
})
}
}
serviceWorkerChangeStateHandler
function serviceWorkerChangeStateHandler(serviceWorker: ServiceWorker | null) {
// When the service worker gets activated reload the page
if (serviceWorker?.state === 'activated') window.location.reload()
else if (serviceWorker?.state === 'installed') {
// Display an alert prompting the user to update
window.Buses.updateAlert(() => serviceWorker?.postMessage({ type: 'SKIP_WAITING' }))
}
}
window.Buses.updateAlert/this.updateAlert
This one is just a function that displays the alert. It accepts a callback function as a parameter that gets called when/if the users clicks on the "Update" button. In these cases the callback is either "window.location.reload()" or a post message to the SW telling it to "SKIP_WAITING"
I've tried commenting out code to see where the issue/s are and the infinite "update prompting" stops if I comment the following sections of code on the "installServiceWorker()" function:
const newServiceWorker = registration.waiting
if (newServiceWorker) newServiceWorker.addEventListener('statechange', () => serviceWorkerChangeStateHandler(newServiceWorker))
serviceWorkerChangeStateHandler(newServiceWorker)
And:
navigator.serviceWorker.addEventListener('message', async (event) => {
if (event.data.meta !== 'workbox-broadcast-update') return
this.updateAlert(() => window.location.reload())
})
This last bit might be causing issues given that it looks like new versions of the service worker fail to "skipWaiting()" on Safari because I've noticed I have 2 service workers I can inspect. This last issue might be unrelated to the other one but I mention it just in case.
Thank you in advance for your time and help!