69
Oine applications Jérôme Van Der Linden - 28/10/2016

Softshake - Offline applications

Embed Size (px)

Citation preview

Page 1: Softshake - Offline applications

Offline applications

Jérôme Van Der Linden - 28/10/2016

Page 3: Softshake - Offline applications

AT AW AD

Page 4: Softshake - Offline applications

AnyWhere ?

Page 5: Softshake - Offline applications

AnyWhere ?

Page 6: Softshake - Offline applications

AnyWhere ?

Page 7: Softshake - Offline applications

Offline applications 5 questions to ask before creating an offline application

Page 8: Softshake - Offline applications

Question #1 What can I do offline ?

Page 9: Softshake - Offline applications

READ

Page 10: Softshake - Offline applications

CREATE

Page 11: Softshake - Offline applications

UPDATE

Page 12: Softshake - Offline applications

UPDATE, SURE ?

Page 13: Softshake - Offline applications

DELETE

Page 14: Softshake - Offline applications

Question #2 How much data is it and where can i store it ?

Page 15: Softshake - Offline applications

Few kilobytes…

Page 16: Softshake - Offline applications

Few megabytes

Hundred of megabytes

(maybe few giga)

Page 17: Softshake - Offline applications

Several gigabytes (or many more) ?

Page 18: Softshake - Offline applications

Storage Solutions

Page 19: Softshake - Offline applications

Application Cache<html manifest="/cache.manifest"> ... </html>

CACHE MANIFEST

# Explicitly cached CACHE: /favicon.ico page.html stylesheet.css images/logo.png scripts/main.js http://cdn.example.com/scripts/main.js

# Resources that require the user to be online. NETWORK: *

# static.html will be served if main.py is inaccessible # offline.jpg will be served in place of all images in images/large/ FALLBACK: /main.py /static.html images/large/ images/offline.jpg

cache.manifest

index.html

http://www.html5rocks.com/en/tutorials/appcache/beginner/http://alistapart.com/article/application-cache-is-a-douchebag

Page 20: Softshake - Offline applications

Application Cache<html manifest="/cache.manifest"> ... </html>

CACHE MANIFEST

# Explicitly cached CACHE: /favicon.ico page.html stylesheet.css images/logo.png scripts/main.js http://cdn.example.com/scripts/main.js

# Resources that require the user to be online. NETWORK: *

# static.html will be served if main.py is inaccessible # offline.jpg will be served in place of all images in images/large/ FALLBACK: /main.py /static.html images/large/ images/offline.jpg

cache.manifest

index.html

http://www.html5rocks.com/en/tutorials/appcache/beginner/http://alistapart.com/article/application-cache-is-a-douchebag

Page 21: Softshake - Offline applications

Service Workers (Cache API)

this.addEventListener('install', function(event) { event.waitUntil( caches.open('v1').then(function(cache) { return cache.addAll([ '/sw-test/', '/sw-test/index.html', '/sw-test/style.css', '/sw-test/app.js', '/sw-test/star-wars-logo.jpg', '/sw-test/gallery/', '/sw-test/gallery/myLittleVader.jpg' ]); }) ); });

2. Installation of Service Worker

if ('serviceWorker' in navigator) { navigator.serviceWorker.register(‘/sw.js') .then(function(registration) { // Registration was successful }).catch(function(err) { // registration failed :( }); }

1. Registration of Service Worker

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

var fetchRequest = event.request.clone();

return fetch(fetchRequest).then( function(response) { if (!response || response.status !== 200) { return response; }

var responseToCache = response.clone();

caches.open('v1').then(function(cache) { cache.put(event.request, responseToCache); });

return response; } ); }) ); });

3 . Fetch and Cache requests

Page 22: Softshake - Offline applications

Service Workers (Cache API)

44+40+

https://jakearchibald.github.io/isserviceworkerready/

27+

Page 23: Softshake - Offline applications

Future of upcoming web development ?

Page 24: Softshake - Offline applications

Web storage (local / session)

if (('localStorage' in window) && window['localStorage'] !== null) { localStorage.setItem(key, value); }

if (key in localStorage) { var value = localStorage.getItem(key); }

1. Store data

2. Retrieve data

if (key in localStorage) { localStorage.removeItem(key); } localStorage.clear();

3. Remove data / clear

Page 25: Softshake - Offline applications

Web SQLvar db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); var msg;

db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');

tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")'); tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")'); msg = '<p>Log message created and row inserted.</p>'; document.querySelector('#status').innerHTML = msg; });

db.transaction(function (tx) { tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) { var len = results.rows.length, i; msg = "<p>Found rows: " + len + "</p>"; document.querySelector('#status').innerHTML += msg; for (i = 0; i < len; i++) { msg = "<p><b>" + results.rows.item(i).log + "</b></p>";

document.querySelector('#status').innerHTML += msg; }

}, null); });

Page 26: Softshake - Offline applications

var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); var msg;

db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');

tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")'); tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")'); msg = '<p>Log message created and row inserted.</p>'; document.querySelector('#status').innerHTML = msg; });

db.transaction(function (tx) { tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) { var len = results.rows.length, i; msg = "<p>Found rows: " + len + "</p>"; document.querySelector('#status').innerHTML += msg; for (i = 0; i < len; i++) { msg = "<p><b>" + results.rows.item(i).log + "</b></p>";

document.querySelector('#status').innerHTML += msg; }

}, null); });

Web SQL

Page 27: Softshake - Offline applications

function onInitFs(fs) {

fs.root.getFile('log.txt', {}, function(fileEntry) {

// Get a File object representing the file, // then use FileReader to read its contents. fileEntry.file(function(file) { var reader = new FileReader();

reader.onloadend = function(e) { var txtArea = document.createElement('textarea'); txtArea.value = this.result; document.body.appendChild(txtArea); };

reader.readAsText(file); }, errorHandler);

}, errorHandler);

}

window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);

FileSystem API

Page 28: Softshake - Offline applications

function onInitFs(fs) {

fs.root.getFile('log.txt', {}, function(fileEntry) {

// Get a File object representing the file, // then use FileReader to read its contents. fileEntry.file(function(file) { var reader = new FileReader();

reader.onloadend = function(e) { var txtArea = document.createElement('textarea'); txtArea.value = this.result; document.body.appendChild(txtArea); };

reader.readAsText(file); }, errorHandler);

}, errorHandler);

}

window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);

FileSystem API

Page 29: Softshake - Offline applications

IndexedDBvar db;

function openDb() { var req = indexedDB.open(DB_NAME, DB_VERSION); req.onsuccess = function (evt) { db = this.result; }; req.onerror = function (evt) { console.error("openDb:", evt.target.errorCode); };

req.onupgradeneeded = function (evt) { var store = evt.currentTarget.result.createObjectStore( DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });

store.createIndex('title', 'title', { unique: false }); store.createIndex('isbn', 'isbn', { unique: true }); }; }

1. Open Database

Page 30: Softshake - Offline applications

IndexedDB

var tx = db.transaction(DB_STORE_NAME, 'readwrite'); var store = tx.objectStore(DB_STORE_NAME);

var obj = { isbn: ‘0062316095’, title: ‘Sapiens: A Brief History of Humankind’, year: 2015 };

var req; try { req = store.add(obj); } catch (e) { // ... } req.onsuccess = function (evt) { console.log("Insertion in DB successful");

// ... }; req.onerror = function() { console.error("Insert error", this.error);

// ... };

2. Insert data

Page 31: Softshake - Offline applications

IndexedDBvar var tx = db.transaction(DB_STORE_NAME, 'readonly'); var store = tx.objectStore(DB_STORE_NAME);

var req = store.openCursor();

req.onsuccess = function (evt) { var cursor = evt.target.result; if (cursor) { alert(cursor.value.title); cursor.continue();

} };

3. Retrieve data (cursor)var var tx = db.transaction(DB_STORE_NAME, 'readonly'); var store = tx.objectStore(DB_STORE_NAME);

var req = store.get(42);

req.onsuccess = function (evt) { var object = evt.target.result; alert(object.title); };

3. Retrieve data (one item)

Page 32: Softshake - Offline applications

IndexedDB

var var tx = db.transaction(DB_STORE_NAME, 'readonly'); var store = tx.objectStore(DB_STORE_NAME);

var index = store.index(‘title’); var req = index.get(‘Sapiens: A Brief History of Humankind’);

req.onsuccess = function (evt) { var result = evt.target.result; if (result) { // ...

} };

3. Retrieve data (index)

Page 33: Softshake - Offline applications

IndexedDB wrappers

• db.js • joqular • TaffyDB

• localForage • IDBWrapper • YDN

Page 34: Softshake - Offline applications

IndexedDB

16+24+ 15+ 10+

8+ 4.4+

Page 35: Softshake - Offline applications

Google Gears

Page 36: Softshake - Offline applications

HTML 5 Storage Limitations

Page 37: Softshake - Offline applications

Quotas

50 %

33 %

20 %

20 %

Free disk space

Space browser can use

Space application (domain) can use

Page 38: Softshake - Offline applications

Quotas

Page 39: Softshake - Offline applications

Users

Page 40: Softshake - Offline applications

https://storage.spec.whatwg.org/ https://developers.google.com/web/updates/2016/06/persistent-storage

if (navigator.storage && navigator.storage.persist) navigator.storage.persist().then(granted => { if (granted) alert("Storage will not be cleared except by explicit user action"); else alert("Storage may be cleared by the UA under storage pressure."); });

if (navigator.storage && navigator.storage.persist) navigator.storage.persisted().then(persistent=>{ if (persistent) console.log("Storage will not be cleared except by explicit user action"); else console.log("Storage may be cleared by the UA under storage pressure."); });

Persistent storage

55+

Page 41: Softshake - Offline applications

Question #3 How to handle offline-online

synchronization ?

Page 42: Softshake - Offline applications

CONFLICTS

Page 43: Softshake - Offline applications

Basic Resolution : based on timestamp « Last version win »

Page 44: Softshake - Offline applications

Optimistic lock

Source : Patterns of Enterprise Application Architecture - Martin Fowler

System transaction boundaries

Business transaction boundaries

Page 45: Softshake - Offline applications

Pessimistic lock

Source : Patterns of Enterprise Application Architecture - Martin Fowler

System transaction boundaries

Business transaction boundaries

Page 46: Softshake - Offline applications

Theory is when you know everything but

nothing works.

Practice is when everything works but no

one knows why.

In our lab, theory and practice are combined: nothing works and no

one knows why!

Page 47: Softshake - Offline applications
Page 48: Softshake - Offline applications

kinto.jsvar db = new Kinto(); var todos = db.collection(‘todos’);

todos.create({ title: ‘buy some bread’), finished : false }) .then(function(res){…}) .catch(function(err){…})

todos.list().then(function(res) { renderTodos(res.data); }) .catch(function(err) {…});

todos.update(todo) .then(function(res) {…}) .catch(function(err) {…});

Create, Read, Update, Delete using IndexedDB

todos.delete(todo.id) .then(function(res) {…}) .catch(function(err) {…});

var syncOptions = { remote: "https://host/kintoapi", headers: {Authorization: …} }; todos.sync(syncOptions) .then(function(res){…}) .catch(function(err){…})

Synchronize with remote

Page 49: Softshake - Offline applications

kinto.jsvar syncOptions = { remote: "https://host/kintoapi", headers: {Authorization: …} }; todos.sync(syncOptions) .then(function(res){…}) .catch(function(err){…})

{ "ok": true, "lastModified": 1434617181458, "errors": [], "created": [], // created locally "updated": [], // updated locally "deleted": [], // deleted locally "published": [ // published remotely { "last_modified": 1434617181458, "done": false, "id": "7ca54d89-479a-4201-8494", "title": "buy some bread", "_status": "synced" } ], "conflicts": [], "skipped": [] }

{ "ok": true, "lastModified": 1434617181458, "errors": [], "created": [], // created locally "updated": [], // updated locally "deleted": [], // deleted locally "published": [], // published remotely "conflicts": [ { "type": "incoming", // or outgoing "local": { "last_modified": 1434619634577, "done": true, "id": "7ca54d89-479a-4201-8494", "title": "buy some bread", "_status": "updated" }, "remote": { "last_modified": 1434619745465, "done": false, "id": "7ca54d89-479a-4201-8494", "title": "buy some bread and wine" } } ], "skipped": [] }

OK Conflicts

Page 50: Softshake - Offline applications

kinto.js{ "ok": true, "lastModified": 1434617181458, "errors": [], "created": [], // created locally "updated": [], // updated locally "deleted": [], // deleted locally "published": [], // published remotely "conflicts": [ { "type": "incoming", // or outgoing "local": { "last_modified": 1434619634577, "done": true, "id": "7ca54d89-479a-4201-8494", "title": "buy some bread", "_status": "updated" }, "remote": { "last_modified": 1434619745465, "done": false, "id": "7ca54d89-479a-4201-8494", "title": "buy some bread and wine" } } ], "skipped": [] }

Conflicts

todos.sync(syncOptions) .then(function(res){

if (res.conflicts.length) { return handleConflicts(res.conflicts); }

}) .catch(function(err){…});

function handleConflicts(conflicts) { return Promise.all(conflicts.map(function(conflict) { return todos.resolve(conflict, conflict.remote); })) .then(function() { todos.sync(syncOptions); }); }

Choose your way to solve the conflict:

• Choose remote or local version • Choose according last_modified • Pick the good fields (need to provide 3-way-merge screen)

Page 51: Softshake - Offline applications

var db = new PouchDB(‘todos’);

db.post({ // can use ‘put’ with an _id title: ‘buy some bread’), finished : false }) .then(function(res){…}) .catch(function(err){…})

db.get(‘mysuperid’).then(function(todo) { // return an object with auto // generated ‘_rev’ field

// update the full doc (with _rev) todo.finished = true;

db.put(todo);

// remove the full doc (with _rev) db.remove(todo); }) .catch(function(err) {…});

Create, Read, Update, Delete using IndexedDB

var localDB = new PouchDB(‘todos’); // Remote CouchDB var remoteDB

= new PouchDB(‘http://host/todos’);

localDB.replicate.to(remoteDB); localDB.replicate.from(remoteDB); // or localDB.sync(remoteDB, { live: true, retry: true }).on('change', function (change) { // something changed! }).on('paused', function (info) { // replication was paused, // usually because of a lost connection }).on('active', function (info) { // replication was resumed }).on('error', function (err) { // unhandled error (shouldn't happen) });

Synchronize with remote

Page 52: Softshake - Offline applications

var myDoc = { _id: 'someid', _rev: '1-somerev' }; db.put(myDoc).then(function () { // success }).catch(function (err) { if (err.name === 'conflict') { // conflict! Handle it! } else { // some other error } });

Immediate conflict : error 409

_rev: ‘1-revabc’ _rev: ‘1-revabc’

_rev: ‘2-revcde’ _rev: ‘2-revjkl’

_rev: ‘1-revabc’

_rev: ‘2-revjkl’_rev: ‘2-revcde’

db.get('someid', {conflicts: true}) .then(function (doc) { // do something with the object }).catch(function (err) { // handle any errors });

{ "_id": "someid", "_rev": "2-revjkl", "_conflicts": ["2-revcde"] }

==>

Eventual conflict

==> remove the bad one, merge, … it’s up to you

Page 53: Softshake - Offline applications
Page 54: Softshake - Offline applications

Question #4

How to communicate with users ?

Page 55: Softshake - Offline applications

Inform the user …

Save Save locally

Send Send when online

Page 56: Softshake - Offline applications

… or not

Outbox (1) Send

Page 57: Softshake - Offline applications

Do no display errors !

Page 58: Softshake - Offline applications

Do not load indefinitelyyyyyyyyyy

Page 59: Softshake - Offline applications

Do not display an empty screen

Page 60: Softshake - Offline applications
Page 61: Softshake - Offline applications

Handling conflicts

Page 62: Softshake - Offline applications

Question #5 Do I really need offline ?

Page 63: Softshake - Offline applications

(2001) (2009) (2020)

Page 64: Softshake - Offline applications
Page 65: Softshake - Offline applications

« You are not on a f*cking plane and if you are, it doesn’t matter »

- David Heinemeier Hansson (2007)

https://signalvnoise.com/posts/347-youre-not-on-a-fucking-plane-and-if-you-are-it-doesnt-matter

Page 66: Softshake - Offline applications

ATAWAD

Unfortunately NOT

AnyWhere !

Page 67: Softshake - Offline applications

User Experience matters !

Page 68: Softshake - Offline applications

Thank you

Page 69: Softshake - Offline applications

Bibliography• http://diveintohtml5.info/offline.html • https://github.com/pazguille/offline-first • https://jakearchibald.com/2014/offline-cookbook/ • https://github.com/offlinefirst/research/blob/master/links.md • http://www.html5rocks.com/en/tutorials/offline/whats-offline/ • http://offlinefirst.org/ • http://fr.slideshare.net/MarcelKalveram/offline-first-the-painless-way • https://developer.mozilla.org/en-US/Apps/Fundamentals/Offline • https://uxdesign.cc/offline-93c2f8396124#.97njk8o5m • https://www.ibm.com/developerworks/community/blogs/worklight/entry/

offline_patterns?lang=en • http://apress.jensimmons.com/v5/pro-html5-programming/ch12.html • http://alistapart.com/article/offline-first • http://alistapart.com/article/application-cache-is-a-douchebag • https://logbook.hanno.co/offline-first-matters-developers-know/ • https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/

Using_Service_Workers • https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API • https://developer.chrome.com/apps/offline_storage • http://martinfowler.com/eaaCatalog/index.html • http://offlinestat.es/ • http://caniuse.com/

Jake Archibald