react-native-firebase icon indicating copy to clipboard operation
react-native-firebase copied to clipboard

🔥 [🐛] [IOS] onMessage callback is not called when push notification was sent via pinpoint

Open dusan-dragon opened this issue 3 years ago • 23 comments
trafficstars

Issue

👋 In our project we are using RNFB v5 and we are migrating to v6 however when we receive push notification on IOS we would expect that onMessage is called. This is not happening when application is in foreground.

It seems that RNFB/messaging does not handle push notifications at all if they were not send via gcm / firebase? I see a lot of weird conditions if ("gcm.message_id") We are sending push notifications to our devices via pinpoint which does not have gcm.message_id in payload and therefor this condition always fails...

Example of such condition: https://github.com/invertase/react-native-firebase/blob/514e6bd51e7624a6403dda706f4e5b65cee63422/packages/messaging/ios/RNFBMessaging/RNFBMessaging%2BAppDelegate.m#L132

If I remove this condition, then callback is called in our application. This seems like a bug to me as it was kind of working in v5 but it is not working in v6.

Here is also how the "userInfo" for received notification looks like for us:

{
    aps =     {
        alert =         {
            body = "Date with time: 1";
            title = "Title2 with datetime";
        };
        "content-available" = 1;
        "mutable-content" = 0;
        sound = "custom.wav";
    };
    data =     {
        jsonBody =         {
            customerId = 1;
            messageCode = "test_notification";
            notificationId = "c964ebb8-85a9-42b7-9be5-00a90ea4cb6b";
        };
    };
}

Project Files

Javascript

Click To Expand

package.json:

"@react-native-firebase/app": "13.0.1",
"@react-native-firebase/messaging": "13.0.1",

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • [ ] I'm not using Pods
  • [x] I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A

Android

Click To Expand

Have you converted to AndroidX?

  • [ ] my application is an AndroidX application?
  • [ ] I am using android/gradle.settings jetifier=true for Android compatibility?
  • [ ] I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- N/A -->

Environment

Click To Expand

react-native info output:

info Fetching system and libraries information...
System:
    OS: macOS 12.0.1
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
    Memory: 243.30 MB / 32.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.13.0 - ~/.nvm/versions/node/v16.13.0/bin/node
    Yarn: 1.22.15 - /usr/local/bin/yarn
    npm: 8.1.0 - ~/.nvm/versions/node/v16.13.0/bin/npm
    Watchman: Not Found
  Managers:
    CocoaPods: 1.11.2 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 14.4, DriverKit 20.2, macOS 11.1, tvOS 14.3, watchOS 7.2
    Android SDK:
      API Levels: 23, 26, 27, 28, 29, 30, 31
      Build Tools: 28.0.3, 29.0.0, 29.0.2, 29.0.3, 30.0.2, 30.0.3, 32.0.0
      System Images: android-27 | Google APIs Intel x86 Atom, android-28 | Intel x86 Atom_64, android-28 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-29 | Google Play Intel x86 Atom, android-30 | Google APIs Intel x86 Atom, android-31 | Google APIs Intel x86 Atom_64
      Android NDK: Not Found
  IDEs:
    Android Studio: 2020.3 AI-203.7717.56.2031.7583922
    Xcode: 12.4/12D4e - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_275 - /Users/dusan.plavak/.sdkman/candidates/java/current/bin/javac
    Python: 2.7.18 - /usr/bin/python
  npmPackages:
    @react-native-community/cli: 6.1.0 => 6.1.0
    react: 17.0.2 => 17.0.2
    react-native: 0.65.1 => 0.65.1
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found
  • Platform that you're experiencing the issue on:
    • [x] iOS
    • [ ] Android
    • [ ] iOS but have not tested behavior on Android
    • [ ] Android but have not tested behavior on iOS
    • [ ] Both
  • react-native-firebase version you're using that has this issue:
    • 13.0.1
  • Firebase module(s) you're using that has the issue:
    • messaging
  • Are you using TypeScript?
    • Y & 4.4.2

dusan-dragon avatar Dec 03 '21 15:12 dusan-dragon

You may want to integrate notifee - in general react-native-firebase is in the business of handling FCM and leaves other notifications alone, yes. Notifee is working through an extension to process all notifications for an app. Originally notifee was also restricting it self to "noticing" (that is, posting events for) only notifications it posted itself, but I think it posts events for all notifications now?

https://notifee.app/react-native/docs/events#foreground-events

mikehardy avatar Dec 03 '21 16:12 mikehardy

Well that added notifee on already however it is not working for us.

My thinking was -> when we receive onMessage callback when app is in foreground, use notifee to show local notification. That is also in "integration guide" for RNFB library: Screenshot 2021-12-03 at 17 41 06

I will test the events again.

ghost avatar Dec 03 '21 16:12 ghost

This is the point where I think everything is just guesses. Nothing beats an App.js you can drop in runnable app. https://stackoverflow.com/help/minimal-reproducible-example

No promises though - I may not have time to look at it for a while, typically the clean App.js results in clean thinking though and the problem + solutions become more obvious

https://github.com/mikehardy/rnfbdemo/blob/master/notifee-demo.sh https://github.com/mikehardy/rnfbdemo/blob/master/make-demo.sh

to generate skeletons

mikehardy avatar Dec 03 '21 16:12 mikehardy

We are having a similar issue after upgrading from version 5 to version 6. Data-only PNs on iOS do not trigger the OnMessage callback while app is in the foreground. In the background it doesn't work either but we're not worrying about that.

Visible PNs work and we can see the callback trigger.

On version 5 it was working. We can still see it working in the older build of our app on the same phone. So we don't think it has anything to do with iOS system or APN requirements.

chrisrollins avatar Dec 07 '21 21:12 chrisrollins

Unfortunately from 5 to version 13 (version 6 was quite a while ago) also includes an incredible amount of version difference, across several breaking changes, in the underying firebase-android-sdk and firebase-ios-sdk, and you may have changed other things at the same time as upgrading react-native-firebase so it does not do much for us, from an information perspective. Still could be anything anywhere.

mikehardy avatar Dec 07 '21 21:12 mikehardy

Sorry, the documentation just calls it v6.x

https://rnfirebase.io/migrating-to-v6

But yes I am aware the entire thing might as well be rebuilt from scratch at this point. I looked at the source code of both versions. I noticed that in v5.x the library implemented didRecieveRemoteNotification and now it does not.

I exhaustively went through everything I could find to troubleshoot this but the fact is that the new library doesn't receive data-only PNs on iOS while the old library does. Do data-only PNs actually work for anybody out there?

chrisrollins avatar Dec 07 '21 22:12 chrisrollins

Yes...but ... you have to have the app "well loved" by the power miser algorithm in iOS, and the device needs to have a lot of power to feed as "power budget" to the internal / blackbox power miser algorithm. If the battery is 100% and you're on power (cable plugged in), and you've used the app recently, and it's not force quit, and power saving isn't on, and background refresh isn't disabled, that's about as solid as a case as you can get for it to work.

It works for me all the time like this, plus in less friendly circumstances when I use my test apps a lot.

If you watch the device logs you may find evidence that it was delivered or not

packages/messaging/ios/RNFBMessaging/RNFBMessaging+NSNotificationCenter.m:      // application:didReceiveRemoteNotification:fetchCompletionHandler: will not get called unless
packages/messaging/ios/RNFBMessaging/RNFBMessaging+UNUserNotificationCenter.m:    // Don't send an event if contentAvailable is true - application:didReceiveRemoteNotification
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:    SEL didReceiveRemoteNotificationWithCompletionSEL =
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:        NSSelectorFromString(@"application:didReceiveRemoteNotification:fetchCompletionHandler:");
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:            respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) {
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:      // application:didReceiveRemoteNotification:fetchCompletionHandler:
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:                                                   didReceiveRemoteNotificationWithCompletionSEL);
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:                      didReceiveRemoteNotificationWithCompletionSEL,
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:    didReceiveRemoteNotification:(NSDictionary *)userInfo
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:    DLog(@"didReceiveRemoteNotification Firebase Auth handeled the notification");
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:    DLog(@"didReceiveRemoteNotification gcm.message_id was present %@", userInfo);
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:        DLog(@"didReceiveRemoteNotification sharedInstance.backgroundMessageHandlerSet = %@",
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:          DLog(@"didReceiveRemoteNotification without waiting for backgroundMessageHandlerSet to "
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:                DLog(@"didReceiveRemoteNotification waiting for "
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:                  ELog(@"didReceiveRemoteNotification timed out waiting for "
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:              DLog(@"didReceiveRemoteNotification after waiting for backgroundMessageHandlerSet");
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.m:      DLog(@"didReceiveRemoteNotification while app was in foreground");
packages/messaging/ios/RNFBMessaging/RNFBMessaging+AppDelegate.h:    didReceiveRemoteNotification:(NSDictionary *)userInfo

mikehardy avatar Dec 07 '21 22:12 mikehardy

we found that if the device was too hot even, it would just silently fail (well, it would drop a message visible in console.app) a data-only FCM. My take on it: you want reliable delivery? notifications are it

That's a huge source of the effort involved in notifee.app in fact - to hook into the notification delivery pathway on ios so you have reliable delivery (notification block in your FCM JSON) but you have control (your code executes before notification presentation). Check the docs there specifically for the extension helper. It's really no fun unfortunately

mikehardy avatar Dec 07 '21 22:12 mikehardy

So you can confirm that data-only notifications do work for you on iOS? Can you show me some example payloads that work for you?

chrisrollins avatar Dec 07 '21 22:12 chrisrollins

Nothing that fancy I think? Ignore the question-y comments, this code actually runs in production as a pub-sub scheduled firebase function, thus with access to the firebase nod admin SDK, that's the API in use here.

        const recipientFcmTokens: string[] = [];
        Object.keys(recipientTokenMap).forEach((key) => {
          recipientFcmTokens.push(recipientTokenMap[`${key}`]);
        });

        console.log('Sending to recipient tokens:', JSON.stringify(recipientFcmTokens));

        // Build up the data payload - it's restricted to string/string so we map options
        // into an 'optionName': value set of keys, and those are unmapped by clients.
        const data: { [key: string]: string } = {
          jobType: 'DeviceEventCheck',
          // sender: req.body.data.senderKullkiId,
        };
        const payload = {
          data,
        };
        // if (req.body.data.options) {
        //   // console.log(
        //   //   'Message contains options: ' + JSON.stringify(req.body.data.options, null, 2)
        //   // );
        //   Object.keys(req.body.data.options).forEach((optionKey) => {
        //     // console.log('key ' + optionKey + ' has ' + req.body.data.options[optionKey]);
        //     payload.data['option' + optionKey] = req.body.data.options[optionKey];
        //   });
        // }
        console.log('Payload is now: ' + JSON.stringify(payload, null, 2));

        // Set up our options
        const options: MessagingOptions = {
          contentAvailable: true,
          // fcm_options: {
          //   analytics_label: 'FCMData' + fcmType,
          // },
        };

        options.priority = 'high'; // is that correct?

        // If it is a device check, allow collapse etc
        // if (fcmType === 'DeviceEventCheck') {
        //   message.android.collapse_key = 'DeviceEventCheck';
        // }

        const failedTokens: string[] = [];
        for (const recipientFcmToken of recipientFcmTokens) {
          const response = await getMessaging(getAdminApp()).sendToDevice(
            recipientFcmToken,
            payload,
            options
          );
          if (response.failureCount > 0) {
            console.log(
              'Failure with token ' + recipientFcmToken + ' - ' + JSON.stringify(response)
            );
            failedTokens.push(recipientFcmToken);
          }
          if (response.successCount > 0) {
            console.log('Success with token ' + recipientFcmToken);
            success = true;
          }

mikehardy avatar Dec 07 '21 23:12 mikehardy

Did you find that the options field is necessary?

chrisrollins avatar Dec 07 '21 23:12 chrisrollins

Absolutely vital per docs. If you don't say content available, you might as well just send it to /dev/null. If you don't set priority high, I don't think it will work for you. This is all documented? https://rnfirebase.io/messaging/usage#data-only-messages

mikehardy avatar Dec 07 '21 23:12 mikehardy

K I'll try it.

Interestingly it does work on the old version of the library without those. I also tried adding some logs to the library to see if it was stopping somewhere, but it seemed like it didn't show up at all.

I'd want to believe you if you told me iOS system requires those fields but that doesn't explain why it worked before we updated the library.

chrisrollins avatar Dec 07 '21 23:12 chrisrollins

I can only refer to this:

Unfortunately from 5 to version 13 (version 6 was quite a while ago) also includes an incredible amount of version difference, across several breaking changes, in the underying firebase-android-sdk and firebase-ios-sdk, and you may have changed other things at the same time as upgrading react-native-firebase so it does not do much for us, from an information perspective. Still could be anything anywhere.

And my personal observation of working code above

mikehardy avatar Dec 07 '21 23:12 mikehardy

😄 @chrisrollins you are kind of hijacking this bug ticket. Imho what you are observing is something else from what I am trying to report here. Maybe creating new issue could be better

ghost avatar Dec 08 '21 05:12 ghost

A reasonable point @dusan-dragon - I rescanned your original message, and I note that you mention you do not use FCM to send the message, rather you use pinpoint. I think based on that the only real solution for you to get foreground handlers called when a notification posts, is Notifee

Note that OneSignal integration works there - https://notifee.app/react-native/docs/integrations/onesignal - I think pinpoint should work as well. Curious how it goes if you try it.

mikehardy avatar Dec 08 '21 05:12 mikehardy

I did not have time to create small reproducible sample app so far, however we have notifee integrated and behaviour is same - no notifee events unless I remove that gcm. message_id check.

I think it is because notifee is not that much about remote push notifications but rather for local notifications so it do not have "handlers / callbacks" for listening to remote notifications 😞

For IOS I see that there is special section for Remote notifications (push) however it is via native code (obj-c) - with service extension which is kind of sad because it suggest that our BE should know about FE implementation and format the push data according to it

Also note that as for pinpoint we do NOT have any library in our mobile app, there is no integration for pinpoint in mobile app (we do not use any) so we were just listening to RNFBv5 callback in our app and then creating local notifications when push received (except when app was killed, then the push was shown automatically by OS)

ghost avatar Dec 08 '21 06:12 ghost

Hmm - well notifee isn't going to solve it then - I understand a little better now I think - first I can say https://github.com/ds300/patch-package and there is no reason you should be blocked on moving forward with the real work of your project while we chat about this. Hack that gcm.message_id check out of the obj-c file and from a feature perspective, get on with life :-). But for a real fix, it would be a breaking change to just remove the check for everyone, but perhaps a toggle you could flip on in firebase.json named something like "onlyHandleGCMNotifications" that defaults to true would be useful? Turn it to false and we pass into that conditional block also?

Does android also pose this restriction or do you get onMessage events on android ?

mikehardy avatar Dec 08 '21 07:12 mikehardy

On android it works fine. As for patch-package we are already using it. We were also patching v5 😄 however I wanted to avoid it for v6 / v13 as it was some time I was working on notifications so It would require some more time to grasp all ios context into my brain 😄 as there is couple places where this check is and I am not sure if I need to fix it everywhere...

So I guess I am just confused little bit on what RNFB v6 cloud-messaging trying to be. Coming from v5 my expectation was that it will be providing listeners for push notifications for all cases (bg, fg, quit, open, touch....) and for both platforms and all OS versions and I as dev can then decide what I can do with it. In v5 it was kind of working with help of some hacking 😄
V5 had also ability to create local notifications. So I expected that when we migrate from v5 - v6 we still be able to keep listening for that events but for creating local notifications we should use notifee...

So in the end I am still not sure if this is bug or just my expectations are simply wrong.

If this is bug then yeah we can work on solution and so far that flag looks good to me.

ghost avatar Dec 08 '21 07:12 ghost

@react-native-firebase/messaging is intended to call JS event handlers for Firebase Cloud Messages related activities Those all have the key we're searching for there, in the FCM JSON

That does include all the app state cases you mention with the exception of device-local notification taps when app is in foreground.

I think that's the full scope. For more complicated creation / interaction with local visible notifications, that's Notifee. Those aren't firebase APIs and the local device API surface area is massive, so it needs its own package.

I think you're understanding things correctly with regard to RNFB +/- Notifee and what's what.

It may be that from v5 to v6+ the RNFB/messaging started constraining what it would react to - with that gcm.message_id conditional - so it was truly FCM-specific. That's not technically wrong for the package but at the same time it is a little bit of an artificial limitation. A bug? A design choice? I dunno, but allowing onMessage to trigger on all remote messages seems fine as capability to allow

mikehardy avatar Dec 08 '21 08:12 mikehardy

Hello 👋, to help manage issues we automatically close stale issues. This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 18 '22 18:04 stale[bot]

As a note, I ran into a similar issue with a backend service using AWS SNS that pushes to APNS (iOS) and FCM (Android). Things work fine on Android without changes, however, patching the removal the gcm.message_id conditionals did resolve my issue to get things firing for remote messages.

In addition, I had to ensure the payload contained notifee_options with the data embedded there AND in the root APS payload to allow for FCM to also get any data. This is a bit messy with redundancy but works for now.

I think the long run will be to migrate APNS to FCM for simplicity. Hopefully, this is helpful for someone.

pixelknitter avatar May 04 '22 22:05 pixelknitter

Hmm - I remain sort of ambivalent about the "only respond if message has gcm.message_id" conditional. It seems like it will be a breaking change to get rid of it (we'll be responding to more messages maybe?) and honestly I'm a little surprised that messages come in without the ID (I suppose there is some other messaging service? why not...).

Anyway, I could be persuaded to merge a PR like this and would be positive on it especially if it contained support for a conditional key in firebase.json that was something like messaging_respond_to_all_messages, default to false, with documentation in the json schema explaining what the key did, and then used that config value to either restrict to gcm.message_id or not (so it is a non-breaking change

That's a little intricate because it touches our config machinery but I have a PR link where you can see the exact changes involved - it isn't too bad, just an android manifest and ios plist placeholder for the value, and config script editions to interpolate it

Here's a commit that adds a key, to show exactly how it is done https://github.com/invertase/react-native-firebase/pull/5600/commits/05d4df9feccee80883ea28fb2826061882225182

mikehardy avatar May 04 '22 22:05 mikehardy

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Dec 05 '22 19:12 github-actions[bot]

@mikehardy Any update on that.

I am facing the exact same issue.

The messaging().onMessage() is not triggered

  • on iOS device
  • when app in foreground
  • when notification is sent through Intercom which is using directly APNS integration

Dependencies:

"react-native": "0.72.5",
"@react-native-firebase/app": "^18.7.2",
"@react-native-firebase/messaging": "^18.7.2",
"@notifee/react-native": "^7.8.0",

dgreasi avatar Dec 12 '23 13:12 dgreasi