You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native 0.77.0 iOS端FCM Token获取报错求助

React Native 0.77.0 iOS推送通知FCM Token获取失败排查

问题现象

在React Native 0.77.0环境下配置iOS推送通知时,调用getToken获取FCM Token抛出以下错误:

App.js:296 Error in token retrieval: NativeFirebaseError: [messaging/unknown] The operation couldn’t be completed. No APNS token specified before fetching FCM Token

用户提供的代码如下:

AppDelegate.swift代码

import UIKit
import Firebase
import UserNotifications
import RNCPushNotificationIOS
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider

@main
class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    
    // Initialize Firebase
    FirebaseApp.configure();

    Messaging.messaging().delegate = self

    // Set the UNUserNotificationCenter delegate to self
    UNUserNotificationCenter.current().delegate = self
  
    // Register for remote notifications
    application.registerForRemoteNotifications()

    // Request notification permissions
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
      if let error = error {
        print("Error requesting notification permissions: \(error)")
      }
      print("Notification permission granted: \(granted)")
    }

    // 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 = [:]
    self.moduleName = "*****"
    self.dependencyProvider = RCTAppDependencyProvider()

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  // // Handle foreground notifications: show alert, sound, and badge even when app is active.
  // func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification,  withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
  //   completionHandler([.badge, .sound])
  // }

  // // Optionally, handle notification responses if needed.
  // func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
  //   // Process the notification response (for example, navigate to a specific screen)
  //   completionHandler()
  // }

  override func sourceURL(for bridge: RCTBridge) -> URL? {
    self.bundleURL()
  }

  override func bundleURL() -> URL? {
  #if DEBUG
    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
  #else
    Bundle.main.url(forResource: "main", withExtension: "jsbundle")
  #endif
  }

  override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    Messaging.messaging().apnsToken = deviceToken
  }
}

App.js中checkAndGetFcmToken函数代码

async function checkAndGetFcmToken() {
    try {
      if (Platform.OS === 'ios') {
        await messaging().registerDeviceForRemoteMessages();
      }

      if (Platform.OS === 'android') {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
        );
        if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
          console.log('Notification permission not granted');
          return;
        }
      }

      const authStatus = await messaging().requestPermission();
      const enabled =
        authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
        authStatus === messaging.AuthorizationStatus.PROVISIONAL;


      if (enabled) {
       
          const token = await messaging().getToken();
          console.log('FCM token:', token);
          if (token) {
            await AsyncStorage.setItem('fcmToken', token);
          } else {
            console.warn('FCM token is still null.');
          }
      
      } else {
        console.log('Notification permission not granted');
      }

    } catch (error) {
      console.error('Error in token retrieval:', error);
    }
  }

问题根源

  1. 时序逻辑错误:Native层先调用application.registerForRemoteNotifications(),再请求权限,导致可能在用户未授权的情况下就发起APNS注册,流程顺序颠倒。
  2. APNS Token未及时同步:getToken调用时机过早,此时系统还未完成APNS Token的回调,Messaging.messaging().apnsToken尚未赋值,FCM无法生成对应Token。
  3. 重复注册操作:iOS平台下,Native层已经调用application.registerForRemoteNotifications(),JS层又调用messaging().registerDeviceForRemoteMessages(),重复操作导致时序混乱。

修复方案

1. 调整Native层流程顺序

先请求通知权限,用户授权后再注册远程通知,确保APNS注册流程的合法性。同时在APNS Token获取后主动触发FCM Token刷新,并监听FCM Token的更新事件。

2. 优化JS层调用逻辑

移除iOS平台下的重复注册代码,改用监听Token刷新事件的方式获取FCM Token,避免因时序问题导致的获取失败。

修改后的代码

AppDelegate.swift

import UIKit
import Firebase
import UserNotifications
import RNCPushNotificationIOS
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider

@main
class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    
    // Initialize Firebase
    FirebaseApp.configure();

    Messaging.messaging().delegate = self

    // Set the UNUserNotificationCenter delegate to self
    UNUserNotificationCenter.current().delegate = self
  
    // 先请求权限,授权后再注册远程通知
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
      if let error = error {
        print("Error requesting notification permissions: \(error)")
      }
      print("Notification permission granted: \(granted)")
      if granted {
        DispatchQueue.main.async {
          application.registerForRemoteNotifications()
        }
      }
    }

    self.initialProps = [:]
    self.moduleName = "*****"
    self.dependencyProvider = RCTAppDependencyProvider()

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  // 监听FCM Token更新事件
  func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
    print("FCM Token updated: \(fcmToken ?? "nil")")
  }

  override func sourceURL(for bridge: RCTBridge) -> URL? {
    self.bundleURL()
  }

  override func bundleURL() -> URL? {
  #if DEBUG
    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
  #else
    Bundle.main.url(forResource: "main", withExtension: "jsbundle")
  #endif
  }

  override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    Messaging.messaging().apnsToken = deviceToken
    // 设置APNS Token后主动刷新FCM Token
    Messaging.messaging().token(completion: { token, error in
      if let error = error {
        print("Failed to refresh FCM token: \(error)")
      } else if let token = token {
        print("FCM token refreshed: \(token)")
      }
    })
  }

  // 处理远程通知注册失败的情况
  override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("Failed to register for remote notifications: \(error)")
  }
}

App.js checkAndGetFcmToken函数

async function checkAndGetFcmToken() {
  try {
    if (Platform.OS === 'android') {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
      );
      if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
        console.log('Notification permission not granted');
        return;
      }
    }

    const authStatus = await messaging().requestPermission();
    const enabled =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

    if (enabled) {
      // 监听FCM Token刷新事件
      const unsubscribe = messaging().onTokenRefresh(async (token) => {
        console.log('FCM token refreshed:', token);
        await AsyncStorage.setItem('fcmToken', token);
        unsubscribe();
      });

      // 尝试获取当前Token,失败则等待刷新事件
      try {
        const token = await messaging().getToken();
        if (token) {
          console.log('FCM token:', token);
          await AsyncStorage.setItem('fcmToken', token);
          unsubscribe();
        }
      } catch (getTokenError) {
        console.log('Waiting for FCM token refresh...');
      }
    } else {
      console.log('Notification permission not granted');
    }
  } catch (error) {
    console.error('Error in token retrieval:', error);
  }
}

内容的提问来源于stack exchange,提问作者Raj M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:28:14