19. 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
20. 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
21. 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
22. Service Workers (Cache API)
44+40+
https://jakearchibald.github.io/isserviceworkerready/
27+
24. 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
25. Web SQL
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);
});
26. 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
27. 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
28. 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
29. IndexedDB
var 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
30. 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
31. IndexedDB
var 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)
32. 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)
40. 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+
44. Optimistic lock
Source : Patterns of Enterprise Application Architecture - Martin Fowler
System transaction
boundaries
Business transaction
boundaries
45. Pessimistic lock
Source : Patterns of Enterprise Application Architecture - Martin Fowler
System transaction
boundaries
Business transaction
boundaries
46. 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!
47.
48. kinto.js
var 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
50. 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)
51. 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
52. 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
65. « 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