When responding to requests with cached entries, while being fast, it comes with a tradeoff that users may end up seeing stale data.
The workbox-broadcast-update
package provides a standard way of notifying
Window Clients
that a cached response has been updated. This is most commonly used along with
the StaleWhileRevalidate
strategy.
Whenever the "revalidate" step of that strategy retrieves a response from the
network that differs from what was previously cached, this module will send a
message (via
postMessage()
)
to all Window Clients within scope of the current service worker.
Window Clients can listen for updates and take appropriate action, like automatically displaying a message to the user letting them know that updates are available.
How are updates determined?
Certain headers of the cached and new
Response
objects are compared, and if any of the headers have different values,
it's considered an update.
By default, the Content-Length
, ETag
, and Last-Modified
headers are
compared.
Workbox uses header values instead of a byte-for-byte comparison of response bodies to be more efficient, in particular for potentially large responses
Using Broadcast Update
The library is intended to be used along with the StaleWhileRevalidate
caching strategy, since that strategy involves returning a cached
response immediately, but also provides a mechanism for updating the
cache asynchronously.
To broadcast updates, you just need to add a broadcastUpdate.BroadcastUpdatePlugin
to your
strategy options.
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
plugins: [new BroadcastUpdatePlugin()],
})
);
In your web app, before the DOMContentLoaded
event fires, you can listen for these events like so:
navigator.serviceWorker.addEventListener('message', async event => {
// Optional: ensure the message came from workbox-broadcast-update
if (event.data.meta === 'workbox-broadcast-update') {
const {cacheName, updatedURL} = event.data.payload;
// Do something with cacheName and updatedURL.
// For example, get the cached content and update
// the content on the page.
const cache = await caches.open(cacheName);
const updatedResponse = await cache.match(updatedURL);
const updatedText = await updatedResponse.text();
}
});
Message format
When a message
event listener is invoked in your web app, the
event.data
property will have the following format:
{
type: 'CACHE_UPDATED',
meta: 'workbox-broadcast-update',
// The two payload values vary depending on the actual update:
payload: {
cacheName: 'the-cache-name',
updatedURL: 'https://example.com/'
}
}
Customize Headers to Check
You can customize the headers to check by setting the headersToCheck
property.
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
plugins: [
new BroadcastUpdatePlugin({
headersToCheck: ['X-My-Custom-Header'],
}),
],
})
);
Advanced Usage
While most developers will use workbox-broadcast-update
as a plugin
of a particular strategy as shown above, it's possible to use the underlying
logic in service worker code.
import {BroadcastCacheUpdate} from 'workbox-broadcast-update';
const broadcastUpdate = new BroadcastCacheUpdate({
headersToCheck: ['X-My-Custom-Header'],
});
const cacheName = 'api-cache';
const request = new Request('https://example.com/api');
const cache = await caches.open(cacheName);
const oldResponse = await cache.match(request);
const newResponse = await fetch(request);
broadcastUpdate.notifyIfUpdated({
cacheName,
oldResponse,
newResponse,
request,
);
Types
BroadcastCacheUpdate
Uses the postMessage()
API to inform any open windows/tabs when a cached
response has been updated.
For efficiency's sake, the underlying response bodies are not compared; only specific response headers are checked.
Properties
-
constructor
void
Construct a BroadcastCacheUpdate instance with a specific
channelName
to broadcast messages onThe
constructor
function looks like:(options?: BroadcastCacheUpdateOptions) => {...}
-
options
BroadcastCacheUpdateOptions optional
-
returns
-
-
notifyIfUpdated
void
Compares two Responses and sends a message (via
postMessage()
) to all window clients if the responses differ. Neither of the Responses can be opaque.The message that's posted has the following format (where
payload
can be customized via thegeneratePayload
option the instance is created with):{ type: 'CACHE_UPDATED', meta: 'workbox-broadcast-update', payload: { cacheName: 'the-cache-name', updatedURL: 'https://example.com/' } }
The
notifyIfUpdated
function looks like:(options: CacheDidUpdateCallbackParam) => {...}
-
options
-
returns
Promise<void>
Resolves once the update is sent.
-
BroadcastCacheUpdateOptions
Properties
-
headersToCheck
string[] optional
-
notifyAllClients
boolean optional
-
generatePayload
void optional
The
generatePayload
function looks like:(options: CacheDidUpdateCallbackParam) => {...}
-
options
-
returns
Record<stringany>
-
BroadcastUpdatePlugin
This plugin will automatically broadcast a message whenever a cached response is updated.
Properties
-
constructor
void
Construct a
workbox-broadcast-update.BroadcastUpdate
instance with the passed options and calls itsnotifyIfUpdated
method whenever the plugin'scacheDidUpdate
callback is invoked.The
constructor
function looks like:(options?: BroadcastCacheUpdateOptions) => {...}
-
options
BroadcastCacheUpdateOptions optional
-
returns
-
Methods
responsesAreSame()
workbox-broadcast-update.responsesAreSame(
firstResponse: Response,
secondResponse: Response,
headersToCheck: string[],
)
Given two Response's
, compares several header values to see if they are
the same or not.
Parameters
-
firstResponse
Response
-
secondResponse
Response
-
headersToCheck
string[]
Returns
-
boolean