iOS 通知在用户活跃时可能失去推送权限

iOS 通知在被抑制时可能会失去推送权限。Discourse 的代码配置为可以选择性地抑制通知,当这种情况发生三次时,它将失去推送权限。

这是推送通知服务工作者的代码。

这段代码在第 178 行有一个严重错误。在那里,服务工作者会检查用户是否处于活动状态(即非空闲状态)。在这种情况下,推送事件会返回 false 而不显示通知。

(它还会检查 payload.hide_when_active,但事实证明 hide_when_active始终为 true,因此当用户处于活动状态时,此代码始终返回 false。)

Apple 禁止静默推送,在三次不显示通知的事件后撤销推送权限

这不符合 Apple 关于推送通知的规定。

https://webkit.org/blog/12945/meet-web-push/

功耗和隐私

WebKit 开源项目和 Apple 都将隐私视为一项基本人权。与 Web 平台的其他特权功能一样,请求推送订阅需要明确的用户手势。它还要求您将 userVisibleOnly 标志设置为 true,并通过在响应推送消息时始终显示通知来兑现这一承诺。

Web 推送 API 不是静默后台运行的邀请,因为这既会违背用户的信任,又会影响用户的电池寿命。

违反 userVisibleOnly 承诺将导致推送订阅被撤销。

(我的重点)

这在 Apple 的 WWDC 关于 Web 推送的视频中得到了更详细的解释,在 9:57 处:

https://developer.apple.com/videos/play/wwdc2022/10098/?time=596

请注意,我们明确表示我们承诺始终让推送对用户可见。虽然 JavaScript 推送 API 的标准可以选择性地允许在响应推送时进行静默 JavaScript 后台运行,但大多数浏览器不支持这一点。Safari 不支持这一点。

……然后在 13:35:

https://developer.apple.com/videos/play/wwdc2022/10098/?time=814

正如我在向您展示如何请求推送订阅的代码时提到的,您必须承诺推送将对用户可见。处理推送事件并不是让您的 JavaScript 进行静默后台运行的邀请。这样做既会违背用户的信任,又会影响用户的电池寿命。在处理推送事件时,您实际上必须将通知发布到通知中心。其他浏览器都有针对违反推送对用户可见承诺的对策,Safari 也是如此。在 macOS Ventura 的测试版中,在三次未能及时发布通知的推送事件后,您网站的推送订阅将被撤销。您需要再次经历权限工作流。

(我的重点)

Apple 建议立即显示通知,而不是在关闭通知后显示

Apple 推荐的代码如下,在 11:39 处:

https://developer.apple.com/videos/play/wwdc2022/10098/?time=699

self.addEventListener('push', (event) => {
    let pushMessageJSON = event.data.json();

    // 我们的服务器将显示通知所需的一切都放在
    // 我们的 JSON 数据中。
    event.waitUntil(self.registration.showNotification(pushMessageJSON.title, {
        body: pushMessageJSON.body,
        tag: pushMessageJSON.tag,
        actions: [{
            action: pushMessageJSON.actionURL,
            title: pushMessageJSON.actionTitle,
        }]
    }));
}

还记得我们订阅推送时,我们的 JavaScript 承诺它们将始终对用户可见吗?这意味着我们必须始终在响应每个推送时显示一个平台原生通知。最好在推送事件处理程序中尽早执行此操作。

Discourse 的代码没有遵循推荐的最佳实践。Discourse 的代码首先关闭所有通知,然后再显示通知。

Discourse 应该始终在响应推送事件时调用 showNotification,并且应该始终尽快执行此操作。

8 个赞

@Falco / @featheredtoast 对此有何看法?

移除关闭和空闲检查会产生什么影响?

总的来说,我对这个空闲检查一点也不确定,感觉它只会引起混淆。

最坏的情况是,如果我们要求在 Android 或 Desktop(非 Safari)上进行此操作,我们始终可以在此处添加一个条件,但我的感觉是,我们应该直接删除这里的代码。

@dfabulich 调试得太棒了 :hugs:

3 个赞

我注意到,只要是在调用 showNotification 之后 执行关闭操作,是可以接受的。

……但我真的不明白关闭通知有什么意义。IMO,不如让它们堆积在通知托盘里算了。

**注意:**此 API 不应仅用于在固定延迟后从屏幕上移除通知,因为此方法还会将通知从任何通知托盘中移除,阻止用户在最初显示通知后与其进行交互。此 API 的有效用途是移除不再相关的通知(例如,在消息应用中用户已在网页上阅读了通知,或者在音乐应用中下一首歌曲已在播放)。

3 个赞

根据我周末对这个问题的阅读,我们甚至不能在推送事件处理程序中使用 async/promises,必须尽快显示它,否则 Apple 将撤销你的通知权限。

这一切都与 @dfabulich 所说的相符。

我认为我们在这里过于“聪明”了,我们应该确实移除所有检查,直接显示通知。

6 个赞

非常感谢 @dfabulich 发现这一点!

3 个赞

好的,我有一个关于此的 PR。

@Falco / @featheredtoast 我们应该合并它吗?

如果我们必须跳过推送通知,我们必须在服务器上而不是在客户端上进行。折叠逻辑一直有些可疑,应用程序通常不会这样做。

2 个赞

是的,只在需要时推送通知确实更有意义。最终应该会抑制其他地方过多的通知,但我对在这里合并它感到满意 :+1:

关于折叠通知,虽然很可惜失去它,但……理想情况下,我们应该能够做到像其他应用程序那样,当用户浏览任何登录设备上的通知时自动将其解除,但这可能需要一些额外的润色。其意图与此相同,即绝不让陈旧的通知堆积如潮。同样,这没关系,但我们需要以后再讨论,具体取决于它带来的麻烦程度。

2 个赞