即使您的應用程序暫時失去網絡連接,Firebase 應用程序也能正常工作。我們提供了幾種工具來監視狀態並將本地狀態與服務器狀態同步,這些工具在本文檔中進行了介紹。
管理狀態
在實時應用程序中,檢測客戶端何時連接和斷開連接通常很有用。例如,您可能希望在客戶端斷開連接時將用戶標記為“離線”。
Firebase 數據庫客戶端提供簡單的原語,當客戶端與 Firebase 數據庫服務器斷開連接時,您可以使用這些原語寫入數據庫。無論客戶端是否完全斷開連接,這些更新都會發生,因此即使連接斷開或客戶端崩潰,您也可以依靠它們來清理數據。所有寫操作,包括設置、更新和刪除,都可以在斷開連接時執行。
下面是一個使用onDisconnect
原語在斷開連接時寫入數據的簡單示例:
Web version 9
import { getDatabase, ref, onDisconnect } from "firebase/database"; const db = getDatabase(); const presenceRef = ref(db, "disconnectmessage"); // Write a string when this client loses connection onDisconnect(presenceRef).set("I disconnected!");
Web version 8
var presenceRef = firebase.database().ref("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().set("I disconnected!");
onDisconnect 如何工作
當您建立onDisconnect()
操作時,該操作存在於 Firebase 實時數據庫服務器上。服務器檢查安全性以確保用戶可以執行請求的寫入事件,並在無效時通知您的應用程序。服務器然後監視連接。如果連接在任何時候超時,或者被實時數據庫客戶端主動關閉,服務器會再次檢查安全性(以確保操作仍然有效),然後調用事件。
您的應用程序可以使用寫入操作的回調來確保onDisconnect
已正確附加:
Web version 9
onDisconnect(presenceRef).remove().catch((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
Web version 8
presenceRef.onDisconnect().remove((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
onDisconnect
事件也可以通過調用.cancel()
來取消:
Web version 9
const onDisconnectRef = onDisconnect(presenceRef); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
Web version 8
var onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
檢測連接狀態
對於許多與狀態相關的功能,讓您的應用知道它何時在線或離線是很有用的。 Firebase 實時數據庫在/.info/connected
提供了一個特殊位置,每次 Firebase 實時數據庫客戶端的連接狀態發生變化時都會更新該位置。這是一個例子:
Web version 9
import { getDatabase, ref, onValue } from "firebase/database"; const db = getDatabase(); const connectedRef = ref(db, ".info/connected"); onValue(connectedRef, (snap) => { if (snap.val() === true) { console.log("connected"); } else { console.log("not connected"); } });
Web version 8
var connectedRef = firebase.database().ref(".info/connected"); connectedRef.on("value", (snap) => { if (snap.val() === true) { console.log("connected"); } else { console.log("not connected"); } });
/.info/connected
是一個布爾值,它在實時數據庫客戶端之間不同步,因為該值取決於客戶端的狀態。換句話說,如果一個客戶端將/.info/connected
讀取為 false,則不能保證另一個客戶端也將讀取為 false。
處理延遲
服務器時間戳
Firebase 實時數據庫服務器提供了一種機制,可以將在服務器上生成的時間戳作為數據插入。此功能與onDisconnect
相結合,提供了一種簡單的方法來可靠地記錄實時數據庫客戶端斷開連接的時間:
Web version 9
import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database"; const db = getDatabase(); const userLastOnlineRef = ref(db, "users/joe/lastOnline"); onDisconnect(userLastOnlineRef).set(serverTimestamp());
Web version 8
var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);
時鐘偏差
雖然firebase.database.ServerValue.TIMESTAMP
更準確,並且對於大多數讀/寫操作更可取,但有時它可以用於估計客戶端相對於 Firebase 實時數據庫服務器的時鐘偏差。您可以將回調附加到位置/.info/serverTimeOffset
以獲取以毫秒為單位的值,Firebase 實時數據庫客戶端添加到本地報告的時間(以毫秒為單位的紀元時間)以估計服務器時間。請注意,此偏移量的準確性可能會受到網絡延遲的影響,因此主要用於發現時鐘時間中的大(> 1 秒)差異。
Web version 9
import { getDatabase, ref, onValue } from "firebase/database"; const db = getDatabase(); const offsetRef = ref(db, ".info/serverTimeOffset"); onValue(offsetRef, (snap) => { const offset = snap.val(); const estimatedServerTimeMs = new Date().getTime() + offset; });
Web version 8
var offsetRef = firebase.database().ref(".info/serverTimeOffset"); offsetRef.on("value", (snap) => { var offset = snap.val(); var estimatedServerTimeMs = new Date().getTime() + offset; });
在線狀態應用程序示例
通過將斷開連接操作與連接狀態監視和服務器時間戳相結合,您可以構建一個用戶存在系統。在此系統中,每個用戶都將數據存儲在數據庫位置以指示實時數據庫客戶端是否在線。客戶端在上線時將此位置設置為 true,在斷開連接時設置時間戳。此時間戳指示給定用戶上次在線的時間。
請注意,您的應用程序應在將用戶標記為在線之前對斷開連接操作進行排隊,以避免在客戶端的網絡連接丟失的情況下在兩個命令都可以發送到服務器之前發生任何競爭情況。
這是一個簡單的用戶存在系統:
Web version 9
import { getDatabase, ref, onValue, push, onDisconnect, set, serverTimestamp } from "firebase/database"; // Since I can connect from multiple devices or browser tabs, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline const db = getDatabase(); const myConnectionsRef = ref(db, 'users/joe/connections'); // stores the timestamp of my last disconnect (the last time I was seen online) const lastOnlineRef = ref(db, 'users/joe/lastOnline'); const connectedRef = ref(db, '.info/connected'); onValue(connectedRef, (snap) => { if (snap.val() === true) { // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect) const con = push(myConnectionsRef); // When I disconnect, remove this device onDisconnect(con).remove(); // Add this device to my connections list // this value could contain info about the device or a timestamp too set(con, true); // When I disconnect, update the last time I was seen online onDisconnect(lastOnlineRef).set(serverTimestamp()); } });
Web version 8
// Since I can connect from multiple devices or browser tabs, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline var myConnectionsRef = firebase.database().ref('users/joe/connections'); // stores the timestamp of my last disconnect (the last time I was seen online) var lastOnlineRef = firebase.database().ref('users/joe/lastOnline'); var connectedRef = firebase.database().ref('.info/connected'); connectedRef.on('value', (snap) => { if (snap.val() === true) { // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect) var con = myConnectionsRef.push(); // When I disconnect, remove this device con.onDisconnect().remove(); // Add this device to my connections list // this value could contain info about the device or a timestamp too con.set(true); // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP); } });