即使應用程式暫時失去網路連線,Firebase 應用程式仍可正常運作。我們提供多種工具,可用於監控狀態,並將本機狀態與伺服器狀態同步,這些工具會在本文件中介紹。
管理在家狀態
在即時應用程式中,偵測用戶端連線和斷線的時間通常很有用。舉例來說,您可能會在使用者的用戶端中斷連線時,將該使用者標示為「離線」。
Firebase 資料庫用戶端提供簡單的原始碼,可在用戶端與 Firebase 資料庫伺服器中斷連時,用於寫入資料庫。無論用戶端是否已順利中斷連線,這些更新都會發生,因此即使連線中斷或用戶端異常終止,您仍可依靠這些更新來清理資料。所有寫入作業 (包括設定、更新和移除) 都可以在斷線時執行。
以下是使用 onDisconnect
原始碼在連線中斷時寫入資料的簡單範例:
Web
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
var presenceRef = firebase.database().ref("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().set("I disconnected!");
onDisconnect 的運作方式
建立 onDisconnect()
作業時,作業會位於 Firebase Realtime Database 伺服器上。伺服器會檢查安全性,確保使用者可以執行要求的寫入事件,並在事件無效時通知您的應用程式。接著,伺服器會監控連線。如果連線在任何時間點逾時,或遭 Realtime Database 用戶端主動關閉,伺服器會再次檢查安全性 (以確保作業仍有效),然後叫用事件。
應用程式可以使用寫入作業的回呼,確保 onDisconnect
已正確附加:
Web
onDisconnect(presenceRef).remove().catch((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
Web
presenceRef.onDisconnect().remove((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
您也可以呼叫 .cancel()
來取消 onDisconnect
事件:
Web
const onDisconnectRef = onDisconnect(presenceRef); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
Web
var onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
偵測連線狀態
對於許多與狀態相關的功能,讓應用程式知道何時上線或離線很有幫助。Firebase Realtime Database 會在 /.info/connected
提供特殊位置,並在 Firebase Realtime Database 用戶端的連線狀態每次變更時更新。範例如下:
Web
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
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
是布林值,不會在 Realtime Database 用戶端之間同步,因為該值取決於用戶端的狀態。換句話說,如果一個用戶端讀取 /.info/connected
為 false,並不保證另一個用戶端也會讀取為 false。
處理延遲時間
伺服器時間戳記
Firebase Realtime Database 伺服器提供一種機制,可將伺服器產生的時間戳記插入為資料。這項功能與 onDisconnect
搭配使用,可讓您輕鬆記錄 Realtime Database 用戶端中斷連線的時間:
Web
import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database"; const db = getDatabase(); const userLastOnlineRef = ref(db, "users/joe/lastOnline"); onDisconnect(userLastOnlineRef).set(serverTimestamp());
Web
var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);
時鐘偏移
雖然 firebase.database.ServerValue.TIMESTAMP
的準確度較高,且適合用於大多數的讀/寫作業,但有時還是有必要估算用戶端與 Firebase Realtime Database 伺服器之間的時鐘偏差。您可以將回呼附加至位置 /.info/serverTimeOffset
,以便取得 Firebase Realtime Database 用戶端在本機回報時間 (紀元時間,以毫秒為單位) 中加上的值,用來估算伺服器時間。請注意,這個偏移值的準確度可能會受到網路延遲影響,因此主要用於發現時鐘時間的差異 (超過 1 秒)。
Web
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
var offsetRef = firebase.database().ref(".info/serverTimeOffset"); offsetRef.on("value", (snap) => { var offset = snap.val(); var estimatedServerTimeMs = new Date().getTime() + offset; });
狀態應用程式範例
您可以結合中斷操作與連線狀態監控和伺服器時間戳記,建立使用者狀態系統。在這個系統中,每位使用者都會在資料庫位置儲存資料,用於指出 Realtime Database 用戶是否連線。用戶端在連線時將這個位置設為 true,並在斷線時設定時間戳記。這個時間戳記表示指定使用者上次上線的時間。
請注意,應用程式應在使用者標示為線上之前,將斷線作業排入佇列,以免在傳送兩個指令至伺服器之前,失去用戶端的網路連線,進而導致競爭狀態。
以下是簡單的使用者狀態系統:
Web
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
// 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); } });