sencha roadshow 2017: build progressive web apps with ext js and cmd
TRANSCRIPT
Agenda
PWA definition•
PWA vs Hybrid vs Native•
PWA architecture•
Web Application Manifest-
Caching using Service Worker-
Web Push Notifications-
Access to Native Hardware-
3
PWA
A progressive web app is a model for creating app-like experiences using the latest
Web technologies progressively
4
PWA features
• Instant loading
• Discoverable
• Network independent
• Responsive
• Installable
• Secure
• Linkable
• Re-engageable (Push messages)
• Work everywhere
• Fast
5
✔️
✔️
✔️
✔️
✔️
✔️
✔️
Best of Web and Native
Linkable•
Discoverable•
Easy to deploy•
Easy to update•
Standards•
Work everywhere•
Offline Access•
Installed Icon•
Push Notifications•
Full Screen Experience•
Fast UI•
Access to sensors and hardware•
7
Progressive Enhancement
It is a web application1.
Add native installation2.
Add offline3. -mode support
Add Web Push notifications4.
Add hardware and platform access5.
8
Steps to implement
• Normal fast Web Application
• Add Web Application Manifest
• Cache app shell and data (Service Worker)
• Offer Web Notifications (Service Worker)
• Offer device specific experience
9
"Add to Home Screen" banner requirements
Service Worker•
Web App Manifest•
HTTPS•
Second visit in a short period of time•
13
Web App Manifest configurations
"progressive": {
"manifest": {
"name": "My Application",
"short_name": "My App",
"icons": [{
"src": "resources/icon-small.png",
"sizes": "96x96"
}, …],
"theme_color": "#054059",
"background_color": "#054059",
"display": "standalone",
"orientation": "portrait",
"start_url": "/index.html"
}
}
app.json
Caching with Service Worker
• Automatically register SW (using sw-precache)
• Automatically cache an app shell
- HTML, Javascript, CSS, fonts, images
• Cache resources
- by adding @sw-cache comments
- configure “runtimeCaching” in app.json file
17
// @sw-cache
Ext.define('App.store.UpcomingEvents', {
extend: 'Ext.data.Store',
proxy: {
type: 'ajax',
// @sw-cache
url: '/api/upcoming-events.json',
reader: {
type: 'json'
}
}
});UpcomingEvents.js
// @sw-cache
Ext.define('App.model.Event', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'date'],
proxy: {
type: 'rest',
// @sw-cache { urlPattern: "\\/api\\/events\\/\\d+" }
url : '/events'
}
}); Event.js
Controlling Cache Behavior
urlPattern•
handler: • networkFirst / cacheFirst / fastest / cacheOnly / networkOnly
options•
debug-
networkTimeoutSeconds-
cache-
name•
maxEntries•
maxAgeSeconds•
20
Controlling Cache Behavior
Ext.define('App.model.Event', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'date'],
proxy: {
type: 'rest',
// @sw-cache { urlPattern: "\\/api\\/events\\/\\d+", options: { cache: { name: 'events', maxEntries: 10 } } }
url : '/events'
}
});Event.js
serviceWorker config in app.json
"serviceWorker": {"runtimeCaching": [
{
"urlPattern": "\\/api\\/events\\/\d+",
"options": {
"cache": {
"name": "events",
"maxEntries": 10
}
}
}, …
]
}
app.json
Web Push Notifications
Gives web applications the ability to
receive messages pushed to them
from a server at any time
Round 1: Service Worker Registration
WebApp
Service Worker
Browser
Push ServerApp Server
Register
ServiceWorkerRegistration
Page Load
Round 1: ServiceWorker Registration
if ('serviceWorker' in navigator) {
if ('PushManager' in window) {
navigator.serviceWorker.register('ServiceWorker.js').then(function(registration) {
//state initializing
});
.catch(function() {
//error handling
});
} else {
//error handling
}
} else {
//error handling
}WebApp
Round 2: Subscription
WebApp
Service Worker
Browser
Push ServerApp Server
Subscribe
PushSubscription
Subscribe push subscriptionsave PushSubscription
User is ready to subscribe
Round 2: Subscription
navigator.serviceWorker.ready.then(function(registration) {
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('...')
})
.then(function(subscription) {
// The subscription was successful
savePushSubscription(subscription);
})
.catch(function(e) {
//error handling
});
});WebApp
Round 3: Push Message
WebApp
Service Worker
Browser
Push ServerApp Server
push message
push message
push message
Something urgent and relevant happened
Round 3: Additional Headers
POST /{user_identifier} HTTP/1.1
Host: {push_server_url}
Prefer: respond-async
TTL: 15
Urgency: high
Topic: upd
Content-Type: text/plain;charset=utf8
Content-Length: 36
{encrypted_message} AppServer
Round 3: Push Message
self.addEventListener('push', function(event) {
var data = event.data.json();
event.waitUntil(self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon,
tag: data.tag
}));
}); ServiceWorker
Round 3: Handle Notification
self.addEventListener('notificationclick', function(event) {
event.notification.close();
event.waitUntil(clients.openWindow('http://mywebsite.com'));
});
ServiceWorker
Round 4: Unsubscription
WebApp
Service Worker
Browser
Push ServerApp Server
unsubscribe
OK
unsubscribe OKremove PushSubscription
User wants to unsubscribe
Round 4: Unsubscription
registration.pushManager.getSubscription().then(function(subscription) {
if (subscription) {
return subscription.unsubscribe().then(function(successful) {
removePushSubscription(subscription);
}).catch(function(e) {
//error handling
});
}
})
.catch(function(error) {
//error handling
}) WebApp
Access to Native Hardware
• Geolocation
• Barcode detection
• WebShare
• WebCrypto
• WebSpeech API
• Device Orientation
• Device Motion API
• Proximity Sensor
• Light Sensor
• Clipboard API
• Battery Status API
• Presentation API
• Gamepad API
• WebVR
• 3d video
• HTML Media Capture
• Web RTC
Capture a photo using Web Camera
View
{xtype : 'video',reference: 'video'
},{
xtype: 'button',text: 'Capture',handler: 'onCaptureTap'
},{
xtype: 'd3-canvas',reference: 'canvas'
}
Capture a photo using Web Camera
onDialogShow: function () {
var player = this.lookup('video'),
handleSuccess = function(stream) {
player.media.dom.srcObject = stream;
};
navigator.mediaDevices.getUserMedia({video: true})
.then(handleSuccess);
},
onCaptureTap: function () {
var player = this.lookup('video'),
snapshotCanvas = this.lookup('canvas'),
context = snapshotCanvas.context2D;
context.drawImage(player.media.dom, 0, 0, width, height);
} ViewController
Progressive Enhancement
1. It is a web application
2. Add native installation
3. Add offline-mode support
4. Add Web Push notifications
5. Add hardware and platform access
39