Włączanie funkcji offline w JavaScript

Aplikacje Firebase działają nawet wtedy, gdy tymczasowo stracą połączenie sieciowe. Udostępniamy kilka narzędzi do monitorowania obecności i synchronizacji stanu lokalnego ze stanem serwera, które zostały przedstawione w tym dokumencie.

Zarządzanie obecnością

W aplikacjach czasu rzeczywistego często przydatne jest wykrywanie, kiedy klienci łączą się i rozłączają. Możesz na przykład oznaczyć użytkownika jako „offline”, gdy jego klient się rozłączy.

Klienci Firebase Database udostępniają proste elementy podstawowe, których można użyć do zapisu w bazie danych, gdy klient rozłączy się z serwerami Firebase Database. Aktualizacje te pojawiają się niezależnie od tego, czy klient rozłączy się prawidłowo, czy nie, więc można na nich polegać, jeśli chodzi o czyszczenie danych nawet w przypadku zerwania połączenia lub awarii klienta. Wszystkie operacje zapisu, w tym ustawianie, aktualizowanie i usuwanie, można wykonać po rozłączeniu.

Oto prosty przykład zapisu danych po rozłączeniu przy użyciu operacji podstawowej 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!");

Jak działa onDisconnect

Po ustanowieniu operacji onDisconnect() operacja ta jest przechowywana na serwerze bazy danych Firebase Realtime Database. Serwer sprawdza bezpieczeństwo, aby upewnić się, że użytkownik może wykonać żądane zdarzenie zapisu, i informuje aplikację, jeśli jest ona nieprawidłowa. Następnie serwer monitoruje połączenie. Jeśli w dowolnym momencie połączenie przekroczy limit czasu lub zostanie aktywnie zamknięte przez klienta Realtime Database, serwer po raz drugi sprawdza bezpieczeństwo (aby upewnić się, że operacja jest nadal ważna), a następnie wywołuje zdarzenie.

Twoja aplikacja może użyć wywołania zwrotnego podczas operacji zapisu, aby upewnić się, że onDisconnect został poprawnie podłączony:

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

Zdarzenie onDisconnect można również anulować, wywołując funkcję .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();

Wykrywanie stanu połączenia

W przypadku wielu funkcji związanych z obecnością przydatne jest, aby aplikacja wiedziała, kiedy jest online, a kiedy offline. Baza danych Firebase Realtime Database udostępnia specjalną lokalizację pod /.info/connected , która jest aktualizowana za każdym razem, gdy zmienia się stan połączenia klienta Firebase Realtime Database. Oto przykład:

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 to wartość logiczna, która nie jest synchronizowana pomiędzy klientami bazy danych czasu rzeczywistego, ponieważ wartość zależy od stanu klienta. Innymi słowy, jeśli jeden klient odczyta /.info/connected jako false, nie ma gwarancji, że inny klient również odczyta false.

Obsługa opóźnień

Znaczniki czasu serwera

Serwery Firebase Realtime Database udostępniają mechanizm wstawiania jako danych znaczników czasu wygenerowanych na serwerze. Ta funkcja w połączeniu z onDisconnect umożliwia łatwe i niezawodne zapisywanie czasu rozłączenia klienta bazy danych czasu rzeczywistego:

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

Przekrzywienie zegara

Chociaż firebase.database.ServerValue.TIMESTAMP jest znacznie dokładniejszy i preferowany w przypadku większości operacji odczytu/zapisu, czasami przydatne może być oszacowanie odchylenia zegara klienta w odniesieniu do serwerów bazy danych Firebase Realtime Database. Możesz dołączyć wywołanie zwrotne do lokalizacji /.info/serverTimeOffset , aby uzyskać wartość w milisekundach, którą klienci Firebase Realtime Database dodają do lokalnego raportowanego czasu (czas epoki w milisekundach) w celu oszacowania czasu serwera. Należy pamiętać, że na dokładność tego przesunięcia może wpływać opóźnienie sieci, dlatego jest ono przydatne przede wszystkim do wykrywania dużych (> 1 sekundy) rozbieżności w czasie zegara.

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

Przykładowa aplikacja obecności

Łącząc operacje rozłączania z monitorowaniem stanu połączenia i znacznikami czasu serwera, możesz zbudować system obecności użytkowników. W tym systemie każdy użytkownik przechowuje dane w lokalizacji bazy danych, aby wskazać, czy klient bazy danych czasu rzeczywistego jest online. Klienci ustawiają tę lokalizację na wartość true, gdy łączą się z Internetem, oraz sygnaturę czasową, gdy się rozłączają. Ten znacznik czasu wskazuje, kiedy dany użytkownik był online po raz ostatni.

Pamiętaj, że aplikacja powinna kolejkować operacje rozłączenia, zanim użytkownik zostanie oznaczony w trybie online, aby uniknąć sytuacji wyścigowych w przypadku utraty połączenia sieciowego klienta przed wysłaniem obu poleceń na serwer.

Oto prosty system obecności użytkownika:

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