通知 · 概念
iOS 与 iPadOS 上的 Web Push(16.4+)
发布于 更新于
iOS 与 iPadOS 16.4(2023-03)为添加到主屏幕的 Web App 加入了 Web Push:与桌面端相同的 Push API、Notifications API 与 Service Worker 组合,经 Apple Push Notification service 投递,并且只有在用户安装应用并点按一个请求权限的控件之后才可用。在 iPhone 的 Safari 标签页里 Notification 接口并不存在,所以未安装的站点连「询问」都做不到。
WebKit 把整个特性绑定在安装上。用户从分享菜单选择「添加到主屏幕」且 manifest 的 display 成员为 standalone 或 fullscreen(或 minimal-ui,任何非默认值)时,站点成为主屏幕 Web App;没有这个 manifest 值,图标只是一个在 Safari 中打开的书签,而 Safari 里没有推送(Web Push for Web Apps on iOS and iPadOS,webkit.org)。自 16.4 起第三方浏览器也可以提供同样的分享菜单项,生成的应用无论由哪个浏览器添加都以 Web App 方式打开。
Safari 标签页暴露了什么
Section titled “Safari 标签页暴露了什么”browser-compat-data 把 iOS 上的 Notification 记为 16.4、部分支持,并附两条备注:除非页面是 manifest 带非默认 display 的主屏幕 Web App,否则接口未定义;通知只能由 Service Worker 发出(api.Notification 的 safari_ios)。两条都有代码后果。标签页里的 Notification.requestPermission() 抛出的是 ReferenceError,不是被拒绝的 Promise;new Notification() 在该平台上完全没有可用路径,registration.showNotification() 是显示通知的唯一方式。
权限、订阅与投递
Section titled “权限、订阅与投递”在已安装的应用内,权限请求必须跟随直接的用户交互,例如点按订阅按钮;随后 iOS 显示系统通知提示,用户在「设置 > 通知」里按 Web App 管理结果,与原生应用一样。pushManager.subscribe() 要求 userVisibleOnly: true 和 VAPID applicationServerKey;返回的 endpoint 位于 *.push.apple.com 主机上,按白名单放行推送端点的服务器必须允许它。WebKit 会撤销 push 事件没有产生可见通知的订阅(Meet Web Push,webkit.org),所以吞掉消息或在 showNotification() 之前失败的处理函数付出的代价是整个订阅,而不是一条提醒。
通知与「专注模式」集成,manifest id 与用户所取名称相同的 Web App 在多台设备之间同步专注设置。Badging API(navigator.setAppBadge())在同一版本为主屏幕 Web App 发布,通知权限授予后即显示角标。
Declarative Web Push(18.4+)
Section titled “Declarative Web Push(18.4+)”iOS 与 iPadOS 18.4 为主屏幕 Web App 增加了第二种形态,Safari 18.5 把它带到 macOS:window.pushManager(BCD api.Window.pushManager:Safari 18.4)让页面无需 Service Worker 即可订阅,而符合声明式 JSON 格式("web_push": 8030 加一个 notification 对象)的推送消息由浏览器直接显示,不运行任何 JavaScript。存在 Service Worker 时它仍可修改通知;若其处理函数失败,声明式消息作为兜底显示,所以上述撤销规则在这条路径上不会生效(Meet Declarative Web Push,webkit.org)。
macOS Ventura 上的 Safari 16 最先发布 Web Push(2022-10),通过 webpushd 守护进程投递,Safari 无需运行。iOS 与 iPadOS 16.4 随后跟进,仅限主屏幕 Web App,本页的 compat 表同时跟踪两者。Android WebView 和其他应用内的 iOS WebView 没有推送;Android 上基于 Chromium 的浏览器在任何标签页里都有。
示例在 iOS 上运行于已安装的应用内,在其他平台运行于任意标签页;第一个示例就是区分两者的守卫。
把未安装的 iPhone 访客引导到安装指引
Section titled “把未安装的 iPhone 访客引导到安装指引”'Notification' in window 这一测试在 iOS 上就是安装检查:标签页里为 false,主屏幕应用里为 true。它在其他平台上也是正确的第一步,所以不需要任何平台嗅探。
async function enablePush(registration, applicationServerKey) { if (!('Notification' in window)) { showAddToHomeScreenGuidance(); // iOS 标签页或应用内嵌网页视图:先安装 return null; } const permission = await Notification.requestPermission(); if (permission !== 'granted') return null; return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey });}从点按处理函数里调用 enablePush();在 iOS 上,直接用户交互之外的调用不弹提示直接兑现 "denied"。
在 push 处理函数中兑现 userVisibleOnly 的承诺
Section titled “在 push 处理函数中兑现 userVisibleOnly 的承诺”每个 push 事件都必须以 showNotification() 结束,包括错误路径,否则 WebKit 撤销订阅。一条通用的兜底通知比什么都不显示更安全。
self.addEventListener('push', (event) => { event.waitUntil((async () => { let payload = { title: '有新内容', body: '' }; try { payload = event.data.json(); } catch { // 载荷格式错误:仍然要显示点什么,否则订阅会被撤销 } await self.registration.showNotification(payload.title, { body: payload.body }); })());});这里的 try/catch 在 iOS 上比别处更重要:一个发送了错误格式 body 的服务端 bug,否则会悄悄让每一位 iPhone 用户退订。
存在声明式管理器时优先使用它
Section titled “存在声明式管理器时优先使用它”探测你将要使用的管理器,而不是探测 PushManager 接口。window.pushManager 只在声明式路径上存在;Service Worker 注册上的 pushManager 需要活跃的 worker。
async function getPushManager() { if ('pushManager' in window) return window.pushManager; // Declarative Web Push,Safari 18.4+ if (!('serviceWorker' in navigator)) return null; // 完全没有推送:只能用应用内消息 const registration = await navigator.serviceWorker.getRegistration(); return registration?.active ? registration.pushManager : null;}没有任何注册时 getRegistration() 以 undefined 兑现;navigator.serviceWorker.ready 在这种情况下会永远等待,这正是探测避开它的原因。
- Web Push for Web Apps on iOS and iPadOS(webkit.org)
- Meet Declarative Web Push(webkit.org)
- Push API(w3.org)
- Web Push 与 PushManager.subscribe()
- iOS 添加到主屏幕
- Notification.requestPermission()
- Badging API
规范
| 规范 | 状态 |
|---|---|
| Web Push(网页推送) | W3C |
- 图例
- 支持
- 部分支持
- 需开启标志
- 不支持
- 未知
| 浏览器 / 平台 | 支持 | 版本 | 置信度 | 来源 | 备注 |
|---|---|---|---|---|---|
| Chrome (Android) | 支持 | 50 | 中 | 来源 | — |
| Chrome (Desktop) | 支持 | 50 | 中 | 来源 | — |
| Edge (Desktop) | 支持 | 17 | 中 | 来源 | — |
| Safari (iOS) | 部分支持 | 16.4 | 中 | 来源 | 1 |
| Safari (macOS) | 支持 | 16 | 中 | 来源 | — |
| Firefox (Desktop) | 支持 | 44 | 中 | 来源 | — |
| Samsung Internet | 支持 | 5.0 | 中 | 来源 | — |
- 仅限已安装到主屏幕的 Web 应用;需要在用户手势下授权,并通过 APNs 投递 Web Push。
生态与商业政策
| 主体 | 类型 | 场景 | 状态 | 赞助 | 备注 |
|---|---|---|---|---|---|
| Apple Push (APNs) | delivery_policy | iOS | 部分支持 | 否 | iOS 上的 Web Push 要求用户先把应用添加到主屏幕。 |
在线试用
在 OpenPWA 演示应用中运行此能力: /demo/#push