Search code examples
javascripthtmlcachingservice-workerworkbox

Is there a way to tell if you're getting cached data from the service worker?


I'm working on an authoring / CMS type application. The application allows concurrent editing of various 'blocks' of data. It has supported offline using appache and indexDB. With this approach I know if my data is coming from the network or from the cache. Now I'd like to migrate things to use service workers and the new cache api.

I need a way to know if my data (request) was served from the cache or the network so I can inform the users they are possibly looking at stale data so any any edits may override data they don't know about. IMO, this would a pretty common thing to do but it's turning out not to be so easy...

I'm currently trying to get things working using WorkBox but I'd be more than happy with a native solution. Using Workbox I've tried to set a new header on the response but Chrome complains about

const apiStrategy = workbox.strategies.cacheFirst({
    cacheName: 'data-cache',
    cacheExpiration: {
        maxEntries: 100,
        maxAgeSeconds: 3600 * 24
    },
    cacheableResponse: { statuses: [200] }
});

workbox.routing.registerRoute(
    new RegExp('/api/'),
    async ({event, url}) => {
        const cachedResponse = await apiStrategy.handle({event, url});
        if (cachedResponse) {
            const responseClone = cachedResponse.clone();
            responseClone.headers.set('x-sw-cache', 'yes');
            return responseClone;
        }
        return cachedResponse;
    }
);

So is there any way to know if the response came from the network or the cache?


Solution

  • Turns out there's a bunch of plugins. I was able to get what I needed using the custom cacheWillUpdate plugin. My code now looks like

    workbox.routing.registerRoute(
        new RegExp('/api/'),
        new workbox.strategies.NetworkFirst({
            cacheName: 'data-cache',
            cacheExpiration: {
                maxEntries: 100,
                maxAgeSeconds: 3600 * 24
            },
            cacheableResponse: { statuses: [ 200 ] },
            plugins: [
                {
                    cacheWillUpdate: ({ response }) => {
                        return newResponse(response.clone(), (headers) => {
                            headers.set("x-sw-cache", 'yes');
                            return headers;
                        });
                    }
                }
            ]
        })
    );
    
    
    const newResponse = (res, headerFn) => {
        const cloneHeaders = () => {
            const headers = new Headers();
            for (const kv of res.headers.entries()) {
                headers.append(kv[0], kv[1]);
            }
            return headers;
        };
    
        const headers = headerFn ? headerFn(cloneHeaders()) : res.headers;
    
        return new Promise((resolve) => {
            return res.blob().then((blob) => {
                resolve(new Response(blob, {
                    status: res.status,
                    statusText: res.statusText,
                    headers: headers
                }));
            });
        });
    };
    

    The newReponse function was taken from How to alter the headers of a Response? thanks @mjs!

    I can now check for the x-sw-cache header to inform my users they may be looking at state data. :)