invertase / react-native-firebase

๐Ÿ”ฅ A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.63k stars 2.2k forks source link

[๐Ÿ›] iOS not receiving silent background push notifications via Firebase #4697

Closed trbsi closed 3 years ago

trbsi commented 3 years ago

I went through every issue reported regarding this problem but I couldn't find a solution. The same code works on Android, but not on iOS. I'm trying to receive silent background push notification so I can modify it via Notifee.

Information: I followed the whole tutorial from https://rnfirebase.io/messaging/usage iOS 14.2 Foreground working fine

I'm sending notification manually and testing when my app is in background. I'm getting notification when I include "notification": {"title": "soemthing"}.

curl -H "Content-type: application/json" -H "Authorization:key=MY_KEY"  -X POST -d '{"content_available": true, "data": { "foo": "1","bar": "2"},"to" : "MY_TOKEN", "apns":{"payload": {"aps": {"content-available" : 1}}, "headers":{  "apns-priority":"5",  "apns-push-type":"background", "apns-topic": "MY_BUNDLE_ID"}}}' https://fcm.googleapis.com/fcm/send

I can confirm that notifications are working.

Here is my code: packages.json

"dependencies": {
    "@notifee/react-native": "^0.15.1",
    "@react-native-async-storage/async-storage": "^1.13.2",
    "@react-native-community/masked-view": "^0.1.10",
    "@react-native-community/netinfo": "^5.9.9",
    "@react-native-firebase/app": "^10.2.0",
    "@react-native-firebase/messaging": "^10.2.0",
    "@react-native-picker/picker": "^1.9.3",
    "@react-navigation/bottom-tabs": "^5.11.2",
    "@react-navigation/native": "^5.8.10",
    "@react-navigation/stack": "^5.12.8",
    "expo": "~39.0.2",
    "expo-splash-screen": "~0.6.2",
    "expo-status-bar": "~1.0.2",
    "expo-updates": "~0.3.2",
    "moment": "^2.22.2",
    "react": "16.13.1",
    "react-native": "~0.63.3",
    "react-native-button": "^2.3.0",
    "react-native-contacts": "^6.0.3",
    "react-native-device-info": "^7.3.1",
    "react-native-gesture-handler": "^1.7.0",
    "react-native-image-picker": "^3.0.1",
    "react-native-keyboard-manager": "^4.0.13-17",
    "react-native-permissions": "^3.0.0",
    "react-native-safe-area-context": "^3.1.9",
    "react-native-screens": "^2.10.1",
    "react-native-toast-message": "^1.4.1",
    "react-native-unimodules": "~0.11.0",
    "react-native-vector-icons": "^5.0.0"
  },

index.json

messaging().setBackgroundMessageHandler(async message => {
    console.log('message background: ');
    console.log(message);
    logToServer('Uลกao u setBackgroundMessageHandler.onMessageReceived');
    logToServer(message);

    const accessToken = await AsyncStorage.getItem('@passwordAccessToken');
    if (null === Firebase.notificationProvider) {
        logToServer('Firebase.notificationProvider je null');
        if (Firebase.PUSH_PROVIDER === Firebase.PUSH_PROVIDER_NOTIFEE) {
            Firebase.notificationProvider = new NotifeePushProvider();
            logToServer('Kreirao je new NotifeePushProvider');
        }
    }

    //when user logs out he shouldn't receive push, with "onMessage()" method I can unsubscribe
    //with setBackgroundMessageHandler() I cannot unsubscribe so I need to make this check
    if (null !== accessToken) {
        Firebase.notificationProvider.displayNotification(message.data);
        logToServer('Pozvao se displayNotification');
    }
});

function HeadlessCheck({ isHeadless }) {
    if (isHeadless) {
        // App has been launched in the background by iOS, ignore
        return null;
    }

    return <App />;
}

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in the Expo client or in a native build,
// the environment is set up appropriately
registerRootComponent(HeadlessCheck);

I even tried with other functions but they are for foreground:


async function onMessageReceived(message) {
    console.log('onMessage: ');
    console.log(message);

    const accessToken = await AsyncStorage.getItem('@passwordAccessToken');
    if (null === accessToken) {
        return;
    }

    Firebase.notificationProvider.displayNotification(message.data);
}

async function onMessageReceivedNotificationOpenedApp(message) {
    logToServer('Usao je u onMessageReceivedNotificationOpenedApp');
    onMessageReceived(message);
}

function subscribeToPushNotifications() {
    messaging().onNotificationOpenedApp(onMessageReceivedNotificationOpenedApp);
    messaging().onMessage(onMessageReceived);
}

It never enters setBackgroundMessageHandler, it enters when I build on Android. As you may see I added logToServer and I cannot see any logs from iOS on my server.

I exhausted all of options, I don't know what's going on.

mikehardy commented 3 years ago

I'm not sure about that FCM JSON, why is content-available in there twice :thinking: - makes the rest seem suspect. I would triple check that.

More importantly, it's important to know that data-only FCM to iOS is not reliable. It works much of the time but there is never a guarantee it will work. If you went through all the other issues you would see advice to launch release mode build on real device from Xcode so you could watch the console output. If you do that then you may notice as mentioned in other issues that the device receives the FCM but just does not decide to launch the app for whatever reason (throttling for CPU usage, temparature, because low power mode is on, etc etc). There is never a guarantee.

For 100% guarantee you must use an actual notification payload (or mixed payload) and have the SDK display a notification itself. That is irritating because it doesn't give you the device API control over the notification that Notifee would, but it is the way iOS and the native firebase-ios-sdk is designed unfortunately.

I believe you still need to ask for permission as well, I don't see that referenced anywhere.

trbsi commented 3 years ago

I tried various combinations for payload. I'm wondering why not even one silent notification is received. I have permission for receiving push, I checked that.

Would non-FCM data-only push notifications be more reliable?

mikehardy commented 3 years ago

data-only push notifications are not reliable. I don't believe sender has anything to do with it as they all go to APNS

you tried various combinations of payload but settled on that is objectively invalid? I don't understand that

Here is a flag for more debugging in Xcode console https://github.com/invertase/react-native-firebase/issues/4603#issuecomment-733462018

Here is the reference https://firebase.google.com/docs/cloud-messaging/http-server-ref

trbsi commented 3 years ago

Not settled, it really doesn't matter which payload I send it's always the same, not working. I'll close the issue, maybe I'll figure something out, if I do I'll post my solution here

mikehardy commented 3 years ago

I will be really curious for any final solution that works, or a root cause (if you see one with debug flag enabled and Xcode console watching...)

trbsi commented 3 years ago

I tried with https://github.com/react-native-push-notification-ios/push-notification-ios The same problem remains.

I tried with sockets, I thought that sockets will be connected all the time so I'll be able to trigger local push notifications via sockets, once app goes to background socket connection breaks.

There is no way to be 100% sure that push will be delivered which is important for me. I trashed my app.

mikehardy commented 3 years ago

Network connections are unreliable by definition, and especially so on mobile of course Notifications are notoriously abused by developers to spam users or wake up apps too much, so they are tightly controlled

There is just no avoiding that, but the more a user uses your app and the more value they receive from it, the more likely they are to allow notifications and the more the power miser algorithm will grant the app wakeups etc., like whatsapp etc

trbsi commented 3 years ago

I even try to send payload with "notification" key, such as

{
    "content_available": true,
    "data": {
        "sender_id": "2",
        "sound": "ping",
        "title": "Hi",
        "body": "Ho",
        "image": ""
    },
    "notification": {
        "title": "",
        "body": "New message incoming..."
    },
    "to": "TOKEN",
    "apns": {
        "payload": {
            "aps": {
                "content-available": 1
            }
        },
        "headers": {
            "apns-priority": "5",
            "apns-push-type": "background",
            "apns-topic": "com.dakovo.ping"
        }
    },
    "android": {
    }
}

I receive content from "notification" but still app does not wake and does not trigger my background event listener

mikehardy commented 3 years ago

Why do you still have invalid JSON? That top content-available is incorrect. I have mentioned it multiple times. Fix your JSON.

When you send notification payload and data together it is called a "mixed" payload. Your app will not wake in that case. Have you examined the payload type vs app state vs handler charts?

https://rnfirebase.io/messaging/usage#message-handlers

rahulje9 commented 3 years ago

Even I'm having issues, For me, I wasn't getting any notification on iOS for a long time and when I tried to test it with the REST API way, I got the push for about a week while I was testing things, and now that too stopped working. I'm not sure what to do at this point. I'm posting the JSON payload I'm sending.

{
    "to": "FCM_TOKEN",
    "content_available": true,
    "mutable_content": true,
    "data": {
      "message": "Batman!",
      "mediaUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/FloorGoban.JPG/1024px-FloorGoban.JPG"
    },
    "notification": {
      "body": "Enter your message",
      "sound": "default"
    }
  }

URL - https://fcm.googleapis.com/fcm/send

mikehardy commented 3 years ago

This issue is about silent ("data only") push. You have a notification chunk, so there is nothing silent about those. They are handled internally by firebase-ios-sdk and are posted to the notification center with no possibility of your handler getting control until the user interacts with the notification by tapping it. At that point the app starts (or is foregrounded) and the handler should be called if configured correctly

rahulje9 commented 3 years ago

I've posted a test notification data which I got from StackOverflow, even with those I'm not getting the push notification all the time. I have got the notification for a while yesterday, from that I'm assuming that I have configured it correctly, please correct me if I'm wrong.

nm-antecuic commented 2 years ago

have anybody managed to fix this issue? I'm losing my mind here. ๐Ÿคฃ foreground and background notifications are working as expected on android, and on iOS as well, except when app is killed. then my data-only messages don't trigger notification at all.

mikehardy commented 2 years ago

@nm-antecuic there is no "fix" as this is not seen as a problem by Apple. Data-only messages do not have guaranteed delivery as a well-documented, intentional choice by Apple. If your app is killed and a data-only message is sent to it, it will probably never arrive.

Some more info that may or may not be useful (but you can watch logs to see iOS deciding not to start your app perhaps): https://github.com/invertase/react-native-firebase/issues/4697#issuecomment-749068019

For reliable delivery send a notification block in the JSON payload of your FCM, and if you want a chance to intercept it prior to posting the notification use a full-featured notification package with an app extension, as is documented for example in Notifee.app

nm-antecuic commented 2 years ago

@mikehardy so I have this feature where I need to send message to all app users and depending on somebody's location show them a notification about that. I thought this is a way to go since we can't store users location on server. I can use Notifee.app for notifications and implement this feature, when notification is sent i can intercept it and show it to specific user no matter in which current state app is? right?

mikehardy commented 2 years ago

I have learned through hard experience to never offer certainty with regard to delivery of remote messages to mobile devices. You'll simply have to do the work to set up a test for your use case and collect results

nm-antecuic commented 2 years ago

@mikehardy I see. Thanks a lot for help! ๐Ÿ‘๐Ÿผ

timothyerwin commented 9 months ago

There is just no avoiding that, but the more a user uses your app and the more value they receive from it, the more likely they are to allow notifications and the more the power miser algorithm will grant the app wakeups etc., like whatsapp etc

@mikehardy do you know if this is how whatsapp is currently able to get messages delivered in the app while in background? the issue is I have a chat app and there is too much delay when opening the app and reading messages, so I need to be able to read them in the background at the time of the push notification.

I know that whatsapp historically had used voip unrestricted access but I believe apple since closed that loop?

I'm just trying to figure out how should I handle this? is messaging.setBackgroundMessageHandler the only way to handle this scenario today or am I missing something?

I've also read that conntent-available: 1 should be sent separately from a normal push notification? is this true? i.e., would I need to send 2, one for display and one for a silent notification?

sorry for all the questions, I'm just confused and don't want to waste a bunch of time going down a lot of rabbit holes if someone knows how this is handled today.

mikehardy commented 9 months ago

Use a real notification block with an extension helper for skinning/ branding on iOS, nothing else is reliable