[译] JavaScript 是如何工作的:Web 推送通知的机制
- 原文地址:How JavaScript works: the mechanics of Web Push Notifications
- 原文作者:Alexander Zlatkov
- 译文出自:掘金翻译计划
- 本文永久链接:github.com/xitu/gold-m…
- 译者:Starrier
- 校对者:allen、老教授
这是专门研究 JavaScript 及其构建组件系列文章的第 9 章。在识别和描述核心元素的过程中,我们还分享了我们在构建一个轻量级 JavaScript 应用程序 SessionStack 时使用的一些经验规则,该应用程序需要健壮、高性能,可以帮助用户实时查看和重现它们的 Web 应用程序缺陷。
如果你错过了前几章,你可以在这里找到它们:
- [译] JavaScript 是如何工作的:对引擎、运行时、调用堆栈的概述
- [译] JavaScript 是如何工作的:在 V8 引擎里 5 个优化代码的技巧
- [译] JavaScript 是如何工作的:内存管理 + 处理常见的4种内存泄漏
- [译] JavaScript 是如何工作的: 事件循环和异步编程的崛起 + 5个如何更好的使用 async/await 编码的技巧
- [译] JavaScript 是如何工作的:深入剖析 WebSockets 和拥有 SSE 技术 的 HTTP/2,以及如何在二者中做出正确的选择
- [译] JavaScript 是如何工作的:与 WebAssembly 一较高下 + 为何 WebAssembly 在某些情况下比 JavaScript 更为适用
- [译] JavaScript 是如何工作的:Web Worker 的内部构造以及 5 种你应当使用它的场景
- [译] JavaScript 是如何工作的:Web Worker 生命周期及用例
今天,我们来关注 Web 推送通知:我们将了解它们的构建组件,探索发送/接收通知的流程,最后分享 SessionStack 是如何利用这些来构建新产品的特性。
推送通知在手机领域被广泛使用。由于某种原因,它们很晚才进入 Web 领域,尽管开发人员呼唤了很久。
概述
Web 推送通知允许用户在 Web 应用程序中选择接收更新信息,这些旨在重新吸引用户群注意的更新信息通常是对用户来说有趣、重要、实时的内容。
推送基于我们在上一篇文章中详细讨论过的 Service Worker。
在这种情况下,使用 Service Worker 的原因是它们在后台工作。这对推送通知非常有用,因为这意味着只有当用户与通知本身进行交互时才会执行它们的代码。
推送和通知
推送和通知是两种不同的 API。
推送
实现推送有三个步骤:
- UI —— 添加必要的客户端逻辑来让用户订阅推送,这是你的 Web 应用程序 UI 需要的 JavaScript 逻辑,这样用户就能给自己注册从而可以收到消息推送。
- 发送推送消息 —— 在服务器上实现 API 调用,该调用将触发对用户设备的推送消息。
- 接受推送消息 —— 一旦推送消息到达浏览器,就进行处理。
现在我们将更详细地描述整个过程。
浏览器支持检测
首先,我们需要检查当前浏览器是否支持推送消息。我们可以通过两个简单的方法检查是否支持推送消息:
- 检查
navigator
对象上的serviceWorker
- 检查
window
对象上的PushManager
两种检查看起来都是这样的:
if (!('serviceWorker' in navigator)) { // Service Worker isn't supported on this browser, disable or hide UI. return; } if (!('PushManager' in window)) { // Push isn't supported on this browser, disable or hide UI. return; }
注册一个 Service Worker
此时,我们知道该功能是受支持的。下一步是注册我们的 Service Worker。
如何注册 Service Worker,你从我们以前的一篇文章中应该已经熟悉了。
请求许可
在注册了 Service Worker 之后,我们可以开始订阅用户。要做到这一点,我们需要得到他的许可才能给他发送推送信息。
获取许可的 API 相对简单,但缺点是 API 已经从接受回调变为返回 Promise,这带来了一个问题:我们无法判断当前浏览器实现了哪个 API 版本,因此你必须实现和处理这两个版本。
看起来是这样的:
function requestPermission() { return new Promise(function(resolve, reject) { const permissionResult = Notification.requestPermission(function(result) { // Handling deprecated version with callback. 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’
。
记住,如果用户单击 Block
按钮,你的 Web 应用程序将无法再次请求用户的许可,直到他们通过更改权限状态手动 “unblock” 你的应用程序的限制。此选项隐藏在设置界面中。
用户订阅使用 PushManager
一旦我们注册了 Service Worker 并获得许可权限,当你在注册你的 Service Worker 时,我们就可以通过调用 registration.pushManager.subscribe()
来订阅用户。
整个片段可能如下所示(包括 Service Workder 注册):
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-encoded
DOMString
或者ArrayBuffer
包含推送服务器用来验证应用服务器的公钥。
你的服务器需要生成一对应用程序服务器密钥 —— 也称为 VAPID 密钥,对于你的服务器来说,它们是唯一的。它们是一对公钥和私钥。私钥被秘密存储在你的终端,而公钥则与客户端交换。这些密钥允许推送服务知道哪个应用服务器订阅了用户,并确保它是触发向该特定用户推送消息的相同服务器。
你只需要为应用程序创建一次私钥/公钥对。做到这一点的方法是去完成这个 —— web-push-codelab.glitch.me/。
浏览器在订阅用户时将 applicationServerKey
(公钥)传递给推送服务,这意味着推送服务可以将应用程序的公钥绑定到用户的 PushSubscription
中。
情况是这样的:
- 你的 web app 被加载时,你可以调用
subscribe()
来传入你的 server 密钥。 - 浏览器向生成端点的推送服务发出请求,将此端点与该键关联并将端点返回给浏览器。
- 浏览器将此端点添加到
PushSubscription
对象中,该对象通过subscribe()
的 promise 返回。
之后,无论你想何时发送推送消息,你都需要创建一个包含使用应用程序服务器的专用密钥签名信息的 Authorization header。当推送服务收到发送推送消息的请求时,它将通过查找已经连接到该特定端点的公钥来验证头(第二步)。
推送对象
PushSubscription
包含用户设备发送推送消息所需的所有信息。就像这样:
{ "endpoint": "https://domain.pushservice.com/some-id", "keys": { "p256dh": "BIPUL12DLfytvTajnryr3PJdAgXS3HGMlLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WArAPIxr4gK0_dQds4yiI=", "auth":"FPssMOQPmLmXWmdSTdbKVw==" } }
endpoint
是推送服务的 URL。要触发推送消息,请对此 URL 发送 POST 请求。
这里的 keys
对象的值是用来加密推送消息带过来的消息数据。
一旦用户被订阅并且你有 PushSubscription
,你需要将它发送到你的服务器。在那里(在服务器上),你将这个订阅存到数据库中,从今以后如果你要向该用户推送消息就使用它。
发送推送消息
当你想向用户发送推送消息时,你首先需要一个推送服务。你要(通过 API 调用)告诉推送服务要发送哪些数据,谁来接收数据以及其他关于怎么发送数据的标准。通常,此 API 调用是在你服务器上完成的。
推送服务
推送服务是接收请求,验证请求并将推送消息传递给对应的浏览器。
注意推送服务不是由你管理的 —— 它是第三方服务。你的服务器通过 API 与 推送服务进行通讯。推送服务的一个例子是 Google 的 FCM。
推送服务处理所有繁重的任务,比如,如果浏览器处于脱机状态,推送服务会在发送相应消息之前对消息进行排队,等待浏览器的再次联机。
每个浏览器都使用它们想要的任何推送服务,这是开发者无法控制的。
然而所有的推送服务都具有相同的 API,因此在实现过程中不会有很大难度。
为了获得 URL 来进行消息推送请求,你需要检查 PushSubscription
对象中存储的 endpoint
值。
推送服务 API
推送服务 API 提供了一种将消息发送给用户的方式。API 基于 Web 推送协议,它是一种定义了如何对推送服务进行 API 调用的 IETF 标准。
你使用推送消息发送的数据必须被加密。这样可以防止推送服务查看发送的数据。这很重要,因为浏览器是可以决定使用哪种推送服务的(它可能使用了一些不受信任且不够安全的服务器)。
对于每个推送消息,你还可以提供下列说明:
- TTL —— 定义消息会在队列中等多久,超过这个时间消息就会被删除不做推送。。
- 优先级 —— 定义消息的优先级,因为推送服务只发送高优先级的消息,以此来保护用户设备的电池寿命。
- Topic —— 给推送消息一个主题,新消息会替换等待中的带相同主题的消息,这样一旦设备处于活动状态,用户将不会收到过时的消息。
浏览器中的推送事件
如上所述,将消息发送到推送服务后,消息将处于挂机状态,直到发生下列情况之一:
- 设备上线。
- 消息由于 TTL 而在队列上过期。
当推送服务传递消息时,浏览器会接收它,解密并在 Service Worker 中分发一个 push
事件。
这里最好的是,即使是你的网页没有打开,浏览器也可以执行你的 Service Worker。将会发生下面的事情:
- 推送消息到达解密它的浏览器
- 浏览器唤醒 Service Worker
-
push
事件被分发给 Service Worker
设置推送事件监听器的代码应该与用 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.'); } });
需要了解 Service Worker 的一点是,你没有 Service Worker 代码运行时长的控制权。浏览器决定何时将其唤醒以及何时终止它。
在 Service Workers 中,event.waitUntil(promise)
通知浏览器工作正在进行,直到 promise 确定为止,如果它想要完成该工作,它不应该终止 sercice worker。
这里是处理 push
事件的例子:
self.addEventListener('push', function(event) { var promise = self.registration.showNotification('Push notification!'); event.waitUntil(promise); });
调用 self.registration.showNotification()
会向用户发送一个通知,并返回一个 promise,只要消息展示了该 promise 就会触发 resolve。
showNotification(title, options)
方法可以在视觉上进行调整以适应你的需求。title
参数是一个 string
,而 options 是一个看起来像这样的对象:
{ "//": "Visual 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'>", "//": "Behavioural Options", "tag": "<String>", "data": "<Anything>", "requireInteraction": "<boolean>", "renotify": "<Boolean>", "silent": "<Boolean>", "//": "Both Visual & Behavioural Options", "actions": "<Array of Strings>", "//": "Information Option. No visual affect.", "timestamp": "<Long>" }
你可以在这里阅读到每个选项内容的更多细节 — developer.mozilla.org/en-US/docs/…。
推送通知是一种可以在有紧急、重要和时间敏感的信息需要与用户进行分享的情况下,吸引用户注意的绝好方式。
例如,我们在 SessionStack 计划利用推送通知来提醒用户,让他们知道自己的产品中何时发生崩溃、问题或异常。这会让用户立即知道出现了问题。然后他们可以利用我们的库所收集的数据(如 DOM 修改、用户交互、网络请求、未处理异常和调试信息),以视频的形式重现问题并查看最终发生在用户身上的一切事情。
原文发布时间为:2018年05月19日
原文作者:LeviDing
本文来源: 掘金 如需转载请联系原作者
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
用 ES6 构建新一代可复用 JS 模块
你是不是也在为可以使用ES6的新特性而兴奋,却不太确定应该从哪开始,或者如何开始?不止你一个人这样!我已经花了一年半的时间去解决这个幸福的难题。在这段时间里 JavaScript 工具链中有几个令人兴奋的突破。 这些突破让我们可以用ES6书写完全的JS模块,而不会为了一些基本的条件而妥协,比如testing,linting 和(最重要的)其他人可以轻易理解我们所写的代码。 在这篇文章中,我们集中精力在如何用ES6构建JS模块,并且无论你在你的网站或者app中使用CommonJS,AMD(asynchronous module definition)或者普通的网页script引入,这个模块都可以轻易被引用。 The Tools 在这个系列文章的第一部分和第二部分,我们来看一下这些卓越的工具们。在这篇文章中,我们详细说明如何编写,编译,打包代码;而在第二篇文章会集中在linting,formatting 和 testing(利用 JSCS,ESLint,mocha,Chai,Karma 和 Istanbul)。让我们来看看在这篇文章中涉及到的工具: Babel(刚刚度过了它的第一个生日)可...
- 下一篇
浏览器前端优化
优化全都是与速度和满意度有关。 从用户体验的角度,我们希望前端提供可以快速加载和执行的网页。 而从开发者体验的角度,我们希望前端是快速、简单而规范的。 这不仅会给我们带来快乐的用户和快乐的开发者,而且由于 Google 偏向于优化,SEO 排名也会显著提高。 如果你已经花费了大量时间来改善你网站的Google Pagespeed Insights分数,那么这将有助于揭示这一切实际上意味着什么,以及我们必须为优化前端所采取的大量策略。 背景 最近我的整个团队有机会花一些时间加快把我们提出的升级变为代码库,可能是用 React。这确实让我思考起了我们该如何创建前端。很快,我意识到浏览器将是我们的方法中的一个重要因素,同时也是我们知识中的大瓶颈。 方法 首先 我们不能控制浏览器或者改变它的行为方式,但是我们可以理解它的工作原理,这样就可以优化我们提供的负载。 幸运的是,浏览器行为的基础原理是相当稳定而且文档齐全的,并且在相当长一段时间内肯定不会发生显著变化。 所以这至少给了我们一个目标。 其次 另一方面,代码、技术栈、架构和模式是我们可以控制的东西。它们更灵活,变化的更快,并给我们这一边提供...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Red5直播服务器,属于Java语言的直播服务器