You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

网站关闭时如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:25:07