exponea / exponea-react-native-sdk

MIT License
8 stars 13 forks source link

iOS Rich Notifications extension - Deep link opens app and web browser #100

Closed JBlazej closed 8 months ago

JBlazej commented 1 year ago

Hi Exponea team,

Description

I implemented the Rich Notifications extension for iOS. However when a notification contains a deep link, the expected behavior is for the app to open. Unfortunately, the app is open together with a web browser.

None: The Only difference between documentation and my implementations is in Podfile. We are using React Native Firebase SDK which needs to set use_frameworks! :linkage => :static

target 'notificationService' do
  use_frameworks! // –> Added
  pod 'ExponeaSDK-Notifications'
end

target 'notificationContent' do
  use_frameworks! // –> Added
  pod 'ExponeaSDK-Notifications'
end

Note: Before integrating the Rich Notifications extension, we successfully implemented Apple Associated Domains and notifications with deep links worked as expected.

Steps to Reproduce

  1. Add Rich Notifications extension
  2. Build App
  3. Send a notification with a deep link
  4. Observe that the app and a web browser are both opened when the notification is tapped.

Dependencies

"dependencies": {
    "@react-navigation/elements": "1.3.19",
    "@react-navigation/native": "6.1.8",
    "@react-navigation/native-stack": "6.9.14",
    "@react-navigation/stack": "6.3.18",
    "expo": "49.0.13",
    "expo-linking": "~5.0.2",
    "react-native": "0.72.6",
    "react-native-exponea-sdk": "1.5.2",
    ...
}

Environment

Screen records

With Rich Notifications extension

https://github.com/exponea/exponea-react-native-sdk/assets/31972521/70d6b02c-204c-42e6-b524-c2407ee479ef

Without Rich Notifications extension

https://github.com/exponea/exponea-react-native-sdk/assets/31972521/9cfc45f7-5ba3-4a2d-bfdb-57c60b90ba61

Expected Behavior

When a notification with a deep link is received, tapping on it should open the app, taking the user to the appropriate in-app content, as it did before implementing the Rich Notifications extension.

adam1929 commented 1 year ago

Hi @JBlazej thank you for reporting. I think that adding use_frameworks into Podfile does not change behaviour. Opening of your app and (then) browser is typical behaviour when app is opened to handle deeplink/universal link but "responses" with FALSE so OS will open browser as fallback. Please write your AppDelegate implementation. There could be few things:

  1. Is extending ExponeaRNAppDelegate ?
  2. what is implementation of your func application( _ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) ?
  3. what is implementation of your application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) ?

Thank you

JBlazej commented 10 months ago

Hi @adam1929 Here is my AppDelegate file. I use Expo and I have a problem with ExponeaRNAppDelegate so I write implementation to AppDelegate.

#import "AppDelegate.h"
#import <ExponeaRNAppDelegate.h>

// @generated begin react-native-quick-actions-import - expo prebuild (DO NOT MODIFY) sync-6ea4aa8a461f1aab4f2c38893cd59140deb974e4
#import "RNQuickActionManager.h"
// @generated end react-native-quick-actions-import
#import <Firebase/Firebase.h>

#import <React/RCTBundleURLProvider.h>

 #import <React/RCTLinkingManager.h>
 #import <adyen_react_native/ADYRedirectComponent.h>
 #import <FBSDKCoreKit/FBSDKCoreKit-swift.h>

@implementation AppDelegate

// @generated begin react-native-quick-actions-delegate - expo prebuild (DO NOT MODIFY) sync-c20e1981d7932e1a91e1d34dbf5c36e3ff230412
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler {
  [RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler];
}
// @generated end react-native-quick-actions-delegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
[FIRApp configure];
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
  self.moduleName = @"main";

  // You can add your custom initial props in the dictionary below.
  // They will be passed down to the ViewController used by React Native.

self.initialProps = @{};
[UNUserNotificationCenter currentNotificationCenter].delegate = self;

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

// Linking API

 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
 {
   if([super application:application openURL:url options:options]){
     return YES;
   }

   if([[FBSDKApplicationDelegate sharedInstance] application:application openURL:url options:options]) {
     return YES;
   }

   if([RCTLinkingManager application:application openURL:url options:options]){
     return YES;
   }

   if([ADYRedirectComponent applicationDidOpenURL:url]) {
      return YES;
   }

   return NO;
 }

// Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {

BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
[Exponea continueUserActivity:userActivity];

  return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       didReceiveNotificationResponse:(UNNotificationResponse *)response
       withCompletionHandler:(void (^)(void))completionHandler
{
    [Exponea handlePushNotificationOpenedWithResponse: response];
    completionHandler();
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{

[Exponea handlePushNotificationToken: deviceToken];
return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];

}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
  return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{

[Exponea handlePushNotificationOpenedWithUserInfo:userInfo];
return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];

}

@end
adam1929 commented 10 months ago

Hi @JBlazej thank you for your AppDelegate source code, it truly helps a lot! I need to point out that I have not experience with Expo + ExponeaSDK combination but I think problem of using both is not wrong here. What I'm seeing is method:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       didReceiveNotificationResponse:(UNNotificationResponse *)response
       withCompletionHandler:(void (^)(void))completionHandler
{
    [Exponea handlePushNotificationOpenedWithResponse: response];
    completionHandler();
}

This method is called when you click on notification. Opening of app is required step and done by iOS system. Opening browser is not expected from our side. So:

  1. Calling of [Exponea handlePushNotificationOpenedWithResponse: response]; is great, it allows SDK to track click event
  2. ^ this method also opens action (defined by UNNotificationResponse::actionIdentifier)
  3. Opening of action URL is handled this way:
    • if you send action URL with type "browser", URL is sent to browser directly
    • if you send action URL with type "deeplink" and URL starts with "https", this URL is handled as Universal Link
    • if you send action URL with type "deeplink" and URL is not HTTPS, it is handled as DeepLink

Universal links are handled by AppDelegate with application:(UIApplication *)application continueUserActivity and Deeplinks are handled by AppDelegate application:(UIApplication *)application openURL.

In case of UL, I can see last row of method as:

return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;

So calling super, I believe, is returning non-nil FALSE, therefore fallback 'result' is not used, so you are telling to system that you don't handle given UL. So system will open browser.

In case of DL, I can see multiple try-to-open URL usages, I'm not able to tell if they return TRUE or FALSE. In case that they not handled DL properly, you are returning FALSE, so iOS system will open browser for that link.

If these info didn't help you, please write back; with some logs (for example try put some logs into DeepLink method with multiple try-to-open URL usages) and some debugging findings. I personally struggles a lot of time with DL vs UL behaviours in app and what AppDelegate method is called and so on :-/

tr3v3r commented 9 months ago

@adam1929 Hi! We faced completely the same issue but we use bare workflow with one expo module installed, so we also have to extend from EXAppDelegateWrapper. We've validated that after replacement with RTCAppDelegate pushes are opened properly.

Here is the EXAppDelegateWrapper itself. I can't see stuff related to deep links here.

https://github.com/expo/expo/blob/b85cbf67f9c0fd0ec6a53473fd5256ab2a60c0ed/packages/expo-modules-core/ios/AppDelegates/EXAppDelegateWrapper.mm#L3

I'd really appreciate any help.

p.s. We even tried explicitly passing a "true" value to application:(UIApplication *)application continueUserActivity. But still no luck.

Both app and browser opens

BTW @JBlazej did you manage to solve this issue?

Updated:

We revealed that the issue was caused by https://github.com/thebergamo/react-native-fbsdk-next (v >= 12 ). Not defined the exact problem but at least we know that after downgrading to v. 11.3 - the issue is gone. Now Pushes are open as expected.

adam1929 commented 8 months ago

Thank you @tr3v3r from your last message with hint to downgrade react-native-fbsdk-next. Could this help to you @JBlazej ? I checked EXAppDelegateWrapper from link posted by @tr3v3r and related files but I cannot see any relations with PushNotifications nor DL/UL handling, so I'm unable to tell which part of Expo is causing incompatibility with SDK.