customerio / customerio-reactnative

MIT License
23 stars 11 forks source link

The operation couldn’t be completed. No APNS token specified before fetching FCM Token #267

Closed huyvvbka closed 1 month ago

huyvvbka commented 2 months ago

SDK version: 3.7.0

Environment: Production

Describe the bug It appears that you're experiencing an issue with the rnfirebase library when using customer.io for push marketing notifications with APNS on iOS. Specifically, when you set config.showPushAppInForeground = true, you encounter the error message "The operation couldn't be completed. No APNS token specified before fetching FCM Token" when calling messaging().getToken() from Firebase. And foreground notification not working

Screenshots

Screenshot 2024-05-15 at 17 19 02

Screenshot 2024-05-15 at 17 19 38

levibostian commented 2 months ago

Hello there.

It sounds like you're trying to get push notifications to show up on your iOS device, correct? The error message, "The operation couldn't be completed. No APNS token specified before fetching FCM Token" is an error that comes from the rnfirebase SDK, not the Customer.io SDK.

The error message happens because rnfirebase is not able to provide you with a FCM device token until it has been able to successfully receive a APN device token.

In order to have your app get a APN device token, you need to:

  1. Follow all of the setup steps
  2. Accept push permissions at runtime

Could you verify for me that you have followed all of these steps?

If you have, could you please provide to me these files:

I understand these files might have sensitive information in them. You can send them to win@customer.io instead of in this public github issue. If you do send the email, be sure to reference this github issue so the ticket can be routed to our team.

Thanks for contacting us today. Have a great day!

huyvvbka commented 2 months ago

Thank you for your response.

The issue mentioned only occurs when I want to add the following code snippet to the file MyAppPushNotificationsHandler.swift:

MessagingPushAPN.initialize { config in
    config.showPushAppInForeground = true // true will display the push when the app is in the foreground
}

In reality, I only receive notifications from Customer.io when the app is in the background. It doesn't work as expected when the app is in the foreground.

import Foundation
import CioMessagingPushAPN
import CioTracking

@objc
public class MyAppPushNotificationsHandler : NSObject {

  public override init() {}

  @objc(setupCustomerIOClickHandling)
  public func setupCustomerIOClickHandling() {
    // This line of code is required in order for the Customer.io SDK to handle push notification click events.
    // We are working on removing this requirement in a future release.
    // Remember to modify the siteId, apiKey and region with your own values.
    CustomerIO.initialize(siteId: "my-siteId", apiKey: "my-apiKey", region: .US) { config in
    }

    MessagingPushAPN.initialize { config in
        config.showPushAppInForeground = true // true will display the push when the app is in the foreground
    }

  }

  @objc(application:deviceToken:)
  public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    MessagingPush.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
  }

  @objc(application:error:)
  public func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    MessagingPush.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
  }
}

@implementation AppDelegate MyAppPushNotificationsHandler* pnHandlerObj = [[MyAppPushNotificationsHandler alloc] init];

// Required to register device token.

// Required for the registrationError event.

@end


- Podfile

def node_require(script)

Resolve script with node to allow for hoisting

require Pod::Executable.execute_command('node', ['-p', "require.resolve( '#{script}', {paths: [process.argv[1]]}, )", dir]).strip end

node_require('react-native/scripts/react_native_pods.rb') node_require('react-native-permissions/scripts/setup.rb')

platform :ios, '13.0' prepare_react_native_project!

⬇️ uncomment wanted permissions (don't forget to remove the last comma)

setup_permissions([

'AppTrackingTransparency',

'BluetoothPeripheral',

'Calendars',

'Camera',

'Contacts',

'FaceID',

'LocationAccuracy',

'LocationAlways',

'LocationWhenInUse'

'MediaLibrary',

'Microphone',

'Motion',

'Notifications',

'PhotoLibrary',

'PhotoLibraryAddOnly',

'Reminders',

'SpeechRecognition',

'StoreKit'

])

If you are using a react-native-flipper your iOS build will fail when NO_FLIPPER=1 is set.

because react-native-flipper depends on (FlipperKit,...) that will be excluded

#

To fix this you can also exclude react-native-flipper using a react-native.config.js

```js

module.exports = {

dependencies: {

...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),

```

flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled

linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green use_frameworks! :linkage => :static end

target 'SampleApp' do config = use_native_modules!

Flags change depending on the env values.

flags = get_default_flags()

pod 'customerio-reactnative/apn', :path => '../node_modules/customerio-reactnative'

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 => true,
:fabric_enabled => false,
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."

)

pod 'Firebase', :modular_headers => true pod 'FirebaseCoreInternal', :modular_headers => true pod 'GoogleUtilities', :modular_headers => true pod 'FirebaseCore', :modular_headers => true pod 'FirebaseCoreExtension', :modular_headers => true pod 'FirebaseABTesting', :modular_headers => true pod 'FirebaseInstallations', :modular_headers => true pod 'GoogleDataTransport', :modular_headers => true pod 'FirebaseSessions', :modular_headers => true pod 'nanopb', :modular_headers => true pod 'AppAuth', :modular_headers => true pod 'GTMSessionFetcher', :modular_headers => true

pod 'react-native-fbsdk-next', :path => '../node_modules/react-native-fbsdk-next' pod 'react-native-date-picker', :path => '../node_modules/react-native-date-picker' pod 'react-native-blob-util', :path => '../node_modules/react-native-blob-util'

target 'SampleAppTests' do inherit! :complete

Pods for testing

end ENVFILES = { 'Prod' => '$(PODS_ROOT)/../../.env.production', }

post_install do |installer| react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false ) __apply_Xcode_12_5_M1_post_install_workaround(installer) installer.pods_project.targets.each do |target| target.build_configurations.each do |config| if target.name == 'react-native-config' config.build_settings['ENVFILE'] = ENVFILES[config.name] end end end end end

target 'ImageNotification' do pod 'Firebase', :modular_headers => true pod 'FirebaseCoreInternal', :modular_headers => true pod 'GoogleUtilities', :modular_headers => true pod 'FirebaseCore', :modular_headers => true pod 'FirebaseCoreExtension', :modular_headers => true pod 'Firebase/Messaging', :modular_headers => true # eg 6.31.0 end

target 'NotificationServiceExtension' do

Notice the '-richpush' in the line below. This line of code is different from what you added for your main target.

pod 'customerio-reactnative-richpush/apn', :path => '../node_modules/customerio-reactnative' end

levibostian commented 2 months ago

Thanks for giving additional information.

Could you please describe the issue that you're encountering? Sorry, I am not completely sure what it is.

I did notice something. It looks like you're using Firebase messaging for push notifications in your app, but you have the APN version of the Customer.io SDK installed in your app. Perhaps this is causing the issues for you? We give instructions in our documentation for FCM. If you are using FCM in your app for push notifications, I suggest following those instructions instead of the APN instructions.

I hope you have a great rest of your day!

huyvvbka commented 2 months ago

Thank you, @levibostian , for your response.

I have re-implemented to receive notifications via FCM as instructed on customer.io. It works well when the application is in the background, but when it is in the foreground, it does not work at all as expected. I have checked extensively, but no notifications are received when in the foreground.

const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log("remoteMessage:", remoteMessage)
});

messaging().onMessage does not trigger.

Please help me!

@objc public class MyAppPushNotificationsHandler : NSObject {

public override init() {}

@objc(setupCustomerIOClickHandling) public func setupCustomerIOClickHandling() { // This line of code is required in order for the Customer.io SDK to handle push notification click events. // We are working on removing this requirement in a future release. // Remember to modify the siteId and apiKey with your own values. CustomerIO.initialize(siteId: "my-siteid", apiKey: "my-apikey", region: Region.US) { config in

  // Automatically register push device tokens to the Customer.io SDK
  config.autoTrackDeviceAttributes = true

  // This is convenient for internal testing.
  // It is optional.
  config.logLevel = .debug
}
MessagingPushFCM.initialize {config in
  config.showPushAppInForeground = true
}

}

// Register device on receiving a device token (FCM) @objc(didReceiveRegistrationToken:fcmToken:) public func didReceiveRegistrationToken(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { MessagingPush.shared.messaging(messaging, didReceiveRegistrationToken: fcmToken) } }


- AppDelegate

import "AppDelegate.h"

import "SampleApp-Swift.h"

import

import "RNBootSplash.h"

import <RNGoogleSignin/RNGoogleSignin.h>

import <React/RCTLinkingManager.h>

import

import

import

import "RNFBMessagingModule.h"

import <React/RCTBundleURLProvider.h>

import <AuthenticationServices/AuthenticationServices.h>

import <SafariServices/SafariServices.h>

import <FBAEMKit/FBAEMKit-Swift.h>

import <FBSDKCoreKit/FBSDKCoreKit-Swift.h>

import <CodePush/CodePush.h>

import

@implementation AppDelegate MyAppPushNotificationsHandler *pnHandlerObj = [[MyAppPushNotificationsHandler alloc] init];

@end


- Podfile

def node_require(script)

Resolve script with node to allow for hoisting

require Pod::Executable.execute_command('node', ['-p', "require.resolve( '#{script}', {paths: [process.argv[1]]}, )", dir]).strip end

node_require('react-native/scripts/react_native_pods.rb') node_require('react-native-permissions/scripts/setup.rb')

platform :ios, '13.0' prepare_react_native_project! use_modular_headers!

⬇️ uncomment wanted permissions (don't forget to remove the last comma)

setup_permissions([

'AppTrackingTransparency',

'BluetoothPeripheral',

'Calendars',

'Camera',

'Contacts',

'FaceID',

'LocationAccuracy',

'LocationAlways',

'LocationWhenInUse'

'MediaLibrary',

'Microphone',

'Motion',

'Notifications',

'PhotoLibrary',

'PhotoLibraryAddOnly',

'Reminders',

'SpeechRecognition',

'StoreKit'

])

If you are using a react-native-flipper your iOS build will fail when NO_FLIPPER=1 is set.

because react-native-flipper depends on (FlipperKit,...) that will be excluded

#

To fix this you can also exclude react-native-flipper using a react-native.config.js

```js

module.exports = {

dependencies: {

...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),

```

flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled

linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green use_frameworks! :linkage => :static end

target 'SampleApp' do config = use_native_modules!

Flags change depending on the env values.

flags = get_default_flags()

pod 'customerio-reactnative/fcm', :path => '../node_modules/customerio-reactnative'

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 => true,
:fabric_enabled => false,
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
#:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."

)

pod 'Firebase' pod 'FirebaseCoreInternal' pod 'GoogleUtilities' pod 'FirebaseCore' pod 'FirebaseCoreExtension' pod 'FirebaseABTesting' pod 'FirebaseInstallations' pod 'GoogleDataTransport' pod 'FirebaseSessions' pod 'nanopb' pod 'AppAuth' pod 'GTMSessionFetcher'

pod 'react-native-fbsdk-next', :path => '../node_modules/react-native-fbsdk-next' pod 'react-native-date-picker', :path => '../node_modules/react-native-date-picker' pod 'react-native-blob-util', :path => '../node_modules/react-native-blob-util'

target 'SampleAppTests' do inherit! :complete

Pods for testing

end ENVFILES = { 'Prod' => '$(PODS_ROOT)/../../.env.production', }

post_install do |installer| react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false ) __apply_Xcode_12_5_M1_post_install_workaround(installer) installer.pods_project.targets.each do |target| target.build_configurations.each do |config| if target.name == 'react-native-config' config.build_settings['ENVFILE'] = ENVFILES[config.name] end end end end end

target 'ImageNotification' do pod 'Firebase', :modular_headers => true pod 'FirebaseCoreInternal', :modular_headers => true pod 'GoogleUtilities', :modular_headers => true pod 'FirebaseCore', :modular_headers => true pod 'FirebaseCoreExtension', :modular_headers => true pod 'Firebase/Messaging', :modular_headers => true # eg 6.31.0 end

target 'NotificationServiceExtension' do pod 'customerio-reactnative-richpush/fcm', :path => '../node_modules/customerio-reactnative' end

levibostian commented 1 month ago

Thanks again for providing more information.

I am glad to hear that you are able to see push notifications while your app is in the background. In regards to push notifications not displaying while your app is in the foreground, this could be caused by this rnfirebase behavior that does not display push notifications while app is in the foreground.

We do have a FCM sample app that does display push notifications sent from Customer.io when the app is in the foreground. The app also does use rnfirebase including onMessage() callback function.

I suggest reviewing the code in our sample app and comparing the app to your app's code. Make modifications to your app to more closely match the rnfirebase setup that the sample app has.

I hope that this app is able to give you the details that you need to get this working. Have a great day!

levibostian commented 1 month ago

Closing issue due to inactivity.

Feel free to add new comments and we would be happy to re-open and help.