Notify a web page served from service-worker cache that it has been served from SW cache

277 Views Asked by At

I developped a service worker which is serving pages from network first (and caching it) or, when offline, serving it from cache.

Ideally, I would like to inform the user (with a banner, or something like this) that the page has been served from the cache because we detected that he was offline.

Do you have an idea on how to implement this ?

Some ideas I had (but didn't really succeeded to implement) :

  • Inject some code in the cached response body (like, injecting some JS code triggering a offline-detected event which may or may not have an event listener on the webpage, depending on if I want to do something or not on this webpage while offline).
    => I didn't found how to append stuff in response's body coming from the cache to do this.
  • Send a postMessage from service worker to the webpage telling that it has been rendered using a cached content.
    => It doesn't seem to be possible as I don't have any MessagePort available in ServiceWorker's fetch event, making it impossible to send any postMessage() to it.

If you have any idea on how to implement this, I would be very happy to discuss about it.

Thanks in advance :)

1

There are 1 best solutions below

0
Frédéric Camblor On

I looked at different solutions :

  • Use navigator.onLine flag on HTML page : this is not reliable on my case, as my service worker might served cached page (because of, let's say, a timeout) whilst the browser can consider to be online
  • Inject custom headers when serving HTML response from cache : I don't see how it might work since response headers are generally not accessible on clientside
  • Call service-worker's postMessage() few seconds after content is served : problem is, in that case, that fetch event on the "starting" HTML page, doesn't have any clientId yet (we have a chicken & egg problem here, since service worker is not yet attached to the client at the moment the root html page is served from cache)

The only remaining solution I found was to inject some code in the cached response.

Something like this on the service worker side (the main idea is to inject some code on cached response body, triggering a served-offline-page event once DOM content has been loaded) :

async function createClonedResponseWithOfflinePageInjectedEvent(response) {
    const contentReader = response.body.getReader();
    let content = '', readResult = {done: false, value: undefined };
    while(!readResult.done) {
        readResult = await contentReader.read();
        content += readResult.value?new TextDecoder().decode(readResult.value):'';
    }

    // Important part here, as we're "cloning" response by injecting some JS code in it
    content = content.replace("</body>", `
        <script type='text/javascript'>
        document.addEventListener('DOMContentLoaded', () => document.dispatchEvent(new Event('served-offline-page')));
        </script>
        </body>
    `);
    return new Response(content, {
        headers: response.headers,
        status: response.status,
        statusText: response.statusText
    });
}

async function serveResponseFromCache(cache, request) {
    try {
        const response = await cache.match(request);
        if(response) {
            console.info("Used cached asset for : "+request.url);

            // isCacheableHTMLPage() will return true on html pages where we're ok to "inject" some js code to notify about 
            if(isCacheableHTMLPage(request.url)) {
                return createClonedResponseWithOfflinePageInjectedEvent(response);
            } else {
                return response;
            }
        } else {
            console.error("Asset neither available from network nor from cache : "+request.url);
            // Redirecting on offline page
            return new Response(null, {
                headers: {
                    'Location': '/offline.html'
                },
                status: 307
            })
        }
    }catch(error) {
        console.error("Error while matching request "+request.url+" from cache : "+error);
    }
}

On the HTML page, this is simple, we only have to write this in the page :

document.addEventListener('served-offline-page', () => {
    console.log("It seems like this page has been served as some offline content !");
})