我在上一篇《使用Service Worker作一个PWA离线网页应用》已经介绍了怎么作离线缓存,这一篇将介绍怎么用Service Worker发送Push(Notification),或者叫web push。Web push在国外的网站很流行,但在国内几乎没见到,主要仍是由于谷歌在境内没法访问,由于web push走的是谷歌FCM通道,须要能接收到谷歌服务器的消息。但正常网络环境下是没法访问谷歌的,使得在国内搞它的意义不是很大,可是毕竟它是一个标准和趋势,作为一个技术人员来研究一下仍是挺有用的。javascript
给用户浏览器或者客户端发送一个Push,这个过程是这样的:php
在浏览器端,注册一个Service Worker以后会返回一个注册的对象,调这个对象的pushManager.subscribe的方法让浏览器弹一个框,询问用户是否容许接受消息通知:html
若是点击容许的话,浏览器就会向FCM请求生成一个subscription(订阅)的标志信息,而后把这个subscription发给服务端存起来,用来发Push给当前用户。服务端使用这个subscription的信息调web push提供的API向FCM发送消息,FCM再下发给对应的浏览器。而后浏览器会触发Service Worker的push事件,让Service Worker调showNotification显示这个push的内容。操做系统就会显示这个Push:java
点击这个框,就能跳到指定的url查看内容。这就是整一个过程,具体来讲:git
在注册完service worker后,调用subscribe询问用户是否容许接收通知,以下代码所示:github
navigator.serviceWorker.register("sw-4.js").then(function(reg){
console.log("Yes, it did register service worker.");
if (window.PushManager) {
reg.pushManager.getSubscription().then(subscription => {
// 若是用户没有订阅
if (!subscription) {
subscribeUser(reg);
} else {
console.log("You have subscribed our notification");
}
});
}
}).catch(function(err) {
console.log("No it didn't. This happened: ", err)
});复制代码
上面代码在发起订阅前先看一下以前已经有没有订阅过了,若是没有的话再发起订阅。发起订阅的subscribeUser实现以下代码所示:web
function subscribeUser(swRegistration) {
const applicationServerPublicKey = "BBlY_5OeDkp2zl_Hx9jFxymKyK4kQKZdzoCoe0L5RqpiV2eK0t4zx-d3JPHlISZ0P1nQdSZsxuA5SRlDB0MZWLw";
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
// 用户赞成
.then(function(subscription) {
console.log('User is subscribed:', JSON.stringify(subscription));
jQuery.post("/add-subscription.php", {subscription: JSON.stringify(subscription)}, function(result) {
console.log(result);
});
})
// 用户不一样意或者生成失败
.catch(function(err) {
console.log('Failed to subscribe the user: ', err);
});
}复制代码
subscribe传两个参数,一个是userVisibleOnly,这个表示消息是否必需要可见,若是设置为不可见,Chrome将会报错:sql
Chrome currently only supports the Push API for subscriptions that will result in user-visible messages. You can indicate this by calling pushManager.subscribe({userVisibleOnly: true}) instead. See https://goo.gl/yqv4Q4 for more details.数据库
但其实这个并不影响,咱们设置成true,可是收到消息后能够不用弹框,能够调postMessage去通知页面作相应的操做。npm
第二个参数applicationServerKey是服务端的公钥,这个能够用web push的Node包生成,先安装一个:
npm install web-push --save复制代码
而后用如下代码生成:
const webpush = require('web-push');
//VAPID keys should only be generated only once.
const vapidKeys = webpush.generateVAPIDKeys();
console.log(vapidKeys.publicKey, vapidKeys.privateKey);复制代码
每运行一次就会生成一对新的密钥对,如:
publicKey: BMgkd1qfOfI6vFBbxIFMgdxDGC6-j8NYTwF_MXOIZ-St9lPhhMdPuUyFfwg1DLY59WP0FEaX84ZJRwgztdpfBHs
privateKey: LUeSF8DCv-NBxIfaeWeKTux858H45_V75vT0zZQLEbY复制代码
公密钥只要能配套就好,公钥在浏览器端使用,用来生成subscription,密钥在服务端使用,用来发Push。
若是用户赞成浏览器就会向FCM服务请求生成subscription,而后执行Promise链里的then,返回该subscription,在这个then里面把这个subscription发给服务端存起来。反之,若是用户不一样意,或者用户没法连到FCM的服务器将会抛异常:
DOMException: Registration failed - push service error
生成的subscription大概长这样:
{"endpoint":"https://fcm.googleapis.com/fcm/send/ci3-kIulf9A:APA91bEaQfDU8zuLSKpjzLfQ8121pNf3Rq7pjomSu4Vg-nMwLGfJSvkOUsJNCyYCOTZgmHDTu9I1xvI-dMVLZm1EgmEH0vDA7QFLjPKShG86W2zwX0IbtBPHEDLO0WgQ8OIhZ6yTnu-S","expirationTime":null,"keys":{"p256dh":"BAdAo6ldzRT5oCN8stqYRemoihPGOEJjrUDL6y8zhdA_swao_q-HlY_69mzIVobWX2MH02TzmtRWj_VeWUFMnXQ=","auth":"SS1PBnGwfMXjpJEfnoUIeQ=="}}复制代码
说了这么久的FCM,FCM究竟是什么呢?
FCM官方是这么介绍的:
Firebase 云信息传递 (FCM) 是一种跨平台消息传递解决方案,可供您免费、可靠地传递消息。
使用 FCM,您能够通知客户端应用存在可同步的新电子邮件或其余数据。您能够发送通知消息以再次吸引用户并促进用户留存。在即时消息传递等使用情形中,一条消息可将最大 4KB 的有效负载传送至客户端应用。
FCM是一种可靠的消息传递平台,它最大的优势是同一套Push机制能够在IOS/Android/Web三端使用:
这个意义是很大的,由于Android的推送一直都比较乱,国内有些APP使用小米的Push服务,有些使用百度的,还有些使用腾讯的信鸽等等,这些Push都须要在后台运行线程,而且不能休眠,这就致使了手机在休眠状态时仍然有不少线程在运行着,使得手机耗电速度很快。最后还直接致使今年工信部出台要成立安卓统一推送联盟。而苹果有一套统一的推送机制,你们把Push发给苹果的服务器,而后再由苹果下发给相应的苹果设备。Safari如今不支持Service Worker,可是能够用Apple Push,缺点是这种推送苹果说不能用来发送重要的数据,而且目测只能弹框显示,没办法在后台处理消息而不弹框。
发送推送能够用FCM提供的web push的库,它支持多种语言,包括Node.js/PHP等版本。用Node.js能够这样发Push:
const webpush = require('web-push');
// 从数据库取出用户的subsciption
const pushSubscription = {"endpoint":"https://fcm.googleapis.com/fcm/send/ci3-kIulf9A:APA91bEaQfDU8zuLSKpjzLfQ8121pNf3Rq7pjomSu4Vg-nMwLGfJSvkOUsJNCyYCOTZgmHDTu9I1xvI-dMVLZm1EgmEH0vDA7QFLjPKShG86W2zwX0IbtBPHEDLO0WgQ8OIhZ6yTnu-S","expirationTime":null,"keys":{"p256dh":"BAdAo6ldzRT5oCN8stqYRemoihPGOEJjrUDL6y8zhdA_swao_q-HlY_69mzIVobWX2MH02TzmtRWj_VeWUFMnXQ=","auth":"SS1PBnGwfMXjpJEfnoUIeQ=="}};
// push的数据
const payload = {
title: '一篇新的文章',
body: '点开看看吧',
icon: '/html/app-manifest/logo_512.png',
data: {url: "https://www.rrfed.com"}
//badge: '/html/app-manifest/logo_512.png'
};
webpush.sendNotification(pushSubscription, JSON.stringify(payload));复制代码
经实验,在大多数状况下这个延迟基本在1s之内,这边刚按下回车运行完,那边浏览器就收到了,可是有时候会发送失败(国内网络问题?)。若是这个代码要在服务端运行的话,那么你应该须要一台香港的服务器。像笔者把发Push的数据和服务放在香港的服务器,须要发Push的时候由华北的服务器作个中转向这台服务器发请求。只要用户能连上FCM那就能够愉快地发Push了,若是用户连不上那就没办法。
用运行在后台的Service Worker接收,监听push事件:
this.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
let notificationData = event.data.json();
const title = notificationData.title;
// 能够发个消息通知页面
//util.postMessage(notificationData);
// 弹消息框
event.waitUntil(self.registration.showNotification(title, notificationData));
});复制代码
主要是调用showNotification进行弹框,或者是使用postMessage通知页面相应地作些处理。经实验,若是用户关闭了浏览器,在关闭期间若是有Push的话等到用户从新打开浏览器会再弹出来。而后用户能够点击弹出来的框打开一个指定的页面,这个须要监听notificationclick事件:
this.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
let notification = event.notification;
notification.close();
event.waitUntil(
clients.openWindow(notification.data.url)
);
});复制代码
调用clients.openWindow打开一个新的页面。
这样就基本完成了一个push推送的搭建,
Service Worker让咱们在Web端也能有像原生APP同样的Push通知,使得Web端愈来愈像原生APP端,随着HTML5的其它新功能如WebAssembly提升运行速度,WebWorker多线程支持,数据库支持大量数据的管理和支持,Websocket进行实时通讯,WebRTC进行P2P多媒体传输,还有WebGL、新进的WebVR等,使得在浏览器端可以作的事情愈来愈多,体验愈来愈丰富,并且这种Web APP仍是跨平台的。Web技术突飞猛进的发展,让咱们相信Web有搞头。
相关阅读: