PWA与Web Push集成:打造实时消息推送体验
PWA与Web Push集成打造实时消息推送体验前言大家好我是前端老炮儿今天咱们来聊聊PWA的另一个核心功能——Web Push推送通知。想象一下你的用户关闭了你的应用但仍然能收到你推送的重要消息这是一种多么棒的体验Web Push让这一切成为可能。什么是Web PushWeb Push是一种允许网站在用户浏览器关闭的情况下向用户发送通知的技术。它基于Service Worker和Push API实现。Web Push工作原理用户订阅 → 生成推送订阅对象 → 发送到服务器保存 ↓ 服务器触发推送 → Push Service → Service Worker → 显示通知实现Web Push的步骤1. 获取VAPID密钥首先需要生成VAPID密钥对用于标识你的应用。# 使用web-push工具生成密钥 npx web-push generate-vapid-keys生成的密钥类似这样Public Key: BEMw57Fd...(省略)...X7p98 Private Key: your_private_key_here2. 请求用户权限在客户端请求推送权限async function requestNotificationPermission() { if (!(Notification in window)) { console.error(浏览器不支持通知); return; } const permission await Notification.requestPermission(); if (permission granted) { console.log(用户允许通知); await subscribeToPush(); } else { console.log(用户拒绝通知); } }3. 订阅推送创建推送订阅async function subscribeToPush() { const registration await navigator.serviceWorker.ready; const subscription await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey) }); // 将订阅对象发送到服务器保存 await saveSubscription(subscription); } function urlBase64ToUint8Array(base64String) { const padding .repeat((4 - base64String.length % 4) % 4); const base64 (base64String padding) .replace(/\-/g, ) .replace(/_/g, /); const rawData window.atob(base64); const outputArray new Uint8Array(rawData.length); for (let i 0; i rawData.length; i) { outputArray[i] rawData.charCodeAt(i); } return outputArray; } async function saveSubscription(subscription) { await fetch(/api/subscribe, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(subscription) }); }4. 在Service Worker中监听推送事件self.addEventListener(push, (event) { const data event.data?.json() || { title: 新消息, body: 您有一条新消息 }; const options { body: data.body, icon: /icons/icon-192x192.png, badge: /icons/badge-72x72.png, data: { url: data.url || / }, actions: [ { action: view, title: 查看 }, { action: dismiss, title: 忽略 } ] }; event.waitUntil( self.registration.showNotification(data.title, options) ); });5. 处理通知点击事件self.addEventListener(notificationclick, (event) { event.notification.close(); // 根据action处理不同的点击事件 if (event.action view) { // 打开指定的URL event.waitUntil( clients.openWindow(event.notification.data.url) ); } });服务器端实现使用Node.js发送推送const webpush require(web-push); // 配置VAPID密钥 webpush.setVapidDetails( mailto:youremail.com, process.env.VAPID_PUBLIC_KEY, process.env.VAPID_PRIVATE_KEY ); // 发送推送通知 async function sendPushNotification(subscription, payload) { try { await webpush.sendNotification(subscription, JSON.stringify(payload)); console.log(推送发送成功); } catch (error) { console.error(推送发送失败:, error); // 如果订阅过期删除订阅 if (error.statusCode 410) { await deleteSubscription(subscription.endpoint); } } } // 示例发送通知 const payload { title: 新订单提醒, body: 您有一个新的订单请及时处理, url: /orders }; await sendPushNotification(userSubscription, payload);订阅管理// 保存订阅 async function saveSubscription(subscription) { await db.collection(subscriptions).updateOne( { endpoint: subscription.endpoint }, { $set: subscription }, { upsert: true } ); } // 删除订阅 async function deleteSubscription(endpoint) { await db.collection(subscriptions).deleteOne({ endpoint }); } // 获取所有订阅 async function getAllSubscriptions() { const subscriptions await db.collection(subscriptions).find().toArray(); return subscriptions; }高级功能1. 自定义通知样式const options { body: 这是通知内容, icon: /icons/icon-192x192.png, badge: /icons/badge-72x72.png, image: /images/notification-image.jpg, sound: /sounds/notification.mp3, vibrate: [100, 50, 100], // 震动模式 data: { url: /notifications, id: 12345 }, tag: order-notification, // 相同tag的通知会替换 renotify: true, // 如果替换通知是否重新通知 requireInteraction: true, // 是否需要用户交互才能关闭 actions: [ { action: accept, title: 接受, icon: /icons/accept.png }, { action: reject, title: 拒绝, icon: /icons/reject.png } ] };2. 静默推送有时候你不想显示通知只是想在后台更新数据self.addEventListener(push, (event) { const data event.data?.json(); if (data?.silent) { // 静默推送不显示通知只更新数据 event.waitUntil( updateCache(data) ); } else { // 显示通知 event.waitUntil( self.registration.showNotification(data.title, data.options) ); } });3. 定时推送结合Background Sync实现定时推送// 注册同步事件 async function registerSync() { if (SyncManager in window) { const registration await navigator.serviceWorker.ready; await registration.sync.register(periodic-sync); } } // Service Worker中处理同步 self.addEventListener(sync, (event) { if (event.tag periodic-sync) { event.waitUntil( fetchAndUpdateData() ); } });最佳实践1. 合理请求权限不要一进入页面就请求权限应该在用户有明确意图时再请求// 不好的做法一进入页面就请求 window.addEventListener(load, () { requestNotificationPermission(); }); // 好的做法用户点击按钮后请求 document.getElementById(enable-notifications).addEventListener(click, () { requestNotificationPermission(); });2. 提供通知设置入口让用户可以随时管理通知权限function showNotificationSettings() { if (Notification in window Notification.permission ! default) { // 打开浏览器通知设置 window.open(chrome://settings/content/notifications); } }3. 处理订阅过期推送订阅可能会过期需要在服务器端处理async function sendPushNotification(subscription, payload) { try { await webpush.sendNotification(subscription, JSON.stringify(payload)); } catch (error) { if (error.statusCode 410 || error.statusCode 404) { // 订阅已过期或不存在删除订阅 await deleteSubscription(subscription.endpoint); } } }4. 限制推送频率不要频繁推送以免打扰用户// 记录上次推送时间 async function canSendNotification(userId) { const lastPush await getLastPushTime(userId); const now Date.now(); // 至少间隔1小时才能再次推送 return now - lastPush 3600000; }浏览器兼容性Web Push目前支持以下浏览器浏览器支持版本Chrome50Firefox44Edge17Safari16.4常见问题Q1: 用户收不到推送通知原因用户未授权通知权限Service Worker未正确注册订阅对象已过期服务器端VAPID密钥配置错误解决方案检查通知权限状态确保Service Worker正确注册处理订阅过期情况核对VAPID密钥配置Q2: 推送通知显示延迟原因网络延迟Push Service负载高浏览器处于休眠状态解决方案使用可靠的Push Service对于重要通知可以结合其他方式如WebSocketQ3: 如何测试推送通知测试方法使用浏览器开发者工具的Push功能使用web-push库的sendNotification方法在localhost环境下测试需要HTTPS总结Web Push是PWA的重要功能之一它可以提高用户留存率即使用户关闭应用也能收到消息增强用户参与度实时推送重要信息提升用户体验个性化的通知内容实现Web Push的关键步骤生成VAPID密钥请求用户权限创建推送订阅在Service Worker中处理推送事件服务器端发送推送希望今天的分享能帮助你实现强大的推送功能如果你有任何问题或建议欢迎在评论区留言关注我每天分享前端干货让我们一起成长