推送通知
APNs 注册、device_tokens 流水线,以及发送测试推送。
仅限真机。 模拟器永远拿不到 APNs 设备令牌,且注册需要付费的 Apple Developer 团队
(aps-environment entitlement)。以下内容都需要一台登录了你团队的真机——见文末的模拟器行为说明。
推送已端到端接好:请求授权 → 向 APNs 注册 → 把令牌 upsert 到 Supabase → 通过一个把 Apple 凭据留在 服务端的 Edge Function 发送测试推送。它标记为 Integrated——在 entitlement + APNs 密钥配好后即生效, 在此之前安全地无操作。
关键文件
令牌流水线
SwiftUI 掌管应用生命周期,因此 AppDelegate(经 @UIApplicationDelegateAdaptor 挂载)把 UIKit 的
APNs 回调桥接进 @Observable 的 NotificationsController:
requestAuthorization() → registerForRemoteNotifications()
│
▼
AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken(_:)
│ 把令牌十六进制编码
▼
NotificationsController.handleDeviceToken(_:)
│
▼
PushTokenRegistrar.upsert(token:userId:) → device_tokens(onConflict "user_id,token")registrar 以复合键 (user_id, token) upsert (user_id, token, platform: "ios", updated_at),因此
重复注册是幂等的。它感知会话:
- 登录且手头已有令牌 → 以新用户身份重新 upsert。
- 登出 →
wipe()删除先前注册的行并清空pushToken。
device_tokens 表不在模板已发布的迁移中——其 SQL(列 + 复合主键 + RLS)已内联在
Supabase 配置。没有该表时,upsert 会静默失败(尽力而为)而非崩溃。
授权与预热
NotificationsController.requestAuthorization() 申请 [.alert, .badge, .sound],刷新已存状态,并在
成功时向 APNs 注册。两个界面会调用它:
- 权限界面(
PermissionsView)——一个预热 UI,在原生弹窗之前说明收益,与照片 / 相机 / 跟踪 并列。可从 Me 页进入;引导页有意不自动跳到这里。被拒绝的权限会把按钮切换为“打开设置”。 - 通知界面(
NotificationsView)——展示当前权限状态、设备令牌(调试)、应用内收件箱与测试推送按钮。
前台呈现、收件箱与点击 → 深度链接
AppDelegate 上的 UNUserNotificationCenterDelegate 处理到达:
- 前台到达 → 以
[.banner, .list, .sound]呈现,并追加到内存收件箱(上限 50 条)。 - 点击 → 若载荷的
userInfo带deeplink(或url)字符串,会被暂存并转交给DeepLinkHandler路由。见深度链接。
用 send-test-push 测试
模板不在应用中携带 Apple 凭据,而是调用 send-test-push Edge Function(校验 JWT),它查出调用者
自己的 iOS device_tokens 并用服务端密钥经 APNs 发送。通知界面的“发送测试推送”按钮驱动它:
// PushTestSender —— 由 NotificationsViewModel 调用
let request = PushTestRequest(
title: title, body: body,
deeplink: "soar:///notifications",
environment: "sandbox" // 沙盒 vs 生产 APNs 主机
)
try await client.functions.invoke("send-test-push", options: .init(body: request))该函数需要这些 Edge 密钥(在 Supabase 配置 中设置):
| 密钥 | 用途 |
|---|---|
APNS_TEAM_ID | Apple Developer Team ID |
APNS_KEY_ID | APNs Auth Key 的 Key ID |
APNS_BUNDLE_ID | 例如 com.soarstarter.SoarStarterSwift |
APNS_PRIVATE_KEY | .p8 APNs Auth Key 的内容 |
它还会清理无效令牌——BadDeviceToken / Unregistered 响应会删除对应行,使表自愈。
让 APNs 环境与构建匹配。 调试构建在沙盒 APNs 主机上注册;TestFlight/App Store 构建用生产。
发到错误主机会返回 BadDeviceToken。aps-environment entitlement 以 development 发布;发布构建在
分发时把它翻为 production。
在模拟器上
registerForRemoteNotifications() 在模拟器里永不产出令牌,因此测试推送按钮保持禁用(它需要非空的
pushToken)。你仍可演练本地路径——请求授权,并把一个 .apns 载荷文件拖到模拟器窗口,观察前台呈现、
收件箱与深度链接路由。真正的 APNs 投递需要真机。
验证
- 在真机上打开通知 → 启用 → 授予弹窗 → 状态翻为已授权,且出现设备令牌(调试)。
- 确认
device_tokens中有一行对应你的user_id。 - 点发送测试推送 → 通知到达;点击它路由到通知界面(
soar:///notifications)。 - 登出 →
device_tokens行被删除。
