即使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个步骤:
- 检查服务工作者是否受支持
- 检查是否支持Push API
- 注册服务人员
- 向用户请求权限
- 订阅用户并获取PushSubscription对象
- 将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')
}
})
}
这permissionResult
value是一个字符串,可以具有以下值:-granted
--default
--denied
此代码使浏览器显示权限对话框:
如果用户单击“阻止”,则您将无法再请求该用户的许可,除非他们在浏览器的高级设置面板中手动进入并取消阻止该网站(极不可能发生)。
订阅用户并获取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/subscription
HTTP 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-push
lib,我们生成一个私有和公共密钥元组,并将它们设置为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初学者手册
更多浏览器教程:
- HTML5提供了一些有用的技巧
- 我如何使基于CMS的网站脱机工作
- 渐进式Web应用程序完整指南
- 提取API
- 推送API指南
- 频道消息传递API
- 服务人员教程
- 缓存API指南
- 通知API指南
- 深入IndexedDB
- Selectors API:querySelector和querySelectorAll
- 通过延迟和异步有效地加载JavaScript
- 文档对象模型(DOM)
- Web存储API:本地存储和会话存储
- 了解HTTP Cookies的工作原理
- 历史API
- WebP图像格式
- XMLHttpRequest(XHR)
- 深入的SVG教程
- 什么是数据网址
- 学习网络平台的路线图
- CORS,跨域资源共享
- 网络工作者
- requestAnimationFrame()指南
- 什么是Doctype
- 使用DevTools控制台和控制台API
- 语音合成API
- 如何在纯JavaScript中等待DOM ready事件
- 如何将类添加到DOM元素
- 如何遍历来自querySelectorAll的DOM元素
- 如何从DOM元素中删除类
- 如何检查DOM元素是否具有类
- 如何更改DOM节点值
- 如何将click事件添加到从querySelectorAll返回的DOM元素列表中
- WebRTC,实时Web API
- 如何在JavaScript中获取元素的滚动位置
- 如何替换DOM元素
- 如何只接受输入文件字段中的图像
- 为什么要使用浏览器的预览版?
- Blob对象
- 文件对象
- FileReader对象
- FileList对象
- ArrayBuffer
- ArrayBufferView
- URL对象
- 类型数组
- DataView对象
- BroadcastChannel API
- Streams API
- FormData对象
- 导航器对象
- 如何使用地理位置API
- 如何使用getUserMedia()
- 如何使用拖放API
- 如何在网页上滚动
- 在JavaScript中处理表单
- 键盘事件
- 鼠标事件
- 触摸事件
- 如何从DOM元素中删除所有子级
- 如何使用原始Javascript创建HTML属性
- 如何检查是否使用JavaScript选中了复选框?
- 如何使用JavaScript复制到剪贴板
- 如何使用JavaScript禁用按钮
- 如何在浏览器中使页面可编辑
- 如何使用URLSearchParams在JavaScript中获取查询字符串值
- 如何一次删除页面上的所有CSS
- 如何使用insertAdjacentHTML
- Safari,退出前警告
- 如何使用JavaScript将图像添加到DOM
- 如何重设表格
- 如何使用Google字体