上节课咱们一块儿了解了sevice worker,它能够用于客户端资源缓存。但sevice worker的用处远远不止于此,它能够拦截全部的客户端http请求并存储返回的response,从而实现离线的客户端正常访问。 离线访问这一特性很容易让人联想到Native App的能力,实际上,sevice worker配合其余的技术,确实是可让web页'Native'化,这就是咱们常说的 PWA。javascript
PWA(progress web app)是致力于在网页应用中实现和原生应用相近的用户体验的渐进式网页应用,由google于2016年提出。 虽然是web页面,但经过一些新技术,使它具备离线访问和推送的能力,同时与native app相比,又具备安装方便、更新便捷的特色。html
能够访问百度的LAVAS首页,获取更多关于pwa的API信息 lavas.baidu.com/pwa/README前端
一个标准的PWA应该具备如下特色java
从技术层面来讲,pwa应实现如下功能:web
sevice worker是由js编写,运行在应用程序和浏览器之间的代理服务器,给 web 应用提供高级的可持续的后台处理能力。 它可以建立有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采起适当的动做。chrome
具体的sevice worker教程,能够参考咱们以前的文章 天天一点网站优化之:前端静态资源缓存 sevice worker数据库
经过在sevice worker中设置资源的离线缓存规则,能够为web应用提供离线访问的能力。npm
注意:pwa要求web应用必需要有离线访问能力。json
容许将站点添加至主屏幕,是 PWA 提供的一项重要功能。虽然目前部分浏览器已经支持向主屏幕添加网页快捷方式以方便用户快速打开站点,可是 PWA 添加到主屏幕的不只仅是一个网页快捷方式, 它将提供更多的功能,更多的样式,让 PWA 具备更加原生的体验。 PWA 添加至桌面的功能实现依赖于 manifest.json,能够经过manifest.json文件配置应用的图标,名称等信息。api
// manifest.json文件配置
{
"short_name": "短名称",
"name": "这是一个完整名称", //定义名称
"icons": [ //自定义图标
{
"src": "love0.png",
"type": "image/png",
"sizes": "48x48"
},
{
"src": "love.png",
"type": "image/png",
"sizes": "144x144"
}
],
"start_url": "index.html", //指定应用打开的url
"display": "fullscreen", //应用打开的展现窗口样式
"theme_color": "#ff4c43" //配置应用打开后窗口的颜色
}
复制代码
<link rel="manifest" href="manifest.json">
复制代码
打开web页,若是这个web页面支持pwa,则能够经过浏览器右上角的更多安装应用
manifest.json文件写定的规则一旦安装,就会一直生效,若是想要修改manifest信息并生效,须要卸载旧的应用程序并从新安装。 能够在pwa中点击右上角三个点选择卸载,或者是在文件夹中直接删除应用。
pwa的推送功能经过Push API 和 Notifications API 实现,其中 PUSH API负责消息推送, Notification API负责展现提醒。
web push 的原理
push Service能够在用户离线时保存消息队列,在上线后统一发送。而且,Push Service会为每一个发起订阅的浏览器生成一个惟一的URL, 这样,咱们在服务端推送消息时,向这个URL进行推送后,Push Service就会知道要通知哪一个浏览器,保存url信息的字段是endpoint.
// 注册sevice worker
if ('serviceWorker' in navigator) {
var publicKey = 'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A';
registerServiceWorker()
.then(registration => {
console.log('ServiceWorker 注册成功!做用域为: ', registration.scope)
// 发起订阅 push sevice 给客户端返回惟一标识
return subscribeUserToPush(registration, publicKey);
})
.then(subscription => {
var body = {subscription: subscription};
// 将生成的客户端订阅信息存储在本身的服务器上
return sendSubscriptionToServer(JSON.stringify(body));
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err)
});
}
// 注册
function registerServiceWorker() {
return navigator.serviceWorker.register('sw.js', {scope: '/'});
}
// 发起订阅
function subscribeUserToPush(registration, publicKey) {
var subscribeOptions = {
userVisibleOnly: true, //推送是否须要显性发送给用户
applicationServerKey: publicKey
};
return registration.pushManager.subscribe(subscribeOptions).then(function (pushSubscription) {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
return pushSubscription;
});
}
复制代码
服务端代码:
const webpush = require('web-push');
// pwa推送信息
const pushMessage = async (ctx) => {
/** * VAPID值 * 这里能够替换为你业务中实际的值 */
const vapidKeys = {
publicKey: 'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A',
privateKey: 'TVe_nJlciDOn130gFyFYP8UiGxxWd3QdH6C5axXpSgM'
};
// 设置web-push的VAPID值
webpush.setVapidDetails(
'mailto:xxx@qq.com',
vapidKeys.publicKey,
vapidKeys.privateKey
);
// 须要推送的客户端信息,此处能够根据业务场景从数据库中获取
let subscription = {
endpoint:'https://updates.push.services.mozilla.com/wpush/v2/gAAAAABdJ-rl96NWkfof3N3cHVJ0vO2-i_9K5eg2WoS9XumIJyYSp-Eeu2MpEV0qoisZipI2mbsYRvceM7F_62QJ0hjsAES8qGflnMmkB_UZjzIi8dI5SGIGrCh2RPurGdrdVL4o9yVo6dx8RfI6MHIqNyaqxYTOC_jH61EtSP9inn_eYdMRw3c',
keys:{
auth:'ehJWs1HwbokEKzf7VDhORQ',
p256dh:'BBNLr0Qjib3QOHN2sFnjWR9Xtcm0kGSzDbqyh7FoXBalUD_yqRBCgBa8oXYIRL_vhdTW5x0hNI_vc_noT_1ekPc'
}
},
data = {
title : '我是通知标题',
body : 'We have received a push message.',
icon : 'love.png',
tag : 'simple-push-demo-notification-tag'
}
webpush.sendNotification(subscription, data).then(data => {
console.log('push service的相应数据:', JSON.stringify(data));
ctx.body = {
success:true,
message:'推送消息成功!'
}
return;
}).catch(err => {
// 判断状态码,440和410表示失效
if (err.statusCode === 410 || err.statusCode === 404) {
console.log('该subscription已失效');
}
else {
console.log(err);
}
})
}
复制代码
notification负责把消息从sw传递给客户端
// sw.js文件
self.addEventListener('push', function(event) {
var
var body = 'We have received a push message.'; //吐送内容
var icon = '/love.png'; //显示在推送上的图标
var tag = 'simple-push-demo-notification-tag'; //string类型,tag相同的推送将自动覆盖,防止推送消息太多致使的用户体验差
var data = {
doge: {
wow: 'such amaze notification data'
}
};
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: data
})
);
});
self.addEventListener('notificationclick', event => {
console.log('用户点击了推送消息')
});
复制代码