iOS PWA 中的推送通知订阅因 SW 未控制应用而静默失败

你好 :waving_hand:

我们在 iOS PWA 设置推送通知时遇到了一些奇怪的行为(或者可能是一个 bug……)。界面显示推送通知订阅已设置,但后台实际上没有任何操作发生,用户将永远无法收到任何推送通知。

(tl;dr:我想我知道如何修复它,并可以提供 PR)

复现步骤 :footprints:

问题在于在 iOS 上安装 PWA 后立即启用推送通知。

  1. 在 iOS 的 Mobile Safari 中打开你的 Discourse 实例
  2. 打开分享菜单并选择“添加到主屏幕”
  3. 在主屏幕上打开新的 PWA
  4. 你会看到一个横幅,询问你是否要启用推送通知
  5. 点击“启用通知”链接
    1. 你会看到系统对话框 “xyz”想要向你发送通知 - 点击允许。
  6. 导航到你的通知偏好设置,你会看到 实时通知 未启用
    1. 在服务器端检查,你也找不到该用户的任何新 PushSubscription 记录。

预期行为 :books:

在第 5 步之后,你应该已经收到一条通知,提示通知已启用。数据库中应该已经为你的当前用户创建了一条新的 PushSubscription 记录,并且他们在 PWA 中接收 实时通知 的设置应该已启用。

问题 :bug:

在刚启动的 PWA 中,服务 Worker 已安装并激活,但尚未控制页面。因此,lib/push-notifications.js 中的 isPushNotificationsSupported() 检查仍会返回 false,因为 navigator.serviceWorker.controller 返回 null。以下是关键的两行代码:

navigator.serviceWorker.controller &&
navigator.serviceWorker.controller.state === "activated"

这导致界面让用户以为一切正常,但他们实际上永远收不到任何推送通知。

在首次加载时,PWA 处于未受控状态,即使用户已授予权限,subscribe() 调用也从未执行……:bug:

如果用户知道如何操作,他们可以自行修复此问题:

  1. 完全关闭 PWA(将其从后台划掉……)
  2. 重新打开 PWA
  3. 进入通知偏好设置
  4. 再次启用 实时通知
  5. → 这次应该会创建一条新的 PushSubscription 记录,通知设置成功。

潜在修复方案 :adhesive_bandage:

我通过注册另一个服务 Worker 来绕过此问题,该 Worker 在安装/激活时调用 skipWaiting()clients.claim()。这样做可以立即接管对 PWA 的控制权,订阅得以成功执行,并创建相应的 PushSubscription 记录。

我已将此作为“热修复”部署到我的某个自定义插件中,这样我就可以在不深入修改 Discourse 核心代码的情况下修复用户的问题……

只需进行两处更改:

# plugin.rb
register_service_worker "push-notification-setup.js"
// assets/push-notification-setup.js
self.addEventListener("install", function () {
  self.skipWaiting();
});
		
		
self.addEventListener("activate", function (event) {	
  event.waitUntil(self.clients.claim());
});

不过,我认为这个修复应该直接集成到 Discourse 核心中。目前这样做,插件会不必要地接管对 PWA 的控制。我可以提供一个 PR,相应地调整 Discourse 核心中的检查逻辑,使订阅功能真正生效。我在这里等待反馈,也欢迎大家就此话题发表意见。

1 个赞

这是针对 Discourse 核心的修复 PR → FIX: Allow push notification subscriptions on first launch of iOS PWA · Pull Request #39885 · discourse/discourse

它解决了此处描述的问题,即订阅静默失败且无法到达服务器。但为了确保绝对安全,目前仍缺少的是:在服务进程控制应用之前,首次收到的推送通知将无法在应用内部进行路由,因此需要用户重启应用。

我也可以在现有 PR 的基础上添加这一功能。但我还不能完全确定该修复方案是否符合预期。

1 个赞