推送通知

APNs 注册、device_tokens 流水线,以及发送测试推送。

仅限真机。 模拟器永远拿不到 APNs 设备令牌,且注册需要付费的 Apple Developer 团队aps-environment entitlement)。以下内容都需要一台登录了你团队的真机——见文末的模拟器行为说明。

推送已端到端接好:请求授权 → 向 APNs 注册 → 把令牌 upsert 到 Supabase → 通过一个把 Apple 凭据留在 服务端的 Edge Function 发送测试推送。它标记为 Integrated——在 entitlement + APNs 密钥配好后即生效, 在此之前安全地无操作。

关键文件

令牌流水线

SwiftUI 掌管应用生命周期,因此 AppDelegate(经 @UIApplicationDelegateAdaptor 挂载)把 UIKit 的 APNs 回调桥接进 @ObservableNotificationsController

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 条)。
  • 点击 → 若载荷的 userInfodeeplink(或 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_IDApple Developer Team ID
APNS_KEY_IDAPNs Auth Key 的 Key ID
APNS_BUNDLE_ID例如 com.soarstarter.SoarStarterSwift
APNS_PRIVATE_KEY.p8 APNs Auth Key 的内容

它还会清理无效令牌——BadDeviceToken / Unregistered 响应会删除对应行,使表自愈。

让 APNs 环境与构建匹配。 调试构建在沙盒 APNs 主机上注册;TestFlight/App Store 构建用生产。 发到错误主机会返回 BadDeviceTokenaps-environment entitlement 以 development 发布;发布构建在 分发时把它翻为 production

在模拟器上

registerForRemoteNotifications() 在模拟器里永不产出令牌,因此测试推送按钮保持禁用(它需要非空的 pushToken)。你仍可演练本地路径——请求授权,并把一个 .apns 载荷文件拖到模拟器窗口,观察前台呈现、 收件箱与深度链接路由。真正的 APNs 投递需要真机。

验证

  • 在真机上打开通知 → 启用 → 授予弹窗 → 状态翻为已授权,且出现设备令牌(调试)。
  • 确认 device_tokens 中有一行对应你的 user_id
  • 发送测试推送 → 通知到达;点击它路由到通知界面(soar:///notifications)。
  • 登出 → device_tokens 行被删除。

相关页面

On this page