Service Workers

writing offline first web applications in the future

slides.forbesl.co.uk

App Cache

CACHE MANIFEST

# updated 2013-06-18T14:51:58.057Z

/
/static/2.0.0/style.css
/static/2.0.0/background.png
/static/2.0.0/image.png

Content + Shell === App

  • Email Clients
  • Magazine Readers
  • Games with downloadable levels
  • RSS readers

Registering a service worker

Must be SSL

<script>
  navigator.serviceWorker.register("/assets/v1/worker.js").then(
    function(serviceWorker) {
      console.log("success!");
      // To use the serviceWorker immediately,
      // you might call location.reload()
    }, function(error) {
      console.error("Installing the worker failed!:", error);
    });
</script>

First Load

  • All requests will come from the network, the service worker is not consulted
  • Documents live out their whole lives using the service worker they start with
  • Gracefull Fallback
  • Reasoning about a page that gets a service worker half way through its livetime is hard

Subsequent Loads

// hosted at: /assets/v1/worker.js
this.version = 1;

var base = 'https://videos.example.com';
var inventory = base + '/services/inventory/data.json';

this.addEventListener('install', function(e) {
});

this.addEventListener('fetch', function(e) {
  var url = e.request.url;
  console.log(url);
  if (url == inventory) {
    e.respondWith(new Response({
      statusCode: 200,
      body: JSON.stringify({/* ... */})
    }));
  }
});

Populating a Cache

this.addEventListener('install', function(e) {
  // Create a cache of resources.
  // Begins the process of fetching them.
  var shellResources = new Cache(
    base + "/assets/v1/base.css",
    base + "/assets/v1/app.js",
    base + "/assets/v1/logo.png",
    base + "/assets/v1/intro_video.webm",
  );

  // The coast is only clear when all the resources are ready.
  e.waitUntil(shellResources.ready());

  // Add Cache to the global so it can be used later during onfetch
  caches.set("shell-v1", shellResources);
});

Fallback to Cache

this.addEventListener('fetch', function(e) {
  // All operations on caches are async, including matching URLs,
  // so we use Promises heavily.
  e.respondWith(fetch(e.request).catch(function() {
    return caches.match(e.request);
  }));
});

Offline First

Offline First

var updatedFromFresh = false;
showSpinner();

var cacheUpdate = fetchData({useCache:true}).then(function (data) {
  if (!updatedFromFresh) {
    updatePage(data);
  }
});

var freshUpdate = fetchData({useCache:false}).then(function (data) {
  updatePage(data);
  updatedFromFresh = true;
});

cacheUpdate.catch(function () {
  return freshUpdate;
}).catch(showNoDataError).then(hideSpinner);

Fetch Data

function fetchData(options) {
  var xhr = new XMLHttpRequest();
  xhr.open('get', 'http://api.example.com/gallery.json');

  if (opts.useCache) {
    if (!navigator.serviceWorker) {
      return Promise.reject(new Error('No worker'));
    }
    xhr.setRequestHeader('x-use-cache', 'true');
  }

  // `sendAsPromised` doesn't really exist yet
  return xhr.sendAsPromised();
}

support x-use-cache

this.addEventListener('fetch', function(e) {
  var useCache = e.request.headers.has('x-use-cache');
  e.request.headers.delete('x-use-cache');
  if (useCache) {
    e.respondWith(caches.match(e.request));
  } else {
    e.respondWith(fetch(e.request));
    caches.get('api').update(e.request);
  }
});

Forbes Lindesay

slides.forbesl.co.uk

Social Networks

Twitter: @ForbesLindesay

GitHub: @ForbesLindesay

Blog: www.forbeslindesay.co.uk

Open Source

Jade

Browserify Middleware

readable-email.org

brcdn.org

tempjs.org