网站关闭时如何通过REST API触发Desktop notification?
嘿,这个问题戳中了网页生命周期的一个关键点——当用户关闭网站标签页时,页面里的所有JavaScript代码都会停止运行,所以你原来在页面内触发通知的逻辑自然就失效了。要实现网站关闭后仍能显示桌面通知,核心是要用 Service Worker,它是独立于页面的后台脚本,只要浏览器在运行,哪怕页面关了,它也能在后台处理事件并触发通知。
下面我一步步给你拆解实现方案,结合你现有的代码来调整:
1. 为什么页面脚本不行?
当用户关闭标签页时,浏览器会销毁页面的所有执行上下文,页面内的new Notification()代码根本没机会运行。而Service Worker是浏览器在后台独立运行的脚本,不受页面生命周期影响,刚好能解决这个问题。
2. 步骤一:修改前端代码,注册Service Worker并订阅Push服务
把你现有的代码更新成下面这样,加入Service Worker注册和Push订阅的逻辑:
var dnperm = document.getElementById('dnperm'); var dntrigger = document.getElementById('dntrigger'); // 新增:注册Service Worker if ('serviceWorker' in navigator && 'PushManager' in window) { window.addEventListener('load', async () => { try { const registration = await navigator.serviceWorker.register('/sw.js'); console.log('Service Worker 注册成功:', registration); } catch (error) { console.error('Service Worker 注册失败:', error); } }); } // 修改原权限请求逻辑为异步版本,增加Push订阅 dnperm.addEventListener('click', async function(e){ e.preventDefault(); if(!window.Notification || !('serviceWorker' in navigator) || !('PushManager' in window)){ alert('Sorry, Notifications or Push are not supported'); return; } // 请求通知权限 const notificationPerm = await Notification.requestPermission(); if (notificationPerm !== 'granted') { alert('Please allow notification permission'); return; } // 获取Service Worker注册实例,准备订阅Push服务 const registration = await navigator.serviceWorker.ready; // 替换成你的VAPID公钥(生成方法后面讲) const vapidPublicKey = '你的VAPID公钥字符串'; const convertedKey = urlBase64ToUint8Array(vapidPublicKey); // 订阅Push服务,拿到订阅对象 const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, // 要求所有推送都显示可见通知 applicationServerKey: convertedKey }); // 把订阅对象发送给后端保存,这样后端就能给这个用户推送消息 await fetch('/api/save-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) }); console.log('Push订阅已保存到后端'); }); // 新增:辅助函数,把VAPID公钥转换成Uint8Array格式 function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0))); } // 保留原有的页面内触发通知逻辑(用于页面打开时的测试) dntrigger.addEventListener('click',function(e){ var notify; e.preventDefault(); console.log(Notification.permission); if(Notification.permission === 'default'){ alert('please allow notification'); }else{ notify = new Notification('new message for you Nicoleta' ,{ body:'how are you ', icon:'assets/images/logo.jpg' }); notify.onclick = function(){ window.location = '?message='+this.tag; } } });
3. 步骤二:创建Service Worker核心文件(sw.js)
在你的网站根目录下新建sw.js文件,这个脚本负责监听后端推送的消息并显示通知:
// sw.js:Service Worker核心逻辑 self.addEventListener('push', (event) => { // 解析后端推送的消息数据,没有数据时用默认值 const payload = event.data?.json() || { title: 'New Notification', body: 'You have a new message', tag: 'default-tag' }; // 通知配置,和你原来的逻辑保持一致 const notificationOptions = { body: payload.body, icon: 'assets/images/logo.jpg', tag: payload.tag }; // 用waitUntil确保Service Worker在通知显示完成前不终止 event.waitUntil( self.registration.showNotification(payload.title, notificationOptions) ); }); // 可选:处理通知点击事件,点击后打开网站指定页面 self.addEventListener('notificationclick', (event) => { event.notification.close(); // 关闭通知 // 打开网站的消息页面 event.waitUntil( clients.openWindow('/?message=' + event.notification.tag) ); });
4. 步骤三:后端如何触发通知?
现在后端不能直接调用页面接口,而是需要用保存的Push订阅信息,通过浏览器的Push服务发送消息给Service Worker。这里需要生成VAPID密钥对来验证后端身份:
生成VAPID密钥对
用Node.js的web-push工具生成(先全局安装):
npm install -g web-push web-push generate-vapid-keys
生成后会得到公钥和私钥,公钥给前端代码用,私钥存在后端用来签名推送请求。
后端发送推送的示例(Node.js)
用web-push库发送消息:
const webpush = require('web-push'); // 配置VAPID密钥 const vapidKeys = { publicKey: '你的VAPID公钥', privateKey: '你的VAPID私钥' }; webpush.setVapidDetails( 'mailto:your-email@example.com', // 联系邮箱,用于浏览器识别推送来源 vapidKeys.publicKey, vapidKeys.privateKey ); // 从数据库取出之前保存的用户订阅对象 const userSubscription = /* 从数据库获取用户的订阅数据 */; // 发送推送消息,内容和你原来的通知保持一致 webpush.sendNotification(userSubscription, JSON.stringify({ title: 'new message for you Nicoleta', body: 'how are you ', tag: 'message-tag' })) .then(() => console.log('推送成功')) .catch(error => console.error('推送失败:', error));
关键注意事项
- HTTPS环境:除了localhost(开发环境),Service Worker和Push API必须在HTTPS网站上运行。
- Service Worker作用域:
sw.js放在根目录时,作用域是整个网站;如果放在子目录,需要在注册时指定作用域。 - 权限关联:用户授予通知权限后,Push权限会自动关联,但必须确保
userVisibleOnly设为true(要求所有推送都显示可见通知)。
这样一来,不管用户的网站页面是打开还是关闭,后端通过Push服务发送消息后,Service Worker都会在后台触发桌面通知,完全满足你的需求。
内容的提问来源于stack exchange,提问作者Nicoleta Wilskon
相关产品推荐
相关产品推荐

