Highlight
Safari 16 在 macOS Ventura 为网站和 Web App 增加基于 Push API、Notifications API、Service Worker 和 VAPID 的标准 Web Push;已按标准实现的站点无需 Safari 私有 API,也不需要 Apple Developer account。
核心内容
网站通知长期有一个现实问题:用户不一定开着你的页面。博客、消息、任务提醒、内部工具更新,都希望在页面关闭后还能通知用户。
Safari 过去有 Safari Push Notifications。它会继续工作。但从 macOS Ventura 的 Safari 16 开始,Safari 支持标准 Web Push(00:43)。Apple 明确说,它使用的是其他浏览器也采用的标准组合:Push API、Notifications API 和 Service Worker(01:08)。
这带来一个直接结果:如果站点已经按 Web 标准实现 Web Push,就不需要为 Safari 写私有分支(01:16)。真正要检查的是两件事。第一,不要用浏览器嗅探把 Safari 排除在外,要用 feature detection。第二,如果服务端严格校验推送 endpoint,要允许 push.apple.com 的任意子域名(01:48)。
这场 session 的示例 App 叫 Browser Pets。它已经有 Service Worker,用来加速页面加载并在多个标签页之间同步。Web Push 要做的事,是在这个基础上增加三个环节:注册或复用 Service Worker、在用户点击按钮后订阅推送、在 Service Worker 里处理 push 和 notificationclick 事件。
详细内容
先安装 Service Worker
(08:27)Web Push 依赖 Service Worker。Service Worker 是独立于当前标签页运行的 JavaScript,它代表整个 domain 工作。Safari 收到服务端推送后,会唤醒它并发送 push 事件。
// BrowserPetsWorker.js
function handleMessageEvent(event) {
// ...
};
self.addEventListener('message', (event) => {
handleMessageEvent(event);
});
function primeCaches() {
// ...
};
self.addEventListener('install', (event) => {
primeCaches();
});
self.addEventListener('fetch', (event) => {
event.respondWith(caches.match(event.request));
});
关键点:
self.addEventListener('message', ...):Service Worker 可以接收页面或其他上下文发送的消息。self.addEventListener('install', ...):安装阶段适合准备缓存等基础状态。primeCaches():示例里用它代表预热缓存的逻辑。self.addEventListener('fetch', ...):Service Worker 拦截网络请求。event.respondWith(caches.match(event.request)):示例从缓存里查找当前请求的响应。
(08:42)页面端先用 feature detection 检查浏览器是否支持 Service Worker。已有注册就复用;没有注册再安装 BrowserPetsWorker.js。
// BrowserPetsMain.js
var registration;
if ('serviceWorker' in navigator) {
let registration = await navigator.serviceWorker.getRegistration();
if (!registration)
registration = await navigator.serviceWorker.register('BrowserPetsWorker.js');
}
关键点:
'serviceWorker' in navigator:用功能检测判断当前浏览器是否支持 Service Worker。navigator.serviceWorker.getRegistration():查询当前 scope 下是否已经有注册过的 Service Worker。if (!registration):避免重复注册同一个 worker。navigator.serviceWorker.register('BrowserPetsWorker.js'):把 worker 脚本安装到浏览器里。
订阅必须来自用户手势
(02:49)Safari 不允许网站在用户没有主动操作时请求推送权限。session 的 demo 里,用户点击一个铃铛按钮后才出现系统通知授权弹窗。
(09:00)Browser Pets 用按钮的 onclick 调用订阅函数。鼠标点击或键盘操作都满足 session 中提到的 user gesture 要求(04:32)。
// BrowserPetsMain.js
async function subscribeToPush() {
// ...
}
// BrowserPetsMain.html
<button onclick="subscribeToPush()">Register for Updates</button>
关键点:
async function subscribeToPush():订阅流程包含异步权限请求和 PushSubscription 获取。<button ...>:把入口放在用户能看见、能理解的界面控件上。onclick="subscribeToPush()":订阅请求由点击事件触发,符合 Safari 的限制。Register for Updates:按钮文案要让用户知道接下来会启用更新通知。
用 VAPID 配置 PushSubscription
(09:19)用户点击按钮后,页面端调用 pushManager.subscribe()。Safari 使用标准 VAPID(Voluntary Application Server Identification)机制,让服务端向 Apple 的 push servers 标识自己(09:29)。
// BrowserPetsMain.js
async function subscribeToPush() {
let serverPublicKey = VAPID_PUBLIC_KEY;
let subscriptionOptions = {
userVisibleOnly: true,
applicationServerKey: serverPublicKey
};
let subscription = await swRegistration.pushManager.subscribe(subscriptionOptions);
sendSubcriptionToServer(subscription);
}
关键点:
VAPID_PUBLIC_KEY:服务端的 VAPID 公钥,用于标识发送推送的服务器。userVisibleOnly: true:页面承诺每次 push 都会产生用户可见通知。applicationServerKey: serverPublicKey:把 VAPID 公钥传给 Push API。swRegistration.pushManager.subscribe(...):基于当前 Service Worker 注册创建推送订阅。subscription:包含服务端发送推送所需的 endpoint 和加密密钥等信息。sendSubcriptionToServer(subscription):把订阅信息传回自己的后端;具体传输方式由站点决定。
(10:21)subscribe() 会触发系统权限弹窗。用户可能拒绝,页面代码要处理失败路径。用户允许后,返回的 PushSubscription 里有精确 endpoint URL 和用于加密传输的 key(10:30)。
Service Worker 收到 push 后必须显示通知
(11:13)服务器发出的 push 到达 Safari 后,Safari 唤醒 Service Worker,并发送 push 事件。Browser Pets 的服务端把通知需要的字段都放在 JSON payload 里,worker 读取后调用 showNotification()。
// BrowserPetsWorker.js
self.addEventListener('push', (event) => {
let pushMessageJSON = event.data.json();
// Our server puts everything needed to show the notification
// in our JSON data.
event.waitUntil(self.registration.showNotification(pushMessageJSON.title, {
body: pushMessageJSON.body,
tag: pushMessageJSON.tag,
actions: [{
action: pushMessageJSON.actionURL,
title: pushMessageJSON.actionTitle,
}]
}));
}
关键点:
self.addEventListener('push', ...):Service Worker 监听来自浏览器的 push 事件。event.data.json():把服务器发送的 PushMessageData 按 JSON 读取。event.waitUntil(...):告诉浏览器该异步任务属于这次 push 事件处理。self.registration.showNotification(...):显示平台原生通知。pushMessageJSON.title:通知标题来自服务端 payload。body和tag:分别配置通知正文和通知标识。actions:为通知配置操作项,这里把服务端给出的 URL 放进action。
这里有一个重要边界。session 多次强调,处理 push 事件不是给 JavaScript 静默后台运行时间(13:39)。Safari 不支持 silent push。处理 push 事件时必须向 Notification Center 发布通知(13:53)。在 macOS Ventura beta 中,如果三次 push 事件没有及时显示通知,站点的 push subscription 会被撤销(14:07)。
点击通知后打开目标页面
(12:06)通知显示出来只是第一步。用户点击通知后,Safari 会把 notificationclick 事件发给 Service Worker。Browser Pets 的示例从通知 action 中取 URL,然后打开新窗口。
// BrowserPetsWorker.js
self.addEventListener('notificationclick', async function(event) {
if (!event.action)
return;
// This always opens a new browser tab,
// even if the URL happens to already be open in a tab.
clients.openWindow(event.action);
});
关键点:
self.addEventListener('notificationclick', ...):监听用户点击通知后的事件。if (!event.action) return;:没有 action URL 时不继续处理。event.action:取出通知里配置的操作值,本例把它当作目标 URL。clients.openWindow(event.action):打开指向该 URL 的新浏览器标签页。- 示例注释说明:这段代码总会打开新标签页,即使相同 URL 已经打开。
调试订阅和事件处理
(12:28)Web Inspector 可以调试页面里的订阅代码,也可以检查 Service Worker 实例,并在事件处理器上设置断点。这样能覆盖两段关键代码:页面端调用 pushManager.subscribe() 的逻辑,以及 worker 里处理 push、notificationclick 的逻辑。
Apple Push Notification servers 在发布 push message 出错时会返回 human readable errors(12:55)。调试时要同时看浏览器端和服务端错误,不要只盯页面 JavaScript。
核心启发
-
做什么:给内容站增加按栏目订阅的 Web Push。 为什么值得做:session 建议在 Web App 内提供细粒度通知控制,用户可以选择博客更新、提交更新等不同类型(04:58)。 怎么开始:先注册 Service Worker,再把“订阅此栏目”按钮绑定到
pushManager.subscribe(),把PushSubscription和栏目 ID 一起传给后端。 -
做什么:给内部运营工具增加后台任务完成提醒。 为什么值得做:Service Worker 可以在网站没有打开标签页时收到 push;macOS Ventura 上即使 Safari 没运行也能处理(07:29)。 怎么开始:任务完成后由服务端向保存的 endpoint 发送 push,worker 在
push事件中用showNotification()展示任务名和结果页链接。 -
做什么:为现有 Chrome/Firefox Web Push 站点补齐 Safari 支持。 为什么值得做:Safari 采用相同 Web 标准;已有标准实现通常不需要改 Safari 专用代码(01:16)。 怎么开始:移除浏览器嗅探,改用
'serviceWorker' in navigator等 feature detection;服务端 endpoint allowlist 放开push.apple.com的子域名。 -
做什么:把通知点击变成明确的深链接入口。 为什么值得做:
notificationclick事件允许 worker 根据通知携带的数据打开对应页面(07:43)。 怎么开始:在 push JSON 里放入actionURL,显示通知时写入actions,点击时用clients.openWindow(event.action)打开详情页。
关联 Session
- What’s new in Safari and WebKit — Safari 16 年度总览,包含 Web Push、Broadcast Channel、Origin private file system 等 Web API 更新。
- What’s new in WKWebView — 面向把 Web 内容嵌入 App 的开发者,讲 WKWebView 的全屏、视口、查找和调试更新。
- What’s new in Safari Web Extensions — Safari Web Extensions 的 Manifest V3、API 更新和跨设备同步。
- Create Safari Web Inspector Extensions — 用 Web Extensions API 给 Safari Web Inspector 增加自定义调试面板。
评论
GitHub Issues · utterances