2

I am using service workers to create an offline page for my website.

At the moment I am saving offline.html into cache so that the browser can show this file if there is no interent connection.

In the fetch event of my service worker I attempt to load index.html, and if this fails (no internet connection) I load offline.html from cache.

However, whenever I check offline mode in developer tools and refresh the page index.html still shows...

The request isn't failing, and it looks like index.html is being cached even though I didn't specify it to be.

Here is my HTML for index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Service Workers - Test</title>
</head>
<body>
    <h1> Online page! </h1>
    <h3> You are connected to the internet. </h3>
</body>
<script>

if ('serviceWorker' in navigator)
{
    navigator.serviceWorker.register('service-worker.js');
}
</script>

</html>

Here is my HTML for offline.html:

<!DOCTYPE html>
<html>
<head>
    <title>You are Offline - Service Workers - Test</title>
</head>
<body>
    <h1> Welcome to the Offline Page!</h1>
    <h2> You are not connected to the internet but you can still do certain things offline. </h2>

</body>

</html>

Here is my javascript for service-worker.js:

const PRECACHE = "version1"
const CACHED = ["offline.html"];

// Caches "offline.html" incase there is no internet
self.addEventListener('install', event => {
    console.log("[Service Worker] Installed");
    caches.delete(PRECACHE)
    event.waitUntil (
        caches.open(PRECACHE)
            .then(cache => cache.addAll(CACHED))
            .then(    _ => self.skipWaiting())
    );
});

// Clears any caches that do not match this version
self.addEventListener("activate", event => {
    event.waitUntil (
        caches.keys()
            .then(keys => {
                return Promise.all (
                    keys.filter(key => {
                        return !key.startsWith(PRECACHE);
                    })
                    .map(key => {
                        return caches.delete(key);
                    })
                );
            })
            .then(() => {
                console.log('[Service Worker] Cleared Old Cache');
            })
    );
});

this.addEventListener('fetch', function(event) {
    if (event.request.method !== 'GET') return;

    console.log("[Service Worker] Handling Request ");

    // If the request to `index.html` works it shows it, but if it fails it shows the cached version of `offline.html`

    // This isn't working because `fetch` doesn't fail when there is no internet for some reason...

    event.respondWith (
        fetch(event.request)
            .then(response => {
                console.log("[Service Worker] Served from NETWORK");
                return response;
            }, () => {
                console.log("[Service Worker] Served from CACHE");
                return catches.match(event.request.url + OFFLINE_URL);
            })
    );
});

I am running a server using python's simple http server like so:

python -m SimpleHTTPServer

Does anyone know why the offline page isn't working and how I can fix this?

Thanks for the help, David

EDIT:

These images are showing that index.html (localhost) is still loading without internet which means it must be cached.

Offline checkbox is ticked

localhost status:200 (from ServiceWorker) | service-worker.js status:failed

Edit 2:

I've tried to add no-cache to the fetch of index.html and it still is fetching index.html when I have offline checked.

fetch(event.request, {cache: "no-cache"}) ...
David Callanan
  • 5,601
  • 7
  • 63
  • 105
  • 1
    Is the worker throwing any warnings and/or errors? Is it installed? Can you see the cache in dev tools? under the application tab? – Schrodinger's cat Aug 02 '17 at 09:23
  • @Schrodinger'scat The service worker is installed and I refreshed the page again to make sure the service worker handled the fetch of the page. In the cache there was `index.html` even though I didn't cache it. – David Callanan Aug 02 '17 at 09:35
  • Ok! Among other things to look for, you have typos it is `caches.match` on the fetch event and not `catches`. Also, If you have an `update on reload` checked, uncheck it, as when you go offline, the browser searches for a new service worker and the request fails.. – Schrodinger's cat Aug 02 '17 at 11:04
  • @Schrodinger'scat I've fixed all my typos but that didn't fix it. When I refresh the page it loads `index.html` which means that page must be cached even when I didn't include it in my cache list `CACHED` – David Callanan Aug 02 '17 at 11:19
  • It looks like the index.html is served from disk cache and bypasses the service worker for some reason - noticed this after trying this out on my machine - This can be circumvented by checking `disable cache`, but that is a hack – Schrodinger's cat Aug 02 '17 at 13:34
  • @Schrodinger'scat Yeah, that obviously isn't the best solution but I don't see how other websites have built offline versions without having this issue. I guess my best solution would be to handle an `offline event` in `index.html` and redirect to `offline.html`. The only thing I don't like about this is it will show `/offline.html` in the browser. Thanks for your help. – David Callanan Aug 02 '17 at 14:16

3 Answers3

4

I think we have all forgotten how the network request works from a browser's point of view.

The issue here is, index.html is served from the disk cache when the service worker intercepts requests. browser ===> Service Worker ===>fetch event

inside the fetch event, we have ,

  • Check If there is network connectivity
    • If there is, fetch from network and respond
    • Else, fetch from cache and respond

Now, how does

If there is network connectivity, fetch from network work?

Service Worker OnFetch ===> Check in Disk Cache ===>Nothing? Fetch Online

The page being fetched here, is index.html

and the cache-control headers for index.html ,

Do Not Specify a no-cache

Hence the whole issue of the offline page not showing up.

Solution

  • Set a cache-control header with limiting values for index.html - On the server side
  • Or, add headers in the fetch request to the effect

    • pragma:no-cache
    • cache-control:no-cache

How Do I add these headers to fetch?

Apparently, fetch and the browser have their own reservations about the request body when it comes to a GET

Also, weirdness and utter chaos happens If you reuse the event.request object, for a fetch request, and add custom headers. The chaos is a list of Uncaught Exceptions due to the fetch event's request.mode attribute , which bars you from adding custom headers to a fetch when under a no-cors or a navigate mode.

Our goal is to :

Identify that the browser is truly offline and then serve a page that says so

Here's How:

Check If you can fetch a dummy html page say test-connectivity.html under your origin, with a custom cache: no-cache header. If you can, proceed, else throw the offline page

self.addEventListener( 'fetch', ( event ) => {
let headers = new Headers();
headers.append( 'cache-control', 'no-cache' );
headers.append( 'pragma', 'no-cache' );
var req = new Request( 'test-connectivity.html', {
    method: 'GET',
    mode: 'same-origin',
    headers: headers,
    redirect: 'manual' // let browser handle redirects
} );
event.respondWith( fetch( req, {
        cache: 'no-store'
    } )
    .then( function ( response ) {
        return fetch( event.request )
    } )
    .catch( function ( err ) {
        return new Response( '<div><h2>Uh oh that did not work</h2></div>', {
            headers: {
                'Content-type': 'text/html'
            }
        } )
    } ) )
} );

The {cache:'no-store'} object as the second parameter to fetch , is an unfortunate NO-OP. Just doesn't work. Just keep it for the sake of a future scenario. It is really optional as of today.

If that worked, then you do not need to build a whole new Request object for fetch

cheers!

The code piece that creates a new request is generously borrowed from @pirxpilot 's answer here

The offline worker for this specific question on pastebin

https://pastebin.com/sNCutAw7

Schrodinger's cat
  • 1,074
  • 10
  • 17
2

David, you have two errors in one line.

Your line

return catches.match(event.request.url + OFFLINE_URL);

should be

return caches.match('offline.html');

It's catches and you haven't defined OFFLINE_URL and you don't need event request url

Karol Klepacki
  • 2,070
  • 1
  • 18
  • 30
  • Oh wow! I used to have `OFFLINE_URL` until I changed it into an array called `CACHED`. Shouldn't I see an error though with the typo... I'll try that now, Thank you. – David Callanan Aug 02 '17 at 09:37
  • I've updated my code, but it still isn't working. For some reason `index.html` is being cached even though I didn't cache it, therefore there is no error and is still showing `index.html` when there is no internet connection. – David Callanan Aug 02 '17 at 09:43
2

I tried your code and I got the same result as you in the dev tools network tab. The network tab says it loaded the index.html from service-worker, but actually the service-worker returns the cached Offline Page as expected!

Offline Page is shown

Stef Chäser
  • 1,911
  • 18
  • 26
  • This is making no sense as it just started working and I haven't changed anything. I am so confused. Thanks for the help! – David Callanan Aug 02 '17 at 12:54
  • What happens if you uncheck `disable cache` and go offline? – Schrodinger's cat Aug 02 '17 at 13:02
  • @Schrodinger'scat `disable cache` is unchecked for me already. For some reason it stopped working again and I've pushed it to http://dcallanan.github.io/service-workers/example1/ Could you tell me if it is working for you? Thanks – David Callanan Aug 02 '17 at 13:07
  • NP! I actually copied over your service worker already, looking now for browser weirdness if any – Schrodinger's cat Aug 02 '17 at 13:08
  • The behaviour is , on first load, it registers, activates, and caches `offline.html` Then once the service worker is controlling the page, if you go offline, a normal reload is doing absolutely **Nothing** to fetch events Whereas, If you `Clean Reload`, the browser then **for realsies** goes offline. Post which, you reload normally, and the offline page loads Either you `clean reload` and then reload for it to work, or , you `disable cache` and reload for it to work. - **This is weird** considering, I also set the headers for the worker to a `no-store private must-revalidate no-cache` – Schrodinger's cat Aug 02 '17 at 13:11
  • What do you mean by a `Clean Reload`? – David Callanan Aug 02 '17 at 13:15
  • `cmd+shift+R` - clean reload - bypasses disk cache – Schrodinger's cat Aug 02 '17 at 13:17
  • Clean reload is now showing the google chrome `there is no internet connection` page. If I try clean reload when `offline` isn't checked, then the next time I check `offline` it still shows the online page. – David Callanan Aug 02 '17 at 13:20