thiezn
thiezn

Reputation: 2034

iOS Apple Push Notifications not arriving on real device

I've been trying to setup Apple Push Notifications (Alerts) on my Swift (iOS >= 15.0) application. For authentication I'm using the new(ish) token based authentication and have added the push notification entitlement. Unfortunately when trying to send a push notification to my real test iPhone device, the notification never arrives.

I'm using the following URL and Payload:

https://api.sandbox.push.apple.com:443/3/device/<device_id>

POST /3/device/c<REDACTED DEVICE ID>6 HTTP/2
Host: api.sandbox.push.apple.com
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-httpx/0.21.1
Apns-Expiration: 0
Apns-Priority: 10
Apns-Topic: <REDACTED BUNDLE_ID e.g. com.my.app>
Apns-Push-Type: alert
Authorization: bearer ey<REDACTED>w
Content-Type: application/json
Content-Length: 78

{"some": "thing", "aps": {"alert": {"title": "test", "subtitle": "subtitle"}}}

Sending this to Apple's sandbox environment returns a HTTP Status Code 200 without any content. This means that at least the device ID and bundle ID are correct. unfortunately the notifications never arrive on my device (even not the following day or after repeated attempts).

I've successfully registered my application with the appropriate permissions as can be seen here:

app notification permissions

When using the simctl command line tool, I'm able to send and see the notification to my xcode simulator and can see the didReceiveRemoteNotification function is being called.

# cat test.apn                                                                                                                                                    
{
  "some": "thing",
  "aps": {
    "alert": {
      "title": "test",
      "subtitle": "subtitle"
    }
  }
}
# xcrun simctl push booted com.my.app test.apn
Notification sent to 'com.my.app'

The following code is what I have in place currently for handling registration for push notifications and catching incoming notifications:


@main
struct MyApp: App {
    
    @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
    
    var body: some Scene {
        WindowGroup {
            Text("Hello")
    }
}


class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    
    func forwardTokenToServer(token: Data) async throws {

        let tokenComponents = token.map { data in String(format: "%02.2hhx", data) }
        let deviceTokenString = tokenComponents.joined()
        
        let device = NotificationDevice(deviceToken: deviceTokenString)
        print("Sending device \(device.name) \(device.type) \(device.id) to backend service...")

        guard let url = URL(string: "https://my.app/api/iosdevices") else {
            throw URLError(.badServerResponse)  // Todo proper errors
         }
    
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        
        let encoder = JSONEncoder()
        guard let deviceData = try? encoder.encode(device) else {
            throw URLError(.badServerResponse) // TODO proper errors
        }
        
        let (_, response) = try await URLSession.shared.upload(
            for: request,
            from: deviceData
        )
        print("Response from backend: \(response)")
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        // Request Alert push notification authorization
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (allowed, error) in
            //This callback does not trigger on main loop be careful
            if allowed {
                print("PushNotification Allowed")
            } else {
                print("Error no auth for push notifications")
            }
        }
        
        UIApplication.shared.registerForRemoteNotifications()
        UNUserNotificationCenter.current().delegate = self
        return true
    }
    
    // Note there is no callback in simulator, must use device to get valid push token
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Task {
             do {
                 try await self.forwardTokenToServer(token: deviceToken)
             } catch {
               print("Failed to forward token to backend.")
             }
        }
    }
    
    // Called when we could not register for push notifications
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications \(error.localizedDescription)")
    }
    
    // Called when we receive a new notificationn
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            print("Got a notification")
    }
}

Any tips or ideas are much appreciated!

Upvotes: 1

Views: 835

Answers (1)

thiezn
thiezn

Reputation: 2034

Finally figured out that the DeviceID in development got changed. When sending a notification with the old DeviceID to apple's APNS server api.sandbox.push.apple.com, it still regarded the old DeviceID as valid so I didn't pick up on it.

Apparently it will take a while before old DeviceIDs become invalid on the apple backend.

Upvotes: 1

Related Questions