WebPush实现网络推送

概述

Web Push是在PWA开发中配合ServiceWorker实现消息推送的技术。大致流程分为以下几步:

  1. 浏览器向推送服务器发起推送订阅请求
  2. 浏览器拿到订阅完成后推送服务器返回的订阅信息,发送到应用服务器进行保存。
  3. 后面应用服务器通过web push向推送服务发送消息,同时携带之前保存的订阅信息,用于让推送服务器知道要推送给谁。
  4. 推送服务器接收到消息后,根据订阅信息将消息推送给指定的浏览器,serviceWorker需要提前监听push事件。

接下来看一下每一步的具体操作。

客户端发起订阅

首先前端在serviceWorker注册完成之后可以获取到一个serviceWorkerRegistration实例对象。使用这个对象的pushManager.subscribe方法进行订阅。

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

// 当然也可以取消订阅,先不关心
pushSubscription.unsubscribe().then(function () {
  console.log('取消订阅成功!')
})

userVisibleOnly参数为了保证推送对用户可见,设置为true即可;applicationServerKey服务器密钥的生成需要借助一个npm包web-push来生成,可以通过代码生成:

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

也可以全局安装web-push,通过命令生成:

web-push generate-vapid-keys --json

以上可以生成一个这样的一对公钥和私钥(不加--json也可以,生成的格式不用而已):

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

以上在应用服务器生成,公钥给到客户端(就是订阅是需要传递的),同时因为公钥是base64编码的字符串,需要将其转成Uint8Array格式才能作subscribe为参数传入。私钥是需要保存在应用服务器的。

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
}

推送服务器返回的订阅信息pushSubscription,是需要传递给应用服务器的,格式如下:

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

应用服务器保存订阅信息

拿到订阅信息之后需要保存到我们自己的应用服务器,可以通过pushSubscription.getKey('p256dh')pushSubscription.getKey('auth')获取密钥和校验码信息,由于通过getKey获取到的是ArrayBuffer类型,所以需要转成base64字符串便于传输。应用服务器可以提供一个接口,比如/api/saveSubscription,然后把数据存储到数据库中。

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)))
}

应用服务器推送消息

我们的Node应用服务端需要使用web-push库实现向推送服务器发送消息。首先通过setVapidDetails进行配置。然后需要配置在云服务申请到的GCM API Key,最后通过sendNotification发送,第一个参数是之前保存的pushSubscription对象,第二参数是要发送给浏览器前端的数据信息。

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({}))

浏览器监听推送

在serviceWorker文件sw.js中,在注册完成之后可以进行监听。这样就可以收到推送消息了。

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

注意:如果不翻一下,推送服务器在国内是用不了的哦。

补充

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('用户已禁止通知权限')
    })
}
整体流程图
webpush流程图.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Service Worker 是浏览器在后台独立于网页运行的脚本。PWA(Progressive Web App)...
    小纪同学_阅读 1,395评论 0 0
  • 最终目的是为了实现消息推送到用户设备并可以显示更新通知。 实施推送的三个关键步骤是: 添加客户端逻辑以订阅用户推送...
    韩_小文阅读 1,336评论 0 0
  • 推送通知,是现在的应用必不可少的功能。那么在 iOS 中,我们是如何实现远程推送的呢?iOS 的远程推送原理又是什...
    皮皮瑞阅读 1,297评论 0 3
  • 该文章是我16年在公司博客上写的,除了证书注册的过程大致没有改变,像接收通知的方法都有所改变,所以将iOS 10 ...
    manger阅读 3,153评论 2 8
  • 转载 推送通知,是现在的应用必不可少的功能。那么在 iOS 中,我们是如何实现远程推送的呢?iOS 的远程推送原理...
    字节码阅读 760评论 1 4