在 JavaScript 中啟用離線功能

即使您的應用程式暫時失去網路連接,Firebase 應用程式也可以正常運作。我們提供了幾種用於監視狀態並將本地狀態與伺服器狀態同步的工具,這些工具將在本文檔中介紹。

管理存在

在即時應用程式中,檢測客戶端何時連接和斷開連接通常很有用。例如,您可能希望在用戶端斷開連線時將使用者標記為「離線」。

Firebase 資料庫用戶端提供簡單的原語,當用戶端與 Firebase 資料庫伺服器斷開連線時,您可以使用這些原語寫入資料庫。無論客戶端是否完全斷開連接,這些更新都會發生,因此即使連接中斷或客戶端崩潰,您也可以依靠它們來清理資料。所有寫入操作,包括設定、更新和刪除,都可以在斷開連接時執行。

以下是使用onDisconnect原語在斷開連線時寫入資料的簡單範例:

Web modular API

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 namespaced API

var presenceRef = firebase.database().ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

onDisconnect 的工作原理

當您建立onDisconnect()操作時,該操作位於 Firebase 即時資料庫伺服器上。伺服器檢查安全性以確保使用者可以執行請求的寫入事件,並通知您的應用程式(如果該事件無效)。然後伺服器監視連接。如果連線在任何時候逾時,或被即時資料庫用戶端主動關閉,伺服器會再次檢查安全性(以確保操作仍然有效),然後呼叫該事件。

您的應用程式可以在寫入操作上使用回調來確保onDisconnect已正確附加:

Web modular API

onDisconnect(presenceRef).remove().catch((err) => {
  if (err) {
    console.error("could not establish onDisconnect event", err);
  }
});

Web namespaced API

presenceRef.onDisconnect().remove((err) => {
  if (err) {
    console.error("could not establish onDisconnect event", err);
  }
});

onDisconnect事件也可以透過呼叫.cancel()取消:

Web modular API

const onDisconnectRef = onDisconnect(presenceRef);
onDisconnectRef.set("I disconnected");
// some time later when we change our minds
onDisconnectRef.cancel();

Web namespaced API

var onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// some time later when we change our minds
onDisconnectRef.cancel();

檢測連線狀態

對於許多與狀態相關的功能,您的應用程式了解其何時在線或離線非常有用。 Firebase 即時資料庫在/.info/connected中提供了一個特殊位置,每次 Firebase 即時資料庫用戶端的連線狀態變更時,該位置都會更新。這是一個例子:

Web modular API

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 namespaced API

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 modular API

import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database";

const db = getDatabase();
const userLastOnlineRef = ref(db, "users/joe/lastOnline");
onDisconnect(userLastOnlineRef).set(serverTimestamp());

Web namespaced API

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 modular API

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 namespaced API

var offsetRef = firebase.database().ref(".info/serverTimeOffset");
offsetRef.on("value", (snap) => {
  var offset = snap.val();
  var estimatedServerTimeMs = new Date().getTime() + offset;
});

狀態應用程式範例

透過將斷開連接操作與連線狀態監控和伺服器時間戳記結合,您可以建立使用者存在系統。在此系統中,每個使用者將資料儲存在資料庫位置以指示即時資料庫用戶端是否線上。客戶端在上線時將此位置設為 true,在斷開連接時設定時間戳記。此時間戳表示給定使用者上次在線的時間。

請注意,您的應用程式應在將使用者標記為線上之前對斷開連接操作進行排隊,以避免在兩個命令發送到伺服器之前用戶端網路連線遺失的情況下出現任何競爭條件。

這是一個簡單的使用者存在系統:

Web modular API

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 namespaced API

// 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);
  }
});