PWA = 普通的网站 + manifest + Service Workersjavascript
manifest文件包含网站相关的信息,包括图标,背景屏幕,颜色和默认方向。css
Service Workers为网站提供了更好的体验(渐进加强),容许将网站添加到设备的主屏幕,离线缓存。html
PWA应该具有的特性:java
Service Workers由JavaScript编写,运行在浏览器后台,基于事件驱动。若是用户浏览器不支持Service Workers的话,并不会形成影响,网站还能够做为普通网站进行浏览,所以作到了“渐进加强”。git
经过Service Workers,能够缓存 UI 外壳(用户界面所必需的最小化的 HTML、CSS 和 JavaScript),动态内容在UI外壳加载后再加载,为用户提供相似原生app的体验。github
从生命周期图中能够看出,当第一次加载页面时,并不会有激活的 Service Worker 来控制页面。只有当 Service Worker 安装完成而且用户刷新了页面或跳转至网站的其余页面,Service Worker 才会激活并开始拦截请求。
若是须要在第一次加载时,就但愿Service Workers激活并开始拦截请求,能够经过以下方式当即激活Service Workers。web
self.addEventListener('install', function(event) { //使 Service Worker 解雇当前活动的worker, 而且一旦进入等待阶段就会激活自身,触发activate事件 event.waitUntil(self.skipWaiting()); });
结合self.clients.claim() 一块儿使用,以确保底层 Service Worker 的更新当即生效。npm
self.addEventListener('activate', function(event) { e.waitUntil( caches.keys().then(function(keyList) { return Promise.all(keyList.map(function(key) { if (key !== cacheName) { console.log('[ServiceWorker] Removing old cache', key); return caches.delete(key); } })); }) ); return self.clients.claim(); //确保底层 Service Worker 的更新当即生效 });
var cacheKey = "first-pwa"; //缓存的key,能够添加多个不一样的缓存 var cacheList = [ //须要缓存的文件列表 '/', 'index.html', 'icon.png', 'main.css' ]; //在安装过程当中缓存已知的资源 self.addEventListener('install', event => { //监听install事件 event.waitUntil( //install完成后 caches.open(cacheKey) //打开cache .then(cache => cache.addAll(cacheList)) //将须要缓存的文件加入cache列表 .then(() => self.skipWaiting()) //使 Service Worker 解雇当前活动的worker, // 而且一旦进入等待阶段就会激活自身,触发activate事件 //无需等待用户跳转或刷新页面 ); }); //拦截fetch请求 self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(response => { //若是请求的资源在缓存中 if (response != null) return response; //返回缓存资源 //经过网络获取资源,并缓存 var requestToCache = event.request.clone(); //克隆当前请求 return fetch(requestToCache.url).then(response => { if (!response || response.status !== 200) { return response; //返回错误的响应 } var responseToCache = response.clone(); //克隆响应 caches.open(cacheKey) .then(cache => { cache.put(requestToCache, responseToCache); //将响应添加到缓存中 }); return response; //返回响应 }); }) ); });
属于Service Workers做用域范围内的全部http请求都将触发fetch事件,包括html、css、js、图片等。json
若是用户在浏览器中启用了节省数据的功能,浏览器在每一个http请求头部中会加入save-data请求头。api
this.addEventListener('fetch', function (event) { if(event.request.headers.get('save-data')){ // 咱们想要节省数据,因此限制了图标和字体 if (event.request.url.includes('fonts.googleapis.com')) { // 不返回任何内容 event.respondWith(new Promise(resolve => resolve(new Response('', { status: 417, statusText: 'Ignore fonts to save data.' }))) ); } } });
mainifest.json须要在网页head标签中引用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato"> <link rel="stylesheet" href="main.css"> <link rel="manifest" href="manifest.json"/> <title>PWA</title> </head> <body> <h1>Hello PWA!</h1> <script type="text/javascript"> if (navigator.serviceWorker != null) { navigator.serviceWorker.register('sw.js').then(registration => { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function (err) { console.log('ServiceWorker registration failed: ', err); }); } else { //serviceWorker is not supported } </script> </body> </html>
manifest.json中包含的字段主要包括:
{ "name": "First PWA", "short_name": "pwa", "display": "standalone", "start_url": "/index.html", "theme_color": "#FFDF00", "background_color": "#FFDF00", "orientation": "landscape", "scope": "/", "icons": [ { "src": "icon.png", "sizes": "144x144", "type": "image/png" } ] }
display:显示模式,默认为browser。包括fullscreen、standalone、minimal-ui 或 browser 。
参考 https://developer.mozilla.org/en-US/docs/Web/Manifest
//监听添加到主屏幕事件 window.addEventListener('beforeinstallprompt', function (event) { // //取消添加 // e.preventDefault(); // return false; event.userChoice.then(function (result) { console.log(result.outcome); if (result.outcome == 'dismissed') { } else { } }); });
目前FireFox、Chrome、Edge 已经支持 Push API。推送的过程主要分为三个步骤:
在订阅前,须要先生成VAPID, VAPID是“自主应用服务器标识” ( Voluntary Application Server Identification ) 的简称。它是一个规范,定义了应用服务器和推送服务之间的握手。
1.客户端订阅消息,此时浏览器会询问用户是否容许消息推送通知。
2.从浏览器获取PushSubscription对象,其中包含了客户端的信息,能够理解为标示设备的id。
var vapidPublicKey = 'BF0eSi4ANvVKr017Gr_Xzb-bN9l8-c3qRUHqVU6C-vFy_i3xgrKDY-13BPF5BVx93IVObJwnwrt5vjX-ltM6Uuo'; function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } function subscribeForPushNotification(registration) { return registration.pushManager.getSubscription() .then(function (subscription) { if (subscription) { return; } return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }) .then(function (subscription) { var rawKey = subscription.getKey ? subscription.getKey('p256dh') : ''; var key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : ''; var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : ''; var authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : ''; var endpoint = subscription.endpoint; return fetch('http://localhost:3001/api/register', { method: 'post', headers: new Headers({ 'content-type': 'application/json' }), body: JSON.stringify({ endpoint: subscription.endpoint, key: key, authSecret: authSecret, }), }); }); }); }
3.将PushSubscription发送到服务端保存。
服务端示例:
this.post('/register', 'register', async (req, res, next) => { try { let {endpoint, authSecret, key} = req.body; let subscriber = { endpoint, keys: { auth: authSecret, p256dh: key } }; subscribers.push(subscriber); res.apiSuccess({}); } catch (err) { next(err); } });
经过Web Push协议将须要推送的消息发送到push service。
使用web-push的发送示例:
this.post('/send', 'send', async (req, res, next) => { try { let message = req.body.message; for (let subscriber of subscribers) { webpush.sendNotification( subscriber, JSON.stringify({ msg:message, url:'http://localhost:3001', icon:'' }) ); } res.apiSuccess({}); } catch (err) { next(err); } });
当push service收到消息后,会将消息保存起来,直到目标设备上线后将消息推送到客户端,或者消息超时再也不发送。
https://github.com/SangKa/PWA-Book-CN
https://developers.google.com/web/fundamentals/push-notifications/how-push-works
https://codelabs.developers.google.com/codelabs/your-first-pwapp/#0