Os aplicativos do Firebase funcionam mesmo se o app perder temporariamente a conexão com a rede. Neste documento, apresentamos várias ferramentas que oferecemos para monitorar a presença e sincronizar o estado local com o estado do servidor.
Como gerenciar a presença
Nos apps em tempo real, costuma ser útil detectar quando os clientes se conectam e desconectam. Por exemplo, para marcar um usuário como off-line quando o cliente dele se desconecta.
Os clientes do Firebase Database oferecem primitivos simples que você usa para gravar no banco de dados quando um cliente se desconecta dos servidores do Firebase Database. Podemos confiar nessas atualizações para limpar os dados quando uma conexão é perdida ou quando ocorre uma falha no cliente porque elas são executadas mesmo quando o cliente se desconecta incorretamente. É possível executar todas as operações de gravação, como configuração, atualização e remoção, após uma desconexão.
Este é um exemplo simples de gravação de dados após a desconexão usando o primitivo onDisconnect
:
API modular da 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!");
API com namespace da Web
var presenceRef = firebase.database().ref("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().set("I disconnected!");
Como o onDisconnect funciona
Quando você estabelece uma operação onDisconnect()
, ela
reside no servidor do 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 app se ele for inválido. Em seguida, o servidor
monitora a conexão. Se, a qualquer momento, o tempo limite da conexão se esgotar ou
se ela for ativamente fechada pelo cliente do Realtime Database, o servidor verificará a segurança
mais uma vez para garantir que a operação ainda seja válida e depois invocará
o evento.
O app usa o retorno de chamada na operação de gravação
para garantir que o onDisconnect
foi corretamente anexado:
API modular da Web
onDisconnect(presenceRef).remove().catch((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
API com namespace da Web
presenceRef.onDisconnect().remove((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
Um evento onDisconnect
também pode ser cancelado ao chamar .cancel()
:
API modular da Web
const onDisconnectRef = onDisconnect(presenceRef); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
API com namespace da Web
var onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
Como detectar um estado de conexão
Para muitos recursos relacionados à presença, é útil que o app
saiba quando está on-line ou off-line. O Firebase Realtime Database
dispõe de um local especial em /.info/connected
, que
é atualizado sempre que o estado da conexão do cliente do Firebase Realtime Database
muda. Confira um exemplo:
API modular da 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"); } });
API com namespace da 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
é um valor booleano não sincronizado entre os clientes do Realtime Database, porque esse valor depende do estado deles. Em outras palavras, se um cliente
lê /.info/connected
como falso, isso não
garante que outro cliente também faça a mesma leitura.
Como gerenciar a latência
Carimbos de data/hora do servidor
Os servidores do Firebase Realtime Database têm um mecanismo para inserir
carimbos de data/hora gerados no servidor como dados. Combinado com o onDisconnect
, esse recurso é uma maneira fácil de armazenar o horário em que um cliente do Realtime Database se desconectou:
API modular da Web
import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database"; const db = getDatabase(); const userLastOnlineRef = ref(db, "users/joe/lastOnline"); onDisconnect(userLastOnlineRef).set(serverTimestamp());
API com namespace da Web
var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);
Defasagem horária
Embora firebase.database.ServerValue.TIMESTAMP
seja muito mais
preciso e indicado para a maioria das operações de leitura/gravação,
em algumas situações é importante estimar a defasagem horária dos clientes em
relação aos servidores do Firebase Realtime Database. Anexe
um retorno de chamada ao local /.info/serverTimeOffset
para receber o valor, em milissegundos, que os clientes do Firebase Realtime Database
adicionam ao horário local informado (tempo de época em milissegundos) para estimar o horário
do servidor. A precisão dessa diferença pode ser afetada pela latência da rede. Portanto, ela é útil principalmente para descobrir grandes discrepâncias de mais de um segundo no horário do relógio.
API modular da 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; });
API com namespace da Web
var offsetRef = firebase.database().ref(".info/serverTimeOffset"); offsetRef.on("value", (snap) => { var offset = snap.val(); var estimatedServerTimeMs = new Date().getTime() + offset; });
Exemplo de app de presença
Combine as operações de desconexão com o monitoramento de estado da conexão e os carimbos de data/hora do servidor para criar um sistema de presença do usuário. Nesse sistema, cada usuário armazena dados em um local do banco de dados para indicar se um cliente do Realtime Database está on-line. Os clientes definem esse local como verdadeiro quando ficam on-line e um carimbo de data/hora quando se desconectam. Esse carimbo de data/hora indica a última vez em que o usuário esteve on-line.
Observe que o app precisa colocar as operações de desconexão em fila antes que um usuário seja marcado como on-line, para evitar quaisquer disputas se a conexão de rede do cliente for perdida antes que os dois comandos possam ser enviados ao servidor.
Confira um sistema simples de presença do usuário:
API modular da 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()); } });
API com namespace da 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); } });