根據您正在構建的應用程序類型,您可能會發現檢測哪些用戶或設備在線非常有用——也稱為檢測“在線狀態”。
例如,如果您正在構建社交網絡之類的應用程序或部署一組 IoT 設備,您可以使用此信息來顯示在線和免費聊天的朋友列表,或按“最後一次看到”對您的 IoT 設備進行排序”
Cloud Firestore 本身不支持在線狀態,但您可以利用其他 Firebase 產品構建在線狀態系統。
解決方案:帶有實時數據庫的雲函數
要將 Cloud Firestore 連接到 Firebase 實時數據庫的原生狀態功能,請使用 Cloud Functions。
使用實時數據庫報告連接狀態,然後使用 Cloud Functions 將該數據鏡像到 Cloud Firestore。
在實時數據庫中使用狀態
首先,考慮傳統在線狀態系統如何在實時數據庫中工作。
網絡
// Fetch the current user's ID from Firebase Authentication. var uid = firebase.auth().currentUser.uid; // Create a reference to this user's specific status node. // This is where we will store data about being online/offline. var userStatusDatabaseRef = firebase.database().ref('/status/' + uid); // We'll create two constants which we will write to // the Realtime database when this device is offline // or online. var isOfflineForDatabase = { state: 'offline', last_changed: firebase.database.ServerValue.TIMESTAMP, }; var isOnlineForDatabase = { state: 'online', last_changed: firebase.database.ServerValue.TIMESTAMP, }; // Create a reference to the special '.info/connected' path in // Realtime Database. This path returns `true` when connected // and `false` when disconnected. firebase.database().ref('.info/connected').on('value', function(snapshot) { // If we're not currently connected, don't do anything. if (snapshot.val() == false) { return; }; // If we are currently connected, then use the 'onDisconnect()' // method to add a set which will only trigger once this // client has disconnected by closing the app, // losing internet, or any other means. userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() { // The promise returned from .onDisconnect().set() will // resolve as soon as the server acknowledges the onDisconnect() // request, NOT once we've actually disconnected: // https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect // We can now safely set ourselves as 'online' knowing that the // server will mark us as offline once we lose connection. userStatusDatabaseRef.set(isOnlineForDatabase); }); });
此示例是一個完整的實時數據庫在線狀態系統。它處理多次斷開連接、崩潰等。
連接到 Cloud Firestore
要在 Cloud Firestore 中實施類似的解決方案,請使用相同的實時數據庫代碼,然後使用 Cloud Functions 使實時數據庫和 Cloud Firestore 保持同步。
如果您還沒有,請將實時數據庫添加到您的項目中並包含上述在線狀態解決方案。
接下來,您將通過以下方法將在線狀態同步到 Cloud Firestore:
- 在本地,到離線設備的 Cloud Firestore 緩存,以便應用知道它處於離線狀態。
- 在全球範圍內,使用 Cloud Function 以便訪問 Cloud Firestore 的所有其他設備都知道此特定設備處於離線狀態。
更新 Cloud Firestore 的本地緩存
讓我們來看看完成第一個問題所需的更改 - 更新 Cloud Firestore 的本地緩存。
網絡
// ... var userStatusFirestoreRef = firebase.firestore().doc('/status/' + uid); // Firestore uses a different server timestamp value, so we'll // create two more constants for Firestore state. var isOfflineForFirestore = { state: 'offline', last_changed: firebase.firestore.FieldValue.serverTimestamp(), }; var isOnlineForFirestore = { state: 'online', last_changed: firebase.firestore.FieldValue.serverTimestamp(), }; firebase.database().ref('.info/connected').on('value', function(snapshot) { if (snapshot.val() == false) { // Instead of simply returning, we'll also set Firestore's state // to 'offline'. This ensures that our Firestore cache is aware // of the switch to 'offline.' userStatusFirestoreRef.set(isOfflineForFirestore); return; }; userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() { userStatusDatabaseRef.set(isOnlineForDatabase); // We'll also add Firestore set here for when we come online. userStatusFirestoreRef.set(isOnlineForFirestore); }); });
通過這些更改,我們現在可以確保本地Cloud Firestore 狀態將始終反映設備的在線/離線狀態。這意味著您可以收聽/status/{uid}
文檔並使用數據更改您的 UI 以反映連接狀態。
網絡
userStatusFirestoreRef.onSnapshot(function(doc) { var isOnline = doc.data().state == 'online'; // ... use isOnline });
全局更新 Cloud Firestore
儘管我們的應用程序正確地向自身報告在線狀態,但此狀態在其他 Cloud Firestore 應用程序中還不准確,因為我們的“離線”狀態寫入僅在本地,並且在連接恢復時不會同步。為了解決這個問題,我們將使用一個雲函數來監視實時數據庫中的status/{uid}
路徑。當實時數據庫值更改時,該值將同步到 Cloud Firestore,以便所有用戶的狀態都是正確的。
節點.js
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); // Since this code will be running in the Cloud Functions environment // we call initialize Firestore without any arguments because it // detects authentication from the environment. const firestore = admin.firestore(); // Create a new function which is triggered on changes to /status/{uid} // Note: This is a Realtime Database trigger, *not* Firestore. exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate( async (change, context) => { // Get the data written to Realtime Database const eventStatus = change.after.val(); // Then use other event data to create a reference to the // corresponding Firestore document. const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`); // It is likely that the Realtime Database change that triggered // this event has already been overwritten by a fast change in // online / offline status, so we'll re-read the current data // and compare the timestamps. const statusSnapshot = await change.after.ref.once('value'); const status = statusSnapshot.val(); functions.logger.log(status, eventStatus); // If the current timestamp for this data is newer than // the data that triggered this event, we exit this function. if (status.last_changed > eventStatus.last_changed) { return null; } // Otherwise, we convert the last_changed field to a Date eventStatus.last_changed = new Date(eventStatus.last_changed); // ... and write it to Firestore. return userStatusFirestoreRef.set(eventStatus); });
部署此功能後,您將擁有一個與 Cloud Firestore 一起運行的完整在線狀態系統。下面是使用where()
查詢監視任何上線或下線用戶的示例。
網絡
firebase.firestore().collection('status') .where('state', '==', 'online') .onSnapshot(function(snapshot) { snapshot.docChanges().forEach(function(change) { if (change.type === 'added') { var msg = 'User ' + change.doc.id + ' is online.'; console.log(msg); // ... } if (change.type === 'removed') { var msg = 'User ' + change.doc.id + ' is offline.'; console.log(msg); // ... } }); });
限制
使用 Realtime Database 為您的 Cloud Firestore 應用添加狀態是可擴展且有效的,但有一些限制:
- 去抖動——當監聽 Cloud Firestore 中的實時變化時,這個解決方案很可能會觸發多個變化。如果這些更改觸發的事件比您想要的多,請手動去抖動 Cloud Firestore 事件。
- 連接性- 此實現衡量與實時數據庫的連接性,而不是與 Cloud Firestore 的連接性。如果每個數據庫的連接狀態不同,此解決方案可能會報告不正確的在線狀態。
- Android - 在 Android 上,實時數據庫在 60 秒不活動後與後端斷開連接。不活動意味著沒有打開的偵聽器或掛起的操作。為了保持連接打開,我們建議您向
.info/connected
之外的路徑添加一個值事件偵聽器。例如,您可以在每個會話開始時執行FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()
。有關詳細信息,請參閱檢測連接狀態。