kk94
kk94

Reputation: 203

Back4App push notifications via Parse Server not working for iOS

Update 2:

Still dealing with the same problem, but I realised that my approach might be flawed - that I cannot send push notifications from Parse Server via Firebase Cloud Messaging to iOS devices the same way I do it for the Android devices.

Instead, as far as I understood, the Parse server will either send a push notification via Firebase (if the recipient device is an Android device) OR via the Apple Push Notification Service (if the recipient device is an iOS device) based on the Installation subscribed to the targeted channel (please correct me if I'm wrong).

What fields in the Installation object are necessary for the Parse Server to send push notifications to iOS devices? Or what values need to be saved for the GCMSenderId and pushType for Installation objects created on iOS devices?

Update I:

Push notifications to iOS from ParseServer via Back4App still does not work.

I tried sending pushes via a Cloud function to bypass the Back4App push dashboard and implemented a very rudimentary function in my main.js:

Parse.Cloud.define('pushsample', async () => {
       
       Parse.Push.send({
         channels: ["TestChannelForMyiOSDevice"],
            data: {
                title: "Hello from Cloud Code",
                alert: "It finally worked!",
            }
        }, { useMasterKey: true });
      
      return 'pushsample called successfully';
});

I am returning a String because I simply copied a template struct from the ParseSwift playground that has a return type String, since this was intended as a quick and dirty test. I didn't figure out how to return a proper server response with error handling yet.

The corresponding ParseCloud struct:

struct CloudFunction: ParseCloud {

    //: Return type of your Cloud Function
    typealias ReturnType = String

    //: These are required by `ParseCloud`
    var functionJobName: String

}

When I call this function, the Back4App server does respond with 'pushsample called successfully' and the push notification also appears in the Back4App Past Pushes list. However, it is only listed there and not actually delivered to my physical iOS device.

I have no experience with JavaScript so far so if anyone can tell me how I can troubleshoot this better or get more diagnostic information from the server that would be a big help already.

As stated in the original post, I use Firebase Cloud Messaging and take advantage of the Channel property saved in the Parse Installation objects on my server to target specific users. This works flawlessly for the Android version of the app, also when using Cloud Code.

For iOS, I am only able to send push notifications from Firebase directly via the "Send your first message" page, targeting my device via its Messaging token. Sending a push notification via channels from Back4App via Firebase does not work.

How can I fix this?

Any input is welcome!

I'm using Parse Server 4.2.0 and ParseSwift 2.5.1 in my app.

 


Original post:

I have an Android app that uses Back4App as a backend and am currently porting it to iOS. I have been struggling with sending push notifications to the iOS app via the Parse Dashboard (or via my Parse backend in general).

In my Android app I implemented push notifications following the Back4App guides by using Firebase Cloud Messaging, which worked perfectly out of the box. I implemented Firebase, registered for push notifications, and saved the GCMSenderId and deviceToken in my Parse Installation and registered Firebase in the Android Push Notifications server settings on Back4App and can send push notifications without any problems.

For the iOS version I tried emulating this. I created the APN .p8 key file, uploaded it to Firebase, implemented Firebase in my App Delegate, registered for push notifications, added push notification capabilities in the App settings, saved the GCMSenderId and the deviceToken in my Parse Installation, and also uploaded the APN file to the iOS Push Notifications server settings on Back4App.

With this setup, I can:

While the push notifications are being sent (according to ParseDashboard), they are not getting delivered to my iOS device. I want to send the pushes via Parse so that I can use the same channel-based distribution system that is already in place with the Android app.

I figured that this is an issue somewhere within my Back4App backend, so I tried setting the "production" value in the APN Auth key part of the Back4App iOS push settings to true and false, but both ways do not work.

How can I fix this?

I started iOS programming recently and have been using SwiftUI for the app so far, so I am quite unfamiliar with the App Delegate lifecycle events, but was able to make it work up until this point. Here is what I am currently using which works in the scenarios listed above:

class AppDelegate: NSObject, UIApplicationDelegate {
    let gcmMessageIDKey = "gcm.message_id"

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()

        Messaging.messaging().delegate = self

        if #available(iOS 10.0, *) {
          // For iOS 10 display notification (sent via APNS)
          UNUserNotificationCenter.current().delegate = self

          let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
          UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: {_, _ in })
        } else {
          let settings: UIUserNotificationSettings =
          UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
          application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
        return true
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

      if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
      }

      print(userInfo)

      completionHandler(UIBackgroundFetchResult.newData)
    }
    
}

@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

    if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
    }

    print(userInfo)

    completionHandler([[.banner, .badge, .sound]])
  }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        
        print("didRegisterForRemoteNotificationsWithDeviceToken: token: \(deviceToken)")
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        
        print("application:didFailToRegisterForRemoteNotificationsWithError: %@", error)
        
    }

  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse,
                              withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo

    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID from userNotificationCenter didReceive: \(messageID)")
    }

    print(userInfo)

    completionHandler()
  }
}

extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {

        guard let deviceTokenFirebase = fcmToken else { return }
        
        var currentInstallation = Installation.current
        currentInstallation?.pushType = "gcm"
        currentInstallation?.GCMSenderId = "mySenderId"
        currentInstallation?.deviceToken = deviceTokenFirebase
        currentInstallation?.save { results in
            
            switch results {
            case .success(let savedInstallation):
                print("Successfully save installation: \(savedInstallation)")
            case .failure(let error):
                print("Failed to update installation: \(error)")
            }
        }  
        
    }
}

Any help is much appreciated!

Upvotes: 1

Views: 518

Answers (1)

kk94
kk94

Reputation: 203

It was the false deviceToken that was saved - it was the Firebase token, not the APN token!

Here is how I figured it out:

I was able to send push notifications from Firebase, but not from ParseServer. The token that I had saved was the token from within the messaging function in my App Delegate - which is a Firebase function, not an APN function. I realised that that token never changed, while I read online that an app re-registers for the APN Service and thus gets a new APN token every time the app starts.

Checking my log, I found that neither didRegisterForRemoteNotificationsWithDeviceToken nor didFailToRegisterForRemoteNotificationsWithError were ever called for my app.

What solved the problem? I commented out the Firebase parts in my didFinishLaunchingWithOptions:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        // FirebaseApp.configure()

        // Messaging.messaging().delegate = self

        if #available(iOS 10.0, *) {
          // For iOS 10 display notification (sent via APNS)
          UNUserNotificationCenter.current().delegate = self

          let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
          UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: { (granted, error) in
                if let error = error {
                    print("requestAuthorization error: \(error)")
                } else if granted {
                    DispatchQueue.main.async {
                        print("granted: now registeringForRemoteNotifications()")
                        UIApplication.shared.registerForRemoteNotifications()
                    }
                }
            })
        } else {
          let settings: UIUserNotificationSettings =
          UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
          application.registerUserNotificationSettings(settings)
        }

        return true
    }

I figure that Firebase somehow prevents the APN functions from being called. Since I won't need Firebase Cloud Messaging anymore, I will only use Firebase Analytics and send my push notifications from my Parse server.

Upvotes: 0

Related Questions