Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥 [🐛] getAPNSToken() just hangs #7272

Closed
2 of 7 tasks
kg-currenxie opened this issue Aug 2, 2023 · 21 comments · Fixed by #7797
Closed
2 of 7 tasks

🔥 [🐛] getAPNSToken() just hangs #7272

kg-currenxie opened this issue Aug 2, 2023 · 21 comments · Fixed by #7797
Labels
Impact: Bug New bug report Platform: iOS Service: Messaging FCM only - ( messaging() ) - do not use for Notifications Workflow: Waiting for User Response Blocked waiting for user response.

Comments

@kg-currenxie
Copy link

kg-currenxie commented Aug 2, 2023

Issue

Hi. For some reason, messaging().getAPNSToken() just hangs forever.
Yes I'm using a real device.

(Auto init is off, swizzling is on)

// Called when user presses a button in a custom modal to allow permissions.
const status = await requestNotifications(['alert', 'sound', 'badge'])
if (status !== RESULTS.GRANTED) {
  return
}
messaging()
  .registerDeviceForRemoteMessages()
  .then(async () => {
    console.log(3)
    const needsToSetAPNSToken = await isEmulator()
    console.log(4)
    if (needsToSetAPNSToken && isIOS()) {
      await messaging().setAPNSToken('test')
    }
    console.log(5) <------------- all logs fine including this line
    const apnsToken = await messaging().getAPNSToken() // keeps waiting forever
    console.log(6) <------------- does not log
    const fcmToken = await messaging().getToken()
    console.log('Push Notification device token received: ', {
      fcmToken,
      apnsToken,
    })

I have tried adding

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
  NSLog(@"DEVICE DID REGISTER:");
  NSLog(@"DEVICE TOKEN: %s", deviceToken);
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
    NSLog(@"DEVICE DID FAIL TO REGISTER: %s", error);
}

but I'm not sure if that's enough, or if they would actually be called? Though, they don't log anything at all (XCode debugger while having a device connected in release mode)

  • I have tried rebooting the phone (surprisingly a lot of people had this suggestion over at Apple's forums)
  • Obviously, rebuild pods, clear build folder etc
  • Checked entitlements, background process modes, and certificates, etc
  • I have tried wrapping getAPNSToken() in a try/catch, but it doesn't seem to throw
  • I have tried calling messaging().unregisterDeviceFromRemoteMessaging() before registering, to see if there's some sort of cache or something
  • I have tried using XCode's Network Instrument, to see if it calls any Apple (or Firebase) endpoint. I'm not sure what it does deep down, but it does not log any network requests to any external domain.
  • I tried looking in the library code, but my objective-C/Swift knowledge is not good enough to track down how the token is fetched, so my investigation is stuck at this point.

For the strange part; this has worked before, and no code has changed since (i know i know, sounds like BS) 😄 but i even went back a few commits, to where our QA team successfully tested a whole flow.
Would there be any possibility of Apple having intermittent issues? Seems unlikely.

So the question is, what are the things that could make this function call just hang, without any error logs or warnings? For example, if i call getToken() without first calling getAPNSToken(), the library does a good job at printing warnings or errors with short explanations. But at this moment, there's just nothing :) Would love more insight from people with a deeper understanding of this 🙏


Project Files

Javascript

Click To Expand

package.json:

{
  "name": "my-mobile-react-native",
  "version": "0.0.1",
  "private": true,
  "main": "index.js",
  "scripts": {
    ...
  },
  "dependencies": {
    "@notifee/react-native": "7.7.1",
    "@react-native-clipboard/clipboard": "1.7.0",
    "@react-native-community/async-storage": "1.12.1",
    "@react-native-community/masked-view": "0.1.11",
    "@react-native-community/netinfo": "9.3.7",
    "@react-native-community/slider": "3.0.3",
    "@react-native-firebase/app": "17.3.2",
    "@react-native-firebase/messaging": "17.3.2",
    "@react-navigation/bottom-tabs": "6.3.2",
    "@react-navigation/drawer": "6.4.2",
    "@react-navigation/native": "6.0.10",
    "@react-navigation/native-stack": "6.6.2",
    "@sentry/react-native": "3.2.11",
    "@tanstack/react-query": "4.29.12",
    "@twilio/twilio-verify-for-react-native": "https://github.com/twilio/twilio-verify-for-react-native.git",
    "@types/react-native-share": "3.3.2",
    "ajv": "8.2.0",
    "appcenter": "4.4.5",
    "appcenter-analytics": "4.4.5",
    "appcenter-crashes": "4.4.5",
    "assert": "2.0.0",
    "axios": "0.26.1",
    "base64-arraybuffer": "0.2.0",
    "core-js": "3.8.2",
    "date-fns": "2.13.0",
    "date-fns-tz": "1.3.4",
    "date-time-format-timezone": "1.0.22",
    "es6-promise": "4.2.8",
    "eslint-plugin-10x": "1.5.0",
    "final-form": "4.20.6",
    "final-form-calculate": "1.3.2",
    "format-string-by-pattern": "1.2.2",
    "husky": "4.3.8",
    "i18next": "19.8.5",
    "iban": "0.0.14",
    "immer": "9.0.6",
    "jsc-android": "250230.2.1",
    "lint-staged": "12.3.4",
    "lodash": "4.17.21",
    "lottie-ios": "3.4.0",
    "lottie-react-native": "5.1.4",
    "normalizr": "3.6.0",
    "querystring": "0.2.1",
    "ramda": "0.27.2",
    "react": "18.0.0",
    "react-dom": "16.8.6",
    "react-error-boundary": "3.1.3",
    "react-final-form": "6.3.0",
    "react-final-form-listeners": "1.0.3",
    "react-hooks-compose": "2.0.7",
    "react-i18next": "11.18.3",
    "react-native": "0.70.4",
    "react-native-asset": "2.0.0",
    "react-native-biometrics": "2.1.4",
    "react-native-blob-util": "0.16.2",
    "react-native-bootsplash": "3.1.2",
    "react-native-codegen": "0.0.13",
    "react-native-config": "1.4.2",
    "react-native-device-info": "10.3.0",
    "react-native-encrypted-storage": "4.0.2",
    "react-native-exit-app": "1.1.0",
    "react-native-gesture-handler": "2.5.0",
    "react-native-in-app-review": "4.2.1",
    "react-native-keyboard-manager": "4.0.13-16",
    "react-native-modal-wrapper": "3.1.1",
    "react-native-modalize": "2.0.13",
    "react-native-navigation-bar-color": "2.0.1",
    "react-native-pdf": "6.6.2",
    "react-native-permissions": "3.7.2",
    "react-native-reanimated": "2.11.0",
    "react-native-safe-area-context": "4.3.1",
    "react-native-screens": "3.13.1",
    "react-native-section-list-get-item-layout": "2.2.3",
    "react-native-share": "7.3.0",
    "react-native-svg": "13.9.0",
    "react-native-webview": "11.26.1",
    "react-redux": "7.2.3",
    "react-redux-promise-listener": "1.0.0",
    "react-router": "5.2.0",
    "reactxp": "2.0.0",
    "reactxp-imagesvg": "2.0.0",
    "redux": "4.0.5",
    "redux-promise-listener": "1.1.1",
    "redux-saga": "1.1.3",
    "redux-saga-jwt": "1.1.1-next.2",
    "reselect": "4.0.0",
    "round.js": "1.1.1",
    "typescript-fsa": "3.0.0",
    "typescript-fsa-reducers": "1.2.1",
    "zustand": "4.3.2"
  },
  "devDependencies": {
    "@babel/core": "7.13.10",
    "@babel/plugin-proposal-decorators": "7.8.3",
    "@babel/preset-env": "7.16.8",
    "@babel/preset-react": "7.18.6",
    "@babel/preset-typescript": "7.17.12",
    "@babel/runtime": "7.17.9",
    "@types/enzyme": "3.10.8",
    "@types/hoist-non-react-statics": "3.3.1",
    "@types/iban": "0.0.32",
    "@types/jest": "29.2.2",
    "@types/lodash": "4.14.179",
    "@types/node": "15.14.3",
    "@types/ramda": "0.27.2",
    "@types/react": "18.0.0",
    "@types/react-final-form-listeners": "1.0.0",
    "@types/react-native": "0.69.0",
    "@types/react-redux": "7.1.24",
    "@typescript-eslint/eslint-plugin": "5.15.0",
    "@typescript-eslint/parser": "5.25.0",
    "babel-eslint": "10.1.0",
    "babel-loader": "8.1.0",
    "babel-plugin-module-resolver": "4.1.0",
    "babel-plugin-ramda": "2.0.0",
    "boxen": "4.2.0",
    "boxen-cli": "1.0.0",
    "cli-table3": "0.6.0",
    "detox": "20.0.1",
    "dot-object": "2.1.4",
    "enzyme": "3.11.0",
    "enzyme-adapter-react-16": "1.15.2",
    "enzyme-to-json": "3.4.4",
    "eslint": "7.32.0",
    "eslint-config-prettier": "8.3.0",
    "eslint-config-react-app": "7.0.1",
    "eslint-import-resolver-typescript": "2.7.0",
    "eslint-loader": "4.0.2",
    "eslint-plugin-detox": "1.0.0",
    "eslint-plugin-i18next": "5.1.1",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-jsonc": "2.3.1",
    "eslint-plugin-prettier": "4.0.0",
    "eslint-plugin-ramda": "2.5.1",
    "eslint-plugin-react": "7.29.4",
    "eslint-plugin-react-hooks": "2.5.0",
    "eslint-plugin-simple-import-sort": "7.0.0",
    "eslint-plugin-typescript-sort-keys": "2.1.0",
    "hoist-non-react-statics": "3.3.2",
    "inquirer": "7.1.0",
    "jest": "29.3.1",
    "metro-react-native-babel-preset": "0.72.3",
    "opentype.js": "1.3.4",
    "patch-package": "6.2.0",
    "postinstall-postinstall": "2.1.0",
    "prettier": "2.7.1",
    "react-native-debugger-open": "0.3.24",
    "react-native-schemes-manager": "2.0.0",
    "react-native-svg-transformer": "1.0.0",
    "redux-devtools-extension": "2.13.8",
    "ts-jest": "29.0.3",
    "typescript": "4.6.3"
  },
  "resolutions": {
    "@types/react": "18.0.0",
    "ansi-regex": "5.0.1",
    "async": "3.2.2",
    "hoist-non-react-statics": "3.3.2",
    "i18next": "20.3.5",
    "minimist": "1.2.5",
    "node-fetch": "2.6.7",
    "nth-check": "2.0.1",
    "react-native-svg": "13.9.0",
    "shell-quote": "1.7.3",
    "unset-value": "2.0.1"
  },
  "xcodeSchemes": {
    "Debug": [
      "Debug.Development",
      "Debug.Demo",
      "Debug.Preprod",
      "Debug.Staging",
      "Debug.Production"
    ],
    "Release": [
      "Release.Development",
      "Release.Demo",
      "Release.Preprod",
      "Release.Staging",
      "Release.Production"
    ],
    "projectDirectory": "iOS"
  },
  "husky": {
    "hooks": {
      "commit-msg": "node ./git-hooks/validate-commit-message.js ${HUSKY_GIT_PARAMS}",
      "pre-commit": "lint-staged",
      "pre-push": "yarn tsc",
      "prepare-commit-msg": "chmod +x ./git-hooks/commit-message.sh && ./git-hooks/commit-message.sh ${HUSKY_GIT_PARAMS}"
    }
  },
  "reactNativePermissionsIOS": [
    "AppTrackingTransparency",
    "Notifications"
  ],
  "lint-staged": {
    "**/*.rb": [
      "bundle exec rubocop -a",
      "git add"
    ],
    "src/**/*.{ts,tsx}": [
      "prettier --write",
      "eslint --fix",
      "git add"
    ],
    "src/configs/locales/*.json": [
      "yarn translations:duplicates",
      "yarn translations:missing",
      "yarn translations:deleted"
    ]
  }
}

firebase.json for react-native-firebase v6:

{
  "react-native": {
    "messaging_ios_auto_register_for_remote_messages": "false",
    "messaging_ios_foreground_presentation_options": [
      "badge",
      "sound",
      "list",
      "banner"
    ]
  }
}

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
source 'https://cdn.cocoapods.org/'

project 'MyMobileReactNative',
  'Debug.Development' => :debug,
  'Debug.Demo' => :debug,
  'Debug.Preprod' => :debug,
  'Debug.Staging' => :debug,
  'Debug.Production' => :debug,
  'Release.Development' => :release,
  'Release.Demo' => :release,
  'Release.Preprod' => :release,
  'Release.Staging' => :release,
  'Release.Production' => :release

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '12.4'
install! 'cocoapods', deterministic_uuids: false

# Some of the pods have an older deployment target, which fails the build
# Force all pods to our target
def fix_deployment_targets(installer)
  installer.pods_project.targets.each do |target|
    # RN-config fix: https://github.com/luggit/react-native-config/issues/365
    if target.name == 'react-native-config'
      phase = target.project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
      phase.shell_script = 'cd ../../'\
                           ' && RNC_ROOT=./node_modules/react-native-config'\
                           ' && export SYMROOT=$RNC_ROOT/ios/ReactNativeConfig'\
                           ' && ruby $RNC_ROOT/ios/ReactNativeConfig/BuildDotenvConfig.rb "${SRC_ROOT}/../"'\
                           ' "${SYMROOT}"'

      target.build_phases << phase
      target.build_phases.move(phase, 0)
    end

    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11'
    end

    if target.name == 'RCT-Folly'
      target.build_configurations.each do |config|
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
      end
    end
  end
end

target 'MyMobileReactNative' do
  config = use_native_modules!

  # https://rnfirebase.io/#altering-cocoapods-to-use-frameworks
  use_frameworks! linkage: :static
  # rubocop:disable Style/GlobalVars
  $RNFirebaseAsStaticFramework = true
  # rubocop:enable Style/GlobalVars

  # Flags change depending on the env values.
  # flags = get_default_flags

  use_react_native!(
    path: config[:reactNativePath],
    # to enable hermes on iOS, change `false` to `true` and then install pods
    # hermes_enabled: flags[:hermes_enabled],
    hermes_enabled: false,
    # fabric_enabled: flags[:fabric_enabled],
    fabric_enabled: false,
    # An absolute path to your application root.
    app_path: "#{Pod::Config.instance.installation_root}/..",
    flipper_configuration: FlipperConfiguration.disabled
  )

  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
    )
    fix_deployment_targets(installer)
  end

  # Custom pods
  pod 'lottie-ios', path: '../node_modules/lottie-ios'
  pod 'lottie-react-native', path: '../node_modules/lottie-react-native'
  pod 'react-native-config', path: '../node_modules/react-native-config'
  pod 'react-native-in-app-review', path: '../node_modules/react-native-in-app-review'
  pod 'ReactNativeKeyboardManager', path: '../node_modules/react-native-keyboard-manager'
  pod 'RNDeviceInfo', path: '../node_modules/react-native-device-info'
  pod 'RNGestureHandler', path: '../node_modules/react-native-gesture-handler'
  pod 'RNSentry', path: '../node_modules/@sentry/react-native'
  pod 'RNSVG', path: '../node_modules/react-native-svg'
end

AppDelegate.mm:

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import <React/RCTAppSetupUtils.h>

#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>

#import <react/config/ReactNativeConfig.h>

static NSString *const kRNConcurrentRoot = @"concurrentRoot";

@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
  RCTTurboModuleManager *_turboModuleManager;
  RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
  std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
  facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif

// CUSTOM: Splash screen
#import "RNBootSplash.h"

// CUSTOM: Firebase
#import <Firebase.h>

// CUSTOM: Appcenter
#import <AppCenterReactNative.h>
#import <AppCenterReactNativeAnalytics.h>
#import <AppCenterReactNativeCrashes.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  RCTAppSetupPrepareApp(application);

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

#if RCT_NEW_ARCH_ENABLED
  _contextContainer = std::make_shared<facebook::react::ContextContainer const>();
  _reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
  _contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
  _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
  bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
#endif

  // CUSTOM: The name here has to be "RXApp"
  NSDictionary *initProps = [self prepareInitialProps];
  UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"RXApp", 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];

  // CUSTOM: Splash screen
  [RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];

  // CUSTOM: Firebase
  [FIRApp configure];

  // CUSTOM: Appcenter
  [AppCenterReactNative register];
  [AppCenterReactNativeAnalytics registerWithInitiallyEnabled:true];
  [AppCenterReactNativeCrashes registerWithAutomaticProcessing];

  return YES;
}

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
  NSLog(@"DEVICE DID REGISTER:");
  NSLog(@"DEVICE TOKEN: %s", deviceToken);
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
    NSLog(@"DEVICE DID FAIL TO REGISTER: %s", error);
}

/// 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<facebook::react::JSExecutorFactory>)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<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                      jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
  return nullptr;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                     initParams:
                                                         (const facebook::react::ObjCTurboModule::InitParams &)params
{
  return nullptr;
}

- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
  return RCTAppSetupDefaultModuleFromClass(moduleClass);
}

#endif

@end


Environment

Click To Expand

react-native info output:

System:
    OS: macOS 13.5
    CPU: (10) arm64 Apple M1 Max
    Memory: 109.11 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 14.19.1 - /var/folders/wl/rld35ngd74l38l3ljkg53hbm0000gn/T/yarn--1690970282310-0.8899052122383881/node
    Yarn: 1.22.17 - /var/folders/wl/rld35ngd74l38l3ljkg53hbm0000gn/T/yarn--1690970282310-0.8899052122383881/yarn
    npm: 6.14.16 - ~/.nvm/versions/node/v14.19.1/bin/npm
    Watchman: 2023.05.01.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: 1.11.3 - /Users/frexuz/.rbenv/shims/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 22.4, iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4
    Android SDK:
      API Levels: 28, 30, 31, 32, 33
      Build Tools: 30.0.2, 30.0.3, 31.0.0, 32.0.0, 33.0.0
      System Images: android-29 | Google Play ARM 64 v8a, android-31 | Google APIs ARM 64 v8a
      Android NDK: Not Found
  IDEs:
    Android Studio: 2022.1 AI-221.6008.13.2211.9619390
    Xcode: 14.3/14E222b - /usr/bin/xcodebuild
  Languages:
    Java: 17.0.7 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.0.0 => 18.0.0
    react-native: 0.70.4 => 0.70.4
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
  • "@react-native-firebase/app": "17.3.2",
  • "@react-native-firebase/messaging": "17.3.2",
  • Firebase module(s) you're using that has the issue:
    • messaging()
  • Are you using TypeScript?
    • "typescript": "4.6.3"


@kg-currenxie kg-currenxie added Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report labels Aug 2, 2023
@kg-currenxie
Copy link
Author

Update: I found this in the docs

- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken {
    NSLog(@"FCM registration token: %@", fcmToken);
    // Notify about received token.
    NSDictionary *dataDict = [NSDictionary dictionaryWithObject:fcmToken forKey:@"token"];
    [[NSNotificationCenter defaultCenter] postNotificationName:
     @"FCMToken" object:nil userInfo:dataDict];
    // TODO: If necessary send token to application server.
    // Note: This callback is fired at each app startup and whenever a new token is generated.
}

And it does log
image

Though, its the FCM token, and not the APNs token. Is there an equivalent method for that?

From the docs

In the same way that Apple platforms typically deliver an APNs device token on app start, FCM provides a registration token via FIRMessagingDelegate's messaging:didReceiveRegistrationToken

@kg-currenxie
Copy link
Author

kg-currenxie commented Aug 3, 2023

Update 🎉 : There seems to be a difference with requesting permissions

Because of Android 13, we recently changed to using react-native-permissions instead of the one from messaging().requestPermission().
Using react-native-permissions have been suggested multiple times in this repo, so I never thought this could be related to APNs tokens.

Here's what made getting the token work again (by using the different permission requests per platform)

  requestPlatformPermission = async (): Promise<boolean> => {
    let status
    let isGranted = false
    if (isIOS()) {
      status = await messaging().requestPermission({
        alert: true,
        sound: true,
        badge: true,
      })
      isGranted = status === messaging.AuthorizationStatus.AUTHORIZED
    } else {
      status = await requestNotifications(['alert', 'sound', 'badge'])
      isGranted = status.status === RESULTS.GRANTED
    }
    return new Promise((resolve) => {
      resolve(isGranted)
    })
  }

and then using this instead before registering the device:

  const isGranted = await this.requestPlatformPermission()
  if (!isGranted) {
    return
  }

  messaging()
    .registerDeviceForRemoteMessages().then(
       ... getAPNSToken() etc ..

any idea why getAPNSToken() "only" works with the permission request from this library? Are the implementations coupled? It think no right? The permission is granted on an OS-level.

  • If not, then why would getting the token hang.
  • If yes, why are many suggestion here to use react-native-permissions.

or are there any other details that I'm missing perhaps?

@tux2nicolae
Copy link

tux2nicolae commented Aug 22, 2023

I'm having the same issue.

@kg-currenxie I'm trying to understand why this issue may be related to requestPermission(), I think the token should be obtain even without calling requestPermission() because you can use the messaging service as data only without notifications and you still need to have a valid token.

The same in our case, I'm pretty sure this worked before and returned null instead, I even created an issue here #6464 that the method returned null except the first time it registers for remote messages. The same seems to happen here, first time the app is installed it seems to return a valid token, the following calls (after the app is restarted) seem to hang (a few months before at least returned null instead)

@kg-currenxie
Copy link
Author

kg-currenxie commented Aug 25, 2023

I couldn't tell you why, just that it worked 😅 with the wrong permission call, getting the token always hung, and changing it made it work 🤷‍♂️

I think the token should be obtain even without calling requestPermission() because you can use the messaging service as data only without notifications and you still need to have a valid token.

well that makes sense too :)

@samih-dev
Copy link

this is critical bug I think specially for apps trying to get the APNS token (after this being a requirement) and the app is live already and has user logged in and registered their devices

for me this is happening on version 18.3.1

@hollanderbart
Copy link

hollanderbart commented Sep 22, 2023

How can an app even function with this bug? I'm at the point where I can better use the native iOS and Android libraries and go with that.

EDIT: I misread the documentation and missed the point where it tells to add the [FIRApp configure]; to the AppDelegate. https://rnfirebase.io/#configure-firebase-with-ios-credentials
After that was added, everything worked again.

@appa-gomi
Copy link

appa-gomi commented Sep 25, 2023

add this line to node_modules/@react-native-firebase/messaging/ios/RNFBMessaging/RNFBMessagingModule.m!

RCT_EXPORT_METHOD(getAPNSToken : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) {
  NSData *apnsToken = [FIRMessaging messaging].APNSToken;
  if (apnsToken) {
    resolve([RNFBMessagingSerializer APNSTokenFromNSData:apnsToken]);
  } else {
#if TARGET_IPHONE_SIMULATOR
#if !TARGET_CPU_ARM64
    DLog(@"RNFBMessaging getAPNSToken - Simulator without APNS support detected, with no token "
         @"set. Use setAPNSToken with an arbitrary string if needed for testing.")
        resolve([NSNull null]);
    return;
#endif
    DLog(@"RNFBMessaging getAPNSToken - ARM64 Simulator detected, but no APNS token set. Assuming "
         @"APNS token is possible. macOS13+ / iOS16+ / M1 mac required for assumption to be valid. "
         @"Use setAPNSToken in testing if needed.");
#endif
    if ([UIApplication sharedApplication].isRegisteredForRemoteNotifications == NO) {
      [RNFBSharedUtils
          rejectPromiseWithUserInfo:reject
                           userInfo:(NSMutableDictionary *)@{
                             @"code" : @"unregistered",
                             @"message" : @"You must be registered for remote messages before "
                                          @"calling getAPNSToken, see "
                                          @"messaging().registerDeviceForRemoteMessages().",
                           }];
      return;
    }
    // -----> this line
    resolve([NSNull null]);
    // <-------------
  }
}

this is work for me!

@github-actions
Copy link

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

@github-actions github-actions bot added the Type: Stale Issue has become stale - automatically added by Stale bot label Oct 23, 2023
@samih-dev
Copy link

samih-dev commented Oct 23, 2023 via email

@github-actions github-actions bot removed the Type: Stale Issue has become stale - automatically added by Stale bot label Oct 23, 2023
@tux2nicolae
Copy link

Still needs to be fixed, @appa-gomi 's solution works for us

Copy link

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

@github-actions github-actions bot added the Type: Stale Issue has become stale - automatically added by Stale bot label Nov 27, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Dec 12, 2023
@dusanristic
Copy link

Hey, is there any progress on this? I started experiencing this issue after upgrading RN Firebase from v14 to v18.6.2. It doesn't happen always, but from time to time. I am using RN v0.72.7.

@mikehardy
Copy link
Collaborator

mikehardy commented Jan 30, 2024

It doesn't happen always, but from time to time. I am using RN v0.72.7.

That means the code is functional or it would never work.
A sometimes error is likely some sort of network interruption. Note that getting these remote messaging tokens relies on permissions (may not be granted?) and network requests (may time out or fail - APNs / FCM token fetch needs to be checked and retried perhaps with incremental backoff)

In the following comment I look more closely at the network failure case, I think that might be the key

@mikehardy
Copy link
Collaborator

@appa-gomi in your comment #7272 (comment) it does look like you've found a resolution for the method call simply hanging.

There appears to be a combination of conditionals where no expected case is hit and the method falls through to the end but without resolving, which would explain the symptoms.

Your proposed resolution of the promise with null even seems correct for this case. The only addition might be to add some more DLogs, for example, to resolve with null as you propose, but immediately after the DLog indicating that it is a simulator that should be capable of getting an APNS token, but didn't.

Then have another DLog after the check for remote message registration where we note to developers that we are on a real device here and are registered, so we should have gotten a token but perhaps there was a network failure and they should retry. Then resolve with null

What do you think?

@hollanderbart and @vid3v - you reacted with a thumbs-down on the @appa-gomi proposed change. Is there a technical reason for that? Did you try it and it did not work? I would love to know if that was the case as it seems that change (or something functionally equivalent but just with more developer messaging) should work

@mikehardy mikehardy reopened this Jan 30, 2024
@mikehardy mikehardy added Platform: iOS Workflow: Waiting for User Response Blocked waiting for user response. Service: Messaging FCM only - ( messaging() ) - do not use for Notifications and removed Help: Needs Triage Issue needs additional investigation/triaging. Type: Stale Issue has become stale - automatically added by Stale bot labels Jan 30, 2024
mikehardy added a commit that referenced this issue Feb 7, 2024
mikehardy added a commit that referenced this issue Feb 8, 2024
mikehardy added a commit that referenced this issue Feb 8, 2024
@dusanristic
Copy link

dusanristic commented Feb 8, 2024

@mikehardy shouldn't we also add resolve([NSNull null]) before return statement on line 190?

@mikehardy
Copy link
Collaborator

@dusanristic

I do not believe so. This shared utils function takes the reject block as a parameter and calls it, in order to meet it's named purpose of rejectPromiseWithUserInfo

@Alex-Whyatt
Copy link

Alex-Whyatt commented Apr 17, 2024

@mikehardy sorry to tag you but I'm wondering whether there's still weight to this issue - I think I'm experiencing it.

package versions "react-native": "0.72.6", "@react-native-firebase/analytics": "19.2.2", "@react-native-firebase/app": "19.2.2", "@react-native-firebase/messaging": "19.2.2", "expo": "49.0.13", "expo-build-properties": "0.11.1",
AppDelegate.mm (auto-generated)
#import "AppDelegate.h"
// @generated begin react-native-maps-import - expo prebuild (DO NOT MODIFY) sync-f2f83125c99c0d74b42a2612947510c4e08c423a
#if __has_include(<GoogleMaps/GoogleMaps.h>)
#import <GoogleMaps/GoogleMaps.h>
#endif
// @generated end react-native-maps-import
#import <Firebase/Firebase.h>

#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// @generated begin react-native-maps-init - expo prebuild (DO NOT MODIFY) sync-ba3e6dd042f7fe1b7ac3c7cee5c8029c482c489a
#if __has_include(<GoogleMaps/GoogleMaps.h>)
  [GMSServices provideAPIKey:@"AIzaSyD8W8_fRaK2QvPeaXDYdak6lyWyHnkB6Qk"];
#endif
// @generated end react-native-maps-init
// @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 = @{};

  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 {
  return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
}

// 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];
  return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)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
{
  return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

@end

We're experiencing this while using expo managed project setup for firebase, we have auto register set to false in our firebase.json, and have a setup like this:

const status = await messaging().hasPermission();

then register for remote notifications:

await messaging().registerDeviceForRemoteMessages();

At this point, isDeviceRegisteredForRemoteNotifications returns 'true' but calling await messaging().getToken(); returns this error: #6893. We can circumvent this by using these setter methods:

const tokenAPNS = await messaging().getAPNSToken();
await messaging().setAPNSToken(tokenAPNS);

However, getAPNSToken hangs when i've asked for permission with messaging.requestPermission(), but not react-native-permissions' requestNotifications, even when set with the same flags: {alert: true, sound: true, badge: true} - with the other library I can get & set device tokens.

@mikehardy
Copy link
Collaborator

I believe I am currently experiencing the same thing as a regression somehow, as I attempt to run e2e tests locally (and in CI) - but I haven't pinned it down yet. something is not working correctly, but it used to work 100% for me.

@mikehardy mikehardy reopened this May 2, 2024
mikehardy added a commit that referenced this issue May 17, 2024
Previously there were cases where notification registration requests would
wait indefinitely for the choreography of "request registration from system",
"wait for system to respond" completed.

There are cases (specifically if you are missing permissions...) where this
choreography will never complete.

Now we have a delayed async task that waits (for 10 seconds currently) and
if it runs and sees that the choreography wasn't complete it cleans up and
rejects so you avoid infinite hangs

See #7272
mikehardy added a commit that referenced this issue May 17, 2024
Related #7272 - the only difference between react-native-permissions and
our requestPermission implementation was an immediate registration for
notifications after a successful grant

Assuming you would never request permission if you did not want to receive
notifications, we now copy this behavior

This allows messaging registration and APNS token tests to pass again on
capable simulators (after altering tests to use the new permission behavior)
@mikehardy
Copy link
Collaborator

This was extremely frustrating! The details provided here - specifically everyone figuring out that permissions was the difference and that react-native-permissions in particular worked, meant I could find my way to a fix though.

The APIs should no longer hang indefinitely no matter what, and our requestPermission method will mirror what react-native-permissions does (an immediate notification registration on successful grant, basically)

This should work once #7797 is merged and released

Thanks @kg-currenxie @Alex-Whyatt et all, very helpful.

mikehardy added a commit that referenced this issue May 17, 2024
Previously there were cases where notification registration requests would
wait indefinitely for the choreography of "request registration from system",
"wait for system to respond" completed.

There are cases (specifically if you are missing permissions...) where this
choreography will never complete.

Now we have a delayed async task that waits (for 10 seconds currently) and
if it runs and sees that the choreography wasn't complete it cleans up and
rejects so you avoid infinite hangs

See #7272
mikehardy added a commit that referenced this issue May 17, 2024
Related #7272 - the only difference between react-native-permissions and
our requestPermission implementation was an immediate registration for
notifications after a successful grant

Assuming you would never request permission if you did not want to receive
notifications, we now copy this behavior

This allows messaging registration and APNS token tests to pass again on
capable simulators (after altering tests to use the new permission behavior)
mikehardy added a commit that referenced this issue May 20, 2024
Previously there were cases where notification registration requests would
wait indefinitely for the choreography of "request registration from system",
"wait for system to respond" completed.

There are cases (specifically if you are missing permissions...) where this
choreography will never complete.

Now we have a delayed async task that waits (for 10 seconds currently) and
if it runs and sees that the choreography wasn't complete it cleans up and
rejects so you avoid infinite hangs

See #7272
mikehardy added a commit that referenced this issue May 20, 2024
Related #7272 - the only difference between react-native-permissions and
our requestPermission implementation was an immediate registration for
notifications after a successful grant

Assuming you would never request permission if you did not want to receive
notifications, we now copy this behavior

This allows messaging registration and APNS token tests to pass again on
capable simulators (after altering tests to use the new permission behavior)
@kg-currenxie
Copy link
Author

@mikehardy awesome!

@Alex-Whyatt
Copy link

@mikehardy thanks for looking into this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Impact: Bug New bug report Platform: iOS Service: Messaging FCM only - ( messaging() ) - do not use for Notifications Workflow: Waiting for User Response Blocked waiting for user response.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants