How Javascript works (Javascript工作原理) (九) 网页消息推送通知机制
个人总结:
1.介绍了网页消息推送通知机制
全文地址:https://github.com/Troland/how-javascript-works
这是 JavaScript 工作原理的第九章。
现在让我们把注意力转移到网页推送通知:我们将会查看其构造,探索发送/接收通知背后的过程以及最后分享一下我们在 SessionStack 是如何计划利用这些功能来创建新的产品功能的。
推送通知这一功能在移动端已经非常普遍。不知为何,网页端的推送通知是千呼万唤始出来,即使大多数开发者强烈地要求实现这一功能。
概述
网页推送通知允许用户选择定时从网络应用获取及时信息。它旨在为用户重新获取其感兴趣,重要和及时的信息。
推送服务是基于服务工作线程的,服务工作线程(service web worker)在之前的文章中有详细阐述过。
这个情况下,之所以采用服务工作线程是因为它会在后台运行,从而不会阻塞界面的渲染。对于推送通知来说,这是相当重要的,因为这意味着只有当用户和推送通知本身进行交互操作才会执行推送通知的相关代码。
消息推送和通知
消息推送和通知是两个不同的接口。
消息推送
实现消息推送大概有以下三个步骤:
- 界面-添加客户端逻辑来让用户订阅推送服务。在网络应用界面中书写 JavaScript 代码逻辑来让用户注册消息推送服务。
- 发送消息-在服务器端实现接口调用来触发向用户设备推送消息。
- 接收消息-一旦在浏览器端接收到推送消息则处理之。
现在,让我们详细阐述整个过程。
兼容性检测
首先,需要检测当前浏览器是否支持消息推送服务。可以采用以下两种简单的检查:
- 检测
navigator对象上的serviceWorker属性 - 检测
window对象上的PushManager属性
都检测代码如下:
if (!('serviceWorker' in navigator)) {
// 当前浏览器不支持服务器工作线程,禁用或者隐藏界面
return;
}
if (!('PushManager' in window)) {
// 当前浏览器不支持推送服务,禁用或者隐藏界面
return;
}
注册服务工作线程
现在,消息推送功能是支持的。下一下即注册服务工作线程。
从之前的文章中你应该很熟悉如何注册服务工作线程。
请求授权
当注册服务工作线程之后,接下来进行用户订阅的相关操作。这需要获得用户的授权来向其推送消息。
获得授权的接口相当的简单但有一个缺点即接口 接受的参数以前是一个回调函数现在是一个 Promise。因为无法知晓当前浏览器支持的接口版本,所以需要进行兼容处理。
类似这样:
function requestPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
// 使用回调来处理废弃的接口版本
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('Permission not granted.');
}
});
}
调用 Notification.requestPermission() 会向用户弹出以下的提示框:

当获得,关闭以及禁止权限的时候,就可以得到 granted,default 或者 denied 的结果字符串。
需要注意的是当用户点击 禁止 按钮,网络应用将不会再次询问用户授权直到用户手动开启更改授权状态。该选项隐藏于设置面板中。
点击地址栏最左边的信息按钮即可弹出授权的弹窗。
通过 PushManager 订阅用户
一旦服务工作线程注册成功且获得授权,就可以在注册服务器线程的时候通过调用 registration.pushManager.subscribe() 来订阅用户。
整个代码片断如下(包括注册服务工作线程):
function subscribeUserToPush() {
return navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
var subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: btoa(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('PushSubscription: ', JSON.stringify(pushSubscription));
return pushSubscription;
});
}
registration.pushManager.subscribe(options) 中有一个 options 对象参数,其中包含有必须或者可选的参数:
- userVisibleOnly:返回的推送订阅是否仅对订阅用户可见。必须设置为
true否则会出错(这是历史原因造成的)。 - applicationServerKey:一个包含公钥的 Base64 编码的
DOMString字符串或者ArrayBuffer,消息推送服务器用来验证应用服务器。
消息推送服务器需要生成一对应用服务器密钥对-即 VAPID 密钥对,这对于消息推送服务器来说是唯一的。它们是由一对公钥和私钥所组成的。私钥秘密存储于推送服务器端,公钥用来和客户端进行交换通讯用的。这些密钥让推送服务辨别订阅用户的应用服务器以及确保触发推送消息到指定用户的是同一个应用服务器。
你只需要一次性生成应用程序私有/公有密钥对。可以访问 https://web-push-codelab.glitch.me/ 生成密钥对。
当订阅用户的时候,浏览器向推送服务传入 applicationServerKey (公钥),意即推送服务把应用服务器公钥和用户的 PushSubscription 绑定在一起。
过程如下:
- 网络应用加载完成然后,调用
subscribe,传入服务器公钥。 - 浏览器向消息推送服务发起请求生成一个端点信息并连同密钥信息一起返回给浏览器。
- 浏览器把端信息添加到由
subscribe()promise 所返回的PushSubscription对象中。
之后,每当需要推送信息的时候,必须发送一个认证头其中包含应用服务器私钥签名的信息。
每当推送服务接收到推送消息的请求,它会通过在传输头中查找已经和指定端(第二步中)绑定的公钥来进行验证。
PushSubscription 对象
PushSubscription 包含了向用户设备推送信息所必备的一切信息。大概包含如下信息:
{
"endpoint": "https://domain.pushservice.com/some-id",
"keys": {
"p256dh":
"BIPUL12DLfytvTajnryr3PJdAgXS3HGMlLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WArAPIxr4gK0_dQds4yiI=",
"auth":"FPssMOQPmLmXWmdSTdbKVw=="
}
}
endpoint 即是推送服务地址。当需要推送消息时,向该地址发起 POST 请求。
keys 对象包含用来加密随推送消息一起发送的信息数据的值。
当用户订阅之后且返回了 PushSubscription 对象,你需要把它保存在推送服务器上。这样就可以把该订阅相关数据保存在数据库之中然后从今以后,就可以根据数据库中的存储值来给指定的用户发送消息。

消息推送
当需要发送消息到用户的时候,首先需要有一个消息推送服务。你通知推送服务(通过接口调用)需要推送的数据,消息推送的目标用户以及任意条件下如何发送消息。一般情况下,这些接口调用是由消息推送服务器来完成的。
消息推送服务
消息推送服务是用来接收消息推送请求,验证请求以及推送消息到指定的用户浏览器端。
请注意这里的消息推送服务并不是由你来控制的-它是第三方服务。服务器只是通过接口来和消息推送服务进行通讯。Google’s FCM 是消息推送服务之一。
消息推送服务会处理核心的事务。比如,当浏览器离线,推送服务在发送各自的消息之前会排队消息且等待直到浏览器连网。
开发人员可以选择让浏览器使用任意的消息推送服务。
然而,所有的消息推送服务都拥有一样的接口,这样就不会由于接口不一而增加消息推送实现的难度。
可以从 PushSubscription 对象的 endpoint 属性值获得处理消息推送的请求 URL 地址。
消息推送接口
消息推送服务接口提供了向用户发送消息的一种方法。该接口是一个被称为 Web Push Protocol 的 IETF 标准协议,里面定义了如何调用消息推送服务。
推送的消息必须得加密。这样可以防止消息推送服务窥视到发送的数据。这是至关重要的因为客户端可以决定使用哪个消息推送服务(可能会使用一些不被信任和不安全的消息推送服务)。
消息推送参数:
- TTL-定义消息在被删除且不能够传输之前在队列中的保存时长。
- Priority-定义了每条消息的优先级,这样就可以让消息推送服务只推送高优先级的消息以方便用户节省设备的电力。
- Topic-为推送消息设置主题名称这样就可以使用相同的主题名称来置换掉挂起的消息,所以一旦设备激活,用户就不会收到过期的消息。

浏览器消息推送事件
每当发送消息到如上的推送服务,消息会处于待发送状态直到发生以下几种情况:
- 设备连网。
- 队列中的消息停留时长超过设置的 TTL。
当消息推送服务传输消息到浏览器,浏览器会接收到,解密,然后分派给服务工作线程 push 事件。
划重点这里即使没有打开网页,浏览器仍然可以执行服务工作线程。会发生如下事件:
- 浏览器解密接收的推送消息。
- 浏览器唤醒服务工作线程。
- 服务工作线程接收到
push事件。
监听推送事件和在 JavaScript 中写的其它事件监听非常类似。
self.addEventListener('push', function(event) {
if (event.data) {
console.log('This push event has data: ', event.data.text());
} else {
console.log('This push event has no data.');
}
});
需要理解服务工作线程的一点即其运行时间是不可人为控制的。只有浏览器可以唤醒和结束它。
在服务工作线程中,event.waitUntil(promise) 告诉浏览器服务工作线程正在处理消息直到 promise 解析完成,如果想要完成消息的处理,那么浏览器就不应该中止服务工作线程。
以下为处理 push 事件的示例:
self.addEventListener('push', function(event) {
var promise = self.registration.showNotification('Push notification!');
event.waitUntil(promise);
});
调用 self.registration.showNotification() 向用户弹出一个通知并且返回一个 promise,一旦通知显示完成即解析完成。
可以采用可视化的方法来设置符合自己需求的 showNotification(title, options) 方法。title 参数是字符串而 options 是一个类似如下的对象:
{
"//": "视觉选项",
"body": "<String>",
"icon": "<URL String>",
"image": "<URL String>",
"badge": "<URL String>",
"vibrate": "<Array of Integers>",
"sound": "<URL String>",
"dir": "<String of 'auto' | 'ltr' | 'rtl'>",
"//": "行为选项",
"tag": "<String>",
"data": "<Anything>",
"requireInteraction": "<boolean>",
"renotify": "<Boolean>",
"silent": "<Boolean>",
"//": "视觉和行为选项",
"actions": "<Array of Strings>",
"//": "信息选项。没有视觉效果",
"timestamp": "<Long>"
}
可以在这里查看到每个选项的更加详细的内容。
每当想要和用户分享紧急,重要及紧迫的信息的时候,消息推送服务是用来通知用户的一个绝佳的方式。
通知处理
服务工作线程可以采用类似如下的代码来进行处理:
self.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
event.notification.close();
event.waitUntil(clients.openWindow('https://developers.google.com/web/'));
});
总结
nodejs 可以使用这里的库来构建推送服务器。
做一个网页消息推送所需要的条件即:
- 消息推送服务器(调用消息推送服务及生成 VAPID 公钥和私钥对)。
- 检查浏览器端兼容性,获取授权,使用消息推送服务器生成的公钥并生成订阅对象,保存该订阅对象到推送服务器上面。
- 消息推送服务(第三方服务)。
一张流程图来表示吧:

How Javascript works (Javascript工作原理) (九) 网页消息推送通知机制的更多相关文章
- IOS 基于APNS消息推送原理与实现(JAVA后台)
Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple Pu ...
- 转:IOS 基于APNS消息推送原理与实现(JAVA后台)
Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple ...
- IOS 基于APNS消息推送原理与实现(JAVA后台)--转
Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple ...
- iOS 基于APNS消息推送原理与实现(包括JAVA后台代码)
Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple ...
- How Javascript works (Javascript工作原理) (一) 引擎,运行时,函数调用栈
个人总结:该系列文章对JS底层的工作原理进行了介绍. 这篇文章讲了 运行时:js其实是和AJAX.DOM.Settimeout等WebAPI独立分离开的 调用栈:JavaScript的堆内存管理 和 ...
- How Javascript works (Javascript工作原理) (八) WebAssembly 对比 JavaScript 及其使用场景
个人总结: webworker有以下三种: Dedicated Workers 由主进程实例化并且只能与之进行通信 Shared Workers 可以被运行在同源的所有进程访问(不同的浏览的选项卡,内 ...
- How Javascript works (Javascript工作原理) (四) 事件循环及异步编程的出现和 5 种更好的 async/await 编程方式
个人总结: 1.讲解了JS引擎,webAPI与event loop合作的机制. 2.setTimeout是把事件推送给Web API去处理,当时间到了之后才把setTimeout中的事件推入调用栈. ...
- JavaScript定时器的工作原理(翻译)
JavaScript定时器的工作原理(翻译) 标签(空格分隔): JavaScript定时器 最近在看ajax原理的时候,看到了一篇国外的文章,讲解了JavaScript定时器的工作原理,帮助我很好的 ...
- JavaScript是如何工作的: Web推送通知的机制
摘要: 如何在Web端推送消息? 这是专门探索 JavaScript 及其所构建的组件的系列文章的第9篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript是如何工作的:引擎,运行时 ...
随机推荐
- ubuntu16.04 安装配置matlab+python +cuda8.0+cudnn+opencv3.1的caffe环境
网络上有很多ubuntu上caffe配置环境的帖子,本人照着其中的许多进行了参考,都出现了或多或少的错误,很多地方也有差异. 于是自己整理了下自己的安装过程,成功进行了测试,跑通了faster-rcn ...
- HDU 2199 Can you solve this equation?【二分查找】
解题思路:给出一个方程 8*x^4 + 7*x^3 + 2*x^2 + 3*x + 6 == Y,求方程的解. 首先判断方程是否有解,因为该函数在实数范围内是连续的,所以只需使y的值满足f(0)< ...
- 在使用easyui datagrid在tab中遇到的问题
当切换tab时,数据加载了,但是table的宽和高不能不能够初始化. 郁闷了好久解决了这个问题: 在页面加载时和切换tab时,获取当前tab的名字,进行内容的初始化 $('a[name="m ...
- 51nod 1576 Tree and permutation(树的重心+dfn序)
乍一看我不会. 先不考虑加点. 先考虑没有那个除\(2\). 考虑每一条边的贡献,假设某一条边把这棵树分成大小为x,y的两个部分. 那么这条边最多可以被使用\(min(x,y)*2\)次(因为有进有出 ...
- php中mysqli 处理查询结果集总结
在PHP开发中,我们经常会与数据库打交道.我们都知道,一般的数据处理操作流程为 接收表单数据 数据入库 //连接数据库 $link = mysqli_connect("my_host&quo ...
- Golang 源码剖析:log 标准库
Golang 源码剖析:log 标准库 原文地址:Golang 源码剖析:log 标准库 日志 输出 2018/09/28 20:03:08 EDDYCJY Blog... 构成 [日期]<空格 ...
- linux下tomcat开机启动简单配置
1.个人标记 caicongyang http://blog.csdn.net/caicongyang 2.正文 在linux文件/etc/rc.d/rc.local的末尾加入例如以下行就可以: ex ...
- adt-bundle-windows加入NDK支持
近期换了个硬盘,曾经都是用eclipse安装adt插件的,如今老了,图省事就下载了adt-bundle-windows,解压缩出来就直接用.但是这个adt-bundle没有集成NDK支持,于是手动安装 ...
- sql两个字段相加减,第三个字段没有值的原因.
错误的写法:(in_story_num-out_story_num) as story_num 正确的写法:(nvl(in_story_num,0)-nvl(out_story_num,0)) as ...
- jQuery操作元素的属性与样式
本文学习如何使用jQuery获取和操作元素的属性和CSS样式. 元素属性和Dom属性 对于下面这样一个标签元素: <img id='img' src="1.jpg" alt= ...