重置推送通知/设备注册/实例 ID - 通过 TestFlight 或 App Store 进行应用程序更新 - Firebase 云消息传递 iOS/React Native Firebase

Bri*_*den 1 ios firebase reactjs react-native react-native-ios

关于这个问题做了很多研究,首先是我的设置:

我的问题非常简单直接,尽管我似乎无法找到解决问题的明确方法。

我部署了应用程序的 4.2 版本。iOS 中的 Firebase 云消息传递 (FCM) 运行得非常好。然后我将 4.3 部署到 TestFlight 开始测试。通过 TestFlight 安装 4.3,FCM 停止工作,没有推送通知。如果我删除该应用程序并通过 TestFlight 推送通知再次安装 4.3,则该设备已注册。

我还可以重现此问题,更新最新发布的 App Store 中我的应用程序的安装版本,这并不奇怪。

我知道 InstanceId/设备令牌基于应用程序构建+设备,因此当应用程序更新时令牌发生变化是有意义的,但当我从 4.2 更新到 4.3 时我的令牌是相同的:

const fcmToken = await firebase.messaging().getToken(); //same whether 4.2 or 4.3
Run Code Online (Sandbox Code Playgroud)

是的, firebase.messaging().getToken() 在初始创建时被缓存。

所以我准备监听令牌更改事件:

firebase.messaging().onTokenRefresh(async () => {
  console.log('======onTokenRefresh=========');
  AsyncStorage.removeItem(FCM_TOKEN_KEY);
  await getToken();
});
Run Code Online (Sandbox Code Playgroud)

那似乎永远不会火。我什至在我的应用程序中添加了一个按钮,以尝试强制使用新的 InstanceId 并在从 TestFlight 或 AppStore 更新版本后向 FCM 注册,但这没有帮助:

export async function forceRefresh() {
  console.log('================force a new registration!!!==========');
  AsyncStorage.removeItem(FCM_TOKEN_KEY);
  firebase.messaging().deleteToken();
  firebase.iid().deleteToken();
  registerForPushNotificationsAsync(true);
} 
Run Code Online (Sandbox Code Playgroud)

不,行不通。

所以这个故事的寓意是,当我更新应用程序的版本时,我的客户(他们的推送通知工作得很好)在将应用程序更新到下一个版本时将不再注册推送通知。

可能是我一直在研究的线索的参考文献:

有趣的是,应用程序更新开始时,推送通知将由于应用程序更新而停止工作,我看到两个不同的令牌,也许第一个是来自 Apple 的 APN 令牌

2020-01-17 18:48:09.371741-0800 native[4462:1287461] -[RNFirebaseMessaging messaging:didReceiveRegistrationToken:] [Line 86] Received new FCM token: eBBgznWj1FU:APA91bF8vTmpkwcojp4oDSKFzlPDp6ylEIe_WGNzu24SKHS6RR-3xPu2-cX-Qyc8rrMIQMvkCJftT9711ll1WdshBWS4iEpZ3XpiPeTynqM-nvDjpAUUUWJpfT5aeo6G_scDsN9iipwI
2020-01-17 18:48:09.378382-0800 native[4462:1287621] 6.15.0 - [Firebase/InstanceID][I-IID014012] Invalidating cached token for 255558254149 (*) due to token is no longer fresh.
Run Code Online (Sandbox Code Playgroud)

然后稍后,加载了推送通知工作的应用程序的先前版本中的先前令牌,我并在日志中看到工作“默认令牌”:

2020-01-17 18:48:09.987571-0800 native[4462:1287610] 6.15.0 - [Firebase/InstanceID][I-IID014001] Token fetch successful, token: dUY5psWDnkv2td1kB_t6Gs:APA91bEaREBt07CWiEyGvP4YAGjxmVQmF0IcXgef5XcvL5KWrHsqcxZZ8L9PqwGzKTPFGy6cdmuVXSvg6kDQjj-652jt5_jbbKMhUFTcam_-FeBp2vGZvBjaBd4aAOtQf1m48htQ8d6B, authorizedEntity: 255558254149, scope:*
2020-01-17 18:48:09.987764-0800 native[4462:1287461] -[RNFirebaseMessaging messaging:didReceiveRegistrationToken:] [Line 86] Received new FCM token: dUY5psWDnkv2td1kB_t6Gs:APA91bEaREBt07CWiEyGvP4YAGjxmVQmF0IcXgef5XcvL5KWrHsqcxZZ8L9PqwGzKTPFGy6cdmuVXSvg6kDQjj-652jt5_jbbKMhUFTcam_-FeBp2vGZvBjaBd4aAOtQf1m48htQ8d6B
2020-01-17 18:48:09.993088-0800 native[4462:1287610] 6.15.0 - [Firebase/InstanceID][I-IID003010] Successfully fetched default token.
2020-01-17 18:48:09.993755-0800 native[4462:1287610] 6.15.0 - [Firebase/InstanceID][I-IID003008] Got default token dUY5psWDnkv2td1kB_t6Gs:APA91bEaREBt07CWiEyGvP4YAGjxmVQmF0IcXgef5XcvL5KWrHsqcxZZ8L9PqwGzKTPFGy6cdmuVXSvg6kDQjj-652jt5_jbbKMhUFTcam_-FeBp2vGZvBjaBd4aAOtQf1m48htQ8d6B
Run Code Online (Sandbox Code Playgroud)

有趣的是,在删除当前版本的应用程序并安装最新版本(在之前的日志中是更新版本)之后,我们可以看到相反的相同过程,首先找到我当前的令牌并被认为不再新鲜:

messaging:didReceiveRegistrationToken:] [Line 86] Received new FCM token: dUY5psWDnkv2td1kB_t6Gs:APA91bEaREBt07CWiEyGvP4YAGjxmVQmF0IcXgef5XcvL5KWrHsqcxZZ8L9PqwGzKTPFGy6cdmuVXSvg6kDQjj-652jt5_jbbKMhUFTcam_-FeBp2vGZvBjaBd4aAOtQf1m48htQ8d6B
2020-01-17 19:03:06.651179-0800 native[4475:1291698] 6.15.0 - [Firebase/InstanceID][I-IID014012] Invalidating cached token for 255558254149 (*) due to token is no longer fresh.
Run Code Online (Sandbox Code Playgroud)

随后,新令牌被加载并被视为默认令牌,我的新安装立即收到 FCM 通知:

2020-01-17 19:03:07.997209-0800 native[4475:1291564] -[RNFirebaseMessaging messaging:didReceiveRegistrationToken:] [Line 86] Received new FCM token: ebz2ACPpBkg0kGsgs9yF7_:APA91bGErCaPMuLyRk-_BLZXUk8_U6FyxvKHbI0NPgddFWl_-nLZuCc6HbHg8kaLMDJiO7sHFS8THAuV132xgri8uQ9YV4g8zDXJySrKsSTNiDq9HcXpzUQXQlPy8bTaxZ3gyRxyCy3p
2020-01-17 19:03:08.018870-0800 native[4475:1291684] 6.15.0 - [Firebase/InstanceID][I-IID014001] Token fetch successful, token: ebz2ACPpBkg0kGsgs9yF7_:APA91bGErCaPMuLyRk-_BLZXUk8_U6FyxvKHbI0NPgddFWl_-nLZuCc6HbHg8kaLMDJiO7sHFS8THAuV132xgri8uQ9YV4g8zDXJySrKsSTNiDq9HcXpzUQXQlPy8bTaxZ3gyRxyCy3p, authorizedEntity: 255558254149, scope:*
2020-01-17 19:03:08.019018-0800 native[4475:1291684] 6.15.0 - [Firebase/InstanceID][I-IID003010] Successfully fetched default token.
2020-01-17 19:03:08.019065-0800 native[4475:1291684] 6.15.0 - [Firebase/InstanceID][I-IID003008] Got default token ebz2ACPpBkg0kGsgs9yF7_:APA91bGErCaPMuLyRk-_BLZXUk8_U6FyxvKHbI0NPgddFWl_-nLZuCc6HbHg8kaLMDJiO7sHFS8THAuV132xgri8uQ9YV4g8zDXJySrKsSTNiDq9HcXpzUQXQlPy8bTaxZ3gyRxyCy3p
Run Code Online (Sandbox Code Playgroud)

刚刚在日志中发现了这个兴趣声明:

在检索发件人 ID“255558254149”的 FCM 令牌之前未设置 APNS 设备令牌。对此 FCM 令牌的通知将不会通过 APNS 传递。设置 APNS 设备令牌后,请务必重新检索 FCM 令牌。

很难相信这种情况如此持续地发生,但似乎确实如此,任何帮助将不胜感激。

Bri*_*den 5

好吧,这最终成为了一个竞争条件,我在这里找到了一个很好的提示

似乎使用react-native-firebase Firebase.messaging().getToken() 并不总是返回最新的令牌 - 请改用 onTokenRefresh 。我的应用程序正在保存并使用旧令牌,而不是更新新令牌 - 非常小的竞争条件。

只需使用 Firebase.messaging.onTokenRefresh() 作为事实来源即可避免迁移应用程序时出现问题。还要确保您获得有效的令牌。您可能希望在迁移时删除您的令牌,例如使用 v4 ->await Firebase.iid().deleteToken() 或 v5 (Firebase.messaging().deleteToken()。然后依靠 TokenRefresh 向您发送一个新令牌发送到您的服务器。

现在,firebase.messaging().deleteToken()上面引用中提到的“不够好”不足以在 TestFlight 或 Apple App Store 进行应用程序更新时导致 tokenRefresh。

我不得不调用异步(当涉及到这个问题时,一切都是异步的,这是经验法则):

firebase.iid().delete();
Run Code Online (Sandbox Code Playgroud)

React Native Firebaseapi 方法删除 InstanceId,这实际上是 Firebase Cloud Messaging (FCM) 用于令牌的内容。

这将触发 onTokenRefresh。所以我所做的是检查应用程序的版本和内部版本号并将其存储在应用程序用户数据(iOS 的 NSDefaults)中,我检查此版本和内部版本是否存在,如果不存在我“刷新”设备令牌。这样这种情况只会发生一次。

我将添加我的代码,希望它可以帮助其他人解决这个问题,本质上,根据我的研究,使用 React Native Firebase 5.x,这是您在 iOS 应用程序中使用 Firebase Cloud Messaging 维护无缝推送通知注册所要做的更新:

在您的 App.js 中添加以下内容:

   configureFirebaseCloudMessaging = async () => {
    //wire up Firebase Cloud Messaging onTokenRefresh listener

    this.fcmOnTokenRefreshUnsubscribe = await firebase.messaging().onTokenRefresh(async fcmToken => {
      console.log('*********************** onTokenRefresh *****************');
      //this is callBack called typically sometime in the future but can be call with app loaded but user not logged in
      //check for that case and exit if there is no accessToken to call APIs
      const accessToken = await AsyncStorage.getItem('access-token');
      if (!accessToken) {
        console.log('************** user is not logged in exit onTokenRefresh do not register device ************');
        return;
      }
      await this.registerDevice(fcmToken, BASE_URL);
      await AsyncStorage.setItem('fcmToken', fcmToken);
      firebase.crashlytics().log(`flushed new fcmToken: ${fcmToken}`);
      console.log('***************** success  account updated with latest token **************');
    });

    //Firebase Cloud Messaging time
    await this.requestPushPermission();
    await this.checkFlushv();
  }

  checkFlushv = async () => {
    let FLUSHV = `${DeviceInfo.getVersion()}-build-${DeviceInfo.getBuildNumber()}`;

    const flush = await AsyncStorage.getItem(FLUSHV);
    console.log('======= checking FLUSHV=========', flush);
    if (flush) {
      console.log('***** device token already been flushed ******');
      return;
    }

    const accessToken = await AsyncStorage.getItem('access-token');
    console.log('flushy access token:', accessToken);
    if (!accessToken) {
      console.log('******** user is not logged in do not flush ************');
      return;
    }

    //force push notifications, this will fire onTokenRefresh callback
    await firebase.iid().delete();

    //iterate all keys and remove other builds to keep tidy and TestFlighters possibly going up and back down build versions for testing
    const keys = await AsyncStorage.getAllKeys();
    const buildKeys = keys.filter(key => {
      return key.indexOf('build') !== -1;
    });
    await AsyncStorage.multiRemove(buildKeys);

    //add current build key so no more flushy
    await AsyncStorage.setItem(FLUSHV, FLUSHV);
    console.log(`=================FLUSHV clear: ${FLUSHV}===================`)
  }

  registerDevice = async (token, baseUrl) => {
    console.log(`******** registerDevice token: ${token}, baseUrl: ${baseUrl}`);
    let data = {
      device: 'firebase',
      token: token
    }

    //register token with  account
    axios.post(`${baseUrl}/myapi/register_device`, data)
      .then(response => {
        return {}
      })
      .catch(err => {
        console.log(err)
        return {}
      })
  }
Run Code Online (Sandbox Code Playgroud)

然后在你的 App.js 中:

  async componentDidUpdate() {
    //a new version of the app could be loaded post/after App.componentDidMount
    await this.checkFlushv();
  }

  async componentWillUnmount() {
    console.log('app componentWillUnmount');
    this.fcmOnTokenRefreshUnsubscribe(); //not really sure unsubcribe is needed but keeping tidy
    this.fcmOnTokenRefreshUnsubscribe = null;
  }

  async componentDidMount() {
    await this.configureFirebaseCloudMessaging();
  }
Run Code Online (Sandbox Code Playgroud)