braze-inc / braze-expo-plugin

Other
12 stars 9 forks source link

[Bug]: iOS not hitting any push notification listeners #22

Open mvolonnino opened 1 year ago

mvolonnino commented 1 year ago

Which Platforms?

iOS

Which React Native Version?

"react-native": "0.72.6",

Which @braze/expo-plugin version?

"@braze/expo-plugin": "^1.1.2",

Which @braze/react-native-sdk SDK version?

"@braze/react-native-sdk": "^8.0.0",

Repro Rate

100%

Steps To Reproduce

Followed the developer guides to implement the braze expo plugin

   plugins: [
      '@react-native-firebase/app',
      '@react-native-firebase/dynamic-links', // https://rnfirebase.io/dynamic-links/usage#ios-setup
      [
        '@braze/expo-plugin',
        {
          baseUrl: 'sdk.iad-05.braze.com',
          androidApiKey:
            getEnvVar(EnvKeys.BRAZE_ANDROID_API_KEY) ||
            process.env.EXPO_PUBLIC_BRAZE_ANDROID_API_KEY,
          iosApiKey:
            getEnvVar(EnvKeys.BRAZE_IOS_API_KEY) || process.env.EXPO_PUBLIC_BRAZE_IOS_API_KEY,
          enableBrazeIosPush: true,
          enableFirebaseCloudMessaging: true,
          firebaseCloudMessagingSenderId: '869676010842',
          androidHandlePushDeepLinksAutomatically: true,
        },
      ],
      [
        'expo-build-properties',
        {
          ios: {
            deploymentTarget: '13.0',
            useFrameworks: 'static',
          },
        },
      ],
    ],

I also have braze.changeUser() when logging in.

I keep getting BadDeviceToken for iOS push notifications. I have FCM setup through EAS credentials as well. Android is working just fine when sending push notifications from braze campaigns. Also should note that when i test FCM push notifications through curl, i receive them just fine on iOS.

I should note as well that i have received In App Messaging on both iOS and Android, so i know the keys and baseUrl is setup correctly.

Expected Behavior

To receive the Test push notifications sending to my iOS device

Actual Incorrect Behavior

No braze push notification comes through, and sometimes ill get a BadDeviceToken show up in the message activity logs.

I should note that everytime i send it to my email that is signed into app on iOS, i get a success message on the push notification sending.

Verbose Logs

No response

Additional Information

No response

jerielng commented 1 year ago

Hi @mvolonnino, couple of questions to clarify:

With this BadDeviceToken error, it's possible that there is a mismatch in the environment (sandbox vs production) and what's being set on the Braze dashboard for your app settings. Are you trying to send these pushes to a local development build or a published .ipa build (TestFlight/App Store)? Could you check on the Braze dashboard what is selected under your push certificate settings? For example, if you are using a build directly from Xcode, you'll want to have the development environment selected with a corresponding sandbox certificate. image

I should note that everytime i send it to my email that is signed into app on iOS, i get a success message on the push notification sending.

I'm not sure I fully understand this part. Do you mind clarifying what you mean by sending to your email?

mvolonnino commented 1 year ago

@jerielng - thank you for responding! I do apologize, i actually figured out the issue last night and forgot to update this post. It was related to the development environment selected with that picture you sent above!

To answer your last question (sorry about being so vague, was a long day of testing & building yesterdayšŸ¤£)

I do have a couple follow up questions thought:

  1. With EAS builds labeled with developmentClient: true, even if I use EAS Build (no --local) flag, as I am building Ad-Hoc to use on my iOS device, that makes it a production environment? Right now I am currently re-platforming our app from pure RN to Expo, and using the same credentials from Apple, so Braze has already been hooked up. When I make TestFlight builds with our development environment on the App now (RN), the Send to Development Environment was checked for it to work - do you have any idea of why now with the Expo EAS Build, that has developmentClient: true would now be needing that switch to change to Send to Production through the Braze Developer Console?

  2. With that change to Send to Production, that will not send to 'actual' production App correct? as it will send to whatever the API key is for that Workspace through the braze developer portal correct? I currently have two different API keys, one for our testing (staging which has been hooked up already with the Send to Development Environment which like i stated has been working for our TestFlight builds of our bare RN app) and one for production which is in the App store.

  3. My next question that I can't seem to find much on at all anymore through the Braze documentation in setting up Push Notifications is getting Rich Notifications to work with Expo Plugin. I remember months ago, I had to create an Rich Notification extension for the bare RN app and set up one or two more things - which I have done and Rich Notifications are working on iOS - but have not seen where / how to set up for the new re-platformed App that we will soon be switching to for our production App as well. These Rich Notifications are a big part of our App and do not want to lose any features we currently have with Braze!

  4. Last question - which i admit, I have not looked to deep into it yet (pun intended on this one) is about Deep Linking. Since I am on Expo 49, I have re-platformed the app to use Expo Router as well, which the big selling point is the deep linking that is built into the Router. Does the Braze plugin work out of the box with that? Or will I need to create my own deep link handler for Braze push notifications? We currently use that and have Deep Link Manager setup in the bare RN App in the app stores now - which would currently be incompatible with Expo and the Expo Router, so just wondering if the Braze Plugin works out of the box, or if Ill have to implement something to allow deep linking through push notifications

jerielng commented 1 year ago
  1. Using Send to Production is for any build that is generated with a Apple distribution certificate vs. a development certificate. So if you are generating an archived build that you send to TestFlight, that will need a Sandbox & Production push certificate + the Send to Production setting on the Braze dashboard. We're currently looking into streamlining that process on our end.
  2. That's correct.Selecting Send to Production will set up the push to be deliverable to an archived version of your app (an .ipa that lives on TestFlight, App Center, App Store, etc.). For this to fully work, you will need to make sure that the push certificate you upload to Braze is configured for Sandbox + Production, and is tied to the bundle ID of that archived app.
  3. You'll need to follow our native integration guide for implementing rich push.
  4. I would recommend looking at our docs on deep linking, which also reference the sample app. That React Native sample should have an end-to-end working example on how to set up deep links in your app. Please let us know if you have any questions around that.
mvolonnino commented 1 year ago

@jerielng - Thank you I appreciate the response!

so I am a little confused. I have set up the deep linking and enabled it on Android like so

  plugins: [
      '@react-native-firebase/app',
      '@react-native-firebase/dynamic-links', // https://rnfirebase.io/dynamic-links/usage#ios-setup
      [
        '@braze/expo-plugin',
        {
          baseUrl: 'sdk.iad-05.braze.com',
          androidApiKey:
            getEnvVar(EnvKeys.BRAZE_ANDROID_API_KEY) ||
            process.env.EXPO_PUBLIC_BRAZE_ANDROID_API_KEY,
          iosApiKey:
            getEnvVar(EnvKeys.BRAZE_IOS_API_KEY) || process.env.EXPO_PUBLIC_BRAZE_IOS_API_KEY,
          // enableBrazeIosPush: true,
          enableFirebaseCloudMessaging: true,
          firebaseCloudMessagingSenderId: '869676010842',
          androidHandlePushDeepLinksAutomatically: true,
        },
      ],
      [
        'expo-build-properties',
        {
          ios: {
            deploymentTarget: '13.0',
            useFrameworks: 'static',
          },
        },
      ],
    ],

with the androidHandlePushDeepLinksAutomatically set to true, I have tested through the Braze dashboard sending an Android push notification with a 'On Click Behavior' set to 'Deep Link into Application'and the deep link attached. This worked perfectly, I clicked on the push notification for Android, and without anything else setup (other than Expo Router and formatting the deep link correctly through the Braze dashboard), it pushed the correct screen with the params and it all worked.

for iOS though, i went through the docs and over the sample app and set this up

useEffect(() => {
    const unsub = braze.addListener(braze.Events.PUSH_NOTIFICATION_EVENT, data => {
      console.log(
        `Push Notification event of type ${data.push_event_type} seen. Title ${data.title}\n and deeplink ${data.deeplink}`
      );
      console.log(JSON.stringify(data, undefined, 2));
    });

    const iosListener = Linking.addEventListener('url', event => {
      if (Platform.OS !== 'ios') return;
      console.log('event', event);
      router.push(event.url);
    });

    // No `url` event is triggered on application start, so this handles
    // the case where a deep link launches the application
    Linking.getInitialURL()
      .then(url => {
        if (url) {
          console.log(`Linking.getInitialURL is ${url}`);
          router.push(url);
        }
      })
      .catch(err => console.error('Error getting initial URL', err));

    // Handles deep links when an iOS app is launched from hard close via push click.
    // Note that this isn't handled by Linking.getInitialURL(), as the app is
    // launched not from a deep link, but from clicking on the push notification.
    // For more detail, see `Braze.getInitialURL` in `index.js`.
    braze.getInitialURL(url => {
      if (url) {
        console.log(`Braze.getInitialURL is ${url}`);
        router.push(url);
      }
    });

    return () => {
      iosListener.remove();
      unsub.remove();
    };
  }, []);

The push notification is setup the same as the Android push notification in the braze dashboard, with the same link and the only thing the push notification does is open the app. None of the listeners fire either on the iOS side that are setup just like in the sample app.

Is there anymore work I am not seeing in through the docs for the Expo Plugin to deep link or get access to the notification like in some of those listeners?

*edit - added a platform check, as with the router.push(url), it was continuously pushing Android and stuck in an infinite loop of always logging & pushing the route in Android. Still no luck in getting the iOS listeners to fire at all

mvolonnino commented 1 year ago

@jerielng - here are some more findings that I have found! Hopefully these make sense, I tried to document each listener as best I could. These were tested in a preview build through EAS Build. This is a build with developmentClient: false and is internally distributed Ad Hoc using on Android device and my iOS device.

import { useEffect } from 'react';
import braze from '@braze/react-native-sdk';
import { Alert } from 'react-native';
import { router } from 'expo-router';
import notifee from '@notifee/react-native';
import * as Linking from 'expo-linking';
import messaging from '@react-native-firebase/messaging';
import dynamicLinks from '@react-native-firebase/dynamic-links';

/**
 * handles deep linking by grabbing the url from the data passed in and calls `router.push` with the url
 * @param data - the data object from the push notification or the event object from the `Linking.addEventListener` listener
 */
const handleDeepLink = (data: any) => {
  // ab_uri is attached from a braze push notification when `Deep Link into Application` is checked
  // should construct FCM push notifications to have a `ab_uri` field instead of `ab_uri` to be consistent
  // linking events fire with a `url` field, so we can use that to push to the router
  const url = data?.ab_uri || data?.url;

  if (url) {
    router.push(url);
  }
};

// this should just be for testing in devClient & preview builds
function devLogs(message: string, data: any) {
  Alert.alert(message, JSON.stringify(data, null, 2));
  console.log(message, JSON.stringify(data, null, 2));
}
/**
  ! have not gotten this to fire at all on either iOS or Android, so badge count is not being incremented on background message received
  @see https://notifee.app/react-native/docs/ios/badges#integration
  @platform ios only
 */
export function setBackgroundMessageHandler() {
  messaging().setBackgroundMessageHandler(async remoteMessage => {
    await notifee.incrementBadgeCount();
    devLogs('setBackgroundMessageHandler', remoteMessage);
  });
}

/**
  So far, this is the only reliable way to setup iOS push listeners for `Braze` is with `notifee` and their methods:
  - `onBackgroundEvent`

  @ios Braze push notifications don't seem to handle deep linking automatically through Expo Router which is a real shame, so we have to handle it manually and push whatever uri is passed from the notification to the router.
      - when trying to utilize Linking.addEventListener('url', ...) to handle deep linking, it fires in an infinite loop on iOS when trying to use it as a braze listener with a deepLink attached
  @android Braze push notifications handle deep linking automatically and through Expo Router, so we don't need to do anything special here.
*/
export function setNotifeeEventHandlers() {
  notifee.onBackgroundEvent(async ({ detail }) => {
    const { notification } = detail;
    if (notification) {
      devLogs('onForegroundEvent', notification);
      handleDeepLink(notification?.data);
      await notifee.decrementBadgeCount().then(v => devLogs('backgroundEventBadgeCount', v));
    }
  });
}

/**
 * This is the only reliable way to setup iOS push listeners for `Braze` is with `notifee` and their methods:
 * - `onForegroundEvent` - fires for all events, even the 'background' events and the onBackgroundEvent does not fire at all
 *
 * @android Braze & FCM push notifications seem to be handled automatically by `Expo Router` and do not hit the notifee listeners
 * @ios Braze and FCM push notifications are handled with the notifee listeners (mainly just the onForegroundEvent). The `braze.getInitialUrl` fires, but so does the `notifee.onForegroundEvent`, so I think its easier to just keep the notifee listener to handle the events instead of handling them in two different places.
 */
function usePushNotificationHandler() {
  // sets up all our push notification listeners that seem to be needed for iOS mainly.
  // - android braze push notifications seem to be handled automatically by Expo Router
  useEffect(function pushNotificationHandler() {
    const androidBrazeListener = braze.addListener(braze.Events.PUSH_NOTIFICATION_EVENT, data => {
      console.log(
        `Push Notification event of type ${data.push_event_type} seen. Title ${data.title}\n and deeplink ${data.deeplink}`
      );
      console.log(JSON.stringify(data, undefined, 2));
    });

    // * working in getting the correct url from the link - now need to have it be formatted correctly for file based routing
    const unsubDynamicLinks = dynamicLinks().onLink(link => {
      console.log('firebaseListener:', JSON.stringify(link, null, 2));
    });

    /**
     * iOS FCM & Braze: All fire on this event, not the other events that are supposed to fire through braze docs
        - Android: This has not fired on Android at all, it seems Expo Router overrides the notification events
          - side note, this does work for FCM push notifications as well which is why there is no `messaging()` listeners.

      @see https://notifee.app/react-native/docs/events#foreground-events
     */
    const unsubNotifeeForeground = notifee.onForegroundEvent(async ({ detail }) => {
      const { notification } = detail;

      if (notification) {
        devLogs('onForegroundEvent', notification);
        handleDeepLink(notification?.data);
        await notifee.decrementBadgeCount().then(v => devLogs('foregroundEventBadgeCount', v));
      }
    });

    /**
      The rest of these listeners were taking from the Braze Sample App and documentation on how push notifications should work with their Expo Plugin
        - these listeners do not seem to be very reliable on iOS, so we are using the notifee listeners instead
        - Android push notifications seem to be handled automatically by Expo Router, and the only that gets hit as well is the `Linking.getInitialURL` listener, but that is not needed as Expo Router handles the routing for us

      Keeping them commented out for documentation purposes

      @see https://github.com/braze-inc/braze-react-native-sdk/blob/master/BrazeProject/BrazeProject.tsx
      @see https://www.braze.com/docs/developer_guide/platform_integration_guides/react_native/push_notifications/
    */

    // ! this only fires if there is an active `notifee` listener for `onForegroundEvent`.
    // if both are active, this will fire nonstop and continuously fire the callback passed - huge issue (obviously)
    // need to test a new build with only this listener active and no notifee installed at all to see if it fires
    // - quick test of just not having notifee listeners and this does not fire, but not sure what notifee is doing in native code by just being installed
    // const iosBrazeListener = Linking.addEventListener('url', async event => {
    //   devLogs('Linking.addEventListener', event);
    //   handleDeepLink(event);
    // });

    // _______________________________________________________________

    // No `url` event is triggered on application start, so this handles
    // the case where a deep link launches the application
    /*
     * Android FCM: this fires from app quit with the url, but is not needed as Expo Router handles the routing for us (if the 'link' attachment is on the FCM payload)
      ! Android Braze: this does not fire, braze is handled automatically by Expo Router with braze push notifications
      ______
      ! iOS FCM: this does not fire, notifee.onForegroundEvent fires with the url attached within the data object on the notification
      ! iOS Braze: this does not fire, braze.getInitialURL fires, but see below comments on why this is not needed
     */
    Linking.getInitialURL()
      .then(url => {
        if (url) {
          devLogs('Linking.getInitialURL', url);
          // router.push(url);
        }
      })
      .catch(err => console.error('Error getting initial URL', err));

    // _______________________________________________________________

    // Handles deep links when an iOS app is launched from hard close via push click.
    // Note that this isn't handled by Linking.getInitialURL(), as the app is
    // launched not from a deep link, but from clicking on the push notification.
    // For more detail, see `Braze.getInitialURL` in `index.js`.
    /*
     * ios only
     * this works in grabbing the initialUrl, but notifee.onForegroundEvent also fires with the same url so this may be redundant
     */
    braze.getInitialURL(url => {
      if (url) {
        devLogs('braze.getInitialURL', url);
        // router.push(url);
      }
    });

    return () => {
      // iosBrazeListener.remove();
      androidBrazeListener.remove();
      unsubDynamicLinks();
      unsubNotifeeForeground();
    };
  }, []);
}

export default usePushNotificationHandler;

Let me know if you would like to discuss anything further / are curious about anything else. Going to try another build without notifee to see if that module is 'overriding' any events or anything like that