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 Cache API and is available within Service Worker – the separate code unit the Web applications running on HTTPS can request the browser to install. This unit is then run in separation from the owning Web application, communicating with it via events. Service Worker is the basic building block of the Progressive Web Apps (PWA) idea. 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.
The content being added to Cache API might be additionally indexed and exposed to the browser using Content Indexing API. As of Spring 2020, this is an early-stage proposal by Google Chrome, available only in this browser on Android via Origin Trial experimentation. The entries added to the index should be cached in Cache API and served offline via Service Worker. The browser might then present the indexed entries for the user while being offline.
API glimpse
Within the owning Web application - Installation
navigator.serviceWorker.register(path)
- Installs the Service Worker code available under
path
. Returns aPromise
. navigator.serviceWorker.ready
- Returns a
Promise
resolved withserviceWorkerRegistration
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 thefetch
event represents a request to the resource already cached within the Service Worker's cache.
See also this website's own Service Worker implementation.
Content Indexing API (Google Chrome experimentation)
serviceWorkerRegistration.index.add({id, url, title, description, icons, category})
- Adds an entry identified by
id
to the offline index, with its metadata. It does not cache the entry – it needs to be separately added using Cache API. serviceWorkerRegistration.index.delete(id)
- Removes the previously added entry identified by
id
from the offline index. serviceWorkerRegistration.index.getAll()
- Returns a
Promise
resolved with the list of entries previously added to the index. self.addEventListener('contentdelete', listener)
- An event fired within the Service Worker when the entry from the index has been deleted by the user. Useful for removing the resources from the Cache API.
Resources
- Service Workers Specification Draft
- Service Workers: an Introduction
- The offline cookbook
- ServiceWorker API - MDN docs
- Service Worker Sample: Pre-fetching Resources During Registration
- Beyond Offline: Using a custom service worker to expand on your browser’s capabilities
- ServiceWorker: Revolution of the Web Platform
- Is ServiceWorker Ready? - Jake Archibald
- Making a Simple Site Work Offline with ServiceWorker
- Indexing your offline-capable pages with the Content Indexing API