Push API 让网络应用从用户代理接收来自服务器发送的消息,无论网络应用是否运行或者在线
网页或者浏览器不在线的时候,推送消息无法被推送到客户端浏览器,此时推送消息就会被 FCM 服务器保存起来,等到网页或者浏览器上线的时候,FCM 服务器才会推送消息到网页或者浏览器
生成服务器公秘钥对
可以使用 web-push 库来生成服务器公秘钥对。
1 2 3 4 5
   | const webpush = require('web-push');
  const vapidKeys = webpush.generateVAPIDKeys();
  const { publicKey, privateKey } = vapidKeys
  | 
 
其中私钥放在服务器保存,公钥用于注册推送订阅。
创建消息推送服务
可以利用 ServiceWorkerRegistration 接口的 pushManager 属性获取到当前 ServiceWorker 对应的 PushManager 接口实例。
PushManager 接口的 subscribe() 方法用于订阅一个消息推送服务。
其接收一组可选的配置项,userVisibleOnly 可选参数指定返回的推送是否只用于创建对用户可见的通知(不指定 true 会在一些浏览器中报错),applicationServerKey 可选参数指定服务的公钥(某些浏览器中是必须的参数)。
返回一个 Promise 的 PushSubscription 接口实例,代表当前推送消息。
在当前 ServiceWorker 没有创建消息推送服务时,新的消息推送会被创建。
1 2 3 4 5 6 7 8 9
   | window.navigator.serviceWorker.ready.then((registration) => {      registration.pushManager.subscribe({     userVisibleOnly: true,     applicationServerKey: '',   }).then((subscription) => {     console.log(subscription)   }) })
  | 
 
1 2 3 4 5 6
   | self.registration.pushManager.subscribe({   userVisibleOnly: true,   applicationServerKey: '', }).then((subscription) => {   console.log(subscription) })
  | 
 
获取推送消息服务
PushManager 接口的 getSubscription() 方法用于获取已经订阅的消息推送服务。
返回一个 Promise 的 PushSubscription 接口实例,代表当前已订阅的消息推送服务,否则返回一个 Promise 的 null。
1 2 3 4 5 6 7 8
   | window.navigator.serviceWorker.ready.then((registration) => {      registration.pushManager.getSubscription().then((subscription) => {     if (subscription != null) {       console.log(subscription)     }   }) })
  | 
 
1 2 3 4 5
   | self.registration.pushManager.getSubscription().then((subscription) => {   if (subscription != null) {     console.log(subscription)   } })
  | 
 
PushSubscription 接口代表了订阅的消息推送。
endpoint 属性代表与消息推送联系的 endpoint。
expirationTime 属性代表订阅的消息推送的到期时间。
options 属性代表创建消息推送时的选项。
getKey() 方法返回一个 ArrayBuffer,代表客户端公钥的密钥,可以将其发送到服务器用于加密推送消息数据。支持传入一个代表用于生成客户端密钥的加密方法的参数,可以是 p256dh 或 auth。
toJSON() 方法标准化地转换消息推送的信息,目前仅包含 endpoint 参数。
unsubscribe() 方法取消消息推送的订阅,返回一个 Promise 的布尔值,代表是否成功取消了消息订阅。
发送推送消息
可以使用 web-push 在服务端来发送推送消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | import express from 'express' import webpush from 'web-push'
  const vapidKeys = {     "publicKey": "BHXrxJPYpQSwGMwcN-HprCaU_Po9POIUvqWFLFq9UUNHP5SNJKxk_Io59y8_twMTOuB5SbpbcPBwHFo2kBUj7vQ",      "privateKey": "Yhd4XF08Efh8HNF_8RDJ9VL6pF-Gos-3KOmgyMEUSf8"  }
 
  webpush.setGCMAPIKey('<Your GCM API Key Here>')
 
  webpush.setVapidDetails(     'mailto:example@yourdomain.org',     vapidKeys.publicKey,     vapidKeys.privateKey )
  const app = express()
  app.get('/push', (req, res) => {          const pushSubscription = {       "endpoint": "https://fcm.googleapis.com/fcm/send/cSAH1Q7Fa6s:APA91bEgYeKNXMSO1rcGAOPzt3L9fMhyjL-zSPV5JfiKwgqtbx_Q4de_8plEY_QViLnhfe6-0fUgdo7Z3Gqpml3zIBSfO6IISDYdF9kzL2h_dbZ_FE_YKbKOG70gMG_A74xwK1vsocCv",        "keys": {         "p256dh": "BAqZaMLZn_rtYeR7WsBLqBWG7uMiOGRyCx2uhOqm0ZaJwDdQac-ubAyRRdLXJVZDOrNe-B3mCTy3g0vHCkeyYyo",          "auth": "fxDt8RtB92KHpQM7HetBUw"        }     }
      webpush.sendNotification(pushSubscription, 'Hello world')       .then(result => {         res.send(result);       }) })
  app.listen(1701, () => {     console.log('Server start success at http://localhost:1701'); })
   | 
 
若使用 firebase 架构可使用 firebase 来实现消息推送。
监听消息推送
ServiceWorkerGlobalScope 接口的 push 事件在每次收到一条推送消息时触发。
返回一个 PushEvent 事件。
1 2 3 4 5
   | self.addEventListener('push', (e) => {   console.log(e)
    self.registration.showNotification(e.data?.json().title ?? 'New Notification') })
  | 
 
PushEvent 接口继承自 ExtendableEvent 接口。
其 data 属性代表该推送消息的内容,是一个 PushMessageData 实例。
PushMessageData 接口包括多种处理推送的消息的方法,类似于 fetch API 中的方法,但允许被调用多次。
其 arrayBuffer() 方法、blob() 方法、json() 方法、text() 方法分别将结果转换成 ArrayBuffer、Blob、JSON 解析结果、字符串。
推送的消息能够自动被加解密,无需做额外的处理。
消息推送权限
PushManager 接口的 permissionState() 方法用于获取当前的请求消息推送权限。
参数同 subscribe 方法的参数。
返回一个 Promise 的 'prompt'、'denied'、'granted' 的字符串枚举。
1 2 3 4 5 6 7 8 9 10 11 12 13
   | self.registration.pushManager.permissionState({   userVisibleOnly: true,   applicationServerKey: '', }).then((state) => {   switch(state) {     case "denied":       break     case "granted":       break     case "prompt":       break   } })
  | 
 
其他
PushManager 接口的 supportedContentEncodings 静态属性返回一组消息推送支持的加密方式。
ServiceWorkerGlobalScope 接口的 pushsubscriptionchange 事件在更新订阅的消息推送时触发(可能原因包括消息推送服务刷新、消息推送服务失效等)。
权限 API
该 API 调用需要用户授予 push 权限,可以调用 Permission.query() 方法或 PushManager.permissionState() 检查用户是否已授予了该权限
示例
类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | type PushEncryptionKeyName = "auth" | "p256dh";
  interface PushSubscriptionJSON {   endpoint?: string;   expirationTime?: EpochTimeStamp | null;   keys?: Record<string, string>; }
  interface PushSubscriptionOptionsInit {   applicationServerKey?: BufferSource | string | null;   userVisibleOnly?: boolean; }
  interface PushEvent extends ExtendableEvent {   readonly data: PushMessageData | null; }
  interface PushManager {   getSubscription(): Promise<PushSubscription | null>;   permissionState(options?: PushSubscriptionOptionsInit): Promise<PermissionState>;   subscribe(options?: PushSubscriptionOptionsInit): Promise<PushSubscription>; }
  interface PushMessageData {   arrayBuffer(): ArrayBuffer;   blob(): Blob;   json(): any;   text(): string; }
  interface PushSubscription {   readonly endpoint: string;   readonly expirationTime: EpochTimeStamp | null;   readonly options: PushSubscriptionOptions;   getKey(name: PushEncryptionKeyName): ArrayBuffer | null;   toJSON(): PushSubscriptionJSON;   unsubscribe(): Promise<boolean>; }
  interface PushSubscriptionOptions {   readonly applicationServerKey: ArrayBuffer | null;   readonly userVisibleOnly: boolean; }
  interface ServiceWorkerRegistration extends EventTarget {   readonly pushManager: PushManager; }
  interface ServiceWorkerGlobalScope extends WorkerGlobalScope {   onpush: ((this: ServiceWorkerGlobalScope, ev: PushEvent) => any) | null;   onpushsubscriptionchange: ((this: ServiceWorkerGlobalScope, ev: Event) => any) | null; }
   | 
 
链接