【问题标题】:Firebase: Getting new token after current FCM token expiresFirebase:在当前 FCM 令牌过期后获取新令牌
【发布时间】:2020-04-01 15:14:09
【问题描述】:

我正在开发一个以 firebase 作为后端的 reactJS 网站。我为用户提供的主要服务之一是每天通知。我已经实现了一个请求许可的代码,如果他们授予访问权限,则将它们存储到我的数据库中。但过了一段时间我注意到令牌即将到期。我对它进行了研究,发现了 onTokenRefresh() 方法。我也实现了。但我相信由于某种原因,它无法正常工作。如果令牌已过期,我将不会获得新令牌。我将在下面粘贴我要执行的操作的 sn-p。

在主页上,

if (!("Notification" in window)) {
      //alert("This browser does not support desktop notification");
    }

    // Check whether notification permissions have already been granted
    else if (Notification.permission === "granted") {
      // Refresh logic here
      this.checkForRefreshToken();
    }

    // Otherwise, ask the user for permission
    else if (Notification.permission !== "denied") {
      //Ask user here, then call another function for storing token
      this.addNewToken()
    }

如果用户是第一次接受通知,我调用 addNewToken() 方法

addNewToken = () => {

    this.auth.onAuthStateChanged(authUser => {

      const messaging = firebase.messaging();
      messaging
        .requestPermission()
        .then(() => {
          return messaging.getToken();
        })
        .then(token => {

          firebase.database().ref('tokens').once('value')
            .then(snapshots => {
              let tokenExist = false
              snapshots.forEach(childSnapshot => {
                // console.log("Child snapshot: ", childSnapshot.key);
                if (childSnapshot.val().token === token) {
                  tokenExist = true
                  return console.log('Device already registered.');
                }
              })
              if (!tokenExist) {
                //  console.log('Device subscribed successfully');
                return firebase.database().ref('tokens').push(token);
              }
            })
        })
        .catch(error => {
          if (error.code === "messaging/permission-blocked") {
            // console.log("Please Unblock Notification Request Manually");
          } else {
            // console.log("Error Occurred", error);
          }
        });

     })

  }

现在如果用户已经订阅,我正在检查是否调用了 onTokenRefresh(),基本上是如果令牌需要刷新。

checkForRefreshToken = () => {
      this.auth.onAuthStateChanged(authUser => {
        const messaging = firebase.messaging();
        messaging.onTokenRefresh(() => {
          messaging.getToken().then((refreshedToken) => {
            const token = refreshedToken;
            firebase.database().ref('tokens').once('value')
            .then(snapshots => {
              let tokenExist = false
              snapshots.forEach(childSnapshot => {

                if (childSnapshot.val().token === token) {

                  tokenExist = true
                  return console.log('Device already registered.');
                }

              })

              if (!tokenExist) {
                return firebase.database().ref('device_ids').push(token);
              }
            })

          })
        })

      })  
    }

我不知道这里出了什么问题,但我无法获得新令牌。

出于测试目的,我有自己的设备,其令牌已过期,我部署了此代码并在设备上打开了我的网站,刷新了页面等,但我没有得到新的令牌。

另外,如果有人能帮助我如何在本地测试过期的令牌,那就太好了。 我为应用找到了不同的方法,但对于网站 (javascript) 却没有。

谢谢

【问题讨论】:

    标签: javascript reactjs firebase firebase-realtime-database firebase-cloud-messaging


    【解决方案1】:

    auth.onAuthStateChanged(...) 将添加一个事件侦听器到事件,表明身份验证状态的变化。在上面的代码中,您希望立即评估 addNewTokencheckForRefreshToken 的代码,但它们只是添加新的事件侦听器。这同样适用于onTokenRefresh()

    因此将addNewToken() 改造成按需函数会产生:

    requestMessagingToken = () => {
        let authUser = this.auth.currentUser;
    
        if (!authUser) {
          console.log("Not logged in!");
          return Promise.resolve(false); // silently ignore & fail
        }
    
        const messaging = firebase.messaging();
    
        return messaging
            .requestPermission()
            .then((permission) => {
                if (permission !== 'granted') {
                  throw 'insufficient permissions'; // notification permission was denied/ignored
                }
    
                return messaging.getToken();
            })
            .then(token => saveMessagingTokenForUser(authUser.uid, token))
            .catch(error => {
                if (error && error.code === "messaging/permission-blocked") {
                    // console.log("Please Unblock Notification Request Manually");
                } else {
                    // console.log("Error Occurred", error);
                }
                return false; // silently fail
            });
    };
    

    这有一个额外的好处是能够使用以下方法轻松处理身份验证状态更改:

    this.auth.onAuthStateChanged(requestMessagingToken);
    

    一旦您被授予发送通知的权限,您就可以使用以下方法激活刷新令牌侦听器:

    const messaging = firebase.messaging();
    
    messaging.onTokenRefresh(() => {
        let authUser = this.auth.currentUser;
    
        if (!authUser) {
            console.log("Not logged in!");
            return; // ignore
        }
    
        messaging.getToken().then((refreshedToken) => {
            return saveMessagingTokenForUser(authUser.uid, refreshedToken);
        }).catch((err) => {
            console.log('Unable to retrieve/store messaging token ', err);
        });
    });
    

    最后,要将令牌保存到数据库中,您可以使用令牌本身作为键,而不是在数据库中搜索匹配的设备令牌作为推送 ID 的值。此外,为了更容易管理过时的令牌,最好按用户拆分令牌。

    "userData": {
      "userId1": {
        "tokens": {
          "deviceToken1": true,
          "deviceToken2": true,
          "deviceToken3": true,
        },
        ...
      },
      "userId2": {
        "tokens": {
          "deviceToken4": true,
          "deviceToken5": true
        },
        ...
      },
      ...
    }
    

    使用这种结构,您可以使用事务检查数据是否已存储在数据库中,并使用云功能检查无效令牌。

    saveMessagingTokenForUser = (uid, token) => {
        return firebase.database().ref('userData/' + uid + '/tokens/' + token)
            .transaction((currentData) => {
                if (currentData != null) {
                    console.log('Device already registered.');
                    return; // abort transaction, no changes needed
                } else {
                    console.log('Saving token to database.');
                    return true;
                }
            })
            .then(() => true); // no errors = success
    };
    

    在服务器上,您可以运行云函数来侦听添加到数据库中的新设备令牌,并检查该用户的其他设备令牌是否过期。

    exports.checkUserForOutdatedTokens = functions.database.ref('/userData/{userId}/tokens/{tokenId}')
        .onCreate((newTokenSnapshot, context) => {
    
        return newTokenSnapshot.ref.once('value')
            .then((tokensSnapshot) => {
                let tokens = Object.keys(tokensSnapshot.val());
    
                const message = {
                    data: {heartbeat: true},
                    tokens: tokens,
                }
    
                return admin.messaging().sendMulticast(message)
                    .then((response) => {
                        if (response.failureCount > 0) {
                            const dataToDelete = {};
                            response.responses.forEach((resp, idx) => {
                                if (!resp.success) {
                                    dataToDelete[tokens[idx]] = null;
                                }
                            });
                            console.log('List of tokens that caused failures: ' + Object.keys(dataToDelete));
    
                            return tokensSnapshot.ref.update(dataToDelete); // deletes failed tokens
                        }
                    });
        });
    });
    

    【讨论】:

    • 非常感谢您的回答。我在这里有一些疑问。 1. 如果你忽略了我的实现逻辑,我怎样才能以最好的方式实现 onTokenRefresh() 。我的意思是,在某种程度上,它就像一个事件监听器。 2. 要触发 onTokenRefresh(),用户至少应该访问我们的网站一次。如果用户的token过期了,很久没有访问我的网站了,怎么触发listner呢?
    • 1) 下面的代码“激活刷新的令牌监听器”是一个监听器,在刷新时更新令牌。 “alerted->get->set”是处理这些事件的最简单方法。 2) 除非 SDK 找到要刷新的现有令牌,否则不会触发令牌刷新事件,如果令牌更改,将触发刷新事件。例如,如果用户清除了他们的浏览数据,SDK 将不会知道它之前持有一个令牌,您应该像第一次手动那样提示您的用户通知权限(在这种情况下不会触发侦听器) .
    猜你喜欢
    • 2018-01-26
    • 2016-09-24
    • 2016-10-13
    • 1970-01-01
    • 2021-10-03
    • 2017-10-19
    • 2020-09-21
    • 2019-07-19
    • 1970-01-01
    相关资源
    最近更新 更多