Web 推送通知 - 解释!
大家好,Dev.to 的各位 🙌🏻 这是我在这里的第一篇帖子。快来吧!🎢
在本文中,我们将针对 3 个主要浏览器平台来了解 Web 中的推送通知 - Chrome、Firefox 和 Safari。
从功能上看,Chrome 和 Firefox 非常相似。Safari 比较奇怪,因为受到 Apple 的限制(我们知道 Apple 的整体安全标准是怎样的)😜
让我们了解 Chrome/Firefox 和 Safari 中通知工作的基本流程。
Chrome/Firefox 通知:
这里需要理解几个术语:
- Service Worker:一个用于网站的工作线程,用于持续监听后台事件,例如“已收到推送通知”。推送 Service Worker 的示例如下:
/* eslint-disable no-unused-vars */ | |
/* eslint-disable no-undef */ | |
/* eslint-disable no-restricted-globals */ | |
const regex = /{{\s*([^}]+)\s*}}/g; | |
var _pushVaribales = ""; | |
function interpolate(messageData) { | |
return function interpolate(o) { | |
return messageData.replace(regex, function (a, b) { | |
var r = o[b]; | |
return typeof r === "string" || typeof r === "number" ? r : a; | |
}); | |
}; | |
} | |
function createTemplateMessage(messageData) { | |
if (Object.keys(_pushVaribales).length > 0) { | |
var message = interpolate(messageData)(_pushVaribales); | |
return message; | |
} else { | |
return messageData; | |
} | |
} | |
function displayNotification(event) { | |
var messageJson = event.data.text(); | |
messageJson = JSON.parse(messageJson); | |
var title = messageJson.title ? messageJson.title : "New message"; | |
var imageUrl = messageJson.iconUrl ? messageJson.iconUrl : "images/icon.png"; | |
var bodyAlert = messageJson.alert ? messageJson.alert : "Example message"; | |
var payloadData = messageJson.payload | |
? messageJson.payload | |
: "Example message"; | |
let messageTemp; | |
if ((messageTemp = regex.exec(bodyAlert)) !== null) { | |
bodyAlert = createTemplateMessage(bodyAlert); | |
} | |
self.registration.showNotification(title, { | |
body: bodyAlert, | |
icon: imageUrl, | |
data: payloadData, | |
}); | |
return Promise.resolve(); | |
} | |
function triggerSeenEvent(strMsg) { | |
send_message_to_all_clients("msgEventSeen:" + strMsg); | |
} | |
function triggerOpenEvent(strMsg) { | |
send_message_to_all_clients("msgEventOpen:" + strMsg); | |
} | |
function onPushNotificationReceived(event) { | |
console.log("Push notification received : ", event); | |
if (event.data) { | |
console.log("Event data is : ", event.data.text()); | |
} | |
event.waitUntil( | |
displayNotification(event).then(() => triggerSeenEvent(event.data.text())) | |
); | |
} | |
self.addEventListener("push", onPushNotificationReceived); | |
function send_message_to_client(client, msg) { | |
return new Promise(function (resolve, reject) { | |
var msg_chan = new MessageChannel(); | |
msg_chan.port1.onmessage = function (event) { | |
if (event.data.error) { | |
reject(event.data.error); | |
} else { | |
resolve(event.data); | |
} | |
}; | |
client.postMessage(msg, [msg_chan.port2]); | |
}); | |
} | |
function send_message_to_all_clients(msg) { | |
clients.matchAll().then((clients) => { | |
clients.forEach((client) => { | |
send_message_to_client(client, msg); | |
}); | |
}); | |
} | |
self.addEventListener("install", function (event) { | |
self.skipWaiting(); | |
console.log("Installed Service Worker : ", event); | |
}); | |
self.addEventListener("message", function (event) { | |
replyPort = event.ports[0]; | |
_pushVaribales = event.data; | |
}); | |
self.addEventListener("activate", function (event) { | |
console.log("Activated Service Worker : ", event); | |
event.waitUntil(self.clients.claim()); | |
}); | |
self.addEventListener("notificationclick", function (event) { | |
console.log( | |
"Notification clicked with tag" + | |
event.notification.tag + | |
" and data " + | |
event.notification.data | |
); | |
let nidjson = event.notification.data; | |
event.notification.close(); | |
event.waitUntil(triggerOpenEvent(nidjson)); | |
}); | |
self.addEventListener("pushsubscriptionchange", function () { | |
console.log("Push Subscription change"); | |
send_message_to_all_clients("updateRegistration:"); | |
}); |
- 订阅令牌:每个浏览器都有唯一的令牌,稍后会用于定位通知。此令牌有有效期,因此您可能需要在一段时间后申请新的令牌。令牌示例如下:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/ckjznRm-rKg:APA91bEs6ZqaooqA5CWnd9LXI1VwpfmQzUFdS_mpNYl8qaSF4WwvCe1h2QVrTYDINEPC0oZIQlYpnYBXx2BiPkORIA4U27pze1JkS0dz7o1kfzWkc5_md5uYnZUvwpyFyvPkoeIlYtMj",
"userAuth": "gpq2UFvdWkyomEWdbJ6n3w==",
"userPublicKey": "BNUOw0iXWjfGaIb7Q77jQaWAra72ga1Y1QHZ+g3TuBuwSr0gS0GMtP2BS/lYNGMwku8sJ4e3esChQHigVKFHnsY="
}
如果您是新手,实现起来可能有点复杂。不用担心,我创建了一个辅助插件,可以让您的操作更轻松——这是easy-web-notifications的链接。
只需npm install easy-web-notifications
按照 README 中的说明进行操作即可,从请求用户通知权限到获取订阅令牌。
Safari 通知:
Safari 中没有 Service Worker 的概念。相反,Safari 会自行处理通知的传递。
但奇怪的是,你无法直接从 Safari 请求通知权限。不行不行不行……
理解词汇:
- 网站推送 ID:这是您的 Web 应用的标识符。如果您的 Web 应用托管为,
https://www.mywebapp.com
则您的网站推送 ID 将为web.com.mywebapp
。这只是惯例,并非规则。请注意,您无法在本地环境中测试 Safari 通知,它需要在启用 HTTPS 的情况下发布。 - 服务器 URL:这是您的后端服务器的 URL。它需要 HTTPS 协议,并且需要来自可信机构的有效证书(自签名证书无效)。
- 注册参数:这是您需要传递给后端服务器以注册设备的额外参数。
- 推送包:查看有关构建推送包的官方文档。
正如您所注意到的,Safari 通知更加严格,并且强制执行更好的安全性,但当然是以实施难度为代价的。
这是否意味着 Chrome 和 Firefox 不安全?
事实并非如此,Chrome 和 Firefox 现在正在强制使用 VAPID 密钥。
等等,VAPID 现在是什么?
VAPID 是自愿应用服务器识别 (VSA) 的缩写。简而言之,它是一种验证浏览器客户端与后端服务器之间通信的方式。它是一组密钥(公钥和私钥)。
正如您所猜测的,公钥和私钥都保存在服务器端,而只有公钥与客户端共享。
当客户端尝试订阅 pushManager 时,它也会携带此 VAPID_PUBLIC_KEY。因此,生成的令牌只有在存在匹配 VAPID 密钥对的服务器端才可用。
如何生成 VAPID 密钥?使用什么库来发送推送通知?
Google 官方文档建议使用这些库在后端实现 Web Push。同样的库也可用于生成 VAPID 密钥。
在 Node.js 中,生成密钥非常简单。
只需执行以下命令npm i -g web-push
,然后执行以下命令,web-push generate-vapid-keys
您应该会看到类似以下内容:
=======================================
Public Key:
BAbNusaoI2KTIogWMlnpZ8nL93ne8GSHXTOxlqxG19Py8V9m9bIarlzIN8PErAsy1NUEahfyLdDuPV7OwFdJYYA
Private Key:
cun3E7GjoCvfmD1NCcIpjYtG4TmZq-0awKtBlPOX3Cc
=======================================
这些库几乎适用于所有主流后端平台,并且维护良好。我推荐使用npm 插件,因为它使用起来非常方便。
最后,什么是 Web Push Mediator?
我们不会直接向浏览器客户端发送通知,而是构建包含所有必需数据(最重要的是令牌)的通知,并将其发送给浏览器平台处理器——中介器!该平台会根据令牌将通知路由到正确的客户端。
此外,我们无法看到通知在所有平台上立即传递,因为这完全取决于中介器。通常情况下,我发现 Chrome 会及时发出通知。
我知道这篇文章很长,但我已经尽力解决了我在开发客户端和服务器端推送通知功能时遇到的每一个问题。希望这篇文章能对你有所帮助!再见!👋🏻
文章来源:https://dev.to/yashsoni/web-push-notifications-explained-141i