前言:
经常做海外项目的一定听过或者知道webPush也就是消息推送,最近这一阵子在做PWA免不了有些需求在探索这方面,以下是调研的一些结果案例希望能帮助到大家。好了废话不多说我们直接开始!
基本概念
** Web Push是一种用于在网页浏览器中发送实时通知的技术。它允许网站向用户发送推送通知,而无需用户在网站上打开或保持浏览器标签打开。Web Push利用浏览器的推送服务,通过请求和订阅的方式将通知推送到用户设备上。**
一般收到消息的状态是这样的,如下图:
准备环境
- MacBook
- Node v16.19.0
- http-server 全局指令
- Web-push 消息通知
- FCM(Firebase Cloud Messaging)
Client消息推送
Notification API是一个浏览器提供的接口,用于向用户显示通知消息。通过使用该 API,开发者可以向用户的操作系统或浏览器发送推送式的通知,下面让我们简单创建一个实例了解一下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>消息测试demo</title>
</head>
<body>
<button>Click</button>
<script>
const button = document.querySelector("button");
button.addEventListener("click", () => {
Notification.requestPermission().then((perm) => {
const notification = new Notification("我是测试");
notification.addEventListener("error", (e) => {
alert(" 请尝试用户打开权限设置 ");
});
if (perm === "granted") {
const notification = new Notification("我是测试");
notification.addEventListener("error", (e) => {
alert(" Error: ");
});
}
});
});
</script>
</body>
</html>
执行全局的http-server的命令
shell
http-server .
// 访问一下 http://localhost:8080/index.html
Tips: 详细代码可以点击 这里
Server && Client 配合使用
简单的消息通知客户端可以控制,那么如何进行服务器分发和在浏览器未开启的时候进行推送呢?我们需要借助一个工具「web-push」下面让我们简单的来看一下!
以express为例
初始化依赖
shell
yarn add body-parser express web-push -S
利用web-push生成秘钥,消息的类型需要遵守web推送消息的协议
javscript
npx web-push generate-vapid-keys
// or 执行任意一行都可以
npx web-push generate-vapid-keys --json
结果如下:
有了初始的秘钥我们需要Node 启动一个服务器来接口客户端的请求,比如:
javscript
// server.js
const express = require('express');
const webpush = require('web-push');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, "client")))
// 实际秘钥要进行加密存储,同时进行接口下发到客户端
const publicVapidKey = "xxxxx";
const privateVapidKey = "xxxxx"; // 跟客户端publicVapidKey保持一致。
webpush.setVapidDetails("mailto:test@test.com", publicVapidKey, privateVapidKey);
app.post('/subscribe', (req, res) => {
const subscription = req.body;
res.status(201).json({});
const payload = JSON.stringify({ title: "测试消息", body: "hello啊 老铁" });
webpush.sendNotification(subscription, payload).catch(console.log);
})
const PORT = 5001;
app.listen(PORT, () => {
console.log(`http://localhost:${PORT}/`);
});
客户端请求
javscript
// clent.js
const publicVapidKey = "xxxx"; // 跟server的privateVapidKey 保持一致
if ("serviceWorker" in navigator) {
registerServiceWorker().catch(console.log);
}
async function registerServiceWorker() {
const register = await navigator.serviceWorker.register("./worker.js", {
scope: "/",
});
// 详细: https://github.com/web-push-libs/web-push#using-vapid-key-for-applicationserverkey
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicVapidKey,
});
await fetch("/subscribe", {
method: "POST",
body: JSON.stringify(subscription),
headers: {
"Content-Type": "application/json",
},
});
}
通过Service Worker来监听服务器推送的消息
javscript
// worker.js
self.addEventListener('push', function(e) {
const data = e.data.json();
self.registration.showNotification(
data.title,
{
body: data.body,
}
);
})
效果如下:
Tips: 详细代码可以点击 这里
FCM(Firebase Cloud Messaging)
因为做海外的关系我们通常都使用google的生态比如说是Firebase这里面集成了很多的轮子,有感兴趣的小伙伴可以研究一下这里就不在赘述了,本次的重点就是FCM(Firebase Cloud Messaging)
Firebase Cloud Messaging (FCM) :是一种跨平台消息传递解决方案,可供您可靠地传递消息,且无需任何费用。
交互界面如下:
投放的状态如下、等待投放完成即可
如果第一次的话 可能需要创建一下
其实我们了解了node服务器发送的消息后,原理也是大同小异详细可以参考文档
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="container">
<div>hello 这是测试消息</div></div>
<div class="message" style="min-height: 80px"></div>
<div>发送消息的token:</div>
</div>
<script src="https://www.gstatic.com/firebasejs/9.14.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.14.0/firebase-messaging-compat.js"></script>
<script>
const firebaseConfig = {
apiKey: "xxxxx",
authDomain: "xxxxx",
projectId: "xxxxx",
storageBucket: "xxxxx",
messagingSenderId: "xxxxx",
appId: "xxxxx",
measurementId: "xxxxx"
};
const app = firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
messaging
.getToken({
vapidKey: `xxxxx`,
})
.then((currentToken) => {
if (currentToken) {
document.querySelector("body").append(currentToken);
sendTokenToServer(currentToken);
} else {
setTokenSentToServer(false);
}
})
.catch((err) => {
console.log(err);
// if error
setTokenSentToServer(false);
});
messaging.onMessage((payload) => {
console.log("Message received ", payload);
const messagesElement = document.querySelector(".message");
const dataHeaderElement = document.createElement("h5");
const dataElement = document.createElement("pre");
dataElement.style = "overflow-x: hidden;";
dataHeaderElement.textContent = "Message Received:";
dataElement.textContent = JSON.stringify(payload, null, 2);
messagesElement.appendChild(dataHeaderElement);
messagesElement.appendChild(dataElement);
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
const notification = new Notification(payload.notification.title, {
body: payload.notification.body,
});
notification.onclick = (event) => {
event.preventDefault();
window.open(payload.data.link, "_blank");
};
}
});
});
function sendTokenToServer(currentToken) {
if (!isTokenSentToServer()) {
setTokenSentToServer(true);
} else {
console.log("Token already available in the server");
}
}
function isTokenSentToServer() {
return window.localStorage.getItem("sentToServer") === "1";
}
function setTokenSentToServer(sent) {
window.localStorage.setItem("sentToServer", sent ? "1" : "0");
}
</script>
</body>
</html>
Service Worker 部分
javscript
// firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/9.14.0/firebase-app-compat.js')
importScripts('https://www.gstatic.com/firebasejs/9.14.0/firebase-messaging-compat.js')
const firebaseConfig = {
apiKey: "xxxxx",
authDomain: "xxxxx",
projectId: "xxxxx",
storageBucket: "xxxxx",
messagingSenderId: "xxxxx",
appId: "xxxxx",
measurementId: "xxxxx",
};
const app = firebase.initializeApp(firebaseConfig)
const messaging = firebase.messaging()
实现的效果如下:
Tips: 详细代码可以点击 这里
收不到消息?
以下是我在本地测试的时候遇到的问题,如果大家遇到其他的问题也欢迎补充~
Macbook没开启消息通知
Mac 电脑开发中需要在系统中开启消息通知:【系统偏好设置】=> 【通知】=> 【chrome 开启通知】
未开启chrome的实验性功能
- 在Chrome浏览器中访问地址:
chrome://flags
。 - 搜索栏中搜索:
notifications
,找到Enable system notifications
选项,将其选项值改为Disabled
,重启浏览器,问题解决。
最后
其实我在整理文章的时候同时也在思考一些问题,下面做了陈列
为什么国内做的少?
Web push 消息推送在海外相对较多的原因主要有以下几点:
- 平台支持:海外的浏览器和操作系统广泛支持 Web push 技术,包括 Chrome、Firefox、Safari 等主流浏览器,以及 Android 和 iOS 等主流操作系统。相比之下,国内浏览器和操作系统对于 Web push 的支持相对较少,限制了国内开发者开展相关业务。
- 开放程度:海外环境中的开发者更容易获取到 Web push 技术相关的开发文档、API 接口等资源,而国内的 Web push 相关技术多数由国内厂商自有的推送服务提供,对于外部开发者或者独立开发团队来说,参与门槛相对较高。
- 用户审核限制:根据国内法规和政策,App 需要完成国家相关部门的审核才能够向用户发送推送消息,这增加了国内 Web push 业务的运营成本和法规合规的要求,相比之下,海外环境中更灵活。
- 市场需求与文化差异:海外市场对于个性化推送、即时通知等需求较高,而且海外用户更接受优质推送服务。而国内用户普遍倾向于使用即时通信工具、社交媒体等方式获取信息和推送服务,对于 Web push 的需求相对较少。
综上所述,海外在 Web push 消息推送领域的发展较为成熟,而国内因为各种技术、法规和市场的限制,导致其规模相对较小。
当浏览器同时开启了HTTP缓存策略和Service Worker时,缓存加载的策略顺序如下:
- 首先,浏览器会检查Service Worker中的缓存策略。Service Worker是一个独立于网页的脚本,可以控制页面的网络请求和响应。如果Service Worker中的缓存中有匹配的资源,浏览器将直接从缓存获取资源,避免发起网络请求。
- 如果Service Worker中没有匹配的缓存资源,则浏览器会检查HTTP缓存。HTTP缓存是基于浏览器和服务器之间的通信协议,在HTTP响应头中使用Cache-Control、Expires等字段来定义资源的缓存行为。浏览器会根据这些字段来判断是否需要重新从服务器请求资源。
- 如果HTTP缓存也没有匹配的资源,浏览器会发起网络请求,从服务器获取最新的资源。
需要注意的是,Service Worker的缓存策略优先级高于HTTP缓存策略。因此,如果Service Worker中有匹配的缓存资源,即使HTTP缓存设置了不被缓存,浏览器也会优先使用Service Worker中的缓存。
参考资料:
- Service worker overview
- 推送通知概览
- 网易云课堂 Service Worker 运用与实践
- 「PWA 与 ServiceWorker」构建渐进式Web 应用
- 通知在 Chrome 和 MacOS 上的其他程序中不起作用
- Chrome插件调用notifications接口无响应
- 如何更改 Chrome 通知设置
- chrome官方打开消息通知
结语
以上就是本次web push 的全部的内容,如果能帮助你的话记得点赞收藏哦~ 再见了 老baby们