跳转到内容

iOS 与 Safari 的 Web Push(16.4+)

发布于 更新于

一句话: Web Push 是 Push API、Notifications API 与 Service Worker 三项 W3C 标准 协同工作的能力,让站点在未被打开时也能通知用户;WebKit 在 iOS 与 iPadOS 16.4 上为 已添加到主屏幕的 Web 应用加入了该能力——权限请求必须由用户的直接交互触发,且订阅必须 承诺展示可见通知。自 iOS 与 iPadOS 18.4 起还有第二种形态——Declarative Web Push (声明式 Web Push),它在无需已安装 Service Worker 的情况下即可订阅并展示通知。

  • 基于标准的推送。 WebKit 实现了与 macOS 上相同的 W3C 标准,因此只要应用是按标准编写的 ——使用特性检测而非浏览器检测——就能自动在 iPhone 与 iPad 上生效。
  • iOS/iPadOS 需要主屏幕 Web 应用。 已添加到主屏幕 的 Web 应用可以请求接收推送通知的 权限;这正是 WebKit 为其加入推送的场景。
  • 通知与其他应用的通知一致。 它们出现在锁定屏幕、通知中心以及配对的 Apple Watch 上, 用户可在「通知」设置中按 Web 应用逐个管理。
  • Focus 与 Badging 同期到来。 主屏幕 Web 应用的通知与「专注模式」集成;16.4 同时加入了 Badging API,Web 应用可以设置图标角标数字。
  • 由 APNs 负责投递。 iOS 与 iPadOS 上的 Web Push 使用与原生推送相同的 Apple 推送通知服务。 你无需加入 Apple Developer Program;但如果你管控服务器的推送端点,请放行 *.push.apple.com 的 URL。

在 iPhone 与 iPad 上,iOS/iPadOS 16.4 是下限:该能力随这一版本到来,面向已添加到主屏幕的 Web 应用。桌面端的时间线更早,且没有安装这一步——WebKit 的 iOS/iPadOS 16.4 文章把 macOS 上的版本记为 macOS Ventura 上的 Safari 16.1(更早的公告写的是 Safari 16);其实现依赖系统守护进程 (webpushd),因此即便 Safari 未在运行,推送也能送达。

  • 图例
  • 支持
  • 部分支持
  • 需开启标志
  • 不支持
  • 未知
浏览器 / 平台支持版本置信度来源备注
Chrome (Android)支持50中来源—
Chrome (Desktop)支持50中来源—
Edge (Desktop)支持17中来源—
Safari (iOS)部分支持16.4中来源1
Safari (macOS)支持16中来源—
Firefox (Desktop)支持44中来源—
Samsung Internet支持5.0中来源—
  1. 仅限已安装到主屏幕的 Web 应用;需要在用户手势下授权,并通过 APNs 投递 Web Push。

生态与商业政策

主体类型场景状态赞助备注
Apple Push (APNs)delivery_policyiOS部分支持否iOS 上的 Web Push 要求用户先把应用添加到主屏幕。

源数据: /compatibility/web-push.json · 全球使用占比: 89 % (StatCounter 2026-05)

来源: 规范 · MDN · 最近核验 2026-06-24 · 置信度: 中 (由来源计算)

「16.4、仅限主屏幕」在代码里长什么样

Section titled “「16.4、仅限主屏幕」在代码里长什么样”

MDN 的 browser-compat-data 把 Notification 接口的 iOS/iPadOS 条目记为 16.4、部分支持, 它附上的两条注记正是这道安装门槛的具体形态:

  • 除非页面是已保存到主屏幕的 Web 应用,否则该接口是 undefined,而且该应用的清单必须有一个 非默认的 display 值。所以在 iOS 上这不是一个你能从中恢复的「权限被拒绝」状态——在普通 Safari 标签页里这个符号根本不存在,触碰构造函数会抛出 ReferenceError。
  • 在 iOS 与 iPadOS 上,通知只能从 Service Worker 发送:请使用 ServiceWorkerRegistration.showNotification(),而不是 new Notification()。

这两条注记解释了为什么一套在桌面版 Safari 上可用的推送集成,在 iPhone 上会显得完全不存在: 清单的 display 值承担着关键作用,而页面作用域的 new Notification() 在该平台上根本没有 受支持的路径。

以下是 WebKit 在 16.4 中推出的、基于 Service Worker 的路径。18.4 新增的无需 Service Worker 的替代方案见下方「Declarative Web Push(18.4+)」一节。

  1. 先安装。 用户从分享菜单把 Web 应用添加到主屏幕。16.4 起第三方浏览器也可以提供 「添加到主屏幕」,且只要清单把 display 设为 standalone 或 fullscreen,无论由哪个 浏览器添加,打开时都以 Web 应用形式运行。
  2. 由直接的用户交互发起请求。 在已安装的 Web 应用内调用 Notification.requestPermission(),且必须响应真实交互——WebKit 将其描述为点按 Web 应用 提供的「订阅」按钮。随后 iOS 或 iPadOS 会弹出系统提示。
  3. 使用 VAPID 订阅。 调用 pushManager.subscribe(),传入 userVisibleOnly: true 与你的 应用服务器密钥,并把返回的 PushSubscription(endpoint 以及 p256dh、auth 密钥) 持久化到服务器端。
  4. 发送并展示。 服务器向 endpoint 投递加密消息;push 事件会启动你的 Service Worker, 在其中调用 showNotification()。

第一个可能失败的环节是权限请求,而在 iOS 上它失败得很硬:正如上表所记录的,普通标签页里 Notification 接口是 undefined,因此 Notification.requestPermission() 会在任何 subscribe() 调用之前抛出 ReferenceError——只包住 subscribe() 的 try 根本看不到它。 请先检测该接口,并把权限请求一并纳入这条受控路径:

// 在已安装的 Web 应用内,从用户交互处理函数中调用:
async function enablePush(swReg, applicationServerKey) {
// 在 iOS/iPadOS 上,主屏幕 Web 应用之外该接口并不存在,普通标签页里读取它会抛出
// ReferenceError。请把这种情况导向安装引导。
if (!('Notification' in window)) {
showAddToHomeScreenGuidance();
return null;
}
const permission = await Notification.requestPermission();
if (permission !== 'granted') return null;
try {
return await swReg.pushManager.subscribe({
userVisibleOnly: true, // 必填:每条推送都必须展示通知
applicationServerKey, // VAPID 密钥——客户端代码中没有 APNs 密钥
});
} catch (error) {
// NotAllowedError 可能表示推送权限被拒、window 作用域不是 HTTPS,或用户代理要求
// userVisibleOnly 为 true 而实际传入了 false。它不能证明应用尚未安装;上面的
// Notification 守卫才用于检测 iOS 上的这道门槛。
reportSubscriptionFailure(error);
return null;
}
}

上文描述的是原始 Web Push:以 JavaScript 为先的设计,由 Service Worker 注册创建订阅,并在 PushEvent 处理函数中调用 showNotification()。WebKit 在 iOS 与 iPadOS 18.4 上为已添加到 主屏幕的 Web 应用推出了 Declarative Web Push,并自 Safari 18.5(随 macOS 15.5 发布)起 在 macOS 上支持:它让你无需已安装的 Service Worker 就能请求推送订阅并展示用户可见通知。

写代码时有两点差异要注意:

  • 不同的 PushManager。 原始 Web Push 唯一可用的 PushManager 是 ServiceWorkerRegistration.pushManager。Declarative Web Push 额外暴露了 window.pushManager,以便在没有 Service Worker 的情况下管理订阅。如果你同时在域名根作用域 注册了 Service Worker,它与 window 对象共享同一个推送订阅——而注销该注册并不会影响这个订阅。
  • 声明式的消息格式。 要让通知以声明式方式被处理,推送消息必须符合声明式标准 JSON 格式 (一个 "web_push": 8030 字段加上一个 notification 对象),这样浏览器无需任何 JavaScript 就掌握了展示通知所需的全部信息。Service Worker 的 JavaScript 可以选择性地修改收到的通知内容。
// Declarative Web Push:无需注册 Service Worker。
const subscription = await window.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: arrayForPublicKey,
});

不要嗅探 Safari,而应做特性检测——WebKit 自己的建议就是:使用特性检测的应用会在推送发布后 自动在 iPhone 与 iPad 上获得该能力。优先使用声明式的、挂在 window 上的 manager,没有它时再 回退到原始 Web Push 所需的、挂在 Service Worker 注册上的那个:

要探测的是可用的 manager,而不是 PushManager 接口:这个全局接口存在,本身并不意味着当前 上下文就能订阅。同时要注意它做不到什么——下面的任何客户端探测都无法确定是否为主屏幕 Web 应用,因此在 iOS 上,一个普通的 Safari 标签页可能通过这些检查,却在之后才失败。真正拦住 这种标签页的,是上文 enablePush() 里的 Notification 守卫;subscribe() 被 reject 本身 不是可靠的安装信号,因为同一条拒绝通道也承载着密钥与订阅错误(见上文按错误名的区分)。

async function getPushManager() {
// Declarative Web Push(18.4+):无需已安装的 Service Worker 即可订阅。
if ('pushManager' in window) return window.pushManager;
if (!('serviceWorker' in navigator)) {
// 完全没有可用来订阅的东西。回退到应用内消息,并展示安装引导,
// 而不是直接失败。
showAddToHomeScreenGuidance();
return null;
}
// 原始 Web Push:订阅挂在 Service Worker 注册上。
// 直接查询注册——`navigator.serviceWorker.ready` 永不 reject,且会为活跃 worker
// 无限期等待,那样会卡在这里,根本走不到下面的安装引导。这里要求 `active`:
// 注册没有活跃 worker 时,subscribe() 会以 "InvalidStateError" 拒绝。
const swReg = await navigator.serviceWorker.getRegistration();
if (!swReg || !swReg.active || !('pushManager' in swReg)) {
showAddToHomeScreenGuidance();
return null;
}
return swReg.pushManager;
}

即便 Service Worker API 存在,navigator.serviceWorker.ready 在这里也是错误的探测手段。MDN 把 它记录为一个永远不会 reject、并会无限期等待页面注册拥有活跃 worker 的 Promise。对于一个 暴露了 Service Worker API、却没有注册(或注册只持有 installing / waiting worker——按 Service Worker 规范,安装失败时它会变成 redundant)且没有 window.pushManager 的上下文—— 普通的 iOS Safari 标签页正是其中之一——await 它会让该函数永远挂住,上文承诺的安装引导也永远 不会出现。getRegistration() 无论如何都会 settle(MDN:它 resolve 为一个 ServiceWorkerRegistration 或 undefined),而 ServiceWorkerRegistration.active 在没有 worker 进入 activating / activated 之前为 null——所以读 active 能给你一个有界答案,而 ready 给你的只是等待。如果你确实需要等待激活,请监听 worker 的 statechange 区分 activated 与 redundant,并加上你自己的超时,而不要去 await ready。

  • 提示需要明确的用户手势。 请求推送订阅必须由用户手势触发,因此在页面加载时弹出的提示 无处着落。请在用户做了推送能帮上忙的操作之后、在上下文中请求。
  • 不允许静默推送。 两种形态都强制 userVisibleOnly: true。在原始 Web Push 下你必须用 JavaScript 兑现这一承诺:只要 PushEvent 处理函数因任何原因没有展示用户可见通知,WebKit 就会 吊销该订阅——而 Service Worker 脚本的 bug、网络状况或设备本地状况都可能让 showNotification() 无法及时被调用。在 Declarative Web Push 下则没有这项惩罚:Service Worker 未能展示通知时,声明式推送消息本身会作为回退被使用。无论哪种形态,推送都不是静默后台 运行的许可证。
  • endpoint 是一个能力 URL。 MDN 的警告是:只要知道该 endpoint,就足以向你的应用发送消息, 因此它需要被保密——否则其他应用可能得以向你的应用推送消息。
  • 为投递限制留余量。 唤醒 Service Worker 会消耗电量,各浏览器做法不同:Firefox 对不产生 通知的推送消息设有配额,Chrome 则没有限制,目前没有标准机制。
  • 在真机上测试。 安装门槛、权限提示与投递都是系统级行为——务必在运行 16.4 及以上版本的 真实 iPhone 或 iPad 上验证。
决策问题 推荐做法 理由
能否向浏览器标签页中的 iOS 用户推送? 不能——先要有主屏幕 Web 应用。 16.4 是为已添加到主屏幕的 Web 应用加入 Web Push 的。
用什么密钥订阅? 标准 VAPID applicationServerKey。 WebKit 实现同一套 W3C Push API;客户端无需 APNs 密钥。
需要 Apple Developer Program 会员资格吗? 不需要。 WebKit 明确说明发送 Web Push 无需加入该计划。
何时请求权限? 在已安装的 Web 应用内,由直接的用户交互触发。 请求推送订阅需要明确的用户手势。
macOS Safari 不安装行吗? 自 macOS Ventura 上的 Safari 16.1 起即支持。 主屏幕这一步是 iOS/iPadOS 的路径。
可以不展示通知就推送吗? 不可以。 userVisibleOnly: true 为强制项;原始 Web Push 下未展示通知的处理函数会被吊销订阅。
必须注册 Service Worker 吗? 18.4+ 的 Declarative Web Push 不需要。 它无需已安装的 Service Worker 即可订阅并展示通知;原始 Web Push 只有 ServiceWorkerRegistration.pushManager。
  • 在 iOS/iPadOS 上,把推送提示置于主屏幕 Web 应用之后;否则展示安装引导。
  • 仅从直接的用户交互、在上下文中请求权限。
  • 使用 userVisibleOnly: true 与 VAPID applicationServerKey 订阅。
  • 将完整的 PushSubscription(endpoint + p256dh + auth)持久化到服务器端,并把 endpoint 当作机密对待。
  • 走原始 Web Push 路径时,始终在 push 处理函数中调用 showNotification()——漏掉一次通知可能让你丢掉订阅。
  • 想在 18.4+ 上走声明式路径,请用 window.pushManager 订阅,并以声明式 JSON 格式发送消息,这样就不需要 Service Worker。
  • 如果你限制了出站推送端点,请放行 *.push.apple.com。
  • 使用特性检测,绝不要用浏览器检测——并且要探测你实际会用到的那个 manager(声明式路径用 window.pushManager,原始路径用注册对象上的 pushManager),而不要把裸的 PushManager 接口当作当前上下文可用的证据。
  • 用 navigator.serviceWorker.getRegistration() 取得注册并要求 registration.active,而不要直接 await navigator.serviceWorker.ready:ready 永不 reject,且会为活跃 worker 无限期等待,探测会挂住而展示不出安装引导。
  • 在调用 Notification.requestPermission() 之前先检测 'Notification' in window——普通 iOS 标签页里该接口是 undefined,读取它抛出的 ReferenceError 是包在 subscribe() 外的 try 永远捕获不到的。
  • 不要把 subscribe() 的拒绝当作安装信号。NotAllowedError 可能表示推送权限被拒、window 作用域不是 HTTPS,或用户代理要求 userVisibleOnly: true 而实际传入了 false。在 iOS 上,应根据符合条件的主屏幕 Web 应用之外不存在 Notification 接口这一点,进入安装引导分支。
  • 给清单一个非默认的 display 值——在 iOS 上没有它,Notification 接口会一直是 undefined。
  • 从 Service Worker 展示通知(registration.showNotification());new Notification() 在 iOS 与 iPadOS 上没有受支持的路径。
  • 在真实的 iOS 16.4+ 设备上验证整个流程。
  • Web Push —— 本页所依赖的跨浏览器协议、VAPID 密钥与 服务端发送路径。
  • iOS 添加到主屏幕 —— 在 iPhone 与 iPad 上 解锁推送的安装步骤。
  • 通知权限 —— 如何请求权限,以及被拒绝意味着什么。
  • Notifications API —— 显示那一半,以及 iOS 的 Service Worker 要求所由来的「持久 / 非持久」之分。