【PWA學(xué)習(xí)】4. 使用 Push API 實(shí)現(xiàn)消息推送

引言

在接下來的內(nèi)容里份氧,我們會(huì)探究 PWA 中的另一個(gè)重要功能——消息推送與提醒(Push & Notification)醉箕。這個(gè)能力讓我們可以從服務(wù)端向用戶推送各類消息并引導(dǎo)用戶觸發(fā)相應(yīng)交互

Web Push 效果

Push API 和 Notification API 其實(shí)是兩個(gè)獨(dú)立的技術(shù)刷袍,完全可以分開使用房匆;不過Push API 和 Notification API相結(jié)合是一個(gè)常見的模式

瀏覽器是如何實(shí)現(xiàn)服務(wù)器消息 Push 的

Web Push 的整個(gè)流程相較之前的內(nèi)容來說有些復(fù)雜补憾。因此酌伊,在進(jìn)入具體技術(shù)細(xì)節(jié)之前腾窝,我們需要先了解一下整個(gè) Push 的基本流程與相關(guān)概念。

如果你對(duì) Push 完全不了解,可能會(huì)認(rèn)為虹脯,Push 是我們的服務(wù)端直接與瀏覽器進(jìn)行交互驴娃,使用長連接、 WebSocket 或是其他技術(shù)手段來向客戶端推送消息循集。然而唇敞,這里的 Web Push 并非如此,它其實(shí)是一個(gè)三方交互的過程咒彤。

在 Push 中登場(chǎng)的三個(gè)重要“角色”分別是:

  • 瀏覽器:就是我們的客戶端
  • Push Service:專門的Push服務(wù)厚棵,你可以認(rèn)為是一個(gè)第三方服務(wù),目前chrome與firefox都有自己的Push Service Service蔼紧。理論上只要瀏覽器支持婆硬,可以使用任意的Push Service
  • 后端服務(wù):這里就是指我們自己的后端服務(wù)

下面就介紹一下這三者在 Web Push 中是如何交互

消息推送流程

下圖來自Web Push協(xié)議草案,是 Web Push 的整個(gè)流程:

    +-------+           +--------------+       +-------------+
    |  UA   |           | Push Service |       | Application |
    +-------+           +--------------+       |   Server    |
        |                      |               +-------------+
        |      Subscribe       |                      |
        |--------------------->|                      |
        |       Monitor        |                      |
        |<====================>|                      |
        |                      |                      |
        |          Distribute Push Resource           |
        |-------------------------------------------->|
        |                      |                      |
        :                      :                      :
        |                      |     Push Message     |
        |    Push Message      |<---------------------|
        |<---------------------|                      |
        |                      |                      |

該時(shí)序圖表明了 Web Push 的各個(gè)步驟奸例,我們可以將其分為訂閱(subscribe)與推送(push)兩部分來看

訂閱 subscribe

  1. Ask Permission:這一步不在上圖的流程中彬犯,這其實(shí)是瀏覽器中的策略。瀏覽器會(huì)詢問用戶是否允許通知查吊,只有在用戶允許后谐区,才能進(jìn)行后面的操作
  2. Subscribe:瀏覽器(客戶端)需要向 Push Service 發(fā)起訂閱(subscribe),訂閱后會(huì)得到一個(gè)PushSubscription對(duì)象
  3. Monitor:訂閱操作會(huì)和 Push Service 進(jìn)行通信逻卖,生成相應(yīng)的訂閱信息宋列,Push Service 會(huì)維護(hù)相應(yīng)信息,并基于此保持與客戶端的聯(lián)系
  4. Distribute Push Resource:瀏覽器訂閱完成后评也,會(huì)獲取訂閱的相關(guān)信息(存在于PushSubscription對(duì)象中)炼杖,我們需要將這些信息發(fā)送到自己的服務(wù)端,在服務(wù)端進(jìn)行保存
訂閱過程

推送 Push Message

  1. 我們的服務(wù)端需要推送消息時(shí)盗迟,不直接和客戶端交互坤邪,而是通過 Web Push 協(xié)議,將相關(guān)信息通知 Push Servic罚缕;
  2. Push Service 收到消息艇纺,通過校驗(yàn)后,基于其維護(hù)的客戶端信息邮弹,將消息推送給訂閱了的客戶端
  3. 最后黔衡,客戶端收到消息,完成整個(gè)推送過程
推送過程

什么是 Push Service

在上面的 Push 流程中腌乡,出現(xiàn)了一個(gè)比較少接觸到的角色:Push Service盟劫。那么什么是 Push Service 呢?

我們來看官方解釋:

A push service receives a network request, validates it and delivers a push message to the appropriate browser. If the browser is offline, the message is queued until the browser comes online.

譯: 推送服務(wù)接收一個(gè)網(wǎng)絡(luò)請(qǐng)求导饲,對(duì)其進(jìn)行驗(yàn)證捞高,并將推送消息傳遞到適當(dāng)?shù)臑g覽器。如果瀏覽器離線渣锦,則消息將排隊(duì)等待瀏覽器在線硝岗。

目前,不同的瀏覽器廠商使用了不同的 Push Service袋毙。例如型檀,chrome 使用了 google 自家的 FCM(前身為GCM),firefox 也是使用自家的服務(wù)听盖。那么我們是否需要寫不同的代碼來兼容不同的瀏覽器所使用的服務(wù)呢胀溺?答案是并不用。Push Service 遵循 Web Push Protocol皆看,其規(guī)定了請(qǐng)求及其處理的各種細(xì)節(jié)仓坞,這就保證了,不同的 Push Service 也會(huì)具有標(biāo)準(zhǔn)的調(diào)用方式

這里再提一點(diǎn):我們?cè)谏弦还?jié)中說了 Push 的標(biāo)準(zhǔn)流程腰吟,其中第一步就是瀏覽器發(fā)起訂閱无埃,生成一個(gè)PushSubscription對(duì)象。Push Service 會(huì)為每個(gè)發(fā)起訂閱的瀏覽器生成一個(gè)唯一的 URL毛雇,這樣我們?cè)诜?wù)端推送消息時(shí)嫉称,向這個(gè) URL 進(jìn)行推送后,Push Service 就會(huì)知道要通知哪個(gè)瀏覽器灵疮。而這個(gè) URL 信息也在PushSubscription對(duì)象里织阅,叫做endpoint

PushSubscription 對(duì)象

那么,如果我們知道了endpoint的值震捣,是否就代表我們可以向客戶端推送消息了呢荔棉?并非如此。下面會(huì)簡(jiǎn)單介紹一下 Web Push 中的安全策略

如何保證 Push 的安全性

在 Web Push 中蒿赢,為了保證客戶端只會(huì)收到其訂閱的服務(wù)端推送的消息(其他的服務(wù)端即使在拿到endpoint也無法推送消息)江耀,需要對(duì)推送信息進(jìn)行數(shù)字簽名。該過程大致如下:

在 Web Push 中會(huì)有一對(duì)公鑰與私鑰诉植∠楣客戶端持有公鑰,而服務(wù)端持有私鑰晾腔∩嘞。客戶端在訂閱時(shí),會(huì)將公鑰發(fā)送給 Push Service灼擂,而 Push Service 會(huì)將該公鑰與相應(yīng)的endpoint維護(hù)起來壁查。而當(dāng)服務(wù)端要推送消息時(shí),會(huì)使用私鑰對(duì)發(fā)送的數(shù)據(jù)進(jìn)行數(shù)字簽名剔应,并根據(jù)數(shù)字簽名生成一個(gè)叫Authorization請(qǐng)求頭睡腿。Push Service 收到請(qǐng)求后语御,根據(jù)endpoint取到公鑰,對(duì)數(shù)字簽名解密驗(yàn)證席怪,如果信息相符則表明該請(qǐng)求是通過對(duì)應(yīng)的私鑰加密而成应闯,也表明該請(qǐng)求來自瀏覽器所訂閱的服務(wù)端。反之亦然

而公鑰與私鑰如何生成挂捻,會(huì)在下面的實(shí)例中講解

如何使用 Push API 來推送向用戶推送信息

為了使文章與代碼更清晰碉纺,將Web Push分為這幾個(gè)部分:

  • 瀏覽器發(fā)起訂閱,并將訂閱信息發(fā)送至后端刻撒;
  • 將訂閱信息保存在服務(wù)端骨田,以便今后推送使用;
  • 服務(wù)端推送消息声怔,向Push Service發(fā)起請(qǐng)求态贤;
  • 瀏覽器接收Push信息并處理。

友情提醒:由于 Chrome 所依賴的 Push Service——FCM 在國內(nèi)不可訪問醋火,所以要正常運(yùn)行 demo 中的代碼需要“梯子”抵卫,或者可以選擇 Firefox 來進(jìn)行測(cè)試

瀏覽器(客戶端)生成 subscription 信息

首先,我們需要使用PushManagersubscribe方法來在瀏覽器中進(jìn)行訂閱

在上一節(jié)中我們已經(jīng)知道了如何注冊(cè) Service Worker胎撇。當(dāng)我們注冊(cè)完 Service Worker 后會(huì)得到一個(gè)Registration對(duì)象介粘,通過調(diào)用Registration對(duì)象的registration.pushManager.subscribe()方法可以發(fā)起訂閱

為了使代碼更清晰,本篇 demo 在之前的基礎(chǔ)上晚树,先抽離出 Service Worker 的注冊(cè)方法:

  • public/index.js
function registerServiceWorker() {
  if (!navigator.serviceWorker) {
    return Promise.reject('系統(tǒng)不支持 service worker')
  }

  return navigator.serviceWorker.register('./sw.js').then(function (reg) {
    registration = reg
  })
}

然后定義了 subscribeAndDistribute() 方法來發(fā)起訂閱:

  • public/index.js
// 訂閱推送并將訂閱結(jié)果發(fā)送給后端
function subscribeAndDistribute(registration) {
  if (!window.PushManager) {
    return Promise.reject('系統(tǒng)不支持消息推送')
  }
  // 檢查是否已經(jīng)訂閱過
  return registration.pushManager
    .getSubscription()
    .then(function (subscription) {
      // 如果已經(jīng)訂閱過姻采,就不重新訂閱了
      if (subscription) {
        distributePushResource(subscription)
      } else {
        return (
          // 訂閱
          registration.pushManager
            .subscribe({
              userVisibleOnly: true,
              applicationServerKey: window.base64ToUint8Array(VAPIDPublicKey),
            })
            .then(function (subscription) {
              distributePushResource(subscription)
            })
        )
      }
    })
}

這里使用了registration.pushManager.subscribe()方法中的兩個(gè)配置參數(shù):userVisibleOnlyapplicationServerKey

  • userVisibleOnly
    userVisibleOnly表明該推送是否需要顯性地展示給用戶,即推送時(shí)是否會(huì)有消息提醒爵憎。如果沒有消息提醒就表明是進(jìn)行“靜默”推送慨亲。在Chrome中,必須要將其設(shè)置為true宝鼓,否則瀏覽器就會(huì)在控制臺(tái)報(bào)錯(cuò):

  • applicationServerKey
    applicationServerKey是一個(gè)客戶端的公鑰刑棵,VAPID定義了其規(guī)范,因此也可以稱為 VAPID keys愚铡。該參數(shù)需要 Unit8Array 類型蛉签。因此定義了一個(gè)urlBase64ToUint8Array方法將 base64 的公鑰字符串轉(zhuǎn)為 Unit8Array。subscribe()也是一個(gè) Promise 方法沥寥,在 then 中我們可以得到訂閱的相關(guān)信息——一個(gè)PushSubscription對(duì)象碍舍。下圖展示了這個(gè)對(duì)象中的一些信息。注意其中的endpoint邑雅,Push Service 會(huì)為每個(gè)客戶端隨機(jī)生成一個(gè)不同的值

    PushSubscription 信息

之后片橡,我們?cè)賹?code>PushSubscription信息發(fā)送到后端。這里定義了一個(gè)distributePushResource()方法淮野,該方法就是一個(gè)普通的 XHR 請(qǐng)求捧书,會(huì)向接口 post 訂閱信息

  • public/index.js
// 將訂閱信息傳給后端服務(wù)器
function distributePushResource(subscription) {
  // 為了方便之后的推送吹泡,為每個(gè)客戶端簡(jiǎn)單生成一個(gè)標(biāo)識(shí)
  const body = {
    subscription,
    uniqueid: new Date().getTime(),
  }
  console.log('uniqueid', body.uniqueid)

  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.timeout = 60000
    xhr.onreadystatechange = function () {
      var response = {}
      if (xhr.readyState === 4 && xhr.status === 200) {
        try {
          response = JSON.parse(xhr.responseText)
        } catch (e) {
          response = xhr.responseText
        }
        resolve(response)
      } else if (xhr.readyState === 4) {
        resolve()
      }
    }
    xhr.onabort = reject
    xhr.onerror = reject
    xhr.ontimeout = reject
    xhr.open('POST', '/subscription', true)
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.send(JSON.stringify(body))
  })
}

當(dāng)網(wǎng)站在完成推送訂閱之后,Web Push API 也提供了相應(yīng)的方法來取消訂閱经瓷。前面提到推送訂閱成功之后 PushManager.subscribe() 方法返回的 pushSubscription 對(duì)象上有一個(gè) unsubscribe() 就是用來取消訂閱的:

pushSubscription.unsubscribe().then(function () {
  console.log('取消訂閱成功爆哑!')
})

在取消訂閱之前,我們可以通過 PushManager.getSubscription() 方法來判斷用戶是否已經(jīng)訂閱了嚎,如果用戶已經(jīng)訂閱過,那么該函數(shù)會(huì)返回 pushSubscription 對(duì)象廊营,這樣接下來再調(diào)用 unsubscribe() 方法最終取消訂閱歪泳。如下所示:

registration.pushManager.getSubscription().then(function (pushSubscription) {
  if (!pushSubscription) {
    // 用戶尚未訂閱
    return
  }
  // 取消訂閱
  return pushSubscription.unsubscribe()
})
.then(function () {
  console.log('取消訂閱!')
})

最后露筒,將這一系列方法組合在一起:

  • public/index.js
const VAPIDPublicKey =
  'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A'
// 注冊(cè) service worker 并緩存 registration
let registration

// 注冊(cè) service worker
registerServiceWorker()
  // 申請(qǐng)桌面通知權(quán)限
  .then(function () {
    requestNotificationPermission()
  })
  // 訂閱推送
  .then(function () {
    subscribeAndDistribute(registration)
  })
  .catch(function (err) {
    console.log(err)
  })

function registerServiceWorker() {
  if (!navigator.serviceWorker) {
    return Promise.reject('系統(tǒng)不支持 service worker')
  }

  return navigator.serviceWorker.register('./sw.js').then(function (reg) {
    registration = reg
  })
}

// 申請(qǐng)桌面通知權(quán)限
function requestNotificationPermission() {
  // 系統(tǒng)不支持桌面通知
  if (!window.Notification) {
    return Promise.reject('系統(tǒng)不支持桌面通知')
  }
  return Notification.requestPermission().then(function (permission) {
    if (permission === 'granted') {
      return Promise.resolve()
    }
    return Promise.reject('用戶已禁止桌面通知權(quán)限')
  })
}

// 訂閱推送并將訂閱結(jié)果發(fā)送給后端
function subscribeAndDistribute(registration) {
  if (!window.PushManager) {
    return Promise.reject('系統(tǒng)不支持消息推送')
  }
  // 檢查是否已經(jīng)訂閱過
  return registration.pushManager
    .getSubscription()
    .then(function (subscription) {
      // 如果已經(jīng)訂閱過呐伞,就不重新訂閱了
      if (subscription) {
        distributePushResource(subscription)
      } else {
        return (
          // 訂閱
          registration.pushManager
            .subscribe({
              userVisibleOnly: true,
              applicationServerKey: window.base64ToUint8Array(VAPIDPublicKey),
            })
            .then(function (subscription) {
              distributePushResource(subscription)
            })
        )
      }
    })
}

// 將訂閱信息傳給后端服務(wù)器
function distributePushResource(subscription) {
  // 為了方便之后的推送,為每個(gè)客戶端簡(jiǎn)單生成一個(gè)標(biāo)識(shí)
  const body = {
    subscription,
    uniqueid: new Date().getTime(),
  }
  console.log('uniqueid', body.uniqueid)

  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.timeout = 60000
    xhr.onreadystatechange = function () {
      var response = {}
      if (xhr.readyState === 4 && xhr.status === 200) {
        try {
          response = JSON.parse(xhr.responseText)
        } catch (e) {
          response = xhr.responseText
        }
        resolve(response)
      } else if (xhr.readyState === 4) {
        resolve()
      }
    }
    xhr.onabort = reject
    xhr.onerror = reject
    xhr.ontimeout = reject
    xhr.open('POST', '/subscription', true)
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.send(JSON.stringify(body))
  })
}

注: 這里為了方便我們后面的推送慎式,為每個(gè)客戶端生成了一個(gè)唯一IDuniqueid伶氢,這里使用了時(shí)間戳生成簡(jiǎn)單的uniqueid

由于userVisibleOnlytrue,所以需要用戶授權(quán)開啟通知權(quán)限瘪吏,因此我們會(huì)看到下面的提示框癣防,選擇“允許”即可。你可以在設(shè)置中進(jìn)行通知的管理

顯示通知彈窗

服務(wù)端存儲(chǔ)客戶端 subscription 信息

為了存儲(chǔ)瀏覽器 post 來的訂閱信息掌眠,服務(wù)端需要增加一個(gè)接口/subscription蕾盯,同時(shí)添加中間件koa-body用于處理 body

  • routes/index.js
const router = require('koa-router')()
const { koaBody } = require('koa-body')

/**
 * @description: 提交 subscription 信息,并保存
 * @return {*}
 */
router.post('/subscription', koaBody(), async (ctx) => {
  let body = ctx.request.body
  await util.saveRecord(body)
  ctx.response.body = {
    success: true,
  }
})

接收到 subscription 信息后蓝丙,需要在服務(wù)端進(jìn)行保存级遭,你可使用任何方式來保存它:mysql、redis渺尘、mongodb… 這里為了方便挫鸽,我使用了nedb來進(jìn)行簡(jiǎn)單的存儲(chǔ)。nedb不需要部署安裝鸥跟,可以將數(shù)據(jù)存儲(chǔ)在內(nèi)存中丢郊,也可以持久化,nedb 的 api 和 mongodb 也比較類似

這里util.saveRecord()做了這些工作:首先医咨,查詢subscription信息是否存在蚂夕,若已存在則只更新uniqueid;否則腋逆,直接進(jìn)行存儲(chǔ)

推送信息

在實(shí)際中婿牍,我們一般會(huì)給運(yùn)營或產(chǎn)品同學(xué)提供一個(gè)推送配置后臺(tái)〕颓福可以選擇相應(yīng)的客戶端等脂,填寫推送信息俏蛮,并發(fā)起推送。為了簡(jiǎn)單起見上遥,我并沒有寫一個(gè)推送配置后臺(tái)搏屑,而只提供了一個(gè) post 接口/push來提交推送信息。后期我們完全可以開發(fā)相應(yīng)的推送后臺(tái)來調(diào)用該接口

  • routes/index.js
/**
 * @description: 消息推送API粉楚,可以在管理后臺(tái)進(jìn)行調(diào)用
 * @return {*}
 */
router.post('/push', koaBody(), async (ctx) => {
    const data = ctx.request.body
  let { uniqueid } = data
  let list = uniqueid ? await util.find({ uniqueid }) : await util.findAll()

  for (let i = 0; i < list.length; i++) {
    let subscription = list[i].subscription
    pushMessage(subscription, JSON.stringify(data))
  }

  ctx.response.body = {
    data: list,
  }
})

來看一下/push接口辣恋。

  1. 首先根據(jù) post 的參數(shù)不同,我們可以通過uniqueid來查詢某條訂閱信息:util.find({uniqueid})模软;也可以從數(shù)據(jù)庫中查詢出所有訂閱信息:util.findAll()
  2. 然后通過pushMessage()方法向 Push Service 發(fā)送請(qǐng)求伟骨。根據(jù)第二節(jié)的介紹,我們知道燃异,該請(qǐng)求需要符合Web Push協(xié)議携狭。然而,Web Push協(xié)議的請(qǐng)求封裝回俐、加密處理相關(guān)操作非常繁瑣逛腿。因此,Web Push為各種語言的開發(fā)者提供了一系列對(duì)應(yīng)的庫:Web Push Libaray仅颇,目前有NodeJS单默、PHP、Python忘瓦、Java 等雕凹。把這些復(fù)雜而繁瑣的操作交給它們可以讓我們事半功倍
  3. 最后返回結(jié)果,這里返回了所有的訂閱信息

web-push

安裝

npm install web-push --save

密鑰

公鑰和私鑰可以在線生成, 也可以通過下面方法用命令生成:

npm install web-push -g

web-push generate-vapid-keys

得到的結(jié)果如下所示:

=======================================

Public Key:
BO45dpS6296H9sSWdVcZsnsYeHXCXgYv-9jEUcyFrNfUvmvWF_c5iniytzHU_pEP9mE50xcUKbJrqh-hmMbvLZs

Private Key:
qXsFHan2sLkH0RFHGkNIkVgiUmox3uVYKwEAphUo7II

=======================================

轉(zhuǎn)碼

正如在訂閱推送中提到的政冻,subscribe 方法通過 applicationServerKey 傳入所需要的公鑰枚抵。一般來說得到的公鑰一般都是 base64 編碼后的字符串,需要將其轉(zhuǎn)換成 Uint8Array 格式才能作為 subscribe 的參數(shù)傳入明场。下面給出一個(gè) base64 轉(zhuǎn) Uint8Array 的函數(shù)實(shí)現(xiàn):

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
}

使用

const webpush = require('web-push')

// VAPID
const VAPIDKeys = {
    publicKey: 'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A',
    privateKey: 'TVe_nJlciDOn130gFyFYP8UiGxxWd3QdH6C5axXpSgM'
}

// 設(shè)置 web-push 的 VAPID 值
webpush.setVapidDetails(
  'mailto:shenxh0928@gmail.com', // 聯(lián)系郵箱
  VAPIDKeys.publicKey,
  VAPIDKeys.privateKey,
)

設(shè)置完成后即可使用webpush.sendNotification()方法向 Push Service 發(fā)起請(qǐng)求汽摹。

最后我們來看下pushMessage()方法的細(xì)節(jié):

/**
 * 向push service推送信息
 * @param {*} subscription
 * @param {*} data
 */
function pushMessage(subscription, data = {}) {
  webpush
    .sendNotification(subscription, data, options)
    .then((res) => {
      console.log('push service的相應(yīng)數(shù)據(jù):', JSON.stringify(res))
      return
    })
    .catch((err) => {
      // 判斷狀態(tài)碼,440和410表示失效
      if (err.statusCode === 410 || err.statusCode === 404) {
        return util.remove(subscription)
      } else {
        console.log('失敗', err)
      }
    })
}

webpush.sendNotification為我們封裝了請(qǐng)求的處理細(xì)節(jié)苦锨。狀態(tài)碼401和404表示該 subscription 已經(jīng)無效逼泣,可以從數(shù)據(jù)庫中刪除

Service Worker 監(jiān)聽 Push 消息

調(diào)用webpush.sendNotification()后,我們就已經(jīng)把消息發(fā)送至 Push Service了舟舒;而 Push Service 會(huì)將我們的消息推送至瀏覽器

要想在瀏覽器中獲取推送信息拉庶,只需在 Service Worker 中監(jiān)聽push的事件即可:

  • public/sw.js
// 監(jiān)聽 push 事件
self.addEventListener('push', function (e) {
  var data = e.data

  if (e.data) {
    data = data.json()
    console.log('push 的數(shù)據(jù)為:', data)
  } else {
    console.log('沒有 push 任何數(shù)據(jù)')
  }
})

Push Service可以在設(shè)備離線時(shí),幫你維護(hù)推送消息秃励。當(dāng)瀏覽器設(shè)備重新聯(lián)網(wǎng)時(shí)氏仗,就會(huì)收到該推送

兼容性

又到了查看兼容性的時(shí)間了。對(duì)于Push API夺鲜,目前 Safari 團(tuán)隊(duì)并沒有明確表態(tài)計(jì)劃支持

Push API 兼容性

其實(shí)比兼容性更大的一個(gè)問題是皆尔,Chrome 所依賴的 FCM 服務(wù)在國內(nèi)是無法訪問的呐舔,而 Firefox 的服務(wù)在國內(nèi)可以正常使用。這也是為什么在代碼中會(huì)有這一項(xiàng)設(shè)置:

const options = {
  proxy: 'http://34.82.107.67' // 使用FCM(Chrome)需要配置代理
}

注: 免費(fèi)代理獲取方法: 戳這里

雖然由于 google 服務(wù)被屏蔽慷蠕,導(dǎo)致國內(nèi) Push 功能無法在 chrome 上使用珊拼,但是作為一個(gè)重要的技術(shù)點(diǎn),Web Push 還是非常值得我們了解與學(xué)習(xí)的

本章分支: push

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末流炕,一起剝皮案震驚了整個(gè)濱河市澎现,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌每辟,老刑警劉巖剑辫,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異影兽,居然都是意外死亡揭斧,警方通過查閱死者的電腦和手機(jī)莱革,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門峻堰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盅视,你說我怎么就攤上這事捐名。” “怎么了闹击?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵镶蹋,是天一觀的道長。 經(jīng)常有香客問我赏半,道長贺归,這世上最難降的妖魔是什么还惠? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任接奈,我火速辦了婚禮,結(jié)果婚禮上比原,老公的妹妹穿的比我還像新娘仲义。我一直安慰自己婶熬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布埃撵。 她就那樣靜靜地躺著赵颅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暂刘。 梳的紋絲不亂的頭發(fā)上饺谬,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音谣拣,去河邊找鬼商蕴。 笑死叠萍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绪商。 我是一名探鬼主播苛谷,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼格郁!你這毒婦竟也來了腹殿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤例书,失蹤者是張志新(化名)和其女友劉穎锣尉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體决采,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡自沧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了树瞭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拇厢。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晒喷,靈堂內(nèi)的尸體忽然破棺而出孝偎,到底是詐尸還是另有隱情,我是刑警寧澤凉敲,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布衣盾,位于F島的核電站,受9級(jí)特大地震影響爷抓,放射性物質(zhì)發(fā)生泄漏势决。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一蓝撇、第九天 我趴在偏房一處隱蔽的房頂上張望果复。 院中可真熱鬧,春花似錦唉地、人聲如沸据悔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽极颓。三九已至,卻和暖如春群嗤,著一層夾襖步出監(jiān)牢的瞬間菠隆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骇径,地道東北人躯肌。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像破衔,于是被迫代替她去往敵國和親清女。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容