APNs
The APNs transport talks to Apple Push Notification service directly — HTTP/2 with token-based (.p8)
authentication. No FCM/Google dependency for Apple devices. It handles iOS and macOS registrations. It ships
in the Shiny.Extensions.Push package (namespace Shiny.Extensions.Push.Apns) — no separate package to
install; just add using Shiny.Extensions.Push.Apns; to reach AddApns.
Configuration
Section titled “Configuration”You need an APNs auth key (.p8) from the Apple Developer portal, its Key ID, your Team ID, and the app’s
bundle id.
push.AddApns(o =>{ o.TeamId = "ABCDE12345"; // 10-char Apple Team ID (JWT "iss") o.KeyId = "KEY1234567"; // 10-char Key ID of the .p8 (JWT "kid") o.BundleId = "com.example.app"; // default apns-topic o.PrivateKeyPath = "AuthKey_KEY1234567.p8"; // or o.PrivateKey = "<PEM contents>" // o.ForceEnvironment = PushEnvironment.Sandbox; // default: honour each registration});The provider builds and caches its ES256 provider JWT (refreshed ~every 50 minutes — Apple rejects tokens regenerated too frequently) and reuses a single pooled HTTP/2 connection across all sends.
Sandbox vs production
Section titled “Sandbox vs production”APNs tokens are environment-specific — a token registered against the sandbox (debug/TestFlight builds)
is not valid in production and vice-versa. Set DeviceRegistration.Environment per device:
await pushManager.RegisterDevice(new DeviceRegistration{ DeviceToken = "<token>", Platform = DevicePlatform.iOS, Environment = PushEnvironment.Sandbox});The provider routes each push to the correct host based on the registration. Use
ApnsOptions.ForceEnvironment only if you want to pin every push to one environment.
Apple-specific payload options
Section titled “Apple-specific payload options”ApplePushOptions on the notification maps to the aps dictionary and APNs headers:
new PushNotification{ Title = "Title", Apple = new ApplePushOptions { Subtitle = "Subtitle", Category = "MESSAGE_CATEGORY", // actionable notifications ThreadId = "thread-1", // grouping MutableContent = true, // Notification Service Extension ContentAvailable = false, // true => silent/background push // TopicOverride / PushTypeOverride for VoIP / complication topics }};Common cross-cutting fields also map to APNs: Badge, Sound, CollapseId (apns-collapse-id),
TimeToLive (apns-expiration), and Priority (10/5; forced to 5 for background pushes).
Failure handling
Section titled “Failure handling”The provider normalizes APNs responses to PushDeliveryStatus:
| APNs result | Status | Manager action |
|---|---|---|
410 Unregistered | TokenExpired | token pruned |
BadDeviceToken / DeviceTokenNotForTopic | InvalidToken | token pruned |
429 TooManyRequests | RateLimited | surfaced (no auto-retry) |
ExpiredProviderToken / InvalidProviderToken | Error | JWT cache invalidated |
Multiple apps
Section titled “Multiple apps”Register one keyed provider per bundle id — see Sending → Multiple apps. Each
key gets its own options, .p8 key, and JWT cache.