认识
一、认识
在一个完备的 Web
推送系统中,服务端通过 web-push
模块生成全局性的 VAPID
密钥对并调用setVapidDetails
配置包含 JWT
签名及必要 HTTP
头的推送请求,自动实现基于 ECDH
、HKDF
和AES-GCM
的 payload
加密,从而将安全、符合 Web
推送协议的通知发送到所有订阅者,而客户端则先通过请求通知权限和注册 Service Worker
获取激活状态,再利用 PushManager
结合 Uint8Array
格式的VAPID
公钥生成订阅对象,并在推送事件中通过 waitUntil
延长生命周期、调用 showNotification
展示通知和 postMessage
实现跨上下文通信,共同构建了一套安全、透明且高效的实时消息传递体系。
web-push
认识: web-push
模块是实现服务端 Web
推送的关键工具,它将复杂的推送协议细节、加密流程以及身份验证机制封装到简单易用的 API
中。通过自动加密消息、内置 VAPID
支持和兼容旧版推送服务,web-push
帮助开发者构建安全、可靠的推送系统,使得跨浏览器的消息推送变得更加便捷和高效。1. web-push
简化消息发送, 开发者只需调用简单的 API
,传入订阅信息和消息内容,无需手动构造复杂的 HTTP
请求或处理加密细节; 2. 自动加密, 模块内部自动完成消息 payload
的加密流程,确保所有数据在传输过程中都是安全的; 3. 内置 VAPID
支持, 只需提供 VAPID
密钥对,web-push
就能生成对应的 JWT
,并将服务器身份信息嵌入到推送请求中,简化了身份验证过程; 4. 兼容性处理, 对于依赖于 GCM
(现 FCM
)的浏览器,web-push
提供了向后兼容的支持,确保推送消息在不同浏览器环境中都能正确传递。
使得服务端向浏览器推送消息通知工作流:
一、VAPID
签名:
-
服务端: 服务启动后, 调用
webPush.generateVAPIDKeys()
来生成全局的VAPID.publicKey
和VAPID.privateKey
。调用webPush.setVapidDetails
来设置全局的VAPID
详细信息, 包括:Subject
, 通常是一个mailto:
邮箱地址或URL
,用于标服务器或应用;VAPID
公钥, 用于客户端加密订阅数据,并在推送请求中附带身份信息;VAPID
私钥, 用于对推送请求进行签名,证明推送请求的合法性。所有用户的推送通知都会使用这同一组VAPID
证书进行身份验证和加密处理。并将生成的VAPID.publicKey
下发到客户端。 -
客户端: 接收到
VAPID.publicKey
, 格式为base64
, 将base64
格式转化为Unit8Array
格式, 以备后续使用。
二、客户端工作流:
-
订阅通知: 客户端通过
Notification.requestPermission
向用户请求通知权限, 用户同意接收通知后, 浏览器通过Service Worker
与PushManager
生成订阅对象,并将其发送到服务端。首先, 客户端需要注册Service Worker
。然后, 客户端通过navigator.serviceWorker
获取到ServiceWorkerContainer
, 用于提供ServiceWorker
的注册、移除、升级和通信的访问。ServiceWorkerContainer
有一个ready
属性, 提供了一种将代码执行延迟到Service Worker
处于活动状态的方法。它返回一个永远不会拒绝的Promise
,并且无限期地等待,直到与当前页面关联的ServiceWorkerRegistration
具有活动的worker
。满足该条件后,它将使用ServiceWorkerRegistration
解析, 最终会返回一个ServiceWorkerRegistration
实例对象。ServiceWorkerRegistration
有一个PushManager
对象实例, 支持订阅,获取活动订阅和访问推送权限状态。PushManager
调用subscribe
并传入之前存储的VAPID
公钥, 来订阅通知, 并且将返回的PushSubscription
数据发送给服务端。 -
接收推送: 当服务端发送推送消息时,浏览器会唤醒已注册的
Service Worker
,处理推送事件并显示通知或传递消息给页面。Service Worker
中注册了push
事件和notificationclick
。当服务端发送推送消息后,push
事件会收到该消息, 在event.waitUntil
回调中, 调用self.registration.showNotification
来显示通知弹窗, 并且这时候可以通过clients.matchAll
获取我们的客户端, 通过postMessage
发送一条消息, 当我们客户端的主线程收到消息之后, 就可以做刷新之类的操作了。用户点击通知弹窗会触发notificationclick
, 这时候可以做相应的处理, 比如打开页面。event.waitUtil
: 接受一个Promise
, 通知浏览器, 在这个Promise
解决之前,请不要将当前Service Worker
实例终止。由于Service Worker
是事件驱动的,waitUntil
通过延长事件的生命周期来保护异步操作,确保诸如资源缓存、消息处理和后台同步等任务能够完整执行。
三、服务端工作流:
-
接收订阅信息: 服务端收到客户端发送的订阅对象,并存储用于后续推送。
-
构造推送请求: 使用
web-push
模块构造HTTP
请求,按照Web
推送协议的要求:-
消息加密: 如果需要传输
payload
,web-push
会自动使用ECDH
密钥交换、HKDF
派生以及AES-GCM
加密算法对数据进行加密,确保消息在传输过程中不被窃取或篡改。 -
VAPID
签名: 模块利用服务端提供的VAPID
密钥对生成JWT
,并将其附加到请求中,证明服务器的合法性。 -
构造
HTTP
请求: 包括必要的头信息(例如TTL
、Content-Encoding
、Authorization
等),确保推送服务器能够正确解析和转发消息。
-
-
发送消息: 推送请求被发送到用户订阅中指定的
endpoint
,推送服务(如FCM
、Mozilla
的推送服务)将通过webPush.sendNotification
消息传递到目标浏览器。
二、语法
const webpush = require('web-push');
const vapidKeys = webpush.generateVAPIDKeys();
webpush.setGCMAPIKey('<Your GCM API Key Here>');
webpush.setVapidDetails(
'mailto:example@yourdomain.org',
vapidKeys.publicKey,
vapidKeys.privateKey
);
const pushSubscription = {
endpoint: '.....',
keys: {
auth: '.....',
p256dh: '.....'
}
};
webpush.sendNotification(pushSubscription, 'Your Push Payload Text');