Offline Mode

Web applications can provide the offline experience using two techniques. The older implementation, Application Cache, is widely implemented in the browsers, but is now in the process of deprecation due to various conceptual and design flaws. It is not covered here.

The modern alternative is called Service Worker. Web applications running on HTTPS can request the browser to install the separate code unit called Service Worker. This unit is then run in separation from the owning Web application, communicating with it via events. Besides being the enabler for multiple complex APIs like Push Notifications, Background Sync or Geofencing, it can work as a fully featured network proxy. It can intercept all the HTTP requests, alter its content or behaviors, or - most notably - manage offline caching.

Code examples adapted from HTML5 Rocks article.

API glimpse

Within the owning Web application - Installation

navigator.serviceWorker.register(path)
Installs the Service Worker code available under path. Returns a Promise.
navigator.serviceWorker.ready
Returns a Promise resolved with serviceWorkerRegistration when the Worker is initialized.
serviceWorkerRegistration.update()
Checks the server for an updated version of the Service Worker without consulting caches.
serviceWorkerRegistration.unregister()
Uninstalls the Service Worker.

Within the Service Worker instance - Cache prefetch

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('my-cache-v1')
      .then(function (cache) {
        return cache.addAll(['/', '/styles/main.css', '/scripts/main.js']);
      })
  );
});
self.addEventListener('install', listener)
An event fired within the Service Worker when it is being installed. Useful to prefetch the resources needed in the offline mode and to prefill the cache.
event.waitUntil(promise)
An install event method that expects a Promise which signals the end of the worker's installation phase when resolved.
caches.open(cacheName)
Returns a Promise resolved with the named cache accessor object that is able to keep the resources needed for the offline mode.
cache.addAll(urls)
Adds all the resources specified with the URLs to the named cache for the future, possibly offline, use.

Within the Service Worker instance - Requests cache

function isSuccessful(response) {
  return response &&
    response.status === 200 &&
    response.type === 'basic';
}

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request)
      .then(function (response) {
        if (response) {
          return response; // Cache hit
        }

        return fetch(event.request.clone())
          .then(function (response) {
            if (!isSuccessful(response)) {
              return response;
            }

            caches.open(CACHE_NAME)
              .then(function (cache) {
                cache.put(event.request, response.clone());
              });

            return response;
          }
        );
      })
    );
});
self.addEventListener('fetch', listener)
An event fired within the Service Worker whenever any of its related browser tabs have issued a HTTP request. Useful to serve already cached response or intercept and cache the incoming response.
event.respondWith(promise)
A fetch event method that expects a Promise which resolves with the request data to be returned to the requesting browser tab.
cache.put(request, response)
Adds the specified response for the request to the named cache for the future, possibly offline, use.
caches.match(event.request)
Returns a Promise resolved when the fetch event represents a request to the resource already cached within the Service Worker's cache.

See also this website's own Service Worker implementation.

Resources