Attivazione delle funzionalità offline in JavaScript

Le applicazioni Firebase funzionano anche se la tua app perde temporaneamente la connessione di rete. In questo documento vengono presentati diversi strumenti per monitorare la presenza e sincronizzare lo stato locale con lo stato del server.

Gestione della presenza

Nelle applicazioni in tempo reale è spesso utile rilevare quando i client si connettono e si disconnettono. Ad esempio, potresti contrassegnare un utente come "offline" quando il suo client si disconnette.

I client di Firebase Database forniscono primitive semplici che puoi utilizzare per scrivere nel database quando un client si disconnette dai server di Firebase Database. Questi aggiornamenti si verificano indipendentemente dal fatto che il client si disconnetta correttamente o meno, quindi puoi utilizzarli per ripulire i dati anche se una connessione viene interrotta o un client si arresta in modo anomalo. Tutte le operazioni di scrittura, tra cui impostazione, aggiornamento e rimozione, possono essere eseguite dopo una disconnessione.

Ecco un semplice esempio di scrittura dei dati al momento della disconnessione mediante l'utilizzo della primitiva 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!");

Come funziona onDisconnect

Quando stabilisci un'operazione onDisconnect(), questa risiede sul server Firebase Realtime Database. Il server controlla la sicurezza per assicurarsi che l'utente possa eseguire l'evento di scrittura richiesto e informa la tua app se non è valido. Il server monitora quindi la connessione. Se in un determinato momento la connessione scade o viene chiusa attivamente dal client Realtime Database, il server controlla la sicurezza una seconda volta (per assicurarsi che l'operazione sia ancora valida) e poi richiama l'evento.

La tua app può utilizzare il callback sull'operazione di scrittura per assicurarsi che onDisconnect sia stato allegato correttamente:

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

Un evento onDisconnect può essere annullato anche chiamando .cancel():

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

Rilevamento dello stato della connessione

Per molte funzionalità relative alla presenza, è utile che la tua app sappia quando è online o offline. Firebase Realtime Database fornisce una posizione speciale in /.info/connected che viene aggiornata ogni volta che cambia lo stato di connessione del client Firebase Realtime Database. Ecco un esempio:

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 è un valore booleano che non viene sincronizzato tra i client Realtime Database perché dipende dallo stato del client. In altre parole, se un client legge /.info/connected come falso, non è garantito che anche un altro client lo legga come falso.

Latenza di elaborazione

Timestamp del server

I server Firebase Realtime Database forniscono un meccanismo per inserire come dati i timestamp generati sul server. Questa funzionalità, combinata con onDisconnect, offre un modo semplice per annotare in modo affidabile il momento in cui un client Realtime Database si è disconnesso:

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

Spostamento dell'orologio

Sebbene firebase.database.ServerValue.TIMESTAMP sia molto più accurato e preferibile per la maggior parte delle operazioni di lettura/scrittura, a volte può essere utile stimare lo sfasamento dell'orologio del client rispetto ai server di Firebase Realtime Database. Puoi collegare un callback alla posizione /.info/serverTimeOffset per ottenere il valore in millisecondi che i client Firebase Realtime Database aggiungono all'ora locale registrata (ora di epoch in millisecondi) per stimare l'ora del server. Tieni presente che l'accuratezza di questo offset può essere influenzata dalla latenza della rete ed è quindi utile principalmente per rilevare discrepanze elevate (> 1 secondo) nell'ora del sistema.

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

App di esempio per la presenza

Combinando le operazioni di disconnessione con il monitoraggio dello stato della connessione e i timestamp del server, puoi creare un sistema di presenza degli utenti. In questo sistema, ogni utente memorizza i dati in una posizione del database per indicare se un cliente Realtime Database è online o meno. I client impostano questa posizione su true quando si connettono e su un timestamp quando si disconnettono. Questo timestamp indica l'ultima volta che l'utente in questione era online.

Tieni presente che l'app deve mettere in coda le operazioni di disconnessione prima che un utente venga contrassegnato come online, per evitare condizioni di gara nel caso in cui la connessione di rete del client venga persa prima che entrambi i comandi possano essere inviati al server.

Ecco un semplice sistema di rilevamento della presenza dell'utente:

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