THE OFFLINE WEB

   
CouchDB      +       PouchDB

Building a more capable web

"A web based OS, but then it wont work offline?"

Ever seen a wild haggis?

Gmail Offline

Thunderbird Offline

Why care?

Why care?

We go offline

Why care?

Thinking outside the
bubble, the world doesnt have ubiqutious internet.

Why care?

That mobile thing got
popular huh?

Why care?

Performance, offline is like, really fast

Why care?

Reliability and Trust

Why care?

Robustness, server downtime doesnt mean your app is unavailable

Why care?

  • Privacy
  • Security
  • Peer to Peer functionality
  • .... and more

Delivering the best
user experience

SOLD!

But how does it work?

It isnt magic

Different applications will have different approaches

Difficulty rating: meh

Warn the user they are
offline

Difficulty rating: hard

Provide users cached data

Difficulty rating: very hard

Allow users minimal
interaction

Difficulty rating: like, super hard

Allow users full interaction with complex app

How do we make this work?

Packaged web apps

  • Embedded Webkit
  • Cordova
  • Firefox + Chrome Apps

Web app manifest

{
  "name": "The Example App",
  "description": "Exciting Open Web development action!",
  "launch_path": "/",
  "version": "1.0"
}
http://www.w3.org/2012/sysapps/manifest/

AppCache

CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js
http://cdn.example.com/scripts/main.js

Application Cache is a Douchebag

Service Workers

navigator.serviceWorker.register("/assets/v1/worker.js").then(
  function(serviceWorker) {
    console.log("success!");
    serviceWorker.postMessage("Howdy from your installing page.");
  }, function(why) {
    console.error("Installing the worker failed!:", why);
  }
);
http://www.w3.org/TR/2014/WD-service-workers-20140508/

localStorage

localStorage.myData = 'quick brown fox';
  • Good for small settings
  • Has to be a string
  • Synchronous, blocks page

WebSQL


IndexedDB Polyfill

IndexedDB

var trans = db.transaction(["todo"], "readwrite");
var store = trans.objectStore("todo");
var request = store.put({
  "text": todoText,
  "timeStamp" : new Date().getTime()
});
  • Powerful, but complicated
  • Implementations still somewhat different
  • Likely want to use a wrapper

So I am ready to go? that didnt seem so hard ...

Offline apps require a
different architecture

May be tempted to
degrade when offline

addEventListener('submit', function() {
  request.post('/create/thing/', function (err, res) {
    if (err) {
      retryPost();
    }
    showNewThing();
  });
});

Dont do that

Hope for the best, plan for the worst

Solution: put all the state in the client, sync data when possible

now you have
N-1 problems

Syncing data is hard

  • Want to sync changes asap
  • Transfer the minimal amount of data
  • Handle unreliable networks
  • Deal with conflicting changes

You do not want to write your own protocol

Things took 2 years to write sync for their todo list

Email and calendar have their own protocol

You do not want to use those protocols

Solutions:

JS Git - Git in JavaScript (yes seriously)

https://github.com/creationix/js-git

Solutions:

Offline first Application Framework

http://hood.ie

PouchDB - The Database that Syncs!

http://pouchdb.com

PouchDB takes CouchDBs storage + replication and puts it in the browser

Stores data locally and syncs in the background

Basic database API

var db = new PouchDB('name');

db.post({'some': 'data'});

db.sync('http://myserver.com/database', {live: true})

db.changes().on('change', function() {
  console.log('Ch-Ch-Changes');
});

Dont care where events come from

elem.addEventListener('submit', function() {
  db.put(formData);
});

db.changes().on('change', function() {
  updateUI();
});

Data holds the state

db.post({
  'type': 'email',
  'subject': 'KITTENS!'
  'state': 'unsent'
});

On the server:

db.changes().on('change', function(change) {
  var doc = change.doc;
  if (doc.type === 'email' && doc.state === 'unsent') {
    sendEmail(doc, function (err, res) {
      if (err) {
        doc.failed = true;
        doc.error = err.message;
      } else {
        doc.state = 'sent';
      }
      db.put(doc);
    }
  }
});

Conflicts

decided by the app

function mergeConflicts(docs) {
  var result = {};
  docs.forEach(function(doc) {
    extend(result, doc);
  });
  return result;
}

DEMO

http://sotr.pouchdb.com

To Surmise

  • Make your application work offline, its the right thing to do.
  • Dont write your own sync protocol, its crazy
  • Haggis exist

THANKS!

Questions?