SlideShare a Scribd company logo
Offline applications
Jérôme Van Der Linden - 28/10/2016
jeromevdl @jeromevdl
Jérôme
Van Der
Linden
@OCTOSuisse @OCTOTechnology
AT AW AD
AnyWhere ?
AnyWhere ?
AnyWhere ?
Offline applications
5 questions to ask before creating an offline application
Question #1
What can I do offline ?
READ
CREATE
UPDATE
UPDATE, SURE ?
DELETE
Question #2
How much
data is it and
where can i store it ?
Few kilobytes…
Few
megabytes
Hundred of
megabytes
(maybe few giga)
Several gigabytes (or many more) ?
Storage Solutions
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
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
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
Service Workers (Cache API)
44+40+
https://jakearchibald.github.io/isserviceworkerready/
27+
Future of upcoming web development ?
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
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);
});
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
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
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
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
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
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)
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)
IndexedDB wrappers
• db.js
• joqular
• TaffyDB
• localForage
• IDBWrapper
• YDN
IndexedDB
16+24+ 15+ 10+
8+ 4.4+
Google Gears
HTML 5 Storage Limitations
Quotas
50 %
33 %
20
%
20
%
Free disk space
Space browser can use
Space application (domain) can use
Quotas
Users
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+
Question #3
How to handle offline-online
synchronization ?
CONFLICTS
Basic Resolution : based on timestamp
« Last version win »
Optimistic lock
Source : Patterns of Enterprise Application Architecture - Martin Fowler
System transaction
boundaries
Business transaction
boundaries
Pessimistic lock
Source : Patterns of Enterprise Application Architecture - Martin Fowler
System transaction
boundaries
Business transaction
boundaries
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!
Softshake - Offline applications
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
kinto.js
var 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
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)
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
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
Softshake - Offline applications
Question #4
How to communicate with users ?
Inform the user …
Save Save locally
Send Send when online
… or not
Outbox (1)Send
Do no display errors !
Do not load indefinitelyyyyyyyyyy
Do not display
an empty screen
Softshake - Offline applications
Handling conflicts
Question #5
Do I really need offline ?
(2001) (2009) (2020)
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
ATAWAD
Unfortunately
NOT
AnyWhere !
User Experience
matters !
Thank you
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

More Related Content

PDF
09.Local Database Files and Storage on WP
PPTX
Simplifying Persistence for Java and MongoDB with Morphia
PDF
Webinar: MongoDB Persistence with Java and Morphia
PPTX
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
PPTX
Morphia: Simplifying Persistence for Java and MongoDB
PPTX
MongoDB: Easy Java Persistence with Morphia
PPT
Spring data presentation
PDF
An introduction into Spring Data
09.Local Database Files and Storage on WP
Simplifying Persistence for Java and MongoDB with Morphia
Webinar: MongoDB Persistence with Java and Morphia
Rapid and Scalable Development with MongoDB, PyMongo, and Ming
Morphia: Simplifying Persistence for Java and MongoDB
MongoDB: Easy Java Persistence with Morphia
Spring data presentation
An introduction into Spring Data

What's hot (20)

DOC
Advanced Hibernate Notes
PDF
Data access 2.0? Please welcome: Spring Data!
PPTX
Indexing & Query Optimization
PDF
Symfony2 from the Trenches
PDF
Java Persistence Frameworks for MongoDB
PDF
Symfony Day 2010 Doctrine MongoDB ODM
PDF
Doctrine MongoDB Object Document Mapper
PPTX
Sequelize
PDF
Ajax chap 4
PPTX
Indexing and Query Optimization
PPTX
MongoDB + Java - Everything you need to know
PDF
Mongo db for C# Developers
PDF
MongoDB Performance Tuning
PPT
Persistences
PDF
MySQL flexible schema and JSON for Internet of Things
PDF
Ajax chap 5
PDF
Mongo db for c# developers
PDF
Saving Data
PDF
Android Data Persistence
PDF
greenDAO
Advanced Hibernate Notes
Data access 2.0? Please welcome: Spring Data!
Indexing & Query Optimization
Symfony2 from the Trenches
Java Persistence Frameworks for MongoDB
Symfony Day 2010 Doctrine MongoDB ODM
Doctrine MongoDB Object Document Mapper
Sequelize
Ajax chap 4
Indexing and Query Optimization
MongoDB + Java - Everything you need to know
Mongo db for C# Developers
MongoDB Performance Tuning
Persistences
MySQL flexible schema and JSON for Internet of Things
Ajax chap 5
Mongo db for c# developers
Saving Data
Android Data Persistence
greenDAO
Ad

Viewers also liked (18)

PPT
Home project sc
PPT
324.ayuda a las personas de la tercera edad
DOC
Bert Savoie Resume
PPTX
Escuela amado nervo
PDF
Garrtech investment has the answer to hollywood real estate
PPT
96. alimentacion sustentable
PPTX
Just one ... Myles Sullivan's paintings
PDF
Volume 24.2
PPT
The housing market.irkutsk.02.11.2016
ODP
Learn to Drift
PDF
Habilidades del Pensamiento en las TIC
PPTX
220.todos contra el bullyng
PDF
Atias Parasitología Médica Cap 3 - El Hospedero
PPTX
Creencias religiosas en los jóvenes y niños
PPTX
Bases de una educación para la paz.
PPTX
Presentation NWA BIZ Con Event
PPTX
163.regio activo
Home project sc
324.ayuda a las personas de la tercera edad
Bert Savoie Resume
Escuela amado nervo
Garrtech investment has the answer to hollywood real estate
96. alimentacion sustentable
Just one ... Myles Sullivan's paintings
Volume 24.2
The housing market.irkutsk.02.11.2016
Learn to Drift
Habilidades del Pensamiento en las TIC
220.todos contra el bullyng
Atias Parasitología Médica Cap 3 - El Hospedero
Creencias religiosas en los jóvenes y niños
Bases de una educación para la paz.
Presentation NWA BIZ Con Event
163.regio activo
Ad

Similar to Softshake - Offline applications (20)

PPTX
Academy PRO: HTML5 Data storage
PDF
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
PDF
An Overview of HTML5 Storage
PDF
Taking Web Apps Offline
PDF
Local data storage for mobile apps
PDF
Local Storage
PDF
Local storage in Web apps
PDF
HTML5 Storage/Cache
PPTX
Local storage
PPT
Persistent Offline Storage White
PDF
[2015/2016] Local data storage for web-based mobile apps
PPTX
In-browser storage and me
PDF
Offline Strategies for HTML5 Web Applications - oscon13
PDF
your browser, my storage
PDF
The Hitchhikers Guide To Html5 Offline Strategies (+firefoxOS)
KEY
Intro to IndexedDB (Beta)
PDF
Offline Storage: Build secure, offline-first apps
PDF
Offline Storage: Build secure, offline-first apps
PDF
Offline strategies for HTML5 web applications - pfCongres2012
PPTX
Dave Orchard - Offline Web Apps with HTML5
Academy PRO: HTML5 Data storage
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
An Overview of HTML5 Storage
Taking Web Apps Offline
Local data storage for mobile apps
Local Storage
Local storage in Web apps
HTML5 Storage/Cache
Local storage
Persistent Offline Storage White
[2015/2016] Local data storage for web-based mobile apps
In-browser storage and me
Offline Strategies for HTML5 Web Applications - oscon13
your browser, my storage
The Hitchhikers Guide To Html5 Offline Strategies (+firefoxOS)
Intro to IndexedDB (Beta)
Offline Storage: Build secure, offline-first apps
Offline Storage: Build secure, offline-first apps
Offline strategies for HTML5 web applications - pfCongres2012
Dave Orchard - Offline Web Apps with HTML5

More from jeromevdl (13)

PDF
Message-Driven Architecture on AWS
PDF
Do more with less code in serverless
PDF
Do more with less code in a serverless world
PDF
DevopsDays Geneva 2020 - Compliance & Governance as Code
PDF
Softshake 2017 - Développer un chatbot Alexa
PDF
Chatbots buzzword ou nouvel eldorado
PDF
Management projet vs management produit
PDF
My Android is not an iPhone like any others (Mdevcon 2014)
PDF
DroidconUK 2013 : Beef up android apps with java tools
PDF
Droidcon Paris 2013 - Musclez vos applications Android avec les outils du mon...
PPTX
Devoxx France 2013 : Musclez vos apps android avec les outils du monde java
PDF
Jug Lausanne Android Janvier2013
ODP
Metroide
Message-Driven Architecture on AWS
Do more with less code in serverless
Do more with less code in a serverless world
DevopsDays Geneva 2020 - Compliance & Governance as Code
Softshake 2017 - Développer un chatbot Alexa
Chatbots buzzword ou nouvel eldorado
Management projet vs management produit
My Android is not an iPhone like any others (Mdevcon 2014)
DroidconUK 2013 : Beef up android apps with java tools
Droidcon Paris 2013 - Musclez vos applications Android avec les outils du mon...
Devoxx France 2013 : Musclez vos apps android avec les outils du monde java
Jug Lausanne Android Janvier2013
Metroide

Recently uploaded (20)

PPTX
UNIT 4 Total Quality Management .pptx
PDF
keyrequirementskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
PDF
PRIZ Academy - 9 Windows Thinking Where to Invest Today to Win Tomorrow.pdf
PDF
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
PPTX
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
PPTX
Welding lecture in detail for understanding
PDF
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
PDF
Model Code of Practice - Construction Work - 21102022 .pdf
PDF
Well-logging-methods_new................
PDF
BMEC211 - INTRODUCTION TO MECHATRONICS-1.pdf
PDF
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
PPTX
CH1 Production IntroductoryConcepts.pptx
DOCX
573137875-Attendance-Management-System-original
PPT
Mechanical Engineering MATERIALS Selection
PPTX
Internet of Things (IOT) - A guide to understanding
PDF
Digital Logic Computer Design lecture notes
PDF
composite construction of structures.pdf
PPTX
MET 305 2019 SCHEME MODULE 2 COMPLETE.pptx
PPTX
additive manufacturing of ss316l using mig welding
PPT
Project quality management in manufacturing
UNIT 4 Total Quality Management .pptx
keyrequirementskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
PRIZ Academy - 9 Windows Thinking Where to Invest Today to Win Tomorrow.pdf
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
Welding lecture in detail for understanding
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
Model Code of Practice - Construction Work - 21102022 .pdf
Well-logging-methods_new................
BMEC211 - INTRODUCTION TO MECHATRONICS-1.pdf
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
CH1 Production IntroductoryConcepts.pptx
573137875-Attendance-Management-System-original
Mechanical Engineering MATERIALS Selection
Internet of Things (IOT) - A guide to understanding
Digital Logic Computer Design lecture notes
composite construction of structures.pdf
MET 305 2019 SCHEME MODULE 2 COMPLETE.pptx
additive manufacturing of ss316l using mig welding
Project quality management in manufacturing

Softshake - Offline applications

  • 1. Offline applications Jérôme Van Der Linden - 28/10/2016
  • 7. Offline applications 5 questions to ask before creating an offline application
  • 8. Question #1 What can I do offline ?
  • 14. Question #2 How much data is it and where can i store it ?
  • 17. Several gigabytes (or many more) ?
  • 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+
  • 23. Future of upcoming web development ?
  • 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)
  • 33. IndexedDB wrappers • db.js • joqular • TaffyDB • localForage • IDBWrapper • YDN
  • 36. HTML 5 Storage Limitations
  • 37. Quotas 50 % 33 % 20 % 20 % Free disk space Space browser can use Space application (domain) can use
  • 39. Users
  • 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+
  • 41. Question #3 How to handle offline-online synchronization ?
  • 43. Basic Resolution : based on timestamp « Last version win »
  • 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!
  • 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
  • 49. kinto.js var 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
  • 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
  • 54. Question #4 How to communicate with users ?
  • 55. Inform the user … Save Save locally Send Send when online
  • 56. … or not Outbox (1)Send
  • 57. Do no display errors !
  • 58. Do not load indefinitelyyyyyyyyyy
  • 59. Do not display an empty screen
  • 62. Question #5 Do I really need offline ?
  • 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
  • 69. 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