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 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
:
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!");
Como o onDisconnect funciona
Quando você estabelece uma operação onDisconnect()
, ela
fica 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
ela for ativamente encerrada 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:
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); } });
Um evento onDisconnect
também pode ser cancelado ao chamar .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();
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
fornece um local especial em /.info/connected
, que
é atualizado sempre que o estado da conexão do cliente do Firebase Realtime Database
muda. Veja um exemplo:
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
é 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 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 e segura de armazenar o horário em que um
cliente do Realtime Database se desconectou:
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);
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 callback 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.
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; });
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 o 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.
Veja um sistema simples de presença do usuário:
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); } });