evollu / react-native-fcm

react native module for firebase cloud messaging and local notification
MIT License
1.73k stars 681 forks source link

I am not receiving data messages to IOS device on background and killed state. #1015

Closed elad823 closed 5 years ago

elad823 commented 5 years ago

Hi, I am not receiving data messages in some cases to the IOS device. My server sends data messages with content_available:"true". My expected results when an notification arrived are : 1) Save the data 2) Present a tray notification if the app is in one of the 3 states: killed, background, foreground but not in the right screen.

My actual results are: 1) When the app is killed the notification never arrives or presented. 2) When the app is in background : the first 9 notifications arrives and presented as expected, then the next notifications are not presented , but when i open the last one that was sent id presented. If I wait about 30 minutes in background state , I can receive again just 9 notifications. 3) When the app is in foreground the notifications arrives and presented as expected 4) Sometimes I get twice or even 4 times the same notification.

Here is my code:

import FCM, { FCMEvent,
              NotificationType,
              WillPresentNotificationResult,
              RemoteNotificationResult } from 'react-native-fcm';
import { Platform,AppState} from 'react-native';
import deviceLog from 'react-native-device-log'
import AnalyticsUtil from '@helpers/AnalyticsHelper';
import {
  PUSH_TYPE_CHAT_MESSAGE,
  PUSH_TYPE_CHAT_INVITATION,
  PUSH_TYPE_CHAT_INVITATION_APPROVED
} from '@helpers/Constants';

import NavigationService from './NavigationService';

import Server from './Server'
import Storage from './RealmStorage';
import Users from './Users';

const FCM_ENABLED=true;

class FCMHelper {

  async setBadge(badge)
  {
    await FCM.setBadgeNumber(badge)
  }

  async resetBadge()
  {
     await FCM.setBadgeNumber(0);
  }

  async claculateBadge(count)
  {
     await FCM.setBadgeNumber(badge);
  }

  async getToken()
  {
    if(!this.enabled)
    {
      console.warn('FCM disabled')
      return null;
    }
    let token = await FCM.getFCMToken();
    return token;
  }

  registerToToken(callback)
  {
    if(!this.enabled)
    {
      console.warn('FCM disabled')
      return null;
    }
    FCM.on(FCMEvent.RefreshToken, token => {
      callback(token);
    });
  }

  registerToNotification(isChatOpen,loadMessages,loadChats,loadChatRequests,isChatRequestsOpen)
  {
      this.isChatOpen =isChatOpen;
      this.loadMessages=loadMessages;
      this.loadChats=loadChats;
      this.loadChatRequests=loadChatRequests;
      this.isChatRequestsOpen=isChatRequestsOpen;
  }

  async initFCM() {
     deviceLog.log("init fcm");
    try
    {
        let result = await FCM.requestPermissions({badge: true, sound: true, alert: true});
        console.log("FCM.requestPermissions result",result)
        FCM.enableDirectChannel();
        let token = await FCM.getFCMToken();
        FCM.getInitialNotification().then(notif => {
          console.log("INITIAL NOTIFICATION", notif);
        });

        FCM.on(FCMEvent.Notification, this.handleNotification);

        FCM.on(FCMEvent.RefreshToken, token => {
            console.log("fcm refresh token",token)
            this.refreshToken(token);
        });

        FCM.on(FCMEvent.DirectChannelConnectionChanged, (data) => {
          console.log('direct channel connected:' , data);
        });

        setTimeout(function() {
          FCM.isDirectChannelEstablished().then(d => console.log('isDirectChannelEstablished:',d));
        }, 1000);
        this.enabled=true;
        console.info('FCM succed','token:',token)
        return token;
    } catch(e)
    {
      console.warn('FCM failed',e)
      this.enabled=false;
      return null;
    }
  }

  handleNotification= async (notif)=>
  {
    deviceLog.log('notification: t:'+notif.type +' nt:'+notif._notificationType,'Appstate:'+AppState.currentState,notif)
    console.log('new notification. Appstate: ',AppState.currentState,' notification: ',notif)
    switch (notif._notificationType) {
        case NotificationType.Remote:
            /*const PUSH_TYPE_CHAT_MESSAGE = "chat_message";
            const PUSH_TYPE_CHAT_INVITATION = "invitation";
            const PUSH_TYPE_CHAT_INVITATION_APPROVED = "chat_invitation_approved";*/
            let event=invitation=inviting=titleText=subtitleText=null;

            switch (notif.type)
            {

              case PUSH_TYPE_CHAT_MESSAGE:
                //save message
                let message= JSON.parse(notif.message);
                let {author,chat,text}= message;
                let chatOpen =this.isChatOpen(chat)
                await Storage.insertChatMessage(chat,author,text,!chatOpen);
                // findout navigation screen compare to 'ChatDetails' and props.chatId == chatid
                await this.loadChats(true);
                if (!chatOpen){
                  FCM.presentLocalNotification({
                        title: "New Message:"  + notif.author_name,
                        body: notif.text,
                        show_in_foreground: true,
                        data:{
                          chat,
                          type: PUSH_TYPE_CHAT_MESSAGE,
                          author
                        },
                        sound: "default",
                        //badge:
                  })
                }
                else{
                    await this.loadMessages(chat,true);
                }
                notif.finish(RemoteNotificationResult.NewData);
                break;

              case PUSH_TYPE_CHAT_INVITATION:
                let chatRequestsOpen =this.isChatRequestsOpen()
                invitation= JSON.parse(notif.invitation);
                event = invitation.event;
                inviting= invitation.inviting;
                titleText = "New chat invitation";
                subtitleText = "from " + inviting.name;
                if (event && event != "null") {
                  subtitleText+= "\nabout " + event;
                }
                await this.loadChatRequests();
                if (!chatRequestsOpen){
                  FCM.presentLocalNotification({
                        title: titleText,
                        body: subtitleText,
                        show_in_foreground: true,
                        data:{
                          chat:invitation.id,
                          author:inviting.id,
                          type: PUSH_TYPE_CHAT_INVITATION
                        },
                        sound: "default",
                        //badge:
                  })
                }
                notif.finish(RemoteNotificationResult.NewData);
                break;

              case PUSH_TYPE_CHAT_INVITATION_APPROVED:
                invitation= JSON.parse(notif.invitation);
                event = invitation.event;
                let chatId= parseInt(notif.chatId)
                let approvedBy= parseInt(notif.approving_by);
                titleText = "New available chat with";

                let user = await Users.fetchUsers([approvedBy],true);
                user =user[0];
                let name = user.name;
                subtitleText = name?name+" ":"";
                subtitleText= subtitleText+ "wants to talk to you";
                if (event && event != "null") {
                  subtitleText+= " about " + event;
                }
                await Storage.insertChatMessage(chatId,approvedBy,subtitleText,true,true);
                // findout navigation screen compare to 'ChatDetails' and props.chatId == chatid
                await this.loadChats(true);
                FCM.presentLocalNotification({
                      title: titleText,
                      body: subtitleText,
                      show_in_foreground: true,
                      data:{
                        chat:chatId,
                        author:approvedBy,
                        type: PUSH_TYPE_CHAT_INVITATION_APPROVED
                      },
                      sound: "default",
                      //badge:
                })
                break;

              default:
                break;
            }
            break;
        case NotificationType.WillPresent:
            //do nothing
            notif.finish(WillPresentNotificationResult.ALL); //other types available: WillPresentNotificationResult.None
            break;
        case NotificationType.NotificationResponse:
          switch (notif.data.type)
          {
            case PUSH_TYPE_CHAT_MESSAGE:
            case PUSH_TYPE_CHAT_INVITATION_APPROVED:
              //navigate to chat
              let {author,chat} =notif.data;
              let user = await Users.fetchUsers([author],true);
              user =user[0];
              let thumbUrl = Users.getThumbUrl(user)
              let name = user.name
              let notifType = notif.data.type==="chat_message"?"Chat Message":"Chat Request Approved";
              AnalyticsUtil.recordEvent("Notification - "+notifType);
              NavigationService.navigate('ChatDetails', { name,thumbUrl,chatId:chat });
              notif.finish();
              break;
            case PUSH_TYPE_CHAT_INVITATION:
              AnalyticsUtil.recordEvent("Notification - Chat Request");
              NavigationService.navigate('ChatRequests');
              notif.finish();
              break;
            default:
            notif.finish();
              break;
        }
        default:
          notif.finish();
          break;
      }
  }

  async refreshToken(token)
  {
     deviceLog.log("refresh fcm token");
    if(!this.enabled)
    {
      console.warn('FCM disabled')
      return;
    }
    token = token||await this.getToken();
    Server.updateFCMToken(token);
  }
}

const fCMHelper = new FCMHelper();
export default fCMHelper;

And here is an example of the notification received from the server:

{
    "to": ".....",
     "content_available":true,
     "priority":"high",
      "show_in_foreground": true,

    "data": {
                "alert": "Chat Message",
                "id": 12234,
                "title": "title",
                "subtitle": "subtitle",
                "chat_id":222,
                "author_id": 333,
                "author_name": "Elad Cohen",
                "text": "text",
                "type": "chat_message",
                "message": {"author":111,"chat":22,"created":null,"id":null,"text":"text"}
    }
}

Thanks Elad

elad823 commented 5 years ago

Is data message sent as silent notification to IOS? If yes thats can explain it:

Important

The system treats silent notifications as low-priority. You can use them to refresh your app’s content, but the system doesn't guarantee their delivery. In addition, the delivery of silent notifications may be throttled if the total number becomes excessive. The actual number of silent notifications allowed by the system depends on current conditions, but don't try to send more than two or three silent notifications per hour.

https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_updates_to_your_app_silently

evollu commented 5 years ago

right, use notification rather than data for reliable notification in iOS

elad823 commented 5 years ago

Ok thanks. But even when i use notification, I still have issue when the app is closed:

In my app when a notfication of a new message arrived, the message should be saved to the local db whether or not the user has opened the notification.

So in a case where the app is closed , the notification do presented , but no event is called. How can I handle this case and save the message to the db?

evollu commented 5 years ago

for iOS, when notification arrives with content_available:true, it will wake JS and callbacks will be executed if they are not registered in a component. however, if user force kill the app, there is no way to run logic until app is opened by user again. system limit

temitope commented 5 years ago

@evollu thank you for an incredible library. Ive benefited so much. Of note, using firebase to send push notifications I had to leave out content_available entirely to avoid errors. upon removal everything was working great. I needed it to allow people to reply to messages from the notification tray (iOS). I run a database save (it is a promise). This works even if the app is killed in my experience. Again, many thanks. If needed i hope to contribute my thanks with code as well. such a good library.