Johan Kohlin
Lecturer at School of engineering, Jönköping University.
An introduction to PWA and the offline experience
menti.com : 68 245
Johan's PWA Boilerplate, free to investigate
Progressive
Webb
Application
sets the environment
{
"name": "Progressive Web application",
"short_name": "PWA",
"description": "my first PWA",
"start_url": "/index.html",
"lang": "en-us",
"display": "standalone",
"orientation": "portrait",
"theme_color": "red",
"background_color": "#cccccc",
"icons": [
{
"src": "images/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "images/icons/icon-96x96.png",
"sizes": "192x192",
"type": "image/png"
}
}
<head>
...
<link rel="manifest" href="app.webmanifest">
</head>
<body>
...
app.webmanifest
index.html
var filesToCache=[
"/",
"/images.html",
"/index.html",
"/css/style.css",
"/app.webmanifest",
"/images/icons/icon-192x192.png",
];
var latestCacheName='App-Shell-v2';
self.addEventListener('install', function (event) { // Lifecycle event #2 * * INSTALL * *
event.waitUntil(
caches.open(latestCacheName)
.then(function (cache) {
return cache.addAll(filesToCache) // cache my app shell
.then(() => self.skipWaiting());
})
);
});
self.addEventListener('activate', event => { // Lifecycle event #3 * * ACTIVATE * *
const activeCache=latestCacheName;
event.waitUntil(
caches.keys()
.then(function(allCaches) {
allCaches.forEach(cache => {
if (activeCache!==cache) {
caches.delete(cache); // remove old caches if there's a new one
}
})
})
);
});
self.addEventListener('fetch', function (event) { // Hijacking a request, listens for FETCH
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response; // Cache found - return local cached file instead
}
return fetch(event.request);
})
);
});
serviceworker.js
PENDING
RESOLVED
REJECTED
with a value
with a reason
.then()
.catch()
orderPromise = orderAtMax("Spicy Brioche Halloumi");
deliverPromise = orderPromise.then( function(burger){ eat(burger) } );
deliverPromise.catch( function(reason) { alert(reason) } );
// or:
orderAtMax("Spicy Brioche Halloumi")
.then(function(burger){
eat(burger); // yummi!
})
.catch(function(reason) {
alert(reason) // sorry, out of Brioche
})
fetch("images.html")
.then(function(response) {
response.text()
.then(function(htmlText) {
document.body.innerHTML=htmlText;
});
})
.catch(function (err){
console.error(err);
});
Deals with the following:
CACHE
but what is a worker?
PUSH / NOTIFICATION
BACKGROUND SYNC
a script file running parallel to the main thread
//main.js
var ww = new Worker('/webworker.js');
ww.onmessage = function(event) {
alert( "The answer to everything is " + event.data );
}
// web worker
var answer = parseInt(Math.random()**Math.atan2(Math.E, Math.PI)*100)
self.onmessage = e => {
self.postMessage(answer)
}
The service worker is a special worker,
with a proxy server like behaviour
All requests goes through the service worker
Being in the middle, it can cache files.
Later, it can return the cached files,
rather than downloading them again
This can be done from index.html
<head>
<script>
navigator.serviceWorker.register('/sw.js')
.then(function (regObj) {
console.log('ServiceWorker registration successful');
})
.catch(err=> {
console.error('blip blop failure: ', err);
});
</script>
</head>
<body>
when installed, cache files for offline use.
var filesToCache=[
"/",
"/images.html",
"/index.html",
"/css/style.css"
];
latestCacheName = 'App-Shell-v9';
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(latestCacheName)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(filesToCache)
.then(() => self.skipWaiting());
})
);
});
sw.js
Service worker is now in control of window(s)
self.addEventListener("activate", event => {
console.log("I am activated and ready to start working!");
});
// if we changed files and need to re-cache new files,
// lets see how to remove old cache first.
here we also remove all old caches, except the latest one
latestCacheName = 'App-Shell-v9';
/************************************************************
Activating new service worker. Remove old named caches
******************************/
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(allCaches => {
allCaches.forEach(cache => {
if (latestCacheName !== cache) { //don't remove the current cache
caches.delete(cache);
}
})
})
);
});
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(latestCacheName)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(filesToCache)
.then(() => self.skipWaiting());
})
);
});
this is where we hijack the request and serve from the cache
/* * * * * * * ***********************
How to respond to a fetch event.
if match in cache, return
cached file, else return
the requested file from
the web server.
******************************/
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
console.log(response.url + " found in cache");
return response;
}
return fetch(event.request); // not in cache. Go get!
})
);
});
Notification.requestPermission()
.then(function(permission){
if (permission === "granted") {
new Notification("Splendid!");
}
});
Notifications are not part of the service worker,
By Johan Kohlin