重置推送 Notifications/Device Registration/Instance Id - 通过 TestFlight 或 App Store 更新应用 - Firebase 云消息传递 iOS/React 本机 Firebase

Reset Push Notifications/Device Registration/Instance Id - App Update via TestFlight or App Store - Firebase Cloud Messaging iOS/React Native Firebase

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

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

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

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

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

const fcmToken = await firebase.messaging().getToken(); //same whether 4.2 or 4.3

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

所以我连接起来监听令牌更改事件:

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

那似乎永远不会火。我什至在我的应用程序中添加了一个按钮,以尝试强制使用新的 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);
} 

不行,不行。

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

可能是我一直在研究的线索的参考资料:

有趣的应用程序更新开始,由于应用程序更新推送通知将停止工作我看到两个不同的标记,也许第一个是 APN token from Apple:

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.

然后稍后我从以前版本的应用程序加载了推送通知工作的先前令牌,我在日志中看到了工作"default token":

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

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

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.

随后新令牌被加载并被视为默认令牌,我的新安装立即收到 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

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

APNS device token not set before retrieving FCM Token for Sender ID '255558254149'. Notifications to this FCM Token will not be delivered over APNS.Be sure to re-retrieve the FCM token once the APNS device token is set.

很难相信这种情况一直在发生,但确实如此,我们将不胜感激任何帮助。

好吧它最终变成了竞争条件,我发现了一个很好的提示here

Seems with react-native-firebase Firebase.messaging().getToken() will not always return the latest token - use onTokenRefresh instead. My app was saving and using an old token and not updating a new one - very small race condition.

Simply use Firebase.messaging.onTokenRefresh() as a source of truth to avoid issues with migrating apps. Also to be sure you get a token that works. You may want to delete your token on a migration such as this with v4 -> await Firebase.iid().deleteToken(), or v5 (Firebase.messaging().deleteToken(). Then rely onTokenRefresh to send you a new one to send to your server.

现在,上面引用中提到的 firebase.messaging().deleteToken() 不会 "good enough" 在有来自 TestFlight 或 Apple App Store 的应用程序更新时导致令牌刷新。

只好调用异步了(遇到这个问题一切都是异步的是经验法则):

firebase.iid().delete();

React Native Firebase api 方法删除了 InstanceId,它实际上是 Firebase 云消息传递 (FCM) 用于令牌的。

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

我将添加我的代码,希望它能帮助其他人解决这个问题,从本质上讲,根据我的研究,使用 React Native Firebase 5.x 这就是你必须做的来保持无缝的推送通知注册在 iOS 个应用更新中使用 Firebase 云消息传递:

在您的 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 {}
      })
  }

然后在你的 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();
  }

我花了很多时间才找到解决方案。

因此,如果您在 iOS 中调用 firebase.iid().delete(),您需要在 APNs 中注册您的新令牌。我没有在 react native firebase 文档中看到这个,只是在 lib 的类型中找到

await firebase.messaging().ios.registerForRemoteNotifications()

请在生成新令牌后调用此方法