Habilitando recursos offline em JavaScript

Os aplicativos do Firebase funcionam mesmo se o aplicativo perder temporariamente a conexão de rede. Fornecemos diversas ferramentas para monitorar a presença e sincronizar o estado local com o estado do servidor, que são apresentadas neste documento.

Gerenciando Presença

Em aplicações em tempo real, muitas vezes é útil detectar quando os clientes se conectam e desconectam. Por exemplo, você pode querer marcar um usuário como 'offline' quando seu cliente se desconectar.

Os clientes do Firebase Database fornecem primitivos simples que você pode usar para gravar no banco de dados quando um cliente se desconecta dos servidores do Firebase Database. Essas atualizações ocorrem independentemente de o cliente se desconectar corretamente ou não, portanto, você pode contar com elas para limpar os dados mesmo se uma conexão for interrompida ou um cliente travar. Todas as operações de gravação, incluindo configuração, atualização e remoção, podem ser executadas após uma desconexão.

Aqui está um exemplo simples de gravação de dados após a desconexão usando a primitiva 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!");

Como funciona o onDisconnect

Quando você estabelece uma operação onDisconnect() , a operação reside no servidor Firebase Realtime Database. O servidor verifica a segurança para garantir que o usuário possa executar o evento de gravação solicitado e informa ao seu aplicativo se ele for inválido. O servidor então monitora a conexão. Se em algum momento a conexão atingir o tempo limite ou for fechada ativamente pelo cliente do Realtime Database, o servidor verificará a segurança uma segunda vez (para garantir que a operação ainda seja válida) e, em seguida, invocará o evento.

Seu aplicativo pode usar o retorno de chamada na operação de gravação para garantir que onDisconnect foi anexado corretamente:

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

Um evento onDisconnect também pode ser cancelado chamando .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();

Detectando o estado da conexão

Para muitos recursos relacionados à presença, é útil que seu aplicativo saiba quando está online ou offline. O Firebase Realtime Database fornece um local especial em /.info/connected que é atualizado sempre que o estado da conexão do cliente do Firebase Realtime Database muda. Aqui está um exemplo:

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 é um valor booleano que não é sincronizado entre clientes do Realtime Database porque o valor depende do estado do cliente. Em outras palavras, se um cliente ler /.info/connected como falso, isso não garante que um cliente separado também lerá falso.

Lidando com latência

Carimbos de data e hora do servidor

Os servidores do Firebase Realtime Database fornecem um mecanismo para inserir carimbos de data/hora gerados no servidor como dados. Esse recurso, combinado com onDisconnect , fornece uma maneira fácil de anotar com segurança o momento em que um cliente do Realtime Database se desconectou:

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

Inclinação do relógio

Embora firebase.database.ServerValue.TIMESTAMP seja muito mais preciso e preferível para a maioria das operações de leitura/gravação, ocasionalmente pode ser útil estimar a distorção do relógio do cliente em relação aos servidores do Firebase Realtime Database. Você pode anexar um retorno de chamada ao local /.info/serverTimeOffset para obter o valor, em milissegundos, que os clientes do Firebase Realtime Database adicionam ao horário local relatado (tempo de época em milissegundos) para estimar o horário do servidor. Observe que a precisão desse deslocamento pode ser afetada pela latência da rede e, portanto, é útil principalmente para descobrir grandes discrepâncias (> 1 segundo) no tempo do relógio.

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

Exemplo de aplicativo de presença

Ao combinar operações de desconexão com monitoramento do estado da conexão e carimbos de data/hora do servidor, você pode construir um sistema de presença do usuário. Nesse sistema, cada usuário armazena dados em um local de banco de dados para indicar se um cliente do Realtime Database está on-line ou não. Os clientes definem esse local como verdadeiro quando ficam on-line e um carimbo de data/hora quando se desconectam. Este carimbo de data/hora indica a última vez que o usuário esteve online.

Observe que seu aplicativo deve enfileirar as operações de desconexão antes que um usuário seja marcado como online, para evitar quaisquer condições de corrida caso a conexão de rede do cliente seja perdida antes que ambos os comandos possam ser enviados ao servidor.

Aqui está um sistema simples de presença do usuário:

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