使用Service Worker發送Push推送

我在上一篇《使用Service Worker作一個PWA離線網頁應用》已經介紹了怎麼作離線緩存,這一篇將介紹怎麼用Service Worker發送Push(Notification),或者叫web push。Web push在國外的網站很流行,但在國內幾乎沒見到,主要仍是由於谷歌在境內沒法訪問,由於web push走的是谷歌FCM通道,須要能接收到谷歌服務器的消息。但正常網絡環境下是沒法訪問谷歌的,使得在國內搞它的意義不是很大,可是畢竟它是一個標準和趨勢,作爲一個技術人員來研究一下仍是挺有用的。javascript

1. 發送Push過程

給用戶瀏覽器或者客戶端發送一個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

(1)瀏覽器發起詢問,生成subscription

在註冊完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究竟是什麼呢?

(2)什麼是FCM

FCM官方是這麼介紹的:

Firebase 雲信息傳遞 (FCM) 是一種跨平臺消息傳遞解決方案,可供您免費、可靠地傳遞消息。

使用 FCM,您能夠通知客戶端應用存在可同步的新電子郵件或其餘數據。您能夠發送通知消息以再次吸引用戶並促進用戶留存。在即時消息傳遞等使用情形中,一條消息可將最大 4KB 的有效負載傳送至客戶端應用。

FCM是一種可靠的消息傳遞平臺,它最大的優勢是同一套Push機制能夠在IOS/Android/Web三端使用:

這個意義是很大的,由於Android的推送一直都比較亂,國內有些APP使用小米的Push服務,有些使用百度的,還有些使用騰訊的信鴿等等,這些Push都須要在後臺運行線程,而且不能休眠,這就致使了手機在休眠狀態時仍然有不少線程在運行着,使得手機耗電速度很快。最後還直接致使今年工信部出臺要成立安卓統一推送聯盟。而蘋果有一套統一的推送機制,你們把Push發給蘋果的服務器,而後再由蘋果下發給相應的蘋果設備。Safari如今不支持Service Worker,可是能夠用Apple Push,缺點是這種推送蘋果說不能用來發送重要的數據,而且目測只能彈框顯示,沒辦法在後臺處理消息而不彈框。

(3)發送推送

發送推送能夠用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了,若是用戶連不上那就沒辦法。

(4)接收推送消息

用運行在後臺的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有搞頭。


相關閱讀:

  1. 爲何要把網站升級到HTTPS
  2. 怎樣把網站升級到http/2
  3. 我是怎樣讓網站用上HTML5 Manifest
  4. 使用Service Worker作一個PWA離線網頁應用
相關文章
相關標籤/搜索