相較於移動端本地應用,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,這個項目可能會繼續跟進