推送API指南

即使Web应用程序当前未在浏览器中打开或未在设备上运行,Push API仍允许Web应用程序接收服务器推送的消息。

即使该Web应用程序当前未在浏览器中打开或未在设备上运行,该Push API仍允许该Web应用程序接收服务器推送的消息。

使用Push API,即使用户没有浏览站点,也可以向用户发送消息,将消息从服务器推送到客户端。

这使您可以发送通知和内容更新,使您能够吸引更多的受众。

这是巨大的,因为与本机应用程序相比,移动网络缺少的支柱之一是能够接收通知以及脱机支持。

有很好的支持吗?

Push API是浏览器API的最新功能,Chrome(台式机和移动设备),Firefox和Opera自2016年以来一直受Edge支持,而Edge自版本17(2018年初)以来受到支持。有关更多信息,请访问:https://caniuse.com/#feat=push-api

IE不支持它,并且Safari有自己的实现

由于Chrome和Firefox支持此功能,因此大约有60%的用户在台式机上浏览时可以访问它,因此安全的使用。

怎么运行的

概述

当用户访问您的Web应用程序时,您可以触发一个面板,询问是否允许发送更新。一种服务人员已安装,并且在后台操作监听推送事件

推送和通知是一个单独的概念和API,有时由于推送通知iOS中使用的术语。基本上,使用Push API接收到push事件时,将调用Notifications API。

你的服务器将通知发送给客户端,如果获得许可,服务工作者会收到推送事件。服务人员通过以下方式对此事件做出反应触发通知

获得用户的许可

使用Push API的第一步是获得用户的许可,以从您接收数据。

许多站点在执行此面板时效果很差,在第一页加载时就显示出来。用户尚未确信您的内容很好,他们将拒绝该许可。明智地做。

共有6个步骤:

  1. 检查服务工作者是否受支持
  2. 检查是否支持Push API
  3. 注册服务人员
  4. 向用户请求权限
  5. 订阅用户并获取PushSubscription对象
  6. 将PushSubscription对象发送到您的服务器

检查服务工作者是否受支持

if (!('serviceWorker' in navigator)) {
  // Service Workers are not supported. Return
  return
}

检查是否支持Push API

if (!('PushManager' in window)) {
  // The Push API is not supported. Return
  return
}

注册服务人员

此代码注册位于worker.js文件放置在域根目录中:

window.addEventListener('load', () => {
  navigator.serviceWorker.register('/worker.js')
  .then((registration) => {
    console.log('Service Worker registration completed with scope: ',
      registration.scope)
  }, (err) => {
    console.log('Service Worker registration failed', err)
  })
})

要了解有关服务人员如何详细工作的更多信息,请查看服务人员指南

向用户请求权限

现在,服务工作者已注册,您可以请求权限。

执行此操作的API随时间变化,从接受回调函数作为参数到返回承诺,打破了向后和向前的兼容性,我们需要做两个都因为我们不知道用户的浏览器实现了哪种方法。

代码如下,调用Notification.requestPermission()

const askPermission = () => {
  return new Promise((resolve, reject) => {
    const permissionResult = Notification.requestPermission((result) => {
      resolve(result)
    })
    if (permissionResult) {
      permissionResult.then(resolve, reject)
    }
  })
  .then((permissionResult) => {
    if (permissionResult !== 'granted') {
      throw new Error('Permission denied')
    }
  })
}

permissionResultvalue是一个字符串,可以具有以下值:-granted--default--denied

此代码使浏览器显示权限对话框:

The browser permission dialogue

如果用户单击“阻止”,则您将无法再请求该用户的许可,除非他们在浏览器的高级设置面板中手动进入并取消阻止该网站(极不可能发生)。

订阅用户并获取PushSubscription对象

如果用户授予我们许可,我们可以订阅该许可并通过致电registration.pushManager.subscribe()

const APP_SERVER_KEY = 'XXX'

window.addEventListener(‘load’, () => { navigator.serviceWorker.register(’/worker.js’) .then((registration) => { askPermission().then(() => { const options = { userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(APP_SERVER_KEY) } return registration.pushManager.subscribe(options) }).then((pushSubscription) => { // we got the pushSubscription object } }, (err) => { console.log(‘Service Worker registration failed’, err) }) })

APP_SERVER_KEY是一个字符串-称为应用服务器密钥或者VAPID密钥-标识应用程序公钥,公钥/私钥对的一部分。

出于安全原因,它将用作验证的一部分,以确保您(只有您自己,而不是其他人)可以将推送消息发送回用户。

将PushSubscription对象发送到您的服务器

在上一小节中,我们获得了pushSubscription对象,其中包含我们需要向用户发送推送消息的所有内容。我们需要将此信息发送到我们的服务器,以便稍后能够发送通知。

我们首先创建对象的JSON表示形式

const subscription = JSON.stringify(pushSubscription)

我们可以使用提取API

const sendToServer = (subscription) => {
  return fetch('/api/subscription', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(subscription)
  })
  .then((res) => {
    if (!res.ok) {
      throw new Error('An error occurred')
    }
    return res.json()
  })
  .then((resData) => {
    if (!(resData.data && resData.data.success)) {
      throw new Error('An error occurred')
    }
  })
}

sendToServer(subscription)

服务器端/api/subscription端点接收POST请求,并且可以将订阅信息存储到其存储中。

服务器端如何工作

到目前为止,我们仅讨论了客户端部分:在将来获得用户通知的权限。

那服务器呢?它应该做什么,以及应该如何与客户互动?

这些服务器端示例使用Express.js(http://expressjs.com/)作为基本HTTP框架,但您可以使用任何语言或框架编写服务器端Push API处理程序

注册新的客户订阅

当客户端发送新的订阅时,请记住我们使用了/api/subscriptionHTTP POST端点,在正文中以JSON格式发送PushSubscription对象的详细信息。

我们初始化Express.js:

const express = require('express')
const app = express()

此实用程序功能可确保请求有效,具有正文和终结点属性,否则它将向客户端返回错误:

const isValidSaveRequest = (req, res) => {
  if (!req.body || !req.body.endpoint) {
    res.status(400)
    res.setHeader('Content-Type', 'application/json')
    res.send(JSON.stringify({
      error: {
        id: 'no-endpoint',
        message: 'Subscription must have an endpoint'
      }
    }))
    return false
  }
  return true
}

下一个实用程序功能将预订保存到数据库,并在插入完成(或失败)时返回已解决的promise。这insertToDatabase函数是一个占位符,我们在这里不赘述:

const saveSubscriptionToDatabase = (subscription) => {
  return new Promise((resolve, reject) => {
    insertToDatabase(subscription, (err, id) => {
      if (err) {
        reject(err)
        return
      }
  <span style="color:#a6e22e">resolve</span>(<span style="color:#a6e22e">id</span>)
})

}) }

我们在下面的POST请求处理程序中使用这些功能。我们检查请求是否有效,然后保存请求,然后返回data.success: true回复给客户端,或出现错误:

app.post('/api/subscription', (req, res) => {
  if (!isValidSaveRequest(req, res)) {
    return
  }

saveSubscriptionToDatabase(req, res.body) .then((subscriptionId) => { res.setHeader(‘Content-Type’, ‘application/json’) res.send(JSON.stringify({ data: { success: true } })) }) .catch((err) => { res.status(500) res.setHeader(‘Content-Type’, ‘application/json’) res.send(JSON.stringify({ error: { id: ‘unable-to-save-subscription’, message: ‘Subscription received but failed to save it’ } })) }) })

app.listen(3000, () => { console.log(‘App listening on port 3000’) })

发送推送消息

现在,服务器已在其列表中注册了客户端,我们可以向其发送Push消息。让我们通过创建一个示例代码片段来查看其工作原理,该代码片段将提取所有订阅,并同时向所有订阅发送一个Push消息。

我们使用图书馆是因为Web推送协议复杂的,而lib使我们可以抽象出许多低级代码,以确保我们可以安全地工作并正确处理任何边缘情况。

本示例使用web-push Node.js图书馆 (https://github.com/web-push-libs/web-push)处理发送Push消息

我们首先初始化web-pushlib,我们生成一个私有和公共密钥元组,并将它们设置为VAPID详细信息:

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

const PUBLIC_KEY = ‘XXX’ const PRIVATE_KEY = ‘YYY’

const vapidKeys = { publicKey: PUBLIC_KEY, privateKey: PRIVATE_KEY }

webpush.setVapidDetails( mailto:[email protected], vapidKeys.publicKey, vapidKeys.privateKey )

然后我们建立一个triggerPush()方法,负责将push事件发送给客户端。它只是在打电话webpush.sendNotification()并捕获任何错误。如果返回错误的HTTP状态代码为410, 意思是不见了,我们将从数据库中删除该订户。

const triggerPush = (subscription, dataToSend) => {
  return webpush.sendNotification(subscription, dataToSend)
  .catch((err) => {
    if (err.statusCode === 410) {
      return deleteSubscriptionFromDatabase(subscription._id)
    } else {
      console.log('Subscription is no longer valid: ', err)
    }
  })
}

我们没有实现从数据库获取订阅,但是将其保留为存根:

const getSubscriptionsFromDatabase = () => {
  //stub
}

代码的实质是POST请求对/api/push端点:

app.post('/api/push', (req, res) => {
  return getSubscriptionsFromDatabase()
  .then((subscriptions) => {
    let promiseChain = Promise.resolve()
    for (let i = 0; i < subscriptions.length; i++) {
      const subscription = subscriptions[i]
      promiseChain = promiseChain.then(() => {
        return triggerPush(subscription, dataToSend)
      })
    }
    return promiseChain
  })
  .then(() => {
    res.setHeader('Content-Type', 'application/json')
    res.send(JSON.stringify({ data: { success: true } }))
  })
  .catch((err) => {
    res.status(500)
    res.setHeader('Content-Type', 'application/json')
    res.send(JSON.stringify({
      error: {
        id: 'unable-to-send-messages',
        message: `Failed to send the push ${err.message}`
      }
    }))
  })
})

上面的代码所做的是:它从数据库中获取所有订阅,然后对其进行迭代,然后调用triggerPush()我们之前解释过的功能。

订阅完成后,我们将返回成功的JSON响应,除非发生错误并且返回500错误。

在现实世界…

除非您有一个非常特殊的用例,或者您只是想学习技术或喜欢DIY,否则不太可能设置自己的Push服务器。相反,您通常希望使用OneSignal(https://onesignal.com),透明地免费处理所有平台(包括Safari和iOS)的Push事件。

接收推送事件

从服务器发送Push事件时,客户端如何获取它?

这很正常JavaScript事件监听器,在push事件,该事件在Service Worker内部运行:

self.addEventListener('push', (event) => {
  // data is available in event.data
})

event.data包含PushMessageData对象,该对象以所需的格式公开用于检索服务器发送的推送数据的方法:

  • arrayBuffer():作为ArrayBuffer目的
  • blob(): 作为一个斑点目的
  • json():解析为JSON格式
  • 文本(): 纯文本

您通常会使用event.data.json()

显示通知

在这里,我们与通知API,但这是有充分理由的,因为Push API的主要用例之一是显示通知。

在我们里面push在Service Worker中的事件侦听器中,我们需要向用户显示通知,并告诉事件要等到浏览器显示该事件后,函数才能终止。我们延长事件的生存时间,直到浏览器完成显示通知为止(直到兑现承诺已解决),否则服务工作者可能会在处理过程中停止:

self.addEventListener('push', (event) => {
  const promiseChain = self.registration.showNotification('Hey!')
  event.waitUntil(promiseChain)
})

有关通知的更多信息通知API指南

免费下载我的JavaScript初学者手册


更多浏览器教程: