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 Activity のプッシュ項目
  6. 06ローカル通知のトリガー

活用シーン

  1. 01APNs リクエストの作成
  2. 02通知許可とアクションの実装
  3. 03Live 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 フレームワーク、Live Activities やブロードキャストプッシュなどのプッシュ主導機能に関する包括的な 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 = 1 回配信し、保存しません
apns-collapse-idいいえ64 バイト以下の文字列デバイス上の一致する通知を置き換えます
apns-idいいえUUID(小文字)トラッキング用に APNs から返されます
authorizationトークン認証bearer {JWT}証明書認証では不要です
apns-unique-idレスポンスのみUUIDPush 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 分
スコープ1 つのキーでチーム内のすべてのアプリと両方の環境に対応します
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 タイムスタンプ(Live Activity)iOS 16.1
content-stateDictLive Activity のコンテンツ更新iOS 16.1
timestampNumberUNIX タイムスタンプ(Live Activity)iOS 16.1
eventStringstart/update/end(Live Activity)iOS 16.1
dismissal-dateNumberUNIX タイムスタンプ(Live Activity)iOS 16.1
attributes-typeStringLive Activity の構造体名iOS 17
attributesDictLive Activity の初期化データiOS 17
アラート辞書のキー
キー型用途
titleString短いタイトル
subtitleString補足の説明
bodyStringメッセージ全文
launch-imageString起動画面のファイル名
title-loc-keyStringタイトルのローカライズキー
title-loc-args[String]タイトルの書式引数
subtitle-loc-keyStringサブタイトルのローカライズキー
subtitle-loc-args[String]サブタイトルの書式引数
loc-keyString本文のローカライズキー
loc-args[String]本文の書式引数
サウンド辞書(クリティカルアラート)
{ "critical": 1, "name": "alarm.aiff", "volume": 0.8 }
割り込みレベルの値
値動作必要条件
passive音も画面の点灯もありません。通知の要約のみです。なし
activeデフォルトです。サウンドとバナーが表示されます。なし
time-sensitive配信予定を中断します。バナーが表示され続けます。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"
}
リッチ通知(サービスエクステンション)
{
    "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"
    }
}
カテゴリ付きの Time-Sensitive 通知
{
    "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サウンドを再生する
.carPlayCarPlay に表示する
.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通知を閉じたときにデリゲートを呼び出します
.allowInCarPlayCarPlay にアクションを表示します
.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 秒
位置情報の認証位置情報トリガーには「使用中のみ」または「常に」の認証が必要です
サービス拡張機能なしローカル通知では UNNotificationServiceExtension は起動しません
バックグラウンドでの起動なしローカル通知ではバックグラウンド処理に content-available を使用できません
アプリ拡張機能アプリ拡張機能からローカル通知をスケジュールすることはできません(アプリグループとメインアプリを使用してください)
保留数の上限アプリごとに保留中の通知リクエストは 64 件までです

Live Activity のプッシュヘッダー

必須ヘッダー
ヘッダー値
apns-push-typeliveactivity
apns-topic{bundleID}.push-type.liveactivity
apns-priority5(通常)または 10(時間制限あり)
イベントの種類
イベント目的必須フィールド
startLive Activity をリモートで開始しますattributes-type、attributes、content-state、timestamp
updateコンテンツを更新しますcontent-state、timestamp
endLive Activity を終了しますtimestamp(content-state は任意)
更新ペイロード
{
    "aps": {
        "timestamp": 1709913600,
        "event": "update",
        "content-state": {
            "homeScore": 2,
            "awayScore": 1,
            "inning": "Top 7"
        }
    }
}
開始ペイロード(プッシュ開始トークン)
{
    "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"
        }
    }
}
開始ペイロード(チャンネルベース)
{
    "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"
        }
    }
}
プッシュ開始トークン
// 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)
}
アクティビティプッシュトークン
// 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)
}

コンテンツ状態のエンコード規則:システムは常にデフォルトの JSONDecoder を使用します。ActivityAttributes.ContentState でカスタムのエンコード戦略を使用しないでください。


ブロードキャストプッシュ API(iOS 18 以降)

個々のデバイストークンを追跡せずに Live Activities を複数のユーザーにプッシュするサーバー機能です。

エンドポイント
POST /4/broadcasts/apps/{TOPIC}
ヘッダー
ヘッダー値
apns-push-typeliveactivity
apns-channel-id{channelID}
authorizationbearer {JWT}
チャンネル経由でのサブスクライブ
try Activity.request(
    attributes: attributes,
    content: .init(state: initialState, staleDate: nil),
    pushType: .channel(channelId)
)
チャンネルのストレージポリシー
ポリシー動作予算
ストレージなし接続中のデバイスにのみ配信します多い
最新メッセージオフラインのデバイス向けに最新のメッセージを保存します少ない

コマンドラインでのテスト

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

コメント