ffline web apps

Slobodan Stojanovic

I am

CTO of Cloud Horizon

I am

JS Belgrade meetup organizer

I am

Why ?

Why would anyone want an offline web app?

"Web" and "online" are two closely associated terms, downright synonymous to many people. So why on earth would we talk about "offline" web technologies, and what does the term even mean?

How do we see future?

...
​Everyone's happy, nothing is broken or smudged. There is infinite wi-fi with infinite bandwidth, no batteries run out, no traffic no delays.
...

Are we always connected to the internet?

What are the problems with online only apps?

Issues in transport

 


Airlines, high-speed trains, subway...

(Hyperloop?)

"Geography" issues

 


There's much better examples than Africa :)

Bad connection

 


Far from modem or any other reason

Other issues?

Why do we treat web apps differently than native?

Native apps examples

 


(Google Maps, Instagram...)

How?

How to build an offline web application?

What are the problems we need to solve?

How to sync the app with the server?

How to solve conflicts?

UX?

Technology

Offline events

navigator.onLine

navigator.onLine is a property that maintains a true/false value (true for online, false for offline). This property is updated whenever the user switches into "Offline Mode"

"online" and "offline"
events

These two events are fired on the <body> of each page when the browser switches between online and offline mode. Additionally, the events bubble up from document.body, to document, ending at window.

Demo

More info

App cache

Benefits

  • Offline browsing: users can navigate a site even when they are offline.
     
  • Speed: cached resources are local, and therefore load faster.
     
  • Reduced server load: the browser only downloads resources that have changed from the server.
<html manifest="example.appcache">
  ...
</html>

index.html

example.appcache

CACHE MANIFEST
# v1 2015/12/26 <- This is just a comment
index.html
cache.html
style.css
image1.png

# Use from network if available
NETWORK:
/api

# Fallback content
FALLBACK:
/foo/bar fallback.html
window.applicationCache
const appCache = window.applicationCache;

switch (appCache.status) {
  case appCache.UNCACHED: // UNCACHED == 0
    return 'UNCACHED';
    break;
  case appCache.IDLE: // IDLE == 1
    return 'IDLE';
    break;
  case appCache.CHECKING: // CHECKING == 2
    return 'CHECKING';
    break;
  case appCache.DOWNLOADING: // DOWNLOADING == 3
    return 'DOWNLOADING';
    break;
  case appCache.UPDATEREADY:  // UPDATEREADY == 4
    return 'UPDATEREADY';
    break;
  case appCache.OBSOLETE: // OBSOLETE == 5
    return 'OBSOLETE';
    break;
  default:
    return 'UKNOWN CACHE STATUS';
    break;
};
events:
// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);

// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);

// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);

// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);

// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);

// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);

// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);

// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);

Demo

"Application Cache is a Douchebag"
  • Files always come from the applicationcache, even if you’re online;
  • The application cache only updates if the content of the manifest itself has changed​;
  • Never ever ever far-future cache the manifest;
  • Non-cached resources will not load on a cached page;
  • ...
Deprecated

This feature has been removed from the Web standards. They recommend using Service Workers instead.

More info

Service Workers

Service workers essentially act as proxy servers that sit between web applications, and the browser and network (when available.)

How it works

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

Cache resources

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

        return fetch(event.request);
      }
    )
  );
});

Cache requests

this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v2').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        // …
        // include other new resources for the new version...
      ]);
    });
  );
});

Updating service worker

this.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v2'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});

Deleting old cache

Demo

More info

Storage

localStorage

The localStorage property allows you to access a local Storage object.

localStorage is similar to sessionStorage. The only difference is that, while data stored inlocalStorage has no expiration time, data stored in sessionStorage gets cleared when the browsing session ends - that is, when the browser is closed.

Demo

More info

WebSQL

Demo

Deprecated

Since November 18, 2010, the W3C announced that Web SQL database is a deprecated specification.

More info

IndexedDb

IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs.

  • It uses indexes to enable high performance searches.
     

  • It lets you store and retrieve objects that are indexed with a key.
     

  • Any objects supported by the structured clone algorithm can be stored.

Structured clone algorithm

The structured clone algorithm is a new algorithm defined by the HTML5 specification for serializing complex JavaScript objects.

 

The algorithm, in essence, walks over all the fields of the original object, duplicating the values of each field into a new object. 

Structured clones can duplicate*:

  • RegExp objects                                                                     
  • Blob objects
  • File objects
  • ImageData objects
  • FileList  objects
  • ...

 

It cannot duplicate:

  • Error objects                                                                         
  • Function objects
  • DOM
  • ...

Concepts

  • IndexedDB databases store key-value pairs
  • IndexedDB is built on a transactional database model
  • The IndexedDB API is mostly asynchronous
  • IndexedDB uses a lot of requests
  • IndexedDB uses DOM events to notify you when results are available
  • IndexedDB is object-oriented
  • IndexedDB does not use SQL
  • IndexedDB adheres to a same-origin policy

IndexedDB API

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB ||
                   window.msIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.

window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || 
                        window.msIDBTransaction || {READ_WRITE: "readwrite"};
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange ||
                     window.msIDBKeyRange;
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)


if (!window.indexedDB)
    window.alert(`Your browser doesn't support a stable version of IndexedDB.
                  Such and such feature will not be available.`);
var db;

var request = indexedDB.open("MyTestDatabase");

request.onerror = function(event) {
  console.error('Database error', event.target.errorCode);
};

request.onsuccess = function(event) {
  db = event.target.result;
};

// This event is only implemented in recent browsers
request.onupgradeneeded = function(event) { 
  var db = event.target.result;

  // ...
};
const dbName = "the_name";

var request = indexedDB.open(dbName, 2);

request.onerror = function(event) { /* Handle errors. */ };
request.onupgradeneeded = function(event) {
  var db = event.target.result;

  // Create an objectStore to hold information about our customers.
  // We're going to use "ssn" as our key path because it's guaranteed to be unique
  var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

  // Create an index to search customers by name. We may have duplicates here.
  objectStore.createIndex("name", "name", { unique: false });

  // Create an index to search customers by email.
  objectStore.createIndex("email", "email", { unique: true });

  // Use transaction oncomplete to make sure the objectStore creation is 
  // finished before adding data into it.
  objectStore.transaction.oncomplete = function(event) {
    // Store values in the newly created objectStore.
    var customerObjectStore = db.transaction("customers", "readwrite")
                                .objectStore("customers");
    for (var i in customerData) {
      customerObjectStore.add(customerData[i]);
    }
  };
};

Demo

More info

More info and resources

Limits

A research report on browser storage.

localForage

Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

PouchDB

Open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser.

Dexie.js

A Minimalistic Wrapper for IndexedDB.

What?

What should work offline on web?

Read more

Designing Offline-First Web Apps

Offline first!

Offline Recipes for Service Workers

Apps that need to work with unstable internet connection

Apps for warehouses, factories, and other places with unstable web conditions..

Content based apps

Where ever you want user to be able to access some content that he already saw regardless of the connection

Serverless web apps

Yes, that's (almost) possible

Better (client side) caching

For better user experience, faster apps

with less server load

Where else?

Slobodan Stojanovic

I am

Offline web apps handbook [WIP]:

This presentation:

Offline web apps

By Slobodan Stojanovic

Offline web apps

  • 2,423