Progressive Web Apps with Service Workers

In this post we will discuss Progressive Web Apps and Service Workers. How can they help modern-day mobile web users, and how are we experimenting with them at Booking.com? We will share some challenges we’ve encountered, as well as some of our learnings.

Jesse Yang
Apr 21, 2016 · 10 min read
Image for post
Image for post

What is a Progressive Web App?

A Progressive Web App (PWA) is a term Google coined to describe its prospect of app-like web experiences, in which web pages are able to offer many features once deemed app-only — connectivity control, push notifications, home screen icons, and the like.

  • Fullscreen mode3
  • Application Cache for offline access4
  • Notifications API5
  • Native dialogs to let users add your app to their home screens with one click
  • A fast and always-usable site even in flaky network connections
  • Push notifications just like native apps

What is a Service Worker?

Quick Facts

  • Service Workers run in a different context, thus have no access to DOM elements or JavaScript variables in the main thread
  • For security reasons the client page (the main thread) must be in https and the service worker script must be in the same origin, but all requests originated from that page can be intercepted by service workers even if they are not in https or served from a different domain
  • A CacheStorage is provided in the worker so that you can store server responses (including headers and response body) locally, and serve them to future requests.
  • Server responses can be forged at the client side if necessary.
  • Everything is asynchronous, and most APIs return a Promise

Browser support

For now, only Chrome, Firefox and Opera have adequate support for service workers. For mobile devices, that means only Android is supported. Since features like homescreen icons and push notifications are integrated in the OS, the whole Progressive Web App initiative really depends on how enthusiastic OS vendors are about it.

What can Service Workers do?

The ServiceWorker API provides very granular methods for developers to intercept requests, to cache and forge responses, opening doors for all kinds of interesting activities like:

  • Precaching assets based on predictions of next user actions (predictions do not rely on service workers per se, but cache manageable can be more programmable with service workers. You can even introduce an expiration time or the LRU algorithm if you want)
  • Serving a cached version when it takes too long to load some resources
  • Rewriting URLs to always be requested with a canonical url10

Service Workers in Action

Now, let’s get our hands dirty and get to grips with the service worker in action.

Registration

Since service workers run in a different context, you’ll need to put the code for the worker in a separate file, then register it in the client page:

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js', { scope: './' }).then(function() {
if (navigator.serviceWorker.controller) {
console.log('The service worker is currently handling network operations.');
} else {
console.log('Failed to register.');
}
});
}
navigator.serviceWorker.register('/scripts/service-worker.js', { scope: '/' })
Server {

listen www.example.com:443 ssl;

...

location /scripts/service-worker.js {
add_header 'Service-Worker-Allowed' '/';
}
}

Inside the worker

Once registered, a service worker will reside in the background intercepting all requests originated from its client pages and staying active until being unregistered.

  • registration — Represents the state of the registration
  • cache — The CacheStorage object in which you can store server responses
  • skipWaiting() — Allowing registration to process from waiting to active state
  • fetch(..) — Part of the GlobalFetch API, also available in the main thread
  • importScripts(..) — Import JS scripts synchronously, ideal for loading a service worker library

Service Workers at Booking.com

Image for post
Image for post

Caching Strategy Examples

The Offline Cookbook15 summarized a few caching strategies for different use cases.

  • cacheOnly — Respond with the cache only, never fire the actual request
  • networkFirst — Always try fetching from the network first and save the latest successful response into the cache, which will be served when the network fails
  • networkOnly — Never uses local cache
toolbox.router.get(/static\/(css|js|images|img)\//,
toolbox.cacheFirst, {
cache: { name: 'static-files' }
}
);
toolbox.router.get(/\/(confirmation|mybooking|myreservations)/i, 
toolbox.networkFirst, {
networkTimeoutSeconds: 10,
cache: { name: 'booking-confirm' }
}
);
toolbox.router.any(/www.google-analytics.com/, toolbox.networkOnly);

Local Shortcuts

Wouldn’t it be nice if users can save a permanent link in bookmarks which will always redirect them to the last booking confirmation they saw?

toolbox.router.get("/confirmations/(.*)", function(request, values, options) {
var url = request.url;
var promise = toolbox.networkFirst(request, values, options);
var confirmationId = values[0];
if (confirmationId) {
// when the request finishes
promise.then(function(response) {
if (!response || response.status !== 200) return;
self.caches.open('last-confirmation').then(function(cache) {
// save a 302 Redirect response to "/confirmation"
var redirectResponse = new Response('Redirecting', {
status: 302,
statusText: 'Found',
headers: {
Location: url
}
});
cache.put('/confirmation', redirectResponse);
});
});
}
return promise;
}, {
networkTimeoutSeconds: 10,
cache: {
name: 'confirmations',
}
});

toolbox.router.get('/confirmation', toolbox.cacheOnly, {
cache: {
name: 'last-confirmation'
}
});

The Secure Domain Problem

To protect users’ data, all parts of our booking process and user account management pages are served via HTTPS, under a separate domain — “secure.booking.com”, instead of “www.booking.com"—the one used for public content such as the Search Results and Hotel Details page.

Final Thoughts

The ServiceWorker API targets a long-standing problem for the mobile web — connectivity. It has the potential to make user experience bearable even when connectivity is bad. It empowers modern web apps with the ability to engage users in more intimate ways, and definitely increases web apps’ competitiveness over native ones.

Resources

  1. Progressive Web Apps
  2. Service Worker Spec
  3. ServiceWorker API doc on MDN
  4. Service Worker Debugging
  5. Recipes 1
  6. Recipes 2
  7. Demos by W3C web mobile group

Booking.com Development

Software development at Booking.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store