一、WebPush简介
1.1 WebPush是什么
WebPush 是一种基于 Web 的推送通知技术,允许网站和 Web 应用在用户浏览器后台向用户发送实时通知,即使用户没有打开网站页面,也可以通过推送通知向用户展示重要信息,如新闻更新、消息提醒、日程提醒等。

WebPush 为 Web 应用提供了类似原生应用的推送通知功能,极大地增强了 Web 应用的交互性和用户体验。
WebPush 技术基于浏览器提供的 Push API 和推送服务提供商(如 Google 的 FCM )的支持,通过 Push API,网站可以向用户的设备注册并请求发送推送消息。
如果想要使用 WebPush 推送通知的功能,需要确保系统开启了 Chrome 浏览器的通知权限。
1.2 WebPush的发展阶段
- 早期探索阶段:在 WebPush 技术出现之前,浏览器并没有一个标准的方式来实现推送通知,一些浏览器尝试通过自己的方式实现类似功能,但这些尝试并没有形成统一的标准。
- 规范制定阶段:Mozilla 在 WebPush 技术的发展中起到了关键作用,其云服务(MCS)实现了规范中的 Push Service------autopush,这为 WebPush 技术的标准化奠定了基础。
- 主流浏览器支持阶段:随着规范的逐渐成熟,主流浏览器开始支持这一技术,Chrome 浏览器和 Firefox 浏览器较早地支持了 WebPush 技术。微软的 Edge 浏览器也在2018年4月支持了 Push 标准。苹果的 Safari 浏览器虽然在移动设备上拥有广泛的用户基础,但直到2024年才宣布将在 macOS 和 iOS 平台上支持 WebPush 标准。
- 技术完善与推广阶段:目前,WebPush 技术已经相对成熟,并且得到了越来越多的开发者和企业的关注和应用。随着技术的不断完善,WebPush有望在未来得到更广泛的应用,进一步推动Web应用的发展。
1.3 国内外发展现状
在国内,WebPush技术的应用还处于起步阶段,整体应用范围相对较窄,主要原因包括以下几点:
- 浏览器支持不全面:虽然 Chrome 和 Firefox 等主流浏览器在国内有较高的使用率,但 Safari 浏览器的用户数量也不容忽视。由于 Safari 浏览器,webPush 的支持相对较晚,这在一定程度上限制了 WebPush 技术在国内的广泛应用。
- 应用场景有限:目前,WebPush 技术在国内的应用场景主要集中在新闻资讯、电商促销等领域。在其他领域,如在线教育、医疗健康等,WebPush 技术的应用还相对较少。
- 用户习惯不够普及:在国内短信和 APP 推送已经成为用户获取信息的主要方式,相比之下,WebPush 相对较新,用户对于这种推送方式的接受度和使用习惯还不够普及。
在国外,WebPush 技术的应用相对较为广泛,以下是国外 WebPush 技术发展的一些特点:
- 主流浏览器的支持:国外主流浏览器对 WebPush 技术的支持相对较为全面,这为 WebPush 技术的广泛应用提供了良好的基础。
- 应用场景丰富:除了常见的新闻资讯、电商促销等领域,WebPush 技术还被应用于社交网络、在线游戏、金融理财等多个领域。
- 技术研究与创新:国外的一些研究机构和企业也在不断对 WebPush 技术进行研究和创新。例如,一些研究机构正在探索如何进一步提高 WebPush 技术的安全性和可靠性。
本文主要讲述在 Chrome 插件环境中实现 WebPush 推送通知,WebPush 一般用于网站,但是在浏览器插件中也是可以使用的,当然处理方式会有一些区别,不过整体的流程基本一致。
二、WebPush推送的完整流程
在 Chrome 插件 中实现 WebPush 推送的完整流程如下:

Chrome 插件首先注册 Service Worker,向 FCM 服务器订阅通知,生成唯一标识该客户端的订阅信息,服务端需要存储订阅信息并监听 Push 事件,当触发推送通知时,需要将通知发到 FCM 服务器,再由 FCM 服务器下发给对应的客户端,最终客户端(Chrome 插件)收到通知并展示。
订阅信息 subscription 的数据结构如下:
js
{
"endpoint": "https://fcm.googleapis.com/fcm/send/dijFAgj1NmE:APA91bE25y2d6IZ-RW79_clh_JRsL9HocllI2nwrFbMGVEbQP82WztIQiRpmmN8vN1MTBzmxTpuiMKG4mlZX1z4_v4In2A6_7gt5XzI4zWHfRZ-AGYC2P9BWxl0nQAmcEa5ba6KhLMJw",
"keys": {
"p256dh": "BEGaXR66ZcdbH61E4fxeVznaX17T422ZNAo6rt5V9NbW2w29YGZdUmBRhrqFHSRBs6C3Uv1rPgzLYYiO8qT5co0=",
"auth": "NC3EjxrOYkfLtiVhJpWmfw=="
}
}
其中关键是 endpoint,是一个指向 FCM 服务器的地址,用于唯一标识当前订阅的客户端。
整体的流程,其实与网站实现 WebPush 推送是类似的,只需把 Chrome 插件换成 浏览器 环境。
三、实现推送通知的两种方式
3.1、Firebase Cloud Messaging (FCM)
如果选择通过这种方式来实现 WebPush 推送通知,需要配置 Firebase Server Key。FCM 是一个强大的推送服务,它允许开发者向用户的设备发送消息,在使用 FCM 时,需要在 Firebase 控制台中创建项目并获取相关的配置信息,其中就包括 Firebase Server Key。这个 Server Key 用于在服务器端向 FCM 发送请求,以触发推送通知。
接入 firebasejs:juejin.cn/post/744999...
3.2、Web Push API 和 VAPID
如果直接使用 Web Push API 并采用 VAPID 来进行身份验证,那么就不需要 Firebase Server Key。在这种情况下,开发者需要生成 VAPID 密钥对,并在订阅推送服务时将公钥提供给客户端;然后,服务器端可以使用 VAPID 私钥来对推送消息进行签名,并通过 Web Push 协议将消息发送到浏览器的推送服务。
这里采用第二种方式进行实现,下面进行具体的代码编写,在 Chrome 插件中实现一个完整的 WebPush 推送通知功能。
三、Chrome插件项目搭建
首先需要搭建一个简单的 Chrome 插件项目,大概的目录结构如下:

配置文件 manifest.json 中需要做一些处理:
1、开启通知权限:"permissions": ["notifications"],这对用户是无感知的,只要配置了那么默认就会开启通知权限;
不同于网站,网站的通知权限是需要用户手动允许/拒绝的:

2、配置 Service Worker:在 background.service_worker 中指定 sw 文件即可,当安装插件时,会自动注册;
js
// manifest.json
{
"manifest_version": 3,
"name": "Chrome插件推送",
"version": "1.0",
"description": "在Chrome插件实现推送功能",
......
"background": {
"service_worker": "sw.js"
}
}
如果是在网站中注册 Service Worker,那么需要手动写代码完成注册:
js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
Chrome 插件的注册方式相对来说,会比较简单方便。
需要注意的是:在 Chrome 插件的环境下,sw.js 文件是放在项目下,而不是托管到服务器中;这是因为 Manifest V3 禁止加载远程代码,Service Worker 必须完全包含在扩展包内,更新需通过 Chrome 应用商店发布新版本。
另外,V2 版本不支持 Service Worker。
chrome://extensions/ 开启开发者模式导入扩展:

Chrome 插件项目已经准备好了,接着需要搭建一个用于发送通知的服务端,这里采用 Node 来实现。
五、Node搭建推送服务端
5.1 生成密钥
使用第三方库 web-push 生成公钥以及私钥,公钥需要发送给客户端(Chrome插件)
js
// 服务端:
const express = require('express');
const webpush = require('web-push');
const app = express();
const port = 3000;
const vapidKeys = webpush.generateVAPIDKeys();
webpush.setVapidDetails('mailto:juejin.cn', vapidKeys.publicKey, vapidKeys.privateKey);
// 获取公钥
app.get('/get-public-key', (req, res) => {
res.status(200).json({ publicKey: vapidKeys.publicKey });
});
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
// 前端:
function getPublicKey() {
fetch('http://localhost:3000/get-public-key')
.then(res => res.json())
.then(data => {
subscribe(data.publicKey); // 拿到后进行订阅
})
}
5.2 订阅通知
客户端在拿到公钥后,在 Service Worker 中调用 self.registration.pushManager.subscribe() 订阅通知,此时需要对 publicKey 进行编码处理:
js
/**
* 将 base64 字符串转换为 Uint8Array
* @param {string} base64String - base64 编码的字符串
* @returns {Uint8Array} 转换后的 Uint8Array
*/
function base64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
// 使用 TextEncoder 替代 window.atob
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
/**
* 订阅推送服务
* @param {string} publicKey - VAPID 公钥
*/
function subscribe(publicKey) {
// 检查是否已经订阅过
self.registration.pushManager.getSubscription()
.then((subReg) => {
if (!subReg) {
self.registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array(publicKey)
})
.then(function(subscription) {
postSubscription(subscription);
})
} else {
postSubscription(subReg);
}
})
}
订阅完成,需要将订阅信息 subscription 发送给服务端进行存储:
js
/**
* 将订阅信息发送到服务器
* @param {PushSubscription} subscription - 推送订阅对象
*/
function postSubscription(subscription) {
fetch('http://localhost:3000/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ subscription })
})
.then(response => {
if (!response.ok) {
throw new Error('订阅信息发送失败');
}
})
}
服务端存储客户端的订阅信息:
js
// 存储订阅信息
const subscriptions = [];
// 接收订阅信息
app.post('/subscribe', bodyParser.json(), (req, res) => {
console.log('req.body', req.body);
const subscription = req.body.subscription;
subscriptions.push(subscription);
res.status(201).json({ message: 'Subscription saved' });
});
5.3 发送通知
服务端提供用于发送通知的接口:
js
// 发送推送消息
app.post('/send-notification', bodyParser.json(), (req, res) => {
const payload = JSON.stringify({
title: '新消息',
body: '您有一条新的通知,请查看。'
});
const promises = subscriptions.map(subscription => {
return webpush.sendNotification(subscription, payload);
});
Promise.all(promises)
.then(() => {
res.status(200).json({ message: 'Notifications sent' });
})
.catch(err => {
console.error(err);
res.status(500).json({ error: 'Failed to send notifications' });
});
});
使用 curl 的方式进行调用:
js
curl -X POST -H "Content-Type: application/json" -d "{}" http://localhost:3000/send-notification
此时会向 FCM 服务器发送通知消息,FCM 会将通知下发到对应的客户端。
5.4 监听消息并展示
在 Chrome 插件的 Service Worker 文件中,监听 Push 事件并展示通知:
js
// 监听推送事件
self.addEventListener('push', function(event) {
event.waitUntil(
self.registration.showNotification('Hello', {
body: 'This is a test notification',
})
);
});
六、自建推送通道
以上的推送通知过程,中间需要经过 FCM 服务器,这种属于系统通道 ,如果不想使用 FCM 服务器,可以采取自建推送通道的方式,完成推送通知的下发功能。
WebSocket 是一种比较好的实现方式,客户端通过与后端建立长连接,监听接收来自服务端的消息,服务端与客户端直接对话,中间不需要经过 FCM 服务器;具体的实现这里不再展开,有兴趣的小伙伴可以去研究下。
七、最后
WebPush 技术的未来发展将呈现出多元化、智能化和个性化的显著特点,随着技术的持续演进以及用户需求的日益多样化,WebPush 不仅能够为用户提供更加丰富和精准的信息推送服务,还将成为用户与网站及应用之间互动和沟通的关键方式之一。