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

Ứng dụng Firebase 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ụ giúp theo dõi sự hiện diện và đồng bộ hoá trạng thái cục bộ với trạng thái máy chủ. Những công cụ này được giới thiệu trong tài liệu này.

Quản lý sự 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 sẽ rất hữu ích. Ví dụ: bạn có thể muốn đánh dấu một người dùng là "đang ngoại tuyến" khi ứng dụng của họ ngắt kết nối.

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

Dưới đây là một ví dụ đơn giản về cách ghi dữ liệu khi ngắt kết nối bằng cách sử dụng dữ liệu gốc onDisconnect:

API mô-đun 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 không gian tên trên web

var presenceRef = firebase.database().ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

Cách tính năng onNgắt kết nối hoạt động

Khi bạn thiết lập một thao tác onDisconnect(), thao tác đó sẽ có trên máy chủ Cơ sở dữ liệu theo thời gian thực của Firebase. Máy chủ sẽ 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 kết nối hoặc bị ứng dụng Cơ sở dữ liệu theo thời gian thực chủ động đóng, thì máy chủ sẽ kiểm tra bảo mật lần thứ hai (để đảm bảo thao tác vẫn hợp lệ), sau đó gọi sự kiện.

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

API mô-đun web

onDisconnect(presenceRef).remove().catch((err) => {
  if (err) {
    console.error("could not establish onDisconnect event", err);
  }
});

API không gian tên trên web

presenceRef.onDisconnect().remove((err) => {
  if (err) {
    console.error("could not establish onDisconnect event", err);
  }
});

Bạn cũng có thể huỷ một sự kiện onDisconnect bằng cách gọi .cancel():

API mô-đun web

const onDisconnectRef = onDisconnect(presenceRef);
onDisconnectRef.set("I disconnected");
// some time later when we change our minds
onDisconnectRef.cancel();

API không gian tên trên 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 sự hiện diện, ứng dụng của bạn cần biết khi nào ứng dụng trực tuyến hoặc ngoại tuyến. Cơ sở dữ liệu theo thời gian thực của Firebase cung cấp một vị trí đặc biệt tại /.info/connected và được cập nhật mỗi khi trạng thái kết nối của ứng dụng Cơ sở dữ liệu theo thời gian thực Firebase thay đổi. Bạn có thể tham khảo ví dụ sau đây:

API mô-đun 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 không gian tên trên 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 Cơ sở dữ liệu theo thời gian thực 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 giá trị false.

Độ trễ xử lý

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

Các máy chủ Cơ sở dữ liệu theo thời gian thực Firebase 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) giúp bạn dễ dàng ghi lại một cách đáng tin cậy thời điểm ứng dụng Cơ sở dữ liệu theo thời gian thực ngắt kết nối:

API mô-đun web

import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database";

const db = getDatabase();
const userLastOnlineRef = ref(db, "users/joe/lastOnline");
onDisconnect(userLastOnlineRef).set(serverTimestamp());

API không gian tên trên web

var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);

Mặt đồng hồ

Mặc dù firebase.database.ServerValue.TIMESTAMP chính xác hơn và phù hợp hơn cho hầu hết các thao tác đọc/ghi, nhưng đôi khi cũng có thể hữu ích khi ước tính độ lệch đồng hồ của ứng dụng đối với các máy chủ của Cơ sở dữ liệu theo thời gian thực Firebase. 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à các ứng dụng Cơ sở dữ liệu theo thời gian thực của Firebase thêm vào thời gian báo cáo cục bộ (thời gian bắt đầu của hệ thống 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ể chịu ảnh hưởng của độ trễ kết nối mạng. Vì vậy, tính năng này chủ yếu hữu ích khi phát hiện sự chênh lệch lớn (> 1 giây) về thời gian đồng hồ.

API mô-đun 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 không gian tên trên 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 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 theo dõi 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 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 Cơ sở dữ liệu theo thời gian thực có trực tuyến hay không. Các ứng dụng đặt vị trí này thành true khi có kết nối mạng và dấu thời gian khi ngắt kết nối. Dấu thời gian này cho biết lần gần đây nhất một người dùng cụ thể có kết nối mạng.

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

Dưới đây là một hệ thống đơn giản giúp xác định sự hiện diện của người dùng:

API mô-đun 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 không gian tên trên 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);
  }
});