allboatsrise / expo-marketingcloudsdk

Expo module for Salesforce Marketing Cloud SDK
MIT License
11 stars 9 forks source link

Receiving empty push notification on Android #39

Open danny88dam opened 1 month ago

danny88dam commented 1 month ago

Hi, I integrated this package in my project and everything works well on iOS devices, but on Android I'm encountering some problems

The os correctly receive the push notifications and shows previews with title/text and image (if set), but when the user taps on it and opens the app the notification request seems to be empty, here and example printed via console.log

{ "notification": { "request": { "trigger": { "channelId": null, "type": "push" }, "content": { "title": null, "dataString": null, "body": null }, "identifier": null }, "date": 0 }, "actionIdentifier": "expo.modules.notifications.actions.DEFAULT" }

For debugging purpose I also send push notification via Expo Push Notification Tool https://expo.dev/notifications and it works well either on iOS and Android devices, so the issue seems to be related to MC package, here an example of received notification from expo tool

{ "notification": { "request": { "trigger": { "remoteMessage": { "originalPriority": 2, "sentTime": 1722597307339, "notification": { "usesDefaultVibrateSettings": false, "color": null, "channelId": null, "visibility": null, "sound": null, "tag": null, "bodyLocalizationArgs": null, "imageUrl": null, "title": "title", "vibrateTimings": null, "ticker": null, "eventTime": null, "body": "text android", "titleLocalizationKey": null, "notificationPriority": null, "icon": null, "usesDefaultLightSettings": false, "sticky": false, "link": null, "titleLocalizationArgs": null, "bodyLocalizationKey": null, "usesDefaultSound": false, "clickAction": null, "localOnly": false, "lightSettings": null, "notificationCount": null }, "data": { "message": "text android", "title": "title", "body": "{}", "scopeKey": "****************", "experienceId": "****************", "projectId": "****************" }, "to": null, "ttl": 0, "collapseKey": "****************", "messageType": null, "priority": 2, "from": "****************", "messageId": "****************" }, "channelId": null, "type": "push" }, "content": { "title": "title", "badge": null, "autoDismiss": true, "data": {}, "body": "text android", "sound": "default", "sticky": false, "subtitle": null }, "identifier": "****************" }, "date": 1722597307339 }, "actionIdentifier": "expo.modules.notifications.actions.DEFAULT" }

the the push notification is logged from the notification response received listener

Notifications.addNotificationResponseReceivedListener(async (response) => { console.log(JSON.stringify(response)) ...

Does anyone have any ideas?

andrejpavlovic commented 1 month ago

I haven't taken a closer look, but are you using latest expo-notifications package? In 0.28.13 there were some android fixes.

danny88dam commented 1 month ago

Yep, just today I updated the libraries and rebuilt the app again with eas build ---clear-cache command. Nothing seems to be changed, here my package.json { "name": "myApp", "version": "1.0.0", "main": "node_modules/expo/AppEntry.js", "scripts": { "start": "expo start", "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", "ts:check": "tsc", "postinstall": "patch-package" }, "dependencies": { "@allboatsrise/expo-marketingcloudsdk": "^51.0.1", "@expo-google-fonts/inter": "^0.2.3", "@expo/html-elements": "^0.9.1", "@expo/metro-runtime": "~3.2.1", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/datetimepicker": "8.0.1", "@react-native-picker/picker": "2.7.5", "@react-navigation/bottom-tabs": "^6.5.14", "@react-navigation/native": "^6.1.12", "@react-navigation/native-stack": "^6.9.20", "@react-navigation/stack": "^6.3.23", "@reduxjs/toolkit": "^2.2.1", "@rneui/base": "^4.0.0-rc.7", "@rneui/themed": "^4.0.0-rc.8", "axios": "^1.6.7", "dotenv": "^16.4.5", "expo": "~51.0.24", "expo-blur": "~13.0.2", "expo-constants": "~16.0.2", "expo-dev-client": "~4.0.21", "expo-device": "~6.0.2", "expo-document-picker": "~12.0.2", "expo-file-system": "~17.0.1", "expo-font": "~12.0.5", "expo-image": "~1.12.13", "expo-linear-gradient": "~13.0.2", "expo-linking": "~6.3.1", "expo-localization": "~15.0.3", "expo-notifications": "~0.28.14", "expo-secure-store": "~13.0.2", "expo-status-bar": "~1.12.1", "expo-updates": "~0.25.21", "expo-web-browser": "~13.0.3", "formik": "^2.4.5", "lodash": "^4.17.21", "patch-package": "^8.0.0", "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.74.3", "react-native-calendars": "^1.1304.1", "react-native-elements": "^3.4.3", "react-native-gesture-handler": "~2.16.1", "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-otp-entry": "^1.6.1", "react-native-pager-view": "6.3.0", "react-native-reanimated": "~3.10.1", "react-native-reanimated-carousel": "^3.5.1", "react-native-render-html": "^6.3.4", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", "react-native-tab-view": "^3.5.2", "react-native-vimeo-iframe": "^1.2.1", "react-native-web": "~0.19.6", "react-native-webview": "13.8.6", "react-redux": "^9.1.0", "redux": "^5.0.1", "redux-persist": "^6.0.0", "reselect": "^5.1.0", "text-clipper": "^2.2.0", "yup": "^1.3.3", "zod": "^3.23.8" }, "devDependencies": { "@babel/core": "^7.20.0", "@types/lodash": "^4.14.202", "@types/react": "~18.2.79", "expo-module-scripts": "^3.5.2", "typescript": "~5.3.3" }, "private": true }

danny88dam commented 1 month ago

I made other test to better understand and delimit the scenario.

I made a campaign via Firebase console and sent push notification to my android device. In this case I correctly received the notification data in app, here the json

{
  "notification": {
    "request": {
      "trigger": {
        "remoteMessage": {
          "originalPriority": 1,
          "sentTime": 1722952149049,
          "notification": {
            "usesDefaultVibrateSettings": false,
            "color": null,
            "channelId": null,
            "visibility": null,
            "sound": null,
            "tag": "campaign_collapse_key_4954627017329097723",
            "bodyLocalizationArgs": null,
            "imageUrl": *********************,
            "title": "test 7",
            "ticker": null,
            "eventTime": null,
            "body": "test 7",
            "titleLocalizationKey": null,
            "notificationPriority": null,
            "icon": null,
            "usesDefaultLightSettings": false,
            "sticky": false,
            "link": null,
            "titleLocalizationArgs": null,
            "bodyLocalizationKey": null,
            "usesDefaultSound": false,
            "clickAction": null,
            "localOnly": false,
            "lightSettings": null,
            "notificationCount": null
          },
          "data": {
            "key1": "value1"
          },
          "to": null,
          "ttl": 2419200,
          "collapseKey": *********************,
          "messageType": null,
          "priority": 1,
          "from": *********************,
          "messageId": "0:1722952154045214%e874ac06e874ac06"
        },
        "channelId": null,
        "type": "push"
      },
      "content": {
        "title": null,
        "badge": null,
        "autoDismiss": true,
        "data": {
          "key1": "value1"
        },
        "body": null,
        "sound": "default",
        "sticky": false,
        "subtitle": null
      },
      "identifier": "0:1722952154045214%e874ac06e874ac06"
    },
    "date": 1722952149049
  },
  "actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}

then I retried to send notification via MC and still received empty data:

{
  "notification": {
    "request": {
      "trigger": {
        "channelId": null,
        "type": "push"
      },
      "content": {
        "title": null,
        "data": {
          "com.salesforce.marketingcloud.notifications.EXTRA_MESSAGE": "__expo_dynamic_extension__#1"
        }
      },
      "identifier": null
    },
    "date": 0
  },
  "actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
andrejpavlovic commented 1 month ago

Can you try the latest expo-notifications version 0.28.15 - expo team screwed up some refactoring and I just want to make sure it's not related to this.

You can also try debugging and seeing how the data comes in here. You could just call super.onMessageReceived(remoteMessage) and not pass it to sfmc for processing to see if you get all the data. https://github.com/allboatsrise/expo-marketingcloudsdk/blob/23ae72d87af34df2f1442f4b988f2fffaa8bf0e8/android/src/main/java/expo/modules/marketingcloudsdk/service/ExpoFirebaseMessagingService.kt#L8-L20

danny88dam commented 1 month ago

Hi, yesterday I noticed the expo-notifcations new version then I updated my packeges but nothig changed.

Follow your suggestion I directly call super.onMessageReceived(remoteMessage) in onMessageReceived method and IT WORKS, so i now I receive something like this

  "notification": {
    "request": {
      "trigger": {
        "remoteMessage": {
          "originalPriority": 2,
          "sentTime": 1723015711410,
          "notification": null,
          "data": {
            "title": "Title",
            "sound": "default",
            "_r": ********************,
            "_mediaAlt": "Wall_BP_Image",
            "alert": "Message Text\n",
            "_sid": "SFMC",
            "_m": "MTY1OjExNDow",
            "_od": ********************,
            "_mt": "1",
            "_h": ********************,
            "_mediaUrl": ********************,
            "subtitle": "Subtitle Text"
          },
          "to": null,
          "ttl": 2419200,
          "collapseKey": null,
          "messageType": null,
          "priority": 2,
          "from": "413196589577",
          "messageId": "0:1723015711420859%e874ac06f9fd7ecd"
        },
        "channelId": null,
        "type": "push"
      },
      "content": {
        "title": "Title",
        "badge": null,
        "autoDismiss": true,
        "data": {
          "title": "Title",
          "sound": "default",
          "_r": ********************,
          "_mediaAlt": "Wall_BP_Image",
          "alert": "Message Text\n",
          "_sid": "SFMC",
          "_m": "MTY1OjExNDow",
          "_od": ********************,
          "_mt": "1",
          "_h": ********************,
          "_mediaUrl": ********************,
          "subtitle": "Subtitle Text"
        },
        "body": null,
        "sound": "custom",
        "sticky": false,
        "subtitle": "Subtitle Text"
      },
      "identifier": "0:1723015711420859%e874ac06f9fd7ecd"
    },
    "date": 1723015711410
  },
  "actionIdentifier": "expo.modules.notifications.actions.DEFAULT"
}
}

In conclution the issue related to pushMessageManager.handleMessage method. I tried to look further by searching the official documentation, but i found nothing. Only this link https://developer.salesforce.com/docs/marketing/mobilepush/guide/troubleshooting-android.html#troubleshoot-multiple-push-sdks talks about handleMessage image but the linked page is 404

My question now is: can this solution be considered stable? Or does it need further investigation?

andrejpavlovic commented 1 month ago

I wonder if it's ok to trigger both, it.pushMessageManager.handleMessage(remoteMessage) and super.onMessageReceived(remoteMessage), since I think both are needed.

danny88dam commented 1 month ago

I think it is a good idea to call them both. I will try it in the next build and let you know. I think the worst case scenario is that the app gets the same notification twice. However, I opened a ticket on MC support describing the scenario, I am waiting for their response and details.

danny88dam commented 1 month ago

[UPDATE] I made a new build trying to call both super.onMessageReceived(remoteMessage) and it.pushMessageManager.handleMessage(remoteMessage). As expected, I get the same push notification twice.

I also noticed another thing, I set up sending the notification with an image, when Android receives the (duplicated) notifications only the first one that is handled by it.pushMessageManager.handleMessage(remoteMessage) is displayed correctly in preview with the image and the body, the last one only shows only title.

I think title is standard fields handled in same way by both MC and Firebase, instead the body and the image fields are apparently sent as a custom fields from MC MobilePush service, so only if the notification was handled by MC's it.pushMessageManager.handleMessage(remoteMessage) method they were rearranged somehow to be correctlty previewed by Android.

Thus these type of rearrangements seems to be one of the features of it.pushMessageManager.handleMessage(remoteMessage)

andrejpavlovic commented 1 month ago

Does it show the notification twice only when the app is running? Or also when the application is not running (killed) and the notification is received, it is still shown twice?

danny88dam commented 1 month ago

In all cases app is runnign foreground, app is runnig background and app is killed the push notification shows twice

spenceps commented 3 weeks ago

I'm in the process of moving from a bare react native project to expo managed. I'm running into the same issue mentioned by @danny88dam. I also got the same result when I called super.onMessageReceived(remoteMessage) for Marketing Cloud pushes as well. I get two pushes. One push has the extra parameters but doesn't have the title and other information filled in.

I'm wondering if there have been any updates on this issue as I continue digging in to understand how this library works and see if I can figure out a way around this issue. Thanks.

andrescalco-borrowell commented 3 weeks ago

I've been having a similar issue, where the push payload on android is null and the data field is encoded on a weird way, but the push is rendered fine on the device.:

{
  "actionIdentifier": "expo.modules.notifications.actions.DEFAULT",
  "notification": {
    "date": 0,
    "request": {
      "identifier": null,
      "content": {
        data:  // see bellow,
        "title": null
      },
      "trigger": {
        "type": "push",
        "channelId": null
      }
    }
  }
}
"data": {
  "com.salesforce.marketingcloud.notifications.EXTRA_MESSAGE": {
    "0": 14,
      "1": 0,
      "2": 0,
      "3": 0,
      "4": 77,
      "5": 0,
      "6": 106,
      "7": 0,
      "8": 107,
      "9": 0,
      "10": 122,
      "11": 0,
      "12": 77,
      "13": 0,
      "14": 122,
      "15": 0,
      "16": 111,
      "17": 0,
      "18": 120,
      "19": 0,
      "20": 77,
      "21": 0,
      "22": 84,
      "23": 0,
      "24": 81,
      "25": 0,
      "26": 54,
      "27": 0,
      "28": 77,
      "29": 0,
      "30": 65,
      "31": 0,
      "32": 0,
      "33": 0,
      "34": 0,
      "35": 0,
      "36": 36,
      "37": 0,
      "38": 0,
      "39": 0,
      "40": 102,
      "41": 0,
      "42": 48,
      "43": 0,
      "44": 50,
      "45": 0,
      "46": 98,
      "47": 0,
      "48": 57,
      "49": 0,
      "50": 52,
      "51": 0,
      "52": 50,
      "53": 0,
      "54": 98,
      "55": 0,
      "56": 45,
      "57": 0,
      "58": 48,
      "59": 0,
      "60": 51,
      "61": 0,
      "62": 97,
      "63": 0,
      "64": 55,
      "65": 0,
      "66": 45,
      "67": 0,
      "68": 52,
      "69": 0,
      "70": 52,
      "71": 0,
      "72": 97,
      "73": 0,
      "74": 50,
      "75": 0,
      "76": 45,
      "77": 0,
      "78": 57,
      "79": 0,
      "80": 98,
      "81": 0,
      "82": 55,
      "83": 0,
      "84": 53,
      "85": 0,
      "86": 45,
      "87": 0,
      "88": 57,
      "89": 0,
      "90": 98,
      "91": 0,
      "92": 54,
      "93": 0,
      "94": 54,
      "95": 0,
      "96": 55,
      "97": 0,
      "98": 97,
      "99": 0,
      "100": 99,
      "101": 0,
      "102": 49,
      "103": 0,
      "104": 98,
      "105": 0,
      "106": 52,
      "107": 0,
      "108": 49,
      "109": 0,
      "110": 56,
      "111": 0,
      "112": 0,
      "113": 0,
      "114": 0,
      "115": 0,
      "116": 0,
      "117": 0,
      "118": 0,
      "119": 0,
      "120": 50,
      "121": 0,
      "122": 0,
      "123": 0,
      "124": 67,
      "125": 0,
      "126": 111,
      "127": 0,
      "128": 110,
      "129": 0,
      "130": 103,
      "131": 0,
      "132": 114,
      "133": 0,
      "134": 97,
      "135": 0,
      "136": 116,
      "137": 0,
      "138": 117,
      "139": 0,
      "140": 108,
      "141": 0,
      "142": 97,
      "143": 0,
      "144": 116,
      "145": 0,
      "146": 105,
      "147": 0,
      "148": 111,
      "149": 0,
      "150": 110,
      "151": 0,
      "152": 115,
      "153": 0,
      "154": 33,
      "155": 0,
      "156": 32,
      "157": 0,
      "158": 89,
      "159": 0,
      "160": 111,
      "161": 0,
      "162": 117,
      "163": 0,
      "164": 114,
      "165": 0,
      "166": 32,
      "167": 0,
      "168": 77,
      "169": 0,
      "170": 111,
      "171": 0,
      "172": 98,
      "173": 0,
      "174": 105,
      "175": 0,
      "176": 108,
      "177": 0,
      "178": 101,
      "179": 0,
      "180": 80,
      "181": 0,
      "182": 117,
      "183": 0,
      "184": 115,
      "185": 0,
      "186": 104,
      "187": 0,
      "188": 32,
      "189": 0,
      "190": 115,
      "191": 0,
      "192": 101,
      "193": 0,
      "194": 116,
      "195": 0,
      "196": 117,
      "197": 0,
      "198": 112,
      "199": 0,
      "200": 32,
      "201": 0,
      "202": 105,
      "203": 0,
      "204": 115,
      "205": 0,
      "206": 32,
      "207": 0,
      "208": 119,
      "209": 0,
      "210": 111,
      "211": 0,
      "212": 114,
      "213": 0,
      "214": 107,
      "215": 0,
      "216": 105,
      "217": 0,
      "218": 110,
      "219": 0,
      "220": 103,
      "221": 0,
      "222": 46,
      "223": 0,
      "224": 0,
      "225": 0,
      "226": 0,
      "227": 0,
      "228": 4,
      "229": 0,
      "230": 0,
      "231": 0,
      "232": 78,
      "233": 0,
      "234": 79,
      "235": 0,
      "236": 78,
      "237": 0,
      "238": 69,
      "239": 0,
      "240": 0,
      "241": 0,
      "242": 0,
      "243": 0,
      "244": 255,
      "245": 255,
      "246": 255,
      "247": 255,
      "248": 20,
      "249": 0,
      "250": 0,
      "251": 0,
      "252": 77,
      "253": 0,
      "254": 111,
      "255": 0,
      "256": 98,
      "257": 0,
      "258": 105,
      "259": 0,
      "260": 108,
      "261": 0,
      "262": 101,
      "263": 0,
      "264": 80,
      "265": 0,
      "266": 117,
      "267": 0,
      "268": 115,
      "269": 0,
      "270": 104,
      "271": 0,
      "272": 32,
      "273": 0,
      "274": 84,
      "275": 0,
      "276": 101,
      "277": 0,
      "278": 115,
      "279": 0,
      "280": 116,
      "281": 0,
      "282": 32,
      "283": 0,
      "284": 83,
      "285": 0,
      "286": 101,
      "287": 0,
      "288": 110,
      "289": 0,
      "290": 100,
      "291": 0,
      "292": 0,
      "293": 0,
      "294": 0,
      "295": 0,
      "296": 255,
      "297": 255,
      "298": 255,
      "299": 255,
      "300": 5,
      "301": 0,
      "302": 0,
      "303": 0,
      "304": 79,
      "305": 0,
      "306": 84,
      "307": 0,
      "308": 72,
      "309": 0,
      "310": 69,
      "311": 0,
      "312": 82,
      "313": 0,
      "314": 0,
      "315": 0,
      "316": 4,
      "317": 0,
      "318": 0,
      "319": 0,
      "320": 80,
      "321": 0,
      "322": 85,
      "323": 0,
      "324": 83,
      "325": 0,
      "326": 72,
      "327": 0,
      "328": 0,
      "329": 0,
      "330": 0,
      "331": 0,
      "332": 255,
      "333": 255,
      "334": 255,
      "335": 255,
      "336": 255,
      "337": 255,
      "338": 255,
      "339": 255,
      "340": 255,
      "341": 255,
      "342": 255,
      "343": 255,
      "344": 0,
      "345": 0,
      "346": 0,
      "347": 0,
      "348": 255,
      "349": 255,
      "350": 255,
      "351": 255,
      "352": 1,
      "353": 0,
      "354": 0,
      "355": 0,
      "356": 7,
      "357": 0,
      "358": 0,
      "359": 0,
      "360": 2,
      "361": 0,
      "362": 0,
      "363": 0,
      "364": 95,
      "365": 0,
      "366": 114,
      "367": 0,
      "368": 0,
      "369": 0,
      "370": 0,
      "371": 0,
      "372": 36,
      "373": 0,
      "374": 0,
      "375": 0,
      "376": 102,
      "377": 0,
      "378": 48,
      "379": 0,
      "380": 50,
      "381": 0,
      "382": 98,
      "383": 0,
      "384": 57,
      "385": 0,
      "386": 52,
      "387": 0,
      "388": 50,
      "389": 0,
      "390": 98,
      "391": 0,
      "392": 45,
      "393": 0,
      "394": 48,
      "395": 0,
      "396": 51,
      "397": 0,
      "398": 97,
      "399": 0,
      "400": 55,
      "401": 0,
      "402": 45,
      "403": 0,
      "404": 52,
      "405": 0,
      "406": 52,
      "407": 0,
      "408": 97,
      "409": 0,
      "410": 50,
      "411": 0,
      "412": 45,
      "413": 0,
      "414": 57,
      "415": 0,
      "416": 98,
      "417": 0,
      "418": 55,
      "419": 0,
      "420": 53,
      "421": 0,
      "422": 45,
      "423": 0,
      "424": 57,
      "425": 0,
      "426": 98,
      "427": 0,
      "428": 54,
      "429": 0,
      "430": 54,
      "431": 0,
      "432": 55,
      "433": 0,
      "434": 97,
      "435": 0,
      "436": 99,
      "437": 0,
      "438": 49,
      "439": 0,
      "440": 98,
      "441": 0,
      "442": 52,
      "443": 0,
      "444": 49,
      "445": 0,
      "446": 56,
      "447": 0,
      "448": 0,
      "449": 0,
      "450": 0,
      "451": 0,
      "452": 4,
      "453": 0,
      "454": 0,
      "455": 0,
      "456": 95,
      "457": 0,
      "458": 115,
      "459": 0,
      "460": 105,
      "461": 0,
      "462": 100,
      "463": 0,
      "464": 0,
      "465": 0,
      "466": 0,
      "467": 0,
      "468": 4,
      "469": 0,
      "470": 0,
      "471": 0,
      "472": 83,
      "473": 0,
      "474": 70,
      "475": 0,
      "476": 77,
      "477": 0,
      "478": 67,
      "479": 0,
      "480": 0,
      "481": 0,
      "482": 0,
      "483": 0,
      "484": 5,
      "485": 0,
      "486": 0,
      "487": 0,
      "488": 97,
      "489": 0,
      "490": 108,
      "491": 0,
      "492": 101,
      "493": 0,
      "494": 114,
      "495": 0,
      "496": 116,
      "497": 0,
      "498": 0,
      "499": 0,
      "500": 50,
      "501": 0,
      "502": 0,
      "503": 0,
      "504": 67,
      "505": 0,
      "506": 111,
      "507": 0,
      "508": 110,
      "509": 0,
      "510": 103,
      "511": 0,
      "512": 114,
      "513": 0,
      "514": 97,
      "515": 0,
      "516": 116,
      "517": 0,
      "518": 117,
      "519": 0,
      "520": 108,
      "521": 0,
      "522": 97,
      "523": 0,
      "524": 116,
      "525": 0,
      "526": 105,
      "527": 0,
      "528": 111,
      "529": 0,
      "530": 110,
      "531": 0,
      "532": 115,
      "533": 0,
      "534": 33,
      "535": 0,
      "536": 32,
      "537": 0,
      "538": 89,
      "539": 0,
      "540": 111,
      "541": 0,
      "542": 117,
      "543": 0,
      "544": 114,
      "545": 0,
      "546": 32,
      "547": 0,
      "548": 77,
      "549": 0,
      "550": 111,
      "551": 0,
      "552": 98,
      "553": 0,
      "554": 105,
      "555": 0,
      "556": 108,
      "557": 0,
      "558": 101,
      "559": 0,
      "560": 80,
      "561": 0,
      "562": 117,
      "563": 0,
      "564": 115,
      "565": 0,
      "566": 104,
      "567": 0,
      "568": 32,
      "569": 0,
      "570": 115,
      "571": 0,
      "572": 101,
      "573": 0,
      "574": 116,
      "575": 0,
      "576": 117,
      "577": 0,
      "578": 112,
      "579": 0,
      "580": 32,
      "581": 0,
      "582": 105,
      "583": 0,
      "584": 115,
      "585": 0,
      "586": 32,
      "587": 0,
      "588": 119,
      "589": 0,
      "590": 111,
      "591": 0,
      "592": 114,
      "593": 0,
      "594": 107,
      "595": 0,
      "596": 105,
      "597": 0,
      "598": 110,
      "599": 0,
      "600": 103,
      "601": 0,
      "602": 46,
      "603": 0,
      "604": 0,
      "605": 0,
      "606": 0,
      "607": 0,
      "608": 3,
      "609": 0,
      "610": 0,
      "611": 0,
      "612": 95,
      "613": 0,
      "614": 109,
      "615": 0,
      "616": 116,
      "617": 0,
      "618": 0,
      "619": 0,
      "620": 1,
      "621": 0,
      "622": 0,
      "623": 0,
      "624": 49,
      "625": 0,
      "626": 0,
      "627": 0,
      "628": 2,
      "629": 0,
      "630": 0,
      "631": 0,
      "632": 95,
      "633": 0,
      "634": 104,
      "635": 0,
      "636": 0,
      "637": 0,
      "638": 0,
      "639": 0,
      "640": 28,
      "641": 0,
      "642": 0,
      "643": 0,
      "644": 88,
      "645": 0,
      "646": 86,
      "647": 0,
      "648": 104,
      "649": 0,
      "650": 117,
      "651": 0,
      "652": 80,
      "653": 0,
      "654": 87,
      "655": 0,
      "656": 51,
      "657": 0,
      "658": 103,
      "659": 0,
      "660": 110,
      "661": 0,
      "662": 105,
      "663": 0,
      "664": 88,
      "665": 0,
      "666": 85,
      "667": 0,
      "668": 81,
      "669": 0,
      "670": 114,
      "671": 0,
      "672": 113,
      "673": 0,
      "674": 47,
      "675": 0,
      "676": 51,
      "677": 0,
      "678": 80,
      "679": 0,
      "680": 99,
      "681": 0,
      "682": 85,
      "683": 0,
      "684": 78,
      "685": 0,
      "686": 81,
      "687": 0,
      "688": 65,
      "689": 0,
      "690": 65,
      "691": 0,
      "692": 65,
      "693": 0,
      "694": 65,
      "695": 0,
      "696": 65,
      "697": 0,
      "698": 65,
      "699": 0,
      "700": 0,
      "701": 0,
      "702": 0,
      "703": 0,
      "704": 5,
      "705": 0,
      "706": 0,
      "707": 0,
      "708": 116,
      "709": 0,
      "710": 105,
      "711": 0,
      "712": 116,
      "713": 0,
      "714": 108,
      "715": 0,
      "716": 101,
      "717": 0,
      "718": 0,
      "719": 0,
      "720": 20,
      "721": 0,
      "722": 0,
      "723": 0,
      "724": 77,
      "725": 0,
      "726": 111,
      "727": 0,
      "728": 98,
      "729": 0,
      "730": 105,
      "731": 0,
      "732": 108,
      "733": 0,
      "734": 101,
      "735": 0,
      "736": 80,
      "737": 0,
      "738": 117,
      "739": 0,
      "740": 115,
      "741": 0,
      "742": 104,
      "743": 0,
      "744": 32,
      "745": 0,
      "746": 84,
      "747": 0,
      "748": 101,
      "749": 0,
      "750": 115,
      "751": 0,
      "752": 116,
      "753": 0,
      "754": 32,
      "755": 0,
      "756": 83,
      "757": 0,
      "758": 101,
      "759": 0,
      "760": 110,
      "761": 0,
      "762": 100,
      "763": 0,
      "764": 0,
      "765": 0,
      "766": 0,
      "767": 0,
      "768": 2,
      "769": 0,
      "770": 0,
      "771": 0,
      "772": 95,
      "773": 0,
      "774": 109,
      "775": 0,
      "776": 0,
      "777": 0,
      "778": 0,
      "779": 0,
      "780": 14,
      "781": 0,
      "782": 0,
      "783": 0,
      "784": 77,
      "785": 0,
      "786": 106,
      "787": 0,
      "788": 107,
      "789": 0,
      "790": 122,
      "791": 0,
      "792": 77,
      "793": 0,
      "794": 122,
      "795": 0,
      "796": 111,
      "797": 0,
      "798": 120,
      "799": 0,
      "800": 77,
      "801": 0,
      "802": 84,
      "803": 0,
      "804": 81,
      "805": 0,
      "806": 54,
      "807": 0,
      "808": 77,
      "809": 0,
      "810": 65,
      "811": 0,
      "812": 0,
      "813": 0,
      "814": 0,
      "815": 0,
      "816": 255,
      "817": 255,
      "818": 255,
      "819": 255,
      "820": 10,
      "821": 0,
      "822": 0,
      "823": 0
  }
}

package.json

  "dependencies": {
    "@allboatsrise/expo-marketingcloudsdk": "51.0.4",
    "expo": "^51.0.26",
    "expo-notifications": "~0.28.15",
    "react": "18.2.0",
    "react-native": "0.74.5",
    "zod": "3.21.4"
    Notifications.getLastNotificationResponseAsync().then(async (response) => {
      if (response?.notification) {
        onOpened(response.notification);
      } else {
        // error handling
      }
    });
spenceps commented 3 weeks ago

Looks like it is this issue in the expo-notifications library https://github.com/expo/expo/issues/30868. @andrejpavlovic has already commented on the issue.

andrescalco-borrowell commented 3 weeks ago

Looks like it is this issue in the expo-notifications library expo/expo#30868. @andrejpavlovic has already commented on the issue.

Awesome! Will try the patch, @spenceps thanks for forwarding me to that thread!

spenceps commented 3 weeks ago

Awesome! Will try the patch, @spenceps thanks for forwarding me to that thread!

@andrescalco-borrowell - The patch didn't work for me. (maybe I did it wrong though) Have you tried it yet?

danny88dam commented 3 weeks ago

Hi @andrescalco-borrowell, I tried out the reported patch here and it doesn't seem to work neither for me.

andrescalco-borrowell commented 3 weeks ago

Unfortunately it did not fix the issue for me =(

spenceps commented 2 weeks ago

The issue turns out to be that Marketing Cloud SDK creates the launch Intent in a way that the custom keys aren't included in the normal way. I found a way around this by overriding the creation of intent as shown here.

I haven't fully tested this yet but it does at least work for the basic case.

This is done in ExpoMarketinCloudSdkApplicationLifecycleListener.kt

private fun getNotificationOptions(context: Context): NotificationCustomizationOptions {
    return NotificationCustomizationOptions.create(
      R.drawable.notification_icon,
        { context, notificationMessage ->
            val requestCode = Random.Default.nextInt()
            val url = notificationMessage.url
            val payload = notificationMessage.customKeys
            val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
                // Add custom keys to the intent
                payload.forEach { (key, value) ->
                    putExtra(key, value)
                }
                if (!url.isNullOrEmpty()) {
                    putExtra("url", url)
                }
            }

            // Return a PendingIntent with the Intent that includes the custom keys
            PendingIntent.getActivity(
                context,
                requestCode,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
        },
        { context, notificationMessage ->
            if (notificationMessage.url.isNullOrEmpty()) {
                NotificationManager.createDefaultNotificationChannel(context)
            } else {
                "UrlNotification"
            }
        }
    )
  }

In the configuration call the above function instead of just providing an image.

SFMCSdk.configure(application, SFMCSdkModuleConfig.build {
      pushModuleConfig = MarketingCloudConfig.builder().apply {
        setApplicationId(getAppId(application))
        setAccessToken(getAccessToken(application))
        setAnalyticsEnabled(getAnalyticsEnabled(application))
        setMarketingCloudServerUrl(getServerUrl(application))
        setDelayRegistrationUntilContactKeyIsSet(getDelayRegistrationUntilContactKeyIsSet(application))
        if(getSenderId(application) != "") setSenderId(getSenderId(application))
        setInboxEnabled(getInboxEnabled(application))
        setMarkMessageReadOnInboxNotificationOpen(getMarkMessageReadOnInboxNotificationOpen(application))
        setNotificationCustomizationOptions(
            getNotificationOptions(application) // HERE IS THE REPLACEMENT
        )
      }.build(application)
    }) { initStatus ->
      // TODO handle initialization status
      Log.e("SMFCSdk: initStatus", initStatus.toString())
    }
danny88dam commented 2 weeks ago

getNotificationOptions(application)

Hi @spenceps I tried out your solution, but I don't get push notification anymore.

Maybe I made some mistake in the implementation of your code, below are the parts I had to add

import android.app.PendingIntent
import java.util.Random
import com.salesforce.marketingcloud.notifications.NotificationManager

I had to change this val requestCode = Random.Default.nextInt() to this val requestCode = Random().nextInt(), because eas build gives me fatal error on Random.Default.

Finally I called the getNotificationOptions function within MarketingCloudConfig.builder().apply as suggested.

Here the complete code of ExpoMarketingCloudSdkApplicationLifecycleListener class

package expo.modules.marketingcloudsdk

import android.app.Application
import android.content.Context
import android.util.Log
import com.salesforce.marketingcloud.MCLogListener
import com.salesforce.marketingcloud.MarketingCloudConfig
import com.salesforce.marketingcloud.MarketingCloudSdk
import com.salesforce.marketingcloud.notifications.NotificationCustomizationOptions
import com.salesforce.marketingcloud.notifications.NotificationManager
import com.salesforce.marketingcloud.sfmcsdk.BuildConfig
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdk
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdkModuleConfig
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogLevel
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogListener
import expo.modules.core.interfaces.ApplicationLifecycleListener

import android.app.PendingIntent
import java.util.Random

class ExpoMarketingCloudSdkApplicationLifecycleListener : ApplicationLifecycleListener {
  override fun onCreate(application: Application) {
    // Initialize logging _before_ initializing the SDK to avoid losing valuable debugging information.
    if(getDebug(application)) {
      SFMCSdk.setLogging(LogLevel.DEBUG, LogListener.AndroidLogger())
      MarketingCloudSdk.setLogLevel(MCLogListener.VERBOSE)
      MarketingCloudSdk.setLogListener(MCLogListener.AndroidLogListener())
      SFMCSdk.requestSdk { sdk ->
        sdk.mp { push ->
          push.registrationManager.registerForRegistrationEvents {
            // Log the registration on successful sends to the MC
            Log.i("~#SFMC-expo", "Registration: $it")
          }
        }
      }
    }

    // Configure Salesforce Marketing Cloud SDK
    SFMCSdk.configure(application, SFMCSdkModuleConfig.build {
      pushModuleConfig = MarketingCloudConfig.builder().apply {
        setApplicationId(getAppId(application))
        setAccessToken(getAccessToken(application))
        setAnalyticsEnabled(getAnalyticsEnabled(application))
        setMarketingCloudServerUrl(getServerUrl(application))
        setDelayRegistrationUntilContactKeyIsSet(getDelayRegistrationUntilContactKeyIsSet(application))
        if(getSenderId(application) != "") setSenderId(getSenderId(application))
        setInboxEnabled(getInboxEnabled(application))
        setMarkMessageReadOnInboxNotificationOpen(getMarkMessageReadOnInboxNotificationOpen(application))
        setNotificationCustomizationOptions(getNotificationOptions(application))
      }.build(application)
    }) { initStatus ->
      // TODO handle initialization status
      Log.e("SMFCSdk: initStatus", initStatus.toString())
    }
  }

  private fun getDebug(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_debug) == "true"
  private fun getAppId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_app_id)
  private fun getAccessToken(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_access_token)
  private fun getServerUrl(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_server_url)
  private fun getSenderId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_sender_id)
  private fun getAnalyticsEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_analytics_enabled) == "true"
  private fun getDelayRegistrationUntilContactKeyIsSet(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_delay_registration_until_contact_key_is_set) == "true"
  private fun getInboxEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_inbox_enabled) == "true"
  private fun getMarkMessageReadOnInboxNotificationOpen(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_mark_message_read_on_inbox_notification_open) == "true"

  private fun getNotificationOptions(context: Context): NotificationCustomizationOptions {
    return NotificationCustomizationOptions.create(
      R.drawable.notification_icon,
        { context, notificationMessage ->
            val requestCode = Random().nextInt()
            val url = notificationMessage.url
            val payload = notificationMessage.customKeys
            val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
                // Add custom keys to the intent
                payload.forEach { (key, value) ->
                    putExtra(key, value)
                }
                if (!url.isNullOrEmpty()) {
                    putExtra("url", url)
                }
            }

            // Return a PendingIntent with the Intent that includes the custom keys
            PendingIntent.getActivity(
                context,
                requestCode,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
        },
        { context, notificationMessage ->
            if (notificationMessage.url.isNullOrEmpty()) {
                NotificationManager.createDefaultNotificationChannel(context)
            } else {
                "UrlNotification"
            }
        }
    )
  }
}

Please tell me if there is something wrong.

spenceps commented 2 weeks ago

I had to change this val requestCode = Random.Default.nextInt() to this val requestCode = Random().nextInt(), because eas build gives me fatal error on Random.Default.

@danny88dam I used import kotlin.random.Random for the random number. I don't know if that is the issue though.

Here is the full file:

package expo.modules.marketingcloudsdk

import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import com.salesforce.marketingcloud.MCLogListener
import com.salesforce.marketingcloud.MarketingCloudConfig
import com.salesforce.marketingcloud.MarketingCloudSdk
import com.salesforce.marketingcloud.notifications.NotificationCustomizationOptions
import com.salesforce.marketingcloud.notifications.NotificationManager
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdk
import com.salesforce.marketingcloud.sfmcsdk.SFMCSdkModuleConfig
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogLevel
import com.salesforce.marketingcloud.sfmcsdk.components.logging.LogListener
import expo.modules.core.interfaces.ApplicationLifecycleListener
import kotlin.random.Random

class ExpoMarketingCloudSdkApplicationLifecycleListener : ApplicationLifecycleListener {
  override fun onCreate(application: Application) {
    // Initialize logging _before_ initializing the SDK to avoid losing valuable debugging information.
    if(getDebug(application)) {
      SFMCSdk.setLogging(LogLevel.DEBUG, LogListener.AndroidLogger())
      MarketingCloudSdk.setLogLevel(MCLogListener.VERBOSE)
      MarketingCloudSdk.setLogListener(MCLogListener.AndroidLogListener())
      SFMCSdk.requestSdk { sdk ->
        sdk.mp { push ->
          push.registrationManager.registerForRegistrationEvents {
            // Log the registration on successful sends to the MC
            Log.i("~#SFMC-expo", "Registration: $it")
          }
        }
      }
    }

    // Configure Salesforce Marketing Cloud SDK
    SFMCSdk.configure(application, SFMCSdkModuleConfig.build {
      pushModuleConfig = MarketingCloudConfig.builder().apply {
        setApplicationId(getAppId(application))
        setAccessToken(getAccessToken(application))
        setAnalyticsEnabled(getAnalyticsEnabled(application))
        setMarketingCloudServerUrl(getServerUrl(application))
        setDelayRegistrationUntilContactKeyIsSet(getDelayRegistrationUntilContactKeyIsSet(application))
        if(getSenderId(application) != "") setSenderId(getSenderId(application))
        setInboxEnabled(getInboxEnabled(application))
        setMarkMessageReadOnInboxNotificationOpen(getMarkMessageReadOnInboxNotificationOpen(application))
        setNotificationCustomizationOptions(
            getNotificationOptions(application)
        )
      }.build(application)
    }) { initStatus ->
      // TODO handle initialization status
      Log.e("SMFCSdk: initStatus", initStatus.toString())
    }
  }

  private fun getNotificationOptions(context: Context): NotificationCustomizationOptions {
    return NotificationCustomizationOptions.create(
      R.drawable.notification_icon,
        { context, notificationMessage ->
            val requestCode = Random.Default.nextInt()
            val url = notificationMessage.url
            val payload = notificationMessage.customKeys
            val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
                // Add custom keys to the intent
                payload.forEach { (key, value) ->
                    putExtra(key, value)
                }
                if (!url.isNullOrEmpty()) {
                    putExtra("url", url)
                }
            }

            // Return a PendingIntent with the Intent that includes the custom keys
            PendingIntent.getActivity(
                context,
                requestCode,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
        },
        { context, notificationMessage ->
            if (notificationMessage.url.isNullOrEmpty()) {
                NotificationManager.createDefaultNotificationChannel(context)
            } else {
                "UrlNotification"
            }
        }
    )
  }
  private fun getDebug(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_debug) == "true"
  private fun getAppId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_app_id)
  private fun getAccessToken(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_access_token)
  private fun getServerUrl(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_server_url)
  private fun getSenderId(context: Context): String = context.resources.getString(R.string.expo_marketingcloudsdk_sender_id)
  private fun getAnalyticsEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_analytics_enabled) == "true"
  private fun getDelayRegistrationUntilContactKeyIsSet(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_delay_registration_until_contact_key_is_set) == "true"
  private fun getInboxEnabled(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_inbox_enabled) == "true"
  private fun getMarkMessageReadOnInboxNotificationOpen(context: Context): Boolean = context.resources.getString(R.string.expo_marketingcloudsdk_mark_message_read_on_inbox_notification_open) == "true"
}
danny88dam commented 2 weeks ago

Hi @spenceps I tryed your version and it seems that the system correctly receives the notification, but addNotificationResponseReceivedListener doesn't trigger when the user taps on the notification, instead when the user tap on the notification handled vai firebase/expo method onMessageReceived addNotificationResponseReceivedListener seems to trrigger regularly.

I also discover that sfmc api handles the opt-out of push noticiation in app, so when the push notification has been sent it wil be delivered to all registered devices, the app of each device is going to check via sfmc sdk whether the user has otped out and if so, it is going to discart the notifification by preventing it to be displayed.

This means that if we handle notifications via firebase/expo the user is always going to receive the notificifation even if he have opted out.

spenceps commented 2 weeks ago

Hi @danny88dam. Can you clarify

addNotificationResponseReceivedListener doesn't trigger when the user taps on the notification, instead when the user tap on the notification handled vai firebase/expo method onMessageReceived addNotificationResponseReceivedListener seems to trrigger regularly

I'm listening for pushes through the expo/firebase method and wasn't aware of any other way.

useEffect(() => {
    const subscription = Notifications.addNotificationResponseReceivedListener(response => {
      const payload = response.notification.request.content.data;
      handlePush(payload);
    });
    return () => {
      subscription.remove();
    };
  }, [handlePush]);

As for opting out on Android, I didn't know about this either. How can a user opt out of pushes in a way that the push is still sent?

danny88dam commented 2 weeks ago

Hi @spenceps I used the same technique, in my case the first thing the listener addNotificationResponseReceivedListener does is console.log Platform.OS and push data.

When the user taps on push nofication handled by modified ExpoMarketingCloudSdkApplicationLifecycleListener, the interaction doesn't trigger the listener because I don't read anything in console.

Regarding the issue of still receiving push notifications even though the user has opted out, let me better explain the scenario. The SFMC api provides methods enablePush and disablePush to opt-in and opt-put. These methods do not work at OS permission level, but only at the application level and the simply tell MC some sort of flat representing whether the user wants to receive notification.

In my test seems that this "flag" is consider only in app SF MC sdk, maybe encapsulated somewhere here

open class ExpoFirebaseMessagingService : ExpoFirebaseMessagingService() {
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    if (PushMessageManager.isMarketingCloudPush(remoteMessage)) {
      SFMCSdk.requestSdk { sdk ->
        sdk.mp {
          it.pushMessageManager.handleMessage(remoteMessage) // MAYBE ENCAPSULATED HERE
        }
      }
      super.onMessageReceived(remoteMessage); // I ADDED THIS LINE FOR TEST
    } else {
      super.onMessageReceived(remoteMessage)
    }
  }
}

So only if the notification is handled by pushMessageManager.handleMessage the opt-in/out flag is considered, notification eventually discarted and not notify to the os.

At least this is what I seem to have understood so far

andrejpavlovic commented 2 weeks ago

I'm wondering if we should be using the NotificationManager.getDefaultNotificationBuilder in order to maintain existing sfmc handling behavior as per Full Control Customization example.

I threw some code together based on that example and @spenceps work. Haven't tested it though.

        setNotificationCustomizationOptions(
          NotificationCustomizationOptions.create { context, notificationMessage ->
            val builder = NotificationManager.getDefaultNotificationBuilder(
              context,
              notificationMessage,
              NotificationManager.createDefaultNotificationChannel(context),
              R.drawable.notification_icon
            )
            builder.setContentIntent(
              NotificationManager.redirectIntentForAnalytics(
                context,
                PendingIntent.getActivity(
                  context,
                  Random.Default.nextInt(),
                  context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
                    // Add custom keys to the intent
                    notificationMessage.customKeys.forEach { (key, value) ->
                      putExtra(key, value)
                    }
                    if (!notificationMessage.url.isNullOrEmpty()) {
                      putExtra("url", notificationMessage.url)
                    }
                  },
                  PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                ),
                notificationMessage,
                true
              )
            )
          }
        )
danny88dam commented 1 week ago

Hi @andrejpavlovic I tested your patch and it seems to work well. Thank you, @spenceps thank you too

spenceps commented 1 week ago

@andrejpavlovic - I tried this out as well and it worked. Thanks everyone.

spenceps commented 1 week ago

@andrejpavlovic - Would this change mess up anyone else that is using this? I'm wondering if this change should be added to the next release.