相较于移动端本地应用,web站点经常缺乏一项经常使用的功能:推送通知。此处的推送通知通常指由浏览器实现的消息推送,换个说法,就是用户在打开浏览器时,不须要进入特定的网站,就能收到该网站推送而来的消息,例如:新评论,新动态等等。git
那么web push到底是怎样的一个流程呢,简单地说,能够分为三个步骤:github
第一步,客户端请求订阅用户,过程以下:web
说明一下这三步,在第一步以前,应用服务器须要生成应用服务器密钥(application server keys),其做用是标识该服务器,保证每次发消息推送的都是同一个服务器。而后,客户端将会请求用户受权消息推送,一旦用户受权,浏览器就会生成一个PushScription,而后这个PushScription将会被发送至服务器,存入数据库,在后面的消息推送中使用。ajax
第二步,应用服务器发送web push协议标准的api,触发推送服务器的消息推送,其中headers必须配置正确,且传送的数据必须是比特流。chrome
应用服务器发送消息推送请求(目的是为了将更新推送到用户的浏览器),为了向推送服务器发出请求,须要查看先前得到的PushScription,取出其中的endpoint,即为推送服务器配置给该用户的访问点。数据库
一个PushScription对象以下:api
{ "endpoint": "https://random-push-service.com/some-kind-of-unique-id-1234/v2/", "keys": { "p256dh" : "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM=", "auth" : "tBHItJI5svbpez7KI4CCXg==" } }
其中的endpoint包含了推送服务器域名,path后面的部分为推送服务器为每一个用户分配的一个标识符。promise
发送数据时,数据必须编码(出于安全性考虑)。推送服务器在接收到这样一个请求以后,当即开始监听用户浏览器是否处于在线状态,如果,则将消息推送发送至浏览器。浏览器
第三步,浏览器端接收消息推送,触发push事件并展现安全
浏览器在接收到推送服务器发来的推送后,将其解码并触发一个push事件。Service Worker因为它能够在浏览器页面未打开,浏览器未打开时执行,所以通常选择它完成web push的最后一步,即响应push事件完成展现通知等业务逻辑。
按照上一部分所说,首先进行用户订阅。
首先注册一个Service Worker,若注册成功,返回的Promise为resolve状态,以下:
function registerServiceWorker() { return navigator.serviceWorker.register('service-worker.js') .then(function(registration) { console.log('Service worker successfully registered.'); return registration; }) .catch(function(err) { console.error('Unable to register service worker.', err); }); }
随后测试window环境下是否有Notification对象(此处以chrome为例,若使用firefox,uc等浏览器,须要遵循其相应标准,调用对应对象方法或引入JS SDK包),测试成功,调用Notification.requestPermission请求用户受权发送推送,若受权成功,将会返回'granted'。
接下来要作的就是使用注册好的Service Worker对象,调用pushManager.subscribe方法,从客户端得到刚刚所说的PushScription对象。
function subscribeUserToPush() { return navigator.serviceWorker.register('service-worker.js') .then(function(registration) { const subscribeOptions = { userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array( 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U' ) }; return registration.pushManager.subscribe(subscribeOptions); }) .then(function(pushSubscription) { console.log('Received PushSubscription: ', JSON.stringify(pushSubscription)); return pushSubscription; }); }
userVisibleOnly是为了保证推送对用户可见,application server key则如前文所说,是推送服务器用以识别应用服务器的密钥,这里的密钥包含了公钥和私钥,传输的是公钥。同时,PushScription的endpoint也是在这个过程当中生成的,生成公钥和私钥可使用web-push库。
这里再次说明一下推送服务器的不可选择性,在调用subscribe生成PushScription时,浏览器会向它指定的中转服务器发送请求来生成endpoint和其他部分,这是无法控制的。
PushScription中的auth和p256dh是用来控制带载荷的push message的。
获取到PushScription对象后,将其发往应用服务器,此处简化了存储,使用nedb存下PushScription并返回Promise:
function saveSubscriptionToDatabase(subscription) { return new Promise(function(resolve, reject) { db.insert(subscription, function(err, newDoc) { if (err) { reject(err); return; } resolve(newDoc._id); }); }); };
存储完毕后,接下来就是开发后台管理逻辑,使得管理员可以触发向用户推送消息的事件,应用服务器所作的逻辑就是遍历在数据库中存储的全部PushScription并推送消息,如下是使用web-push库完成配置密钥及联系邮箱的示例:
const vapidKeys = { publicKey: 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U', privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls' }; webpush.setVapidDetails( 'mailto:web-push-book@gauntface.com', vapidKeys.publicKey, vapidKeys.privateKey );
不要忘了配置你在谷歌云服务(例如FCM)申请到的GCMApiKey:
webpush.setGCMAPIKey('<Your GCM API Key Here>');
配置完成后,就能够将subscription发送出去,使用web-push的sendNotification接口:
webpush.sendNotification(pushSubscription, 'Your Push Payload Text');
推送服务器发送消息后,会触发浏览器的push事件,为了控制service worker的逻辑,须要使用event.waitUntil方法,此方法接收一个promise参数,在promise变为resolved状态后,浏览器就会检查通知是否已被展现,如果,则关闭service worker。
若是不处理未正常执行的promise,部分浏览器如chrome会展现默认消息框:
展现一个通知调用的为showNotification方法,传的参数包括title等,以下:
var title = 'Yay a message.'; var body = 'We have received a push message.'; var icon = '/images/icon-192x192.png'; var tag = 'simple-push-demo-notification-tag'; event.waitUntil( self.registration.showNotification(title, { body: body, icon: icon, tag: tag }) );
而展现notification时,除了控制它的视图层之外,也能够控制它的逻辑层,例如点击消息通知后进行某些操做等等,在先前调用showNotification时能够传入一些参数,例如,根据不一样的action执行不一样的操做:
self.addEventListener('notificationclick', function(event) { if (!event.action) { // Was a normal notification click console.log('Notification Click.'); return; } switch (event.action) { case 'coffee-action': console.log('User ❤️️\'s coffee.'); break; case 'doughnut-action': console.log('User ❤️️\'s doughnuts.'); break; case 'gramophone-action': console.log('User ❤️️\'s music.'); break; case 'atom-action': console.log('User ❤️️\'s science.'); break; default: console.log(`Unknown action clicked: '${event.action}'`); break; } });
你们要是感兴趣能够看看个人github~https://github.com/proempire,这个项目可能会继续跟进