aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

Notifications.Push.onNotificationOpened is not triggered for iOS background app #11303

Closed tyndria closed 1 year ago

tyndria commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Authentication, Analytics, GraphQL API, Push Notifications

Amplify Categories

auth, function, api, analytics, notifications

Environment information

``` System: OS: macOS 12.6.2 CPU: (8) x64 Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz Memory: 142.73 MB / 16.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 17.6.0 - ~/.nvm/versions/node/v17.6.0/bin/node Yarn: 1.22.19 - ~/.nvm/versions/node/v17.6.0/bin/yarn npm: 8.5.1 - ~/.nvm/versions/node/v17.6.0/bin/npm Watchman: 2023.02.13.00 - /usr/local/bin/watchman Browsers: Chrome: 112.0.5615.137 Safari: 15.6.1 Safari Technology Preview: 14.2 npmPackages: @apollo/client: ^3.7.7 => 3.7.7 @apollo/client/cache: undefined () @apollo/client/core: undefined () @apollo/client/errors: undefined () @apollo/client/link/batch: undefined () @apollo/client/link/batch-http: undefined () @apollo/client/link/context: undefined () @apollo/client/link/core: undefined () @apollo/client/link/error: undefined () @apollo/client/link/http: undefined () @apollo/client/link/persisted-queries: undefined () @apollo/client/link/retry: undefined () @apollo/client/link/schema: undefined () @apollo/client/link/subscriptions: undefined () @apollo/client/link/utils: undefined () @apollo/client/link/ws: undefined () @apollo/client/react: undefined () @apollo/client/react/components: undefined () @apollo/client/react/context: undefined () @apollo/client/react/hoc: undefined () @apollo/client/react/hooks: undefined () @apollo/client/react/parser: undefined () @apollo/client/react/ssr: undefined () @apollo/client/testing: undefined () @apollo/client/testing/core: undefined () @apollo/client/utilities: undefined () @apollo/client/utilities/globals: undefined () @aws-amplify/rtn-push-notification: ^1.1.1 => 1.1.1 @aws-amplify/ui-react-native: ^1.2.14 => 1.2.14 @aws-sdk/client-lex-models-v2: ^3.262.0 => 3.279.0 @aws-sdk/client-lex-runtime-v2: ^3.262.0 => 3.279.0 (3.186.1) @babel/core: ^7.12.9 => 7.21.0 @babel/runtime: ^7.12.5 => 7.21.0 @formatjs/intl-datetimeformat: ^6.5.1 => 6.5.1 @formatjs/intl-getcanonicallocales: ^2.1.0 => 2.1.0 @formatjs/intl-locale: ^3.1.1 => 3.1.1 @formatjs/intl-numberformat: ^8.3.5 => 8.3.5 @formatjs/intl-pluralrules: ^5.1.10 => 5.1.10 @notifee/react-native: ^7.4.0 => 7.5.0 @react-native-async-storage/async-storage: ^1.18.1 => 1.18.1 @react-native-community/eslint-config: ^2.0.0 => 2.0.0 @react-native-community/netinfo: ^9.3.9 => 9.3.9 @react-native-community/push-notification-ios: ^1.10.1 => 1.10.1 @react-native-firebase/app: ^17.3.2 => 17.3.2 @react-native-masked-view/masked-view: ^0.2.8 => 0.2.8 @react-native-picker/picker: ^2.4.8 => 2.4.8 @react-navigation/bottom-tabs: ^6.5.5 => 6.5.7 @react-navigation/native: ^6.0.14 => 6.1.6 @react-navigation/native-stack: ^6.9.2 => 6.9.12 @react-navigation/stack: ^6.3.13 => 6.3.16 @reduxjs/toolkit: ^1.9.3 => 1.9.3 @reduxjs/toolkit-query: 1.0.0 @reduxjs/toolkit-query-react: 1.0.0 @rneui/base: ^4.0.0-rc.7 => 4.0.0-rc.7 @rneui/themed: ^4.0.0-rc.7 => 4.0.0-rc.7 @tsconfig/react-native: ^2.0.2 => 2.0.3 @types/jest: ^26.0.23 => 26.0.24 @types/lodash: ^4.14.191 => 4.14.191 @types/react: ^18.0.21 => 18.0.28 @types/react-native: ^0.70.6 => 0.70.11 @types/react-native-vector-icons: ^6.4.13 => 6.4.13 @types/react-test-renderer: ^18.0.0 => 18.0.0 @types/styled-components: ^5.1.26 => 5.1.26 @types/styled-components-react-native: ^5.2.1 => 5.2.1 @types/uuid: ^9.0.0 => 9.0.1 @typescript-eslint/eslint-plugin: ^5.37.0 => 5.53.0 (3.10.1) @typescript-eslint/parser: ^5.37.0 => 5.53.0 (3.10.1) HelloWorld: 0.0.1 amazon-cognito-identity-js: ^6.2.0 => 6.2.0 apollo3-cache-persist: ^0.14.1 => 0.14.1 aws-amplify: ^5.1.3 => 5.1.3 babel-jest: ^26.6.3 => 26.6.3 babel-plugin-module-resolver: ^5.0.0 => 5.0.0 date-fns: ^2.29.3 => 2.29.3 eslint: ^7.32.0 => 7.32.0 eslint-config-prettier: ^8.6.0 => 8.6.0 (6.15.0) eslint-plugin-import: ^2.27.5 => 2.27.5 eslint-plugin-prettier: ^4.2.1 => 4.2.1 (3.1.2) eslint-plugin-react-hooks: ^4.6.0 => 4.6.0 example: 0.0.1 graphql: ^16.6.0 => 16.6.0 (15.8.0) hermes-inspector-msggen: 1.0.0 husky: ^8.0.0 => 8.0.3 jest: ^26.6.3 => 26.6.3 lint-staged: ^13.1.0 => 13.1.2 lodash: ^4.17.21 => 4.17.21 metro-react-native-babel-preset: 0.72.3 => 0.72.3 prettier: ^2.8.3 => 2.8.4 react: ^18.2.0 => 18.2.0 react-intl: ^6.2.8 => 6.2.10 react-native: 0.70.6 => 0.70.6 react-native-autoheight-webview: ^1.6.5 => 1.6.5 react-native-config: ^1.5.0 => 1.5.0 react-native-device-info: ^10.4.0 => 10.4.0 react-native-flash-message: ^0.4.0 => 0.4.0 react-native-gesture-handler: ^2.9.0 => 2.9.0 react-native-get-random-values: ^1.8.0 => 1.8.0 react-native-gifted-chat: ^2.0.1 => 2.0.1 react-native-iap: ^12.10.0 => 12.10.0 react-native-linear-gradient: ^2.6.2 => 2.6.2 react-native-localize: ^2.2.6 => 2.2.6 react-native-render-html: ^6.3.4 => 6.3.4 react-native-safe-area-context: ^4.5.0 => 4.5.0 react-native-screens: ^3.18.2 => 3.20.0 react-native-secure-key-store: ^2.0.10 => 2.0.10 react-native-splash-screen: ^3.3.0 => 3.3.0 react-native-svg: ^13.8.0 => 13.8.0 react-native-svg-transformer: ^1.0.0 => 1.0.0 react-native-url-polyfill: ^1.3.0 => 1.3.0 react-native-vector-icons: ^9.2.0 => 9.2.0 react-native-webview: ^11.26.1 => 11.26.1 react-redux: ^8.0.5 => 8.0.5 react-test-renderer: 18.1.0 => 18.1.0 reactotron-react-native: ^5.0.3 => 5.0.3 redux-persist: ^6.0.0 => 6.0.0 redux-persist/integration/react: undefined () styled-components: ^5.3.9 => 5.3.9 styled-components/macro: undefined () styled-components/native: undefined () styled-components/primitives: undefined () typescript: ^4.8.3 => 4.9.5 uuid: ^9.0.0 => 9.0.0 (3.4.0, 8.3.2, 7.0.3) npmGlobalPackages: @aws-amplify/cli: 11.1.0 corepack: 0.10.0 npm: 8.5.1 typescript: 4.6.4 yarn: 1.22.19 ```

Describe the bug

Notifications.Push.onNotificationOpened is not triggered when notification is clicked for background app. What's happening instead:

Push Notifications are tested using Pinpoint "Test Messaging" service. Both Open your app and Open a deep link are tried.

Expected behavior

Notifications.Push.onNotificationOpened is triggered when notification is clicked for background app

Reproduction steps

  1. Previously Push Notifications were integrated using @aws-amplify/pushnotification and @react-native-community/push-notification-ios.
  2. Then Migration guide is used: https://docs.amplify.aws/lib/push-notifications/migrate-from-previous-version/q/platform/react-native/

aws-exports.js


const awsmobile = {
    "aws_project_region": "us-east-1",
    "aws_mobile_analytics_app_id": "xxx"",
    "aws_mobile_analytics_app_region": "us-east-1",
    "Analytics": {
        "AWSPinpoint": {
            "appId": "xxx"",
            "region": "us-east-1"
        }
    },
    "Notifications": {
        "Push": {
            "AWSPinpoint": {
                "appId": "xxx",
                "region": "us-east-1"
            }
        }
    },
    "aws_appsync_graphqlEndpoint": "https://xxx.appsync-api.us-east-1.amazonaws.com/graphql",
    "aws_appsync_region": "us-east-1",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "xxx",
    "aws_cognito_identity_pool_id": "xxx"",
    "aws_cognito_region": "us-east-1",
    "aws_user_pools_id": "us-east-1_Pfa4jHXjO",
    "aws_user_pools_web_client_id": "xxx"",
    "oauth": {},
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "NAME",
        "FAMILY_NAME"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ]
};

export default awsmobile;

Manual configuration

No response

Additional configuration

AppDelegate.h

``` #import #import @interface AppDelegate : UIResponder @property(nonatomic, strong) UIWindow *window; @end ```

AppDelegate.mm

``` #import "AppDelegate.h" #import "AmplifyPushNotification.h" #import #import #import #import #import "RNSplashScreen.h" #import #import #if RCT_NEW_ARCH_ENABLED #import #import #import #import #import #import #import static NSString *const kRNConcurrentRoot = @"concurrentRoot"; @interface AppDelegate () { RCTTurboModuleManager *_turboModuleManager; RCTSurfacePresenterBridgeAdapter *_bridgeAdapter; std::shared_ptr _reactNativeConfig; facebook::react::ContextContainer::Shared _contextContainer; } @end #endif @implementation AppDelegate // Required for deep linking - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { return [RCTLinkingManager application:application openURL:url options:options]; } // Required for Push Notifications - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [AmplifyPushNotification didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [AmplifyPushNotification didReceiveRemoteNotification:userInfo withCompletionHandler:completionHandler]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [FIRApp configure]; RCTAppSetupPrepareApp(application); RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; #if RCT_NEW_ARCH_ENABLED _contextContainer = std::make_shared(); _reactNativeConfig = std::make_shared(); _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer]; bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; #endif NSDictionary *initProps = [self prepareInitialProps]; UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"AstuvetMobile", initProps); if (@available(iOS 13.0, *)) { rootView.backgroundColor = [UIColor systemBackgroundColor]; } else { rootView.backgroundColor = [UIColor whiteColor]; } self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [UIViewController new]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; [RNSplashScreen show]; return YES; } /// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. /// /// @see: https://reactjs.org/blog/2022/03/29/react-v18.html /// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). /// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`. - (BOOL)concurrentRootEnabled { // Switch this bool to turn on and off the concurrent root return true; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; #ifdef RCT_NEW_ARCH_ENABLED initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]); #endif return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; #else return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif } #if RCT_NEW_ARCH_ENABLED #pragma mark - RCTCxxBridgeDelegate - (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge { _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge delegate:self jsInvoker:bridge.jsCallInvoker]; return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager); } #pragma mark RCTTurboModuleManagerDelegate - (Class)getModuleClassFromName:(const char *)name { return RCTCoreModulesClassProvider(name); } - (std::shared_ptr)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr)jsInvoker { return nullptr; } - (std::shared_ptr)getTurboModule:(const std::string &)name initParams: (const facebook::react::ObjCTurboModule::InitParams &)params { return nullptr; } - (id)getModuleInstanceFromClass:(Class)moduleClass { return RCTAppSetupDefaultModuleFromClass(moduleClass); } #endif @end ```

Podfile

``` require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' platform :ios, '13.0' install! 'cocoapods', :deterministic_uuids => false target 'AstuvetMobile' do config = use_native_modules! # Flags change depending on the env values. flags = get_default_flags() use_react_native!( :path => config[:reactNativePath], # Hermes is now enabled by default. Disable by setting this flag to false. # Upcoming versions of React Native may rely on get_default_flags(), but # we make it explicit here to aid in the React Native upgrade process. :hermes_enabled => false, # false is set for debug purpose: https://github.com/facebook/react-native/issues/34624 :fabric_enabled => flags[:fabric_enabled], # Enables Flipper. # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable the next line. # :flipper_configuration => FlipperConfiguration.enabled, # disabled because we need use_frameworks for firebase # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) use_frameworks! :linkage => :static $RNFirebaseAsStaticFramework = true target 'AstuvetMobileTests' do inherit! :complete # Pods for testing end post_install do |installer| react_native_post_install( installer, # Set `mac_catalyst_enabled` to `true` in order to apply patches # necessary for Mac Catalyst builds :mac_catalyst_enabled => false ) __apply_Xcode_12_5_M1_post_install_workaround(installer) end end ```

index.js

``` import 'react-native-gesture-handler'; import 'react-native-get-random-values'; import 'react-native-url-polyfill/auto'; import { AppRegistry } from 'react-native'; import { Amplify, Notifications } from 'aws-amplify'; import App from './src/modules/App/App.tsx'; import { name as appName } from './app.json'; import awsconfig from './src/aws-exports'; Amplify.configure(awsconfig); Notifications.Push.enable(); AppRegistry.registerComponent(appName, () => App); ```

App.ts

``` import { Notifications } from 'aws-amplify'; const App = () => { const tokenListener = useRef(); const foregroundListener = useRef(); const notificationOpenedListener = useRef(); const handlePermissions = async () => { const status = await Notifications.Push.getPermissionStatus(); if (status === 'GRANTED') { // no further action is required, user has already granted permissions return; } if (status === 'DENIED') { // further attempts to request permissions will no longer do anything // TODO return; } if (status === 'SHOULD_REQUEST') { // go ahead and request permissions from the user await Notifications.Push.requestPermissions(); } if (status === 'SHOULD_EXPLAIN_THEN_REQUEST') { // you should display some explanation to your user before requesting permissions // TODO explain // then request permissions await Notifications.Push.requestPermissions(); } }; useAsyncEffect(async () => { handlePermissions(); }); useEffect(() => { tokenListener.current = Notifications.Push.onTokenReceived(async token => { ... }); foregroundListener.current = Notifications.Push.onNotificationReceivedInForeground(notification => { ... }); notificationOpenedListener.current = Notifications.Push.onNotificationOpened(notification => { ... }); return () => { tokenListener.current?.remove(); foregroundListener.current?.remove(); notificationOpenedListener.current?.remove(); }; }, []); return ( ... ); }; export default App; ```

Mobile Device

iPhone 14

Mobile Operating System

iOS 16.4.1

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

nadetastic commented 1 year ago

Hi @tyndria thank you for opening this issue.

I see you mentioned that Notifications.Push.onNotificationOpened is not triggered when notification is clicked for background app, however it looks like you are triggering Notifications.Push.onNotificationReceivedInForeground

Since this is for an app in the background, you may need to use .onNotificationReceivedInBackground instead. [1]

Could you try this?

[1] https://docs.amplify.aws/lib/push-notifications/interact-with-notifications/q/platform/react-native/#respond-to-a-notification-being-received

tyndria commented 1 year ago

@nadetastic thanks a lot for the quick response!

Sorry, I'm not sure I totally got your proposal. My goal is to handle click on notification in order to navigate user to another screen.

So when:

P.S. I also see a warning "Warning: Application delegate received call to -application:didReceiveRemoteNotification:fetchCompletionHandler: but the completion handler was never called." in xcode console when background notification comes:

2023-04-28 10:06:00.892244+0400 AstuvetMobile[9059:898783] Warning: Application delegate received call to -application:didReceiveRemoteNotification:fetchCompletionHandler: but the completion handler was never called.

2023-04-28 10:06:00.895976+0400 AstuvetMobile[9059:899134] [javascript] 'BACKGROUND', { body: 't',
  title: 't ',
  deeplinkUrl: 'astuvet://chat?intentName=GenericIntent&intent=PuppyCleanliness_Introduction',
  data: { pinpoint: { deeplink: 'astuvet://chat?intentName=GenericIntent&intent=PuppyCleanliness_Introduction' } } }
tyndria commented 1 year ago

@nadetastic hi again!

My fault, I haven't tried to:

When I did it, onNotificationOpened started working for background app.

But for now I wasn't able to make getLaunchNotification working for terminated app, probably I will open issue later if I'm not able to solve it by myself

Probably this step with yarn.lock and node_modules could be added to the "Migration guide"