mcpField
A
axiom-push-notifications-ref

axiom-push-notifications-ref

github.com/charleswiltgen开发1.2k65 次安装今天更新
代码仓库

关于 axiom-push-notifications-ref

供开发者实现 Apple 推送通知时查阅的 API 参考,汇集 APNs 传输、UserNotifications、负载格式、Live Activities 和本地通知资料。

主要功能

  1. 01APNs 端点、标头与响应
  2. 02JWT 身份验证参考
  3. 03负载字段与示例
  4. 04UserNotifications API 参考
  5. 05Live Activities 推送字段
  6. 06本地通知触发器

适用场景

  1. 01构造 APNs 请求
  2. 02实现通知授权与操作
  3. 03准备 Live Activity 负载
  4. 04安排本地通知

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-push-notifications-ref

安装后在 Claude Code 中输入/axiom-push-notifications-ref确认已加载。

详细介绍

推送通知 API 参考

APNs HTTP/2 传输、UserNotifications 框架以及实时活动和广播推送等推送驱动功能的全面 API 参考。

快速参考

// AppDelegate — minimal remote notification setup
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        return true
    }

    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let token = deviceToken.map { String(format: "%02x", $0) }.joined()
        sendTokenToServer(token)
    }

    func application(_ application: UIApplication,
                     didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Registration failed: \(error)")
    }

    // Show notifications when app is in foreground
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
        return [.banner, .sound, .badge]
    }

    // Handle notification tap / action response
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse) async {
        let userInfo = response.notification.request.content.userInfo
        // Route to appropriate screen based on userInfo
    }
}

APNs 传输参考

端点
环境主机端口
开发api.sandbox.push.apple.com443 或 2197
生产api.push.apple.com443 或 2197
请求格式
POST /3/device/{device_token}
Host: api.push.apple.com
Authorization: bearer {jwt_token}
apns-topic: {bundle_id}
apns-push-type: alert
Content-Type: application/json
APNs 标头
标头必需值说明
apns-push-type是alert、background、liveactivity、voip、complication、fileprovider、mdm、location必须与负载内容匹配
apns-topic是Bundle ID(或 .push-type.liveactivity 后缀)基于令牌的身份验证所必需
apns-priority否10(立即)、5(省电)、1(低)默认值:alert 为 10,background 为 5
apns-expiration否UNIX 时间戳或 00 = 投递一次,不存储
apns-collapse-id否字符串,≤64 字节替换设备上匹配的通知
apns-id否UUID(小写)APNs 返回的跟踪标识符
authorization令牌身份验证bearer {JWT}证书身份验证不需要
apns-unique-id仅响应UUID与 Push Notifications Console 的投递日志配合使用
响应代码
状态含义常见原因
200成功
400请求错误JSON 格式错误、缺少必需标头
403禁止JWT 已过期、团队或密钥错误、主题不匹配
404未找到设备令牌路径无效
405不允许使用该方法未使用 POST
410已注销设备令牌不再有效(应用已卸载)
413负载过大超过 4KB(VoIP 为 5KB)
429请求过多APNs 限流
500内部服务器错误APNs 问题,请重试
503服务不可用APNs 过载,请退避后重试

JWT 身份验证参考

JWT 标头
{ "alg": "ES256", "kid": "{10-char Key ID}" }
JWT 声明
{ "iss": "{10-char Team ID}", "iat": {unix_timestamp} }
规则
规则详情
算法ES256(P-256 曲线)
签名密钥APNs 身份验证密钥(来自开发者门户的 .p8 文件)
令牌有效期最长 1 小时(超过后返回 403 ExpiredProviderToken)
刷新间隔20 到 60 分钟之间
作用范围一个密钥适用于团队中的所有应用和两个环境
Authorization 标头格式
authorization: bearer eyAia2lkIjog...

负载参考

aps 字典键
键类型用途起始版本
alertDict/String提醒内容iOS 10
badgeNumber应用图标标记(0 表示移除)iOS 10
soundString/Dict音频播放iOS 10
thread-idString通知分组iOS 10
categoryString可操作通知类型iOS 10
content-availableNumber (1)静默后台推送iOS 10
mutable-contentNumber (1)触发服务扩展iOS 10
target-content-idString窗口/内容标识符iOS 13
interruption-levelStringpassive/active/time-sensitive/criticaliOS 15
relevance-scoreNumber 0-1通知摘要排序iOS 15
filter-criteriaString专注模式过滤条件匹配iOS 15
stale-dateNumberUNIX 时间戳(实时活动)iOS 16.1
content-stateDict实时活动内容更新iOS 16.1
timestampNumberUNIX 时间戳(实时活动)iOS 16.1
eventStringstart/update/end(实时活动)iOS 16.1
dismissal-dateNumberUNIX 时间戳(实时活动)iOS 16.1
attributes-typeString实时活动结构体名称iOS 17
attributesDict实时活动初始化数据iOS 17
提醒字典键
键类型用途
title字符串简短标题
subtitle字符串次要描述
body字符串完整消息
launch-image字符串启动屏幕文件名
title-loc-key字符串标题的本地化键
title-loc-args[String]标题格式参数
subtitle-loc-key字符串副标题的本地化键
subtitle-loc-args[String]副标题格式参数
loc-key字符串正文的本地化键
loc-args[String]正文格式参数
声音字典(关键提醒)
{ "critical": 1, "name": "alarm.aiff", "volume": 0.8 }
中断级别值
值行为要求
passive无声音或唤醒。仅显示通知摘要。无
active默认。声音 + 横幅。无
time-sensitive突破计划送达时间。横幅会持续显示。时间敏感通知功能
critical绕过勿扰模式和响铃开关。Apple 批准 + 权限
示例负载
基本提醒
{
    "aps": {
        "alert": {
            "title": "New Message",
            "subtitle": "From Alice",
            "body": "Hey, are you free for lunch?"
        },
        "badge": 3,
        "sound": "default"
    }
}
使用 loc-key/loc-args 本地化
{
    "aps": {
        "alert": {
            "title-loc-key": "MESSAGE_TITLE",
            "title-loc-args": ["Alice"],
            "loc-key": "MESSAGE_BODY",
            "loc-args": ["Alice", "lunch"]
        },
        "sound": "default"
    }
}
静默后台推送
{
    "aps": {
        "content-available": 1
    },
    "custom-key": "sync-update"
}
丰富通知(Service Extension)
{
    "aps": {
        "alert": {
            "title": "Photo shared",
            "body": "Alice shared a photo with you"
        },
        "mutable-content": 1,
        "sound": "default"
    },
    "image-url": "https://example.com/photo.jpg"
}
关键提醒
{
    "aps": {
        "alert": {
            "title": "Server Down",
            "body": "Production database is unreachable"
        },
        "sound": { "critical": 1, "name": "default", "volume": 1.0 },
        "interruption-level": "critical"
    }
}
带类别的时间敏感通知
{
    "aps": {
        "alert": {
            "title": "Package Delivered",
            "body": "Your order has been delivered to the front door"
        },
        "interruption-level": "time-sensitive",
        "category": "DELIVERY",
        "sound": "default"
    },
    "order-id": "12345"
}

UNUserNotificationCenter API 参考

关键方法
方法用途
requestAuthorization(options:)请求权限
notificationSettings()检查当前状态
add(_:)安排通知请求
getPendingNotificationRequests()列出已安排的通知
removePendingNotificationRequests(withIdentifiers:)取消已安排的通知
getDeliveredNotifications()列出通知中心中的通知
removeDeliveredNotifications(withIdentifiers:)从通知中心移除通知
setNotificationCategories(_:)注册可操作类型
setBadgeCount(_:)更新角标(iOS 16+)
supportsContentExtensions检查是否支持内容扩展
UNAuthorizationOptions
选项用途
.alert显示提醒
.badge更新角标计数
.sound播放声音
.carPlay在 CarPlay 中显示
.criticalAlert关键提醒(需要权限)
.provisional无需提示即可试行送达
.providesAppNotificationSettings“在设置中配置”按钮
.announcementSiri 播报(iOS 15+ 已弃用)
UNAuthorizationStatus
值含义
.notDetermined尚未显示提示
.denied用户拒绝或已在设置中关闭
.authorized用户明确授权
.provisional试行送达
.ephemeralApp Clip 临时状态
请求授权
let center = UNUserNotificationCenter.current()

let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
if granted {
    await MainActor.run {
        UIApplication.shared.registerForRemoteNotifications()
    }
}
检查设置
let settings = await center.notificationSettings()

switch settings.authorizationStatus {
case .authorized: break
case .denied:
    // Direct user to Settings
case .provisional:
    // Upgrade to full authorization
case .notDetermined:
    // Request authorization
case .ephemeral:
    // App Clip — temporary
@unknown default: break
}
代理方法
// Foreground presentation — called when notification arrives while app is active
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            willPresent notification: UNNotification) async
    -> UNNotificationPresentationOptions {
    return [.banner, .sound, .badge]
}

// Action response — called when user taps notification or action button
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse) async {
    let actionIdentifier = response.actionIdentifier
    let userInfo = response.notification.request.content.userInfo

    switch actionIdentifier {
    case UNNotificationDefaultActionIdentifier:
        // User tapped notification body
        break
    case UNNotificationDismissActionIdentifier:
        // User dismissed (requires .customDismissAction on category)
        break
    default:
        // Custom action
        break
    }
}

// Settings — called when user taps "Configure in App" from notification settings
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            openSettingsFor notification: UNNotification?) {
    // Navigate to in-app notification settings
}

UNNotificationCategory 和 UNNotificationAction API

类别注册
let likeAction = UNNotificationAction(
    identifier: "LIKE",
    title: "Like",
    options: []
)

let replyAction = UNTextInputNotificationAction(
    identifier: "REPLY",
    title: "Reply",
    options: [],
    textInputButtonTitle: "Send",
    textInputPlaceholder: "Type a message..."
)

let deleteAction = UNNotificationAction(
    identifier: "DELETE",
    title: "Delete",
    options: [.destructive, .authenticationRequired]
)

let messageCategory = UNNotificationCategory(
    identifier: "MESSAGE",
    actions: [likeAction, replyAction, deleteAction],
    intentIdentifiers: [],
    hiddenPreviewsBodyPlaceholder: "New message",
    categorySummaryFormat: "%u more messages",
    options: [.customDismissAction]
)

UNUserNotificationCenter.current().setNotificationCategories([messageCategory])
操作选项
选项效果
.authenticationRequired需要解锁设备
.destructive以红色文字显示
.foreground将应用启动到前台
类别选项
选项效果
.customDismissAction关闭时触发代理方法
.allowInCarPlay在 CarPlay 中显示操作
.hiddenPreviewsShowTitle隐藏预览时显示标题
.hiddenPreviewsShowSubtitle隐藏预览时显示副标题
.allowAnnouncementSiri 可播报(iOS 15+ 已弃用)
UNNotificationActionIcon(iOS 15+)
let icon = UNNotificationActionIcon(systemImageName: "hand.thumbsup")
let action = UNNotificationAction(
    identifier: "LIKE",
    title: "Like",
    options: [],
    icon: icon
)

UNNotificationServiceExtension API

在显示前修改通知内容。在单独的扩展进程中运行。

生命周期
方法时间窗口用途
didReceive(_:withContentHandler:)~30 秒修改通知内容
serviceExtensionTimeWillExpire()截止时间到达时调用立即送达尽力处理后的内容
实现
class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest,
                             withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        guard let content = bestAttemptContent,
              let imageURLString = content.userInfo["image-url"] as? String,
              let imageURL = URL(string: imageURLString) else {
            contentHandler(request.content)
            return
        }

        // Download and attach image
        let task = URLSession.shared.downloadTask(with: imageURL) { url, _, error in
            defer { contentHandler(content) }
            guard let url = url, error == nil else { return }

            let attachment = try? UNNotificationAttachment(
                identifier: "image",
                url: url,
                options: [UNNotificationAttachmentOptionsTypeHintKey: "public.jpeg"]
            )
            if let attachment = attachment {
                content.attachments = [attachment]
            }
        }
        task.resume()
    }

    override func serviceExtensionTimeWillExpire() {
        if let content = bestAttemptContent {
            contentHandler?(content)
        }
    }
}
支持的附件类型
类型扩展名最大大小
图像.jpg, .gif, .png10 MB
音频.aif, .wav, .mp35 MB
视频.mp4, .mpeg50 MB
负载要求

通知负载必须在 aps 字典中包含 "mutable-content": 1,服务扩展才能触发。


本地通知 API

触发类型
触发器使用场景重复
UNTimeIntervalNotificationTriggerN 秒后是(≥60s)
UNCalendarNotificationTrigger特定日期/时间是
UNLocationNotificationTrigger进入/离开区域是
时间间隔触发器
let content = UNMutableNotificationContent()
content.title = "Reminder"
content.body = "Time to take a break"
content.sound = .default

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 300, repeats: false)

let request = UNNotificationRequest(
    identifier: "break-reminder",
    content: content,
    trigger: trigger
)

try await UNUserNotificationCenter.current().add(request)
日历触发器
var dateComponents = DateComponents()
dateComponents.hour = 9
dateComponents.minute = 0

let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

let request = UNNotificationRequest(
    identifier: "daily-9am",
    content: content,
    trigger: trigger
)

try await UNUserNotificationCenter.current().add(request)
位置触发器
import CoreLocation

let center = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)
let region = CLCircularRegion(center: center, radius: 100, identifier: "apple-park")
region.notifyOnEntry = true
region.notifyOnExit = false

let trigger = UNLocationNotificationTrigger(region: region, repeats: false)

let request = UNNotificationRequest(
    identifier: "arrived-at-office",
    content: content,
    trigger: trigger
)

try await UNUserNotificationCenter.current().add(request)
限制
限制详情
最小重复间隔UNTimeIntervalNotificationTrigger 为 60 秒
位置授权位置触发器需要“使用 App 期间”或“始终”授权
不支持服务扩展本地通知不会触发 UNNotificationServiceExtension
不会在后台唤醒本地通知不能使用 content-available 进行后台处理
App 扩展不能从 App 扩展中安排本地通知(请使用 App Group + 主 App)
待处理数量上限每个 App 最多可有 64 个待处理通知请求

Live Activity 推送标头

必需标头
标头值
apns-push-typeliveactivity
apns-topic{bundleID}.push-type.liveactivity
apns-priority5(常规)或 10(时间敏感)
事件类型
事件用途必需字段
start远程启动 Live Activityattributes-type, attributes, content-state, timestamp
update更新内容content-state, timestamp
end结束 Live Activitytimestamp(content-state 可选)
更新负载
{
    "aps": {
        "timestamp": 1709913600,
        "event": "update",
        "content-state": {
            "homeScore": 2,
            "awayScore": 1,
            "inning": "Top 7"
        }
    }
}
启动负载(Push-to-Start Token)
{
    "aps": {
        "timestamp": 1709913600,
        "event": "start",
        "content-state": {
            "homeScore": 0,
            "awayScore": 0,
            "inning": "Top 1"
        },
        "attributes-type": "GameAttributes",
        "attributes": {
            "homeTeam": "Giants",
            "awayTeam": "Dodgers"
        },
        "alert": {
            "title": "Game Starting",
            "body": "Giants vs Dodgers is about to begin"
        }
    }
}
启动负载(基于 Channel)
{
    "aps": {
        "timestamp": 1709913600,
        "event": "start",
        "content-state": {
            "homeScore": 0,
            "awayScore": 0,
            "inning": "Top 1"
        },
        "attributes-type": "GameAttributes",
        "attributes": {
            "homeTeam": "Giants",
            "awayTeam": "Dodgers"
        }
    }
}
结束负载
{
    "aps": {
        "timestamp": 1709913600,
        "event": "end",
        "dismissal-date": 1709917200,
        "content-state": {
            "homeScore": 5,
            "awayScore": 3,
            "inning": "Final"
        }
    }
}
Push-to-Start Token
// Observe push-to-start tokens (iOS 17.2+)
for await token in Activity<GameAttributes>.pushToStartTokenUpdates {
    let tokenString = token.map { String(format: "%02x", $0) }.joined()
    sendPushToStartTokenToServer(tokenString)
}
Activity 推送令牌
// Observe activity-specific push tokens
for await tokenData in activity.pushTokenUpdates {
    let token = tokenData.map { String(format: "%02x", $0) }.joined()
    sendActivityTokenToServer(token, activityId: activity.id)
}

Content-state 编码规则:系统始终使用默认的 JSONDecoder — 不要在 ActivityAttributes.ContentState 中使用自定义编码策略。


广播推送 API(iOS 18+)

面向多个接收方的服务端推送,用于 Live Activities,无需跟踪各设备的令牌。

端点
POST /4/broadcasts/apps/{TOPIC}
标头
标头值
apns-push-typeliveactivity
apns-channel-id{channelID}
authorizationbearer {JWT}
通过 Channel 订阅
try Activity.request(
    attributes: attributes,
    content: .init(state: initialState, staleDate: nil),
    pushType: .channel(channelId)
)
Channel 存储策略
策略行为预算
不存储仅向已连接的设备发送更高
最新消息为离线设备存储最新消息更低

命令行测试

JWT 生成
JWT_ISSUE_TIME=$(date +%s)
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
发送提醒推送
curl -v \
  --header "apns-topic: $TOPIC" \
  --header "apns-push-type: alert" \
  --header "authorization: bearer $AUTHENTICATION_TOKEN" \
  --data '{"aps":{"alert":"test"}}' \
  --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
发送 Live Activity 推送
curl \
  --header "apns-topic: com.example.app.push-type.liveactivity" \
  --header "apns-push-type: liveactivity" \
  --header "apns-priority: 10" \
  --header "authorization: bearer $AUTHENTICATION_TOKEN" \
  --data '{
      "aps": {
          "timestamp": '$(date +%s)',
          "event": "update",
          "content-state": { "score": "2-1" }
      }
  }' \
  --http2 https://api.sandbox.push.apple.com/3/device/$ACTIVITY_PUSH_TOKEN
模拟器推送
xcrun simctl push booted com.example.app payload.json
模拟器负载文件
{
    "Simulator Target Bundle": "com.example.app",
    "aps": {
        "alert": { "title": "Test", "body": "Hello" },
        "sound": "default"
    }
}

资源

WWDC:2021-10091, 2023-10025, 2023-10185, 2024-10069

文档:/usernotifications, /usernotifications/sending-notification-requests-to-apns, /usernotifications/generating-a-remote-notification, /activitykit

技能:axiom-push-notifications, axiom-push-notifications-diag, axiom-extensions-widgets

评论

支持 Markdown