16
votes

I was having no issues with sending push notifications from FCM to my iOS app before one of my certificates expired. After changing it, FCM is no longer delivering messages. I read this article (https://firebase.googleblog.com/2017/01/debugging-firebase-cloud-messaging-on.html) and here are the validation steps I've gone through so far, but am beating my head on the wall now...

  • Commented out any connectToFCM functions
  • Downloaded Pusher and successfully can send notifications to the device using the APNs cert
  • Successfully made curl calls to FCM with success (response below)
    • {"multicast_id":7774794018682407760,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1493321644068397%b76e8527b76e8527"}]}
  • Tried recreating both development and production certs
  • Tried exporting them from keychain with and without a password

Any one have experience with this super frustrating thing, and have advice on how to proceed?

It's also worth noting I'm not able to remove the APNs certs, I see the option, but it's greyed out and I can't select it.

3
did you upload the new certificate to firebase?washloops
Yes, I've uploaded multiple versions in an attempt to get it working.Jake Lisby
Solution: I reinstalled the app and it worked.TIMEX

3 Answers

3
votes

This is helpful in understanding the flow.

enter image description here
image courtesy of The Firebase Blog

You've tested that the APN sends to the app. So the certificate is fine.

From the blog you linked, getting a successful response by making a curl call only means that the message is being received by FCM. It does not mean that the message is making it to the APNs.

Send a message directly using the Firebase Notifications panel, to see if the FCM is communicating with the APNs.

If that works, there's an issue with your message format.

If it doesn't work.

Ensure:

  1. the message priority is set to high, so they are sent immediately.

  2. "content_available": true

  3. uninstall and reinstall the app

  4. check your server code.

2
votes

Instead of using APNS certificates for push notification you can create one APNs Key and use it for all applications.

APNs Use the Apple Push Notification service for your notification requests. One key is used for all of your apps. For more information refer to the Local and Remote Notification Programming Guide.

Even FCM support this. now you can avoid the headache of creating APNS Certificate for each App ID.

2
votes

Please update your APNs Certificates(Production and Development) in YourProjectSetting=>cloudmessaging=> iOS app configuration. Firebase Cloud Messaging can use either an APNs authentication key or APNs certificate to connect with APNs

Note: confirm your Project credentials(SenderID, Legacy server key , Server key).

HTTP POST Request: For sending notifications.

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u......7Udno5aA
{
    "registration_ids": ["regId1","regId2"],
    "data": {
        "title": "App Name",
        "message": "Hello this is for testing",
        "body": "Hello this is for testing"

    },
    "content-available": true,
    "priority": "high",
    "notification": {
        "title": "App Name",
        "message": "Hello this is for testing",
        "body": "Hello this is for testing"
    }
}

Add following code in your AppDelegate.swift

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            self.fcmInitialSetup(application)
            return true
        }
        func fcmInitialSetup(_ application: UIApplication){

            // [START register_for_notifications]
            if #available(iOS 10.0, *) {
                let uns: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
                application.registerUserNotificationSettings(uns)
                application.registerForRemoteNotifications()

            } else {
                let settings: UIUserNotificationSettings =
                    UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
                application.registerUserNotificationSettings(settings)
            }

            application.registerForRemoteNotifications()

            // [END register_for_notifications]

            FIRApp.configure()

            // Add observer for InstanceID token refresh callback.
            NotificationCenter.default.addObserver(self, selector: #selector(self.tokenRefreshNotification), name: NSNotification.Name.firInstanceIDTokenRefresh, object: nil)

            if let token = FIRInstanceID.instanceID().token() {
                sendTokenToServer(token)
            }
        }

        func sendTokenToServer(_ currentToken: String) {
            print("sendTokenToServer() Token: \(currentToken)")
            // Send token to server ONLY IF NECESSARY

            print("InstanceID token: \(currentToken)")
            self.token = currentToken
            UserDefaults.standard.set(self.token, forKey: "token")
            UserDefaults.standard.synchronize()
            if self.token != nil{
                let userInfo = ["token": self.token]
                NotificationCenter.default.post(
                    name: Notification.Name(rawValue: self.rkey), object: nil, userInfo: userInfo)
            }
        }


        // NOTE: Need to use this when swizzling is disabled
        func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
            let tokenChars = (deviceToken as NSData).bytes.bindMemory(to: CChar.self, capacity: deviceToken.count)
            var tokenString = ""

            for i in 0..<deviceToken.count {
                tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
            }

            FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.unknown)
            print("Device Token:", tokenString)
            print("FIRInstanceID.instanceID().token() Token:", FIRInstanceID.instanceID().token())
            if let tokenData = FIRInstanceID.instanceID().token(){
                UserDefaults.standard.set(tokenData, forKey: "token")
                UserDefaults.standard.synchronize()
                let userInfo = ["token": tokenData]
            }
        }

        func tokenRefreshNotification(_ notification: Notification) {
            // NOTE: It can be nil here
            //        print("Token:\(FIRInstanceID.instanceID().token()!)")
            if let refreshedToken = FIRInstanceID.instanceID().token() {
                print("InstanceID token: \(refreshedToken)")
                UserDefaults.standard.set(refreshedToken, forKey: "token")
                UserDefaults.standard.synchronize()
                print("update now \(self.token)")
                if self.token != nil{
                    let userInfo = ["token": self.token]
                    NotificationCenter.default.post(
                        name: Notification.Name(rawValue: self.rkey), object: nil, userInfo: userInfo)
                }

            }

            // Connect to FCM since connection may have failed when attempted before having a token.
            connectToFcm()
        }
        // [END refresh_token]
        func connectToFcm() {
            FIRMessaging.messaging().connect { (error) in
                if (error != nil) {
                    print("Unable to connect with FCM. \(error)")
                } else {
                    print("Connected to FCM.")
                }
            }
        }

        func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
            print(userInfo)
        }

        func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
            print("Within open URL")
            return true
        }


        // [START receive_apns_token_error]
        func application( _ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError
            error: Error ) {
            print("Registration for remote notification failed with error: \(error.localizedDescription)")
            // [END receive_apns_token_error]
            let userInfo = ["error": error.localizedDescription]
            NotificationCenter.default.post(
                name: Notification.Name(rawValue: rkey), object: nil, userInfo: userInfo)
        }

        func registrationHandler(_ token: String!, error: NSError!) {
            if (token != nil) {
                self.token = token!
                print("Registration Token: \(self.token)")
                UserDefaults.standard.set(self.token, forKey: "token")
                UserDefaults.standard.synchronize()
                let userInfo = ["token": self.token]
                NotificationCenter.default.post(
                    name: Notification.Name(rawValue: self.rkey), object: nil, userInfo: userInfo)
            } else {
                print("Registration to GCM failed with error: \(error.localizedDescription)")
                let userInfo = ["error": error.localizedDescription]
                NotificationCenter.default.post(
                    name: Notification.Name(rawValue: self.rkey), object: nil, userInfo: userInfo)
            }
        }

        func registerForPushNotifications(_ application: UIApplication) {
            let notificationSettings = UIUserNotificationSettings(
                types: [.badge, .sound, .alert], categories: nil)
            application.registerUserNotificationSettings(notificationSettings)
        }

        func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
            if notificationSettings.types != UIUserNotificationType() {
                application.registerForRemoteNotifications()
            }
        }

        // [START receive_message]
        func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                         fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            // If you are receiving a notification message while your app is in the background,
            // this callback will not be fired till the user taps on the notification launching the application.
            // TODO: Handle data of notification

            // Print message ID. add Toast
            print(userInfo);

            print(application.keyWindow?.visibleViewController() ?? "")

            print("Message ID: \(userInfo["gcm.message_id"]!)")


            // Print full message.
            print("%@", userInfo)
        }
        // [END receive_message]



        func applicationDidBecomeActive(_ application: UIApplication) {
            connectToFcm()
        }

        // [START disconnect_from_fcm]
        func applicationDidEnterBackground(_ application: UIApplication) {
            //        FIRMessaging.messaging().disconnect()
            //        print("Disconnected from FCM.")
        }

        func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

        }

        // [END disconnect_from_fcm]


// [START ios_10_message_handling]
@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {

    // Receive displayed notifications for iOS 10 devices.
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                                        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        // Print message ID.
        print("Message ID: \(userInfo["gcm.message_id"]!)")
        // Print message ID. add Toast

        // Print full message.
        print("%@", userInfo)
        // Print full message.
        print("%@", userInfo)
    }
}

extension AppDelegate : FIRMessagingDelegate {
    // Receive data message on iOS 10 devices.
    func applicationReceivedRemoteMessage(_ remoteMessage: FIRMessagingRemoteMessage) {
        print("%@", remoteMessage.appData)
    }
}

// [END ios_10_message_handling]

Reference: https://firebase.google.com/docs/cloud-messaging/ios/device-group

Hope this will help you.