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

推送通知订阅:用户设备端关闭后的状态同步与恢复问题

推送订阅状态同步与旧订阅清理方案

针对你遇到的Chrome中禁用再启用通知后订阅丢失、状态不同步、旧订阅无法关联的问题,整理几个可落地的实操方案:

一、解决后台权限状态不更新的问题

后台运行时Notification.permission不会自动更新,不能只靠应用启动时的检查,得主动监听状态变化:

  • 监听应用前后台切换:通过visibilitychange事件,当应用切回前台时立刻检查权限和订阅状态
  • 监听权限变更事件:Chrome 84+支持notificationpermissionchange事件,权限发生变化时会直接触发,不受应用前后台状态影响
    // 监听通知权限变更
    navigator.permissions.query({ name: 'notifications' })
      .then(status => {
        status.addEventListener('change', () => {
          syncSubscriptionStatus();
        });
      });
    
    // 监听应用前后台切换
    document.addEventListener('visibilitychange', () => {
      if (!document.hidden) {
        syncSubscriptionStatus();
      }
    });
    

二、处理Chrome禁用再启用后的订阅重建

Chrome中禁用通知会直接删除现有订阅,因此重新启用后pushManager.getSubscription()返回null是预期行为,此时直接按以下逻辑处理:

  1. 检查权限为granted但无订阅时,立即创建新订阅
  2. 新订阅创建完成后同步到后端,同时清理该设备对应的旧订阅

三、关联设备与旧订阅的关键思路

由于权限被移除后无法获取旧订阅对象,要实现「每个设备一个订阅」,需给设备添加唯一标识:

  • 首次创建订阅时,生成一个设备唯一ID(用crypto.randomUUID()即可),存储到本地(localStorage或indexedDB)
  • 将设备ID、订阅信息、用户标识(email/customerCode)一起发送到后端
  • 后续重建订阅时,携带同一个设备ID,后端根据该ID找到用户对应的旧订阅,直接删除或标记为active: false
    // 获取或生成设备唯一ID
    let deviceId = localStorage.getItem('push_device_id');
    if (!deviceId) {
      deviceId = crypto.randomUUID();
      localStorage.setItem('push_device_id', deviceId);
    }
    
    // 创建新订阅并同步到后端
    const registration = await navigator.serviceWorker.ready;
    const subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: '你的VAPID公钥'
    });
    
    await fetch('/api/push-subscriptions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        deviceId,
        email: '当前用户邮箱',
        customerCode: '当前用户编码',
        subscription: JSON.stringify(subscription),
        endpoint: subscription.endpoint,
        active: true
      })
    });
    

四、后端配合逻辑

结合你提供的接口结构,后端需要补充以下逻辑:

  1. 新增查询能力:支持通过deviceId + email/customerCode定位用户的旧订阅
  2. 处理新订阅请求时:
    • 先查询该设备ID对应的旧订阅记录
    • 若存在旧订阅,调用delete_push_subscription接口删除,或更新其active字段为false
    • 保存新的订阅记录
  3. 前端开关切换时,直接根据设备ID找到对应订阅,更新active字段

五、绕过pushsubscriptionchange事件的限制

既然Android Chrome和桌面Chrome不支持该事件,采用以下替代方案:

  • 应用每次启动、切前台时,强制检查订阅状态:若权限为granted但无订阅,立即重建并同步到后端
  • 后端定期清理失效订阅:推送通知时若收到410(订阅失效)状态码,自动将该订阅标记为active: false或删除

六、前端开关同步逻辑

用户通过前端开关切换通知状态时:

  • 打开开关:先检查权限,无权限则发起权限请求;有权限但无订阅则创建新订阅,同步后端将对应记录的active设为true
  • 关闭开关:若本地存在订阅对象,先调用subscription.unsubscribe(),再请求后端将对应设备的订阅active设为false

内容的提问来源于stack exchange,提问作者Michal Kucharski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 01:51:20