WebPush實(shí)現(xiàn)網(wǎng)絡(luò)推送

概述

Web Push是在PWA開(kāi)發(fā)中配合ServiceWorker實(shí)現(xiàn)消息推送的技術(shù)。大致流程分為以下幾步:

  1. 瀏覽器向推送服務(wù)器發(fā)起推送訂閱請(qǐng)求
  2. 瀏覽器拿到訂閱完成后推送服務(wù)器返回的訂閱信息,發(fā)送到應(yīng)用服務(wù)器進(jìn)行保存。
  3. 后面應(yīng)用服務(wù)器通過(guò)web push向推送服務(wù)發(fā)送消息,同時(shí)攜帶之前保存的訂閱信息,用于讓推送服務(wù)器知道要推送給誰(shuí)。
  4. 推送服務(wù)器接收到消息后,根據(jù)訂閱信息將消息推送給指定的瀏覽器,serviceWorker需要提前監(jiān)聽(tīng)push事件。

接下來(lái)看一下每一步的具體操作。

客戶(hù)端發(fā)起訂閱

首先前端在serviceWorker注冊(cè)完成之后可以獲取到一個(gè)serviceWorkerRegistration實(shí)例對(duì)象。使用這個(gè)對(duì)象的pushManager.subscribe方法進(jìn)行訂閱。

const registrtion = await navigator.serviceWorker.register('./sw.js')
const pushSubscription = await registrtion.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: base64ToUint8Array('BAvgL3epn4A5JN5tBhKsjpOUXnqYcbOGEitOw8KOAedS69NgcZ814NuP7IV5Ei5Wz2VBralB1WYkazdLBRMq3yw')
})

// 當(dāng)然也可以取消訂閱,先不關(guān)心
pushSubscription.unsubscribe().then(function () {
  console.log('取消訂閱成功!')
})

userVisibleOnly參數(shù)為了保證推送對(duì)用戶(hù)可見(jiàn),設(shè)置為true即可;applicationServerKey服務(wù)器密鑰的生成需要借助一個(gè)npm包web-push來(lái)生成,可以通過(guò)代碼生成:

const webpush = require('web-push')
const vapidKeys = webpush.generateVAPIDKeys()

也可以全局安裝web-push,通過(guò)命令生成:

web-push generate-vapid-keys --json

以上可以生成一個(gè)這樣的一對(duì)公鑰和私鑰(不加--json也可以,生成的格式不用而已):

{
    "publicKey":"BAvgL3epn4A5JN5tBhKsjpOUXnqYcbOGEitOw8KOAedS69NgcZ814NuP7IV5Ei5Wz2VBralB1WYkazdLBRMq3yw",
    "privateKey":"SMIFmRPZHtLhIfxEqkbzanZg7NH1FlrFF8cf_K3BeWQ"
}

以上在應(yīng)用服務(wù)器生成,公鑰給到客戶(hù)端(就是訂閱是需要傳遞的),同時(shí)因?yàn)楣€是base64編碼的字符串,需要將其轉(zhuǎn)成Uint8Array格式才能作subscribe為參數(shù)傳入。私鑰是需要保存在應(yīng)用服務(wù)器的。

function base64ToUint8Array (base64String) {
    let padding = '='.repeat((4 - base64String.length % 4) % 4)
    let base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
    let rawData = atob(base64)
    let outputArray = new Uint8Array(rawData.length)
    for (let i = 0; i < rawData.length; i++) {
        outputArray[i] = rawData.charCodeAt(i)
    }
    return outputArray
}

推送服務(wù)器返回的訂閱信息pushSubscription,是需要傳遞給應(yīng)用服務(wù)器的,格式如下:

{
    "endpoint": "https://fcm.googleapis.com/xxxxx",
    "keys": {
        "p256dh": "xxxxxx",
        "auth": "xxxxxx"
    }
}

應(yīng)用服務(wù)器保存訂閱信息

拿到訂閱信息之后需要保存到我們自己的應(yīng)用服務(wù)器,可以通過(guò)pushSubscription.getKey('p256dh')pushSubscription.getKey('auth')獲取密鑰和校驗(yàn)碼信息,由于通過(guò)getKey獲取到的是ArrayBuffer類(lèi)型,所以需要轉(zhuǎn)成base64字符串便于傳輸。應(yīng)用服務(wù)器可以提供一個(gè)接口,比如/api/saveSubscription,然后把數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)中。

fetch('/api/saveSubscription', {
    method: 'post',
    body: JSON.stringify({
        endpoint: pushSubscription.endpoint,
        keys: {
            p256dh: uint8ArrayToBase64(pushSubscription.getKey('p256dh')),
            auth: uint8ArrayToBase64(pushSubscription.getKey('auth')),
        }
    })
})
function uint8ArrayToBase64 (arr) {
  return btoa(String.fromCharCode.apply(null, new Uint8Array(arr)))
}

應(yīng)用服務(wù)器推送消息

我們的Node應(yīng)用服務(wù)端需要使用web-push庫(kù)實(shí)現(xiàn)向推送服務(wù)器發(fā)送消息。首先通過(guò)setVapidDetails進(jìn)行配置。然后需要配置在云服務(wù)申請(qǐng)到的GCM API Key,最后通過(guò)sendNotification發(fā)送,第一個(gè)參數(shù)是之前保存的pushSubscription對(duì)象,第二參數(shù)是要發(fā)送給瀏覽器前端的數(shù)據(jù)信息。

const webpush = require('web-push')
const vaipdKeys = {
    publicKey: '', // 公鑰
    privateKey: '' //私鑰
}
webpush.setVapidDetails(
    'mailto:郵箱地址.com', // 注意前綴mailto:不能丟
    vaipdKeys.publicKey,
    vaipdKeys.privateKey
)
webpush.setGCMAPIKey('<Your GCM API Key Here>')
webpush.sendNotification(pushSubscription, JSON.stringfy({}))

瀏覽器監(jiān)聽(tīng)推送

在serviceWorker文件sw.js中,在注冊(cè)完成之后可以進(jìn)行監(jiān)聽(tīng)。這樣就可以收到推送消息了。

self.addEventListener('push', e => {
    if(e.data) {
        // 推送的消息在data屬性中,通過(guò)text()可以解析成字符串
        console.log(e.data.text())
        // 也可以通過(guò)json()解析成json等
    }
})

注意:如果不翻一下,推送服務(wù)器在國(guó)內(nèi)是用不了的哦。

補(bǔ)充

Notification

配合使用的 Notification API,判斷是否支持并允許通知:

function requestNotificationPermission () {
    if (!window.Notification) {
        return Promise.reject('瀏覽器不支持桌面通知')
    }
    return Notification.requestPermission().then(function (permission) {
        if (permission === 'granted') {
            return Promise.resolve()
        }
        return Promise.reject('用戶(hù)已禁止通知權(quán)限')
    })
}
整體流程圖
webpush流程圖.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容