PWA
Progressive Web Applications

Damian Sosnowski

https://www.gft.com/pl/pl/index/o-nas/kariera/oferty-pracy/
Web apps on mobile

Native
Installable
Can work offline
Performance
Device integration
Web
Works "everywhere"
Easily accessible
Store - independent
Security / Privacy
Bound to the store
Platform specific
Complex installation
Security / Privacy
Not installable
Requires internet connection
Performance
Obligatory AppStore

Most of the apps are "found" on the internet.



Is there something better?

PWA
"The websites that took all the right vitamins. "
Installable
Can work offline
Easily accessible
Better performance
Store independent
What is PWA
Simply a web page / web app, with few additional perks
Linkable
Responsive design
Safe - https://
Discoverable
Installable
Offline friendly
App shell architecture
Always up to date
Notifications
Already done
Web App Manifest
Service Workers
Progressive!

Let's do some PWA
Web Manifest
Escaping the browser tabs.
Installable web application



manifest.json
{
"name": "My PWA app",
"short_name": "PWAApp",
"icons": [{
"src": "images/touch/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "images/touch/apple-touch-icon.png",
"sizes": "152x152",
"type": "image/png"
}],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2"
}Most apps
"display": "standalone"
Games
"display": "fullscreen",
"orientation": "portrait"
...
"orientation": "landscape"
Manifest
<html>
<head>
<title>Standalone Web App</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- ... -->
</body>
</html>- Served over HTTPS
- Have a valid service worker registered
- Visited twice, with at least 5 minutes between visits
PWA application flow

Remember!

No browser navigation!
You have to provide your own custom navigation methods.
Service worker
The core of PWA
What is service worker?

Basic Service Worker
//app.js
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/worker.js');
console.log('ServiceWorker registration successful');
}
catch (e) {
console.log('ServiceWorker registration failed: ');
}
}//worker.js
console.log('Worker!');Basic Service Worker
Service Worker life cycle



Offline support
Caching resources using Service Worker
Caching
self.addEventListener('install', (event) => {
//open cache
let openCachePromise = caches.open('app-cache-v1');
//add all resources to cache
let addAllPromise = openCachePromise.then((cache) => {
return cache.addAll([
'/',
'/styles/main.css',
'/script/main.js'
]);
});
//worker will wait till all operations are finished
event.waitUntil(addAllPromise);
});
Caching - return data
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});When to cache
install
Open cache, add all required resources
activate
Remove all outdated caches
self.addEventListener('activate', (event) => {
event.waitUntil(caches.delete('old_cache_name')); // don't do it this way ;)
});Application Shell Architecture

What storage should I use?

Background Sync

Background Sync
//app.js
swRegistration.sync.register('syncTag');
//worker.js
self.addEventListener('sync', (event) => {
if (event.tag == 'myFirstSync') {
//read data from DB
//find unsychronized records
//load new records from the server
event.waitUntil(doSomeStuff());
}
});
Push notifications

Websockets vs Push Notifcations


Quite complex API



Step 1 - permission
Notification.requestPermission().then((result) => {
if (result === 'denied') {
console.log('Permission wasn\'t granted.');
return;
}
if (result === 'default') {
console.log('The permission request was dismissed.');
return;
}
// Do something with the granted permission.
});var permission = Notification.permission;Step 2 - registration
navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: '-long-string-'
}).then(function(sub) {
console.log('Endpoint URL: ', sub.endpoint);
//now you can send the data to the server
sendSubscriptionDataToServer(sub);
}).catch(function(e) {
console.error('Unable to subscribe to push', e);
});
});
{
"endpoint": "https://android.googleapis.com/gcm/send/f1Ls...iYNxIVpLIYeZ8kq_A",
"keys": {
"p256dh": "BLc4xRzKlKORKWlbdgFaBrrPK3ydWAH...IO4475rds=",
"auth": "5I2Bu2oKdyy9CwL8QVF0NQ=="
}
}Step 3 - send message
const webpush = require('web-push');
// VAPID keys should only be generated only once.
const vapidKeys = webpush.generateVAPIDKeys();
webpush.setVapidDetails(
'mailto:example@yourdomain.org',
vapidKeys.publicKey,
vapidKeys.privateKey
);
// This is the same output of calling JSON.stringify on a PushSubscription
const pushSubscription = {
endpoint: '.....',
keys: {
auth: '.....',
p256dh: '.....'
}
};
webpush.sendNotification(pushSubscription, 'Your Push Payload Text');Step 4 - Receive push message
//worker.js
self.addEventListener('push', (event) => {
let options = {
body: event.data.text(),
icon: 'images/example.png',
vibrate: [100, 50, 100],
data: {
customData: ''
}
};
event.waitUntil(
self.registration.showNotification('Hello!', options)
);
});Messages configuration

Summing up
Decent PWA should be:
1. Installable
2. Native-like application shell
3. Offline support (cache + sync)
4. Push notifications
Web Application + some tricks!
+ additional complexity
Browsers support




Frameworks

@angular/service-worker
Examples
Thank you
Damian Sosnowski
PWA
By sosnowsd
PWA
- 475