JavaScript에서 오프라인 기능 사용 설정

Firebase 애플리케이션은 일시적으로 네트워크 연결이 끊겨도 정상적으로 작동합니다. 이 문서에서는 접속 상태를 모니터링하고 로컬 상태와 서버 상태를 동기화하는 여러 가지 도구를 소개합니다.

접속 상태 관리

실시간 애플리케이션에서는 클라이언트가 연결되거나 연결이 해제되는 시점을 감지하면 유용한 경우가 많습니다. 예를 들어 클라이언트의 연결이 끊기면 사용자를 '오프라인'으로 표시할 수 있습니다.

Firebase 데이터베이스 클라이언트는 Firebase 데이터베이스 서버와 연결이 끊길 때 데이터베이스에 데이터를 쓰는 데 사용할 수 있는 간단한 기본 요소를 제공합니다. 이러한 업데이트는 클라이언트 연결이 정상적으로 해제되었는지 여부와 관계없이 발생하므로 연결이 갑자기 끊기거나 클라이언트가 다운되어도 이 업데이트를 사용하여 데이터를 정리할 수 있습니다. 연결이 끊겨도 설정, 업데이트, 삭제 등의 모든 쓰기 작업을 수행할 수 있습니다.

다음은 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!");

onDisconnect의 작동 방식

onDisconnect() 작업을 설정하면 Firebase Realtime Database 서버에 작업이 상주하게 됩니다. 서버는 보안 검사를 실행하여 요청된 쓰기 이벤트를 사용자가 수행할 수 있는지 확인하고 문제가 있으면 앱에 알립니다. 이제 서버는 연결 상태를 모니터링하다가 연결이 타임아웃되거나 Realtime Database 클라이언트에 의해 종료되면 서버는 보안 검사를 다시 실행하여 작업이 계속 유효한지 확인한 후 이벤트를 호출합니다.

앱에서는 쓰기 작업의 콜백을 사용하여 onDisconnect가 정상적으로 연결되었는지 확인할 수 있습니다.

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

.cancel()을 호출하여 onDisconnect 이벤트를 취소할 수도 있습니다.

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

연결 상태 감지

접속 상태 관련 기능에서는 앱의 온라인 또는 오프라인 전환 시점을 확인하면 유용한 경우가 많습니다. Firebase Realtime DatabaseFirebase Realtime Database 클라이언트의 연결 상태가 변경될 때마다 업데이트되는 특수 위치인 /.info/connected를 제공합니다. 예를 들면 다음과 같습니다.

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는 불리언 값이며, 이 값은 클라이언트의 상태에 좌우되므로 Realtime Database 클라이언트 간에 동기화되지 않습니다. 즉, 클라이언트 중 하나에서 /.info/connected를 읽은 결과가 false이더라도 다른 클라이언트에서는 다른 값으로 읽힐 수 있습니다.

지연 시간 처리

서버 타임스탬프

Firebase Realtime Database 서버는 서버에서 생성한 타임스탬프를 데이터로 삽입하는 메커니즘을 제공합니다. 이 기능과 onDisconnect를 함께 사용하면 Realtime Database 클라이언트의 연결이 끊긴 시간을 쉽고 정확하게 기록할 수 있습니다.

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

시계 보정값

대부분의 읽기/쓰기 작업에서는 firebase.database.ServerValue.TIMESTAMP가 훨씬 더 정확하고 바람직하지만, Firebase Realtime Database 서버를 기준으로 클라이언트 시계 보정값을 측정하면 유용한 경우도 있습니다. 이 값을 밀리초 단위로 가져오려면 /.info/serverTimeOffset 위치에 콜백을 연결합니다. Firebase Realtime Database 클라이언트는 이 값을 로컬 보고 시간(밀리초 단위 에포크 시간)에 더하여 서버 시간을 추정합니다. 이 오프셋의 정확성은 네트워킹 지연 시간의 영향을 받을 수 있으므로 1초 이상의 상당한 시간 오차를 파악하는 데 주로 사용됩니다.

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

샘플 접속 상태 앱

연결 해제 작업과 연결 상태 모니터링 및 서버 타임스탬프를 결합하여 사용자 접속 상태 시스템을 구축할 수 있습니다. 이 시스템에서 각 사용자는 특정 데이터베이스 위치에 데이터를 저장하여 Realtime Database 클라이언트가 온라인 상태인지 여부를 알립니다. 클라이언트는 이 위치를 온라인으로 전환될 때 true로, 연결이 끊길 때 타임스탬프로 설정합니다. 이 타임스탬프는 사용자가 마지막으로 온라인 상태였던 시간을 나타냅니다.

사용자가 온라인으로 표시되기 전에 연결 해제 작업을 큐에 두어야 합니다. 그래야 두 명령이 모두 서버로 전송되기 전에 클라이언트의 네트워크 연결이 끊겨도 경합 상태가 발생하지 않습니다.

다음은 간단한 사용자 접속 상태 시스템입니다.

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