ionic-team / capacitor-plugins

Official plugins for Capacitor ⚡️
486 stars 563 forks source link

bug: PushNotifications not triggering code within `pushNotificationReceived` (data notification) #200

Closed nschipperbrainsmith closed 3 years ago

nschipperbrainsmith commented 4 years ago

Bug Report

A push notification send through firebase isn't triggering the code located within the addEventListener for pushNotificationReceived.

Capacitor Version

Installed Dependencies:
  @capacitor/cli 1.4.0
  @capacitor/core 1.4.0
  @capacitor/android 1.4.0
  @capacitor/ios 1.4.0

Affected Platform(s)

Current Behavior

Currently when a data notification is pushed through Firebase, to a device which is no longer in the foreground ( has been (non forcefully) killed by swiping it away in the recent application viewer): The notification is handled by CapacitorFirebaseMessagingService#onMessageReceived yet it is never handled by the pushNotificationReceived within the application code (like it should according to the documentation).

The reason for this is the fact that PushNotifications#sendRemoteMessage expects the bridge to be set within: PushNotifications#getPushNotificationsInstance. This is however never the case as the plugin load method is not called as such the static staticBridge is never assigned. And the call is never relayed down to the application Typescript code.

Expected Behavior

I would expect the getPushNotificationsInstance method to initialize the application / load it so the staticBridge property is set. Otherwise the documentation will have to be updated to reflect the fact that listening for pushNotificationReceived only works when the application is active and the user hasn't (none forcefully) killed the app by swiping it away within the recent application viewer.

Sample Code

Notification we push to firebase:

{
    "to": "<<address>>",
    "data" : {
        "body" : "Body of Your Notification in data"
    },
    "priority": "high"
}

Code within the AppComponent

...
async initializePushNotificationListener() {
    console.debug("AppComponent#initializePushNotificationListener initializing notification listeners");
    await PushNotifications.register();
    console.debug("AppComponent#initializePushNotificationListener notification listeners registered");
    // On success, we should be able to receive notifications
    PushNotifications.addListener("registration", (token: PushNotificationToken) => {
        // tslint:disable-next-line:max-line-length
        console.debug(`AppComponent#initializePushNotificationListener Push registration success, token: ${token.value}`);
    });
    PushNotifications.addListener("registrationError",
        (error: any) => {
            alert("Error on registration: " + JSON.stringify(error));
        }
    );
    // Show us the notification payload if the app is open on our device
    PushNotifications.addListener("pushNotificationReceived",
        (notification: PushNotification) => {
            console.info("AppComponent#initializePushNotificationListener notification received", notification);
            LocalNotifications.schedule({
                notifications: [
                    {
                        title: "Title",
                        body: "Body",
                        id: 1,
                        schedule: {at: new Date(Date.now() + 1000 * 20)},
                        sound: null,
                        attachments: null,
                        actionTypeId: "",
                        extra: null
                    }
                ]
            }).catch(error => {
                console.debug("AppComponent#initializePushNotificationListener notifications denied", error);
            });
        }
    );
    // Method called when tapping on a notification
    PushNotifications.addListener("pushNotificationActionPerformed",
        (notification: PushNotificationActionPerformed) => {
            alert("Push action performed: " + JSON.stringify(notification));
        }
    );
}
...

Other Technical Details

npm --version output: 6.11.3 node --version output: 10.17.0

Tested on two different devices:

Other Information

Might relate to: https://github.com/ionic-team/capacitor/issues/1928#issuecomment-572607628

kainosnoema commented 2 years ago

After digging into this quite a bit today, I discovered that the Capacitor push-notifications plugin (even the latest) only implements the userNotificationCenter(_:willPresent:withCompletionHandler:) protocol, which only handles iOS notifications that are received when the app is fully in the foreground, regardless of whether they're data only or not: https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter.

It should be possible to also implement UIApplication's didReceiveRemoteNotification protocol, which receives remote notifications even while the app is running in the background (and not fully closed). This would solve a lot of the complaints I've seen around how the plugin handles notifications. As it is, we'll have to implement this ourselves and pass it to the plugin as a workaround.

adamrz commented 2 years ago

@kainosnoema I have been struggling with this for ages and not being a native Swift developer, I have been unable to get workarounds to function properly. Any insight on what you did/will do to implement this would be greatly appreciated!

ajincentfit commented 2 years ago

@kainosnoema I've also been struggling with wonky iOS notifications. Regular notifications come through fine, but trying to implement local notifications via push notifications to do actionable notifications is wonky. If you end up solving this, a fork would be much appreciated.

kainosnoema commented 2 years ago

@ajincentfit honestly we'll probably end up doing just that—I have quite a bit of native Swift experience so can pull it off with some time. Will keep you posted.

I love Capacitor in most areas, so it's kind of wild to me that something so critical and core to mobile development has such poor support here at v3. 😢

askilondz commented 2 years ago

Ok this needs to be fixed. Background notifications is a critical feature for pretty much any push notifications scenario... this plugin is almost useless without it...this has been my biggest hiccup with Capacitor 3. Everyone has to do workarounds to make this plugin work and almost a year later nothing has changed.

RRGT19 commented 2 years ago

Still not working :(

Installed Dependencies:

  @capacitor/android: 3.5.1
  @capacitor/ios: 3.5.1
  @capacitor/cli: 3.5.1
  @capacitor/core: 3.5.1
  @capacitor/push-notifications: "^1.0.9",
  @capacitor-community/fcm: "^2.0.2",
LouieSankey commented 2 years ago

@kainosnoema did you ever find a solution for this?.. I've been trying to implement a custom IOS plugin to solve the issue but I'm not having any luck.

kainosnoema commented 2 years ago

@LouieSankey unfortunately no. We're still dealing with a reduced experience here. I have a feeling one of the reasons Ionic isn't fixing this is because Apple doesn't actually allow the embedded web view (WKWebView) to run or make network requests while in the background, so there's not much in the JS you could do with background push. I wish they'd clarify though as it's very confusing and surprising.

Your best bet is to probably handle background push and do whatever fetching/processing you need to do on the native side, and pass it back to the web view when the app is foregrounded and restored.

terryjiang2020 commented 2 years ago

Looks like there is no solution for this as @theproducer closed it with a PR which added a doc claiming this issue is not going to be fixed.

morsagmon commented 1 year ago

I'm receiving background notifications, but not foreground. The pushNotificationReceived listener is not kicking in. However, the pushNotificationActionPerformed is kicking in upon tapping the notification. This is with Android on Samsung Galaxy A32 and Xiaomi Rednote 8. Angular 13 Ionic 6.

Any updates on this?

fromage9747 commented 1 year ago

@morsagmon I had given up on this but since my push notification token expired and I needed to find a method of refreshing it, it set me out to research this again.

From recent research, it would appear that in order for this to work your push notifications need to be data only.

When they are data only they are "silent". The app receives them but then you need to handle them with local notifications. Which isn't too much of an issue as this now gives you the ability to add action buttons to your notifications as well as many other cool features.

Another thing to add is that it's best to not bother with the built-in ionic/capacitor PushNotifications plugin as this does not give you anyway to refresh a token. The only way for the user to refresh the token is to uninstall and reinstall the app. Not very good UX. So instead make use of @capacitor-firebase/messaging. This comes with two methods that you will use to refresh the token. DeleteToken() and getToken(). I am still in the process of implementing this but it all looks quite promising.

morsagmon commented 1 year ago

@fromage9747 Thanks for the input. Not sure I know what is the @capacitor-firebase/messaging. I am following this scheme: https://capacitorjs.com/docs/guides/push-notifications-firebase It is just that the pushNotificationReceived listener is not kicking in.

I have no problem with the token not being refreshed in the background. Sure will appreciate to see what you come up with once it's working.

anthonyjuarezsolis commented 1 year ago

Has anyone found a solution?

fromage9747 commented 1 year ago

Yeah, forget about the built in push notifications from capacitor/Ionic. Use angular firebase messaging and only send data messages so that they are silent. These will work in the background and foreground. Then when the message is received, you launch a local notification through ionic which also gives you more functionality and control like action buttons.

morsagmon commented 1 year ago

The following works for me. Capturing in foreground (app is in focus):

import {ActionPerformed, PushNotifications, PushNotificationSchema, Token} from '@capacitor/push-notifications';

PushNotifications.addListener(
      'pushNotificationReceived',
      async (notification: PushNotificationSchema) => {
        const data = notification.data.data; //data.learnerId , data.notificationCode, ...
        console.log('Push received: ' + JSON.stringify(notification));
        //What to do next. Notification received on the device.
        //alert('Push notification received: ' + notification);
        this.processNotificationReceived(JSON.parse(data), false);
      });

And this captures a tap on a notification that was received when the app is in the background:

    PushNotifications.addListener(
      'pushNotificationActionPerformed',
      async (notification: ActionPerformed) => {
        const data = notification.notification.data.data;
        console.log('Action performed: ' + notification.notification);
        console.log('data: ', data);        
        //What to do next. data holds all payload fields of the notification object. JSON structure: notification.notification.data.user.learnerId
        //notificationCode: 1=HelperInvitation; 2=HelperResponse; 3=ChatInvitation; 4=ChatMessage (not used); 5=HelperEvent; 9=NotificationMessage
        this.processNotificationReceived(JSON.parse(data));
      }
    );

The only problem I still have is with background notifications - they are received, but only showing a small icon in the upper status bar of the receiving machine, not popping up a visible message. The user must swipe down to reveal notifications, and there it is - ready to be tappable.

From what I learned, this is controlled by the visibility property of android in the notification payload, but I could not get this to work - here's my notification function in Firebase (triggered by RTDB insert record):

const functions = require("firebase-functions");
const admin = require('firebase-admin'); //required to access the FB RT DB
const serviceAccount = require("./<my-service-account-file>.json");
const tgjs = require("./tigergraph");
const cors = require('cors')({origin: true});

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "<my-rtdb-url>"
});

exports.sendHelperInvitation = functions.database.ref('/HelpersInvitations/{helper_invitation_id}')
    .onCreate(async (snapshot, context) => {

      const learnerId = snapshot.val().learnerId;
      const learnerDeviceId = snapshot.val().learnerDeviceId;
      const learnerName = snapshot.val().learnerName;
      const learnerImage = snapshot.val().learnerImage;
      const helperId = snapshot.val().helperId;
      const helperDeviceId = snapshot.val().helperDeviceId;
      const caseInvitationId = snapshot.val().caseInvitationId;
      const title = snapshot.val().title;
      const body = snapshot.val().body;
      const imageLink = snapshot.val().imageLink

      let objData = {
          learnerId: learnerId,
          learnerDeviceId: learnerDeviceId,
          learnerName: learnerName,
          helperId: helperId,
          helperDeviceId: helperDeviceId,
          caseInvitationId: caseInvitationId,
          imageLink: imageLink,
          notificationCode: "1"
      }

      functions.logger.log('Image URL ' , learnerImage);

      //Notification payload
      const payload = {
        token: helperDeviceId,
        notification: {          
          "title": title,
          "body": body,                    
          "image": learnerImage,
          // click_action: 'com.skillblaster.app.helperinvitationnotification' //Tag associated with triggering an activity on the helper's app when background notification
        },        
        data:{
          data: JSON.stringify(objData)
        },
        android:{
          "priority":"high",
          "notification": {
            "notification_priority": "PRIORITY_HIGH", 
            "default_vibrate_timings": true,
            "default_light_settings": true,
            //"visibility": firebase.messaging.NotificationAndroidVisibility.PUBLIC   // <<<=== NOT WORKING!!!
          }
        },
        apns:{
          "headers":{
            "apns-priority":"5"
          },
          payload: {
            aps: {
              'mutable-content': 1
            }
          },
          fcm_options: {
            image: learnerImage
          }
        },
        webpush: {
          "headers": {
            "Urgency": "high"
          }
        }
      };

      //    //Send the notification  
      functions.logger.log('In sendHelperInvitation. helperDeviceId: ' , helperDeviceId);
      functions.logger.log('In sendHelperInvitation. payload: ' , payload);
      return admin.messaging().send(payload);

    });
ionitron-bot[bot] commented 1 year ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of the plugin, please create a new issue and ensure the template is fully filled out.