Bật các chức năng ngoại tuyến trong JavaScript

Các ứng dụng Firebase vẫn hoạt động ngay cả khi ứng dụng của bạn tạm thời mất kết nối mạng. Chúng tôi cung cấp một số công cụ để theo dõi trạng thái hiện diện và đồng bộ hoá trạng thái cục bộ với trạng thái máy chủ. Các công cụ này được giới thiệu trong tài liệu này.

Quản lý trạng thái hiện diện

Trong các ứng dụng theo thời gian thực, việc phát hiện thời điểm ứng dụng kết nối và ngắt kết nối thường rất hữu ích. Ví dụ: bạn có thể muốn đánh dấu người dùng là "ngoại tuyến" khi ứng dụng của họ ngắt kết nối.

Ứng dụng Firebase Database cung cấp các thành phần cơ bản đơn giản mà bạn có thể dùng để ghi vào cơ sở dữ liệu khi một ứng dụng ngắt kết nối khỏi máy chủ Firebase Database máy chủ. Các bản cập nhật này xảy ra bất kể ứng dụng có ngắt kết nối một cách sạch sẽ hay không, vì vậy, bạn có thể dựa vào các bản cập nhật này để dọn dẹp dữ liệu ngay cả khi kết nối bị gián đoạn hoặc ứng dụng gặp sự cố. Bạn có thể thực hiện tất cả các thao tác ghi, bao gồm cả việc đặt, cập nhật và xoá khi ngắt kết nối.

Sau đây là một ví dụ đơn giản về việc ghi dữ liệu khi ngắt kết nối bằng cách sử dụng onDisconnect thành phần cơ bản:

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

Cách hoạt động của onDisconnect

Khi bạn thiết lập một thao tác onDisconnect(), thao tác đó tồn tại trên máy chủ Firebase Realtime Database. Máy chủ kiểm tra tính bảo mật để đảm bảo người dùng có thể thực hiện sự kiện ghi được yêu cầu và thông báo cho ứng dụng của bạn nếu sự kiện đó không hợp lệ. Sau đó, máy chủ sẽ giám sát kết nối. Nếu tại bất kỳ thời điểm nào, kết nối hết thời gian chờ hoặc bị ứng dụng Realtime Database đóng chủ động, thì máy chủ sẽ kiểm tra tính bảo mật lần thứ hai (để đảm bảo thao tác vẫn hợp lệ) rồi gọi sự kiện.

Ứng dụng của bạn có thể sử dụng lệnh gọi lại trên thao tác ghi để đảm bảo onDisconnect được đính kèm chính xác:

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

Bạn cũng có thể huỷ sự kiện onDisconnect bằng cách gọi .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();

Phát hiện trạng thái kết nối

Đối với nhiều tính năng liên quan đến trạng thái hiện diện, ứng dụng của bạn cần biết thời điểm ứng dụng đang trực tuyến hoặc ngoại tuyến. Firebase Realtime Database cung cấp một vị trí đặc biệt tại /.info/connected được cập nhật mỗi khi trạng thái kết nối của ứng dụng Firebase Realtime Database thay đổi. Sau đây là một ví dụ:

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 là một giá trị boolean không được đồng bộ hoá giữa các ứng dụng Realtime Database vì giá trị này phụ thuộc vào trạng thái của ứng dụng. Nói cách khác, nếu một ứng dụng đọc /.info/connected là false, thì điều này không đảm bảo rằng một ứng dụng riêng biệt cũng sẽ đọc là false.

Xử lý độ trễ

Dấu thời gian của máy chủ

Máy chủ Firebase Realtime Database cung cấp một cơ chế để chèn dấu thời gian được tạo trên máy chủ dưới dạng dữ liệu. Tính năng này, kết hợp với onDisconnect, cung cấp một cách dễ dàng để ghi lại thời điểm mà ứng dụng Realtime Database ngắt kết nối một cách đáng tin cậy:

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

Độ lệch đồng hồ

Mặc dù firebase.database.ServerValue.TIMESTAMP chính xác hơn nhiều và được ưu tiên cho hầu hết các thao tác đọc/ghi, nhưng đôi khi bạn có thể ước tính độ lệch đồng hồ của ứng dụng so với máy chủ của Firebase Realtime Database. Bạn có thể đính kèm lệnh gọi lại vào vị trí /.info/serverTimeOffset để lấy giá trị (tính bằng mili giây) mà Firebase Realtime Database ứng dụng thêm vào thời gian được báo cáo cục bộ (thời gian epoch tính bằng mili giây) để ước tính thời gian của máy chủ. Xin lưu ý rằng độ chính xác của độ lệch này có thể bị ảnh hưởng bởi độ trễ mạng, vì vậy, độ lệch này chủ yếu hữu ích cho việc phát hiện các sai lệch lớn (> 1 giây) về thời gian đồng hồ.

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

Ứng dụng mẫu về trạng thái hiện diện

Bằng cách kết hợp các thao tác ngắt kết nối với tính năng giám sát trạng thái kết nối và dấu thời gian của máy chủ, bạn có thể xây dựng một hệ thống trạng thái hiện diện của người dùng. Trong hệ thống này, mỗi người dùng lưu trữ dữ liệu tại một vị trí cơ sở dữ liệu để cho biết ứng dụng Realtime Database có đang trực tuyến hay không. Ứng dụng đặt vị trí này thành true khi trực tuyến và dấu thời gian khi ngắt kết nối. Dấu thời gian này cho biết thời điểm gần đây nhất mà người dùng được chỉ định trực tuyến.

Xin lưu ý rằng ứng dụng của bạn nên xếp hàng các thao tác ngắt kết nối trước khi người dùng được đánh dấu là trực tuyến, để tránh mọi tình trạng xung đột trong trường hợp kết nối mạng của ứng dụng bị mất trước khi cả hai lệnh có thể được gửi đến máy chủ.

Sau đây là một hệ thống trạng thái hiện diện đơn giản của người dùng:

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