การเปิดใช้งานความสามารถออฟไลน์ใน JavaScript

แอปพลิเคชัน Firebase ทำงานได้แม้ว่าแอปของคุณจะขาดการเชื่อมต่อเครือข่ายชั่วคราว เรามีเครื่องมือหลายอย่างสำหรับการตรวจสอบสถานะและการซิงโครไนซ์สถานะท้องถิ่นกับสถานะเซิร์ฟเวอร์ ตามที่แนะนำในเอกสารนี้

การจัดการสถานะ

ในแอปพลิเคชันแบบเรียลไทม์ มักจะมีประโยชน์ในการตรวจหาเมื่อไคลเอ็นต์เชื่อมต่อและยกเลิกการเชื่อมต่อ ตัวอย่างเช่น คุณอาจต้องการทำเครื่องหมายผู้ใช้เป็น 'ออฟไลน์' เมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อ

ไคลเอนต์ฐานข้อมูล Firebase มีพื้นฐานง่ายๆ ที่คุณสามารถใช้เพื่อเขียนไปยังฐานข้อมูลเมื่อไคลเอนต์ยกเลิกการเชื่อมต่อจากเซิร์ฟเวอร์ฐานข้อมูล Firebase การอัปเดตเหล่านี้เกิดขึ้นไม่ว่าไคลเอ็นต์จะยกเลิกการเชื่อมต่ออย่างสมบูรณ์หรือไม่ ดังนั้นคุณจึงสามารถพึ่งพาการอัปเดตเหล่านี้เพื่อล้างข้อมูลได้ แม้ว่าการเชื่อมต่อจะหลุดหรือไคลเอ็นต์ล้มเหลวก็ตาม การดำเนินการเขียนทั้งหมด รวมถึงการตั้งค่า การอัพเดต และการลบ สามารถดำเนินการได้เมื่อมีการยกเลิกการเชื่อมต่อ

นี่คือตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อขาดการเชื่อมต่อโดยใช้ onDisconnect ดั้งเดิม:

Web modular API

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 namespaced API

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

onDisconnect ทำงานอย่างไร

เมื่อคุณสร้างการดำเนินการ onDisconnect() การดำเนินการนั้นจะคงอยู่บนเซิร์ฟเวอร์ Firebase Realtime Database เซิร์ฟเวอร์ตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้สามารถดำเนินการเขียนกิจกรรมที่ร้องขอได้ และแจ้งให้แอปของคุณทราบหากไม่ถูกต้อง เซิร์ฟเวอร์จะตรวจสอบการเชื่อมต่อ หาก ณ จุดใดการเชื่อมต่อหมดเวลาหรือปิดโดยไคลเอ็นต์ฐานข้อมูลเรียลไทม์ เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเป็นครั้งที่สอง (เพื่อให้แน่ใจว่าการดำเนินการยังคงใช้งานได้) จากนั้นจึงเรียกใช้เหตุการณ์

แอปของคุณสามารถใช้การโทรกลับในการดำเนินการเขียนเพื่อให้แน่ใจว่าได้แนบ onDisconnect อย่างถูกต้อง:

Web modular API

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

Web namespaced API

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

เหตุการณ์ onDisconnect สามารถยกเลิกได้โดยการเรียก .cancel() :

Web modular API

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

Web namespaced API

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

การตรวจจับสถานะการเชื่อมต่อ

สำหรับฟีเจอร์ที่เกี่ยวข้องกับการแสดงตนหลายอย่าง จะมีประโยชน์สำหรับแอปของคุณที่จะรู้ว่าแอปออนไลน์หรือออฟไลน์เมื่อใด ฐานข้อมูลเรียลไทม์ Firebase จัดเตรียมตำแหน่งพิเศษที่ /.info/connected ซึ่งได้รับการอัปเดตทุกครั้งที่สถานะการเชื่อมต่อของไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase เปลี่ยนแปลง นี่คือตัวอย่าง:

Web modular API

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 namespaced API

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 เป็นค่าบูลีนที่ไม่ได้รับการซิงโครไนซ์ระหว่างไคลเอนต์ฐานข้อมูลเรียลไทม์ เนื่องจากค่าจะขึ้นอยู่กับสถานะของไคลเอนต์ กล่าวอีกนัยหนึ่ง หากไคลเอนต์รายหนึ่งอ่าน /.info/connected เป็นเท็จ นี่ไม่รับประกันว่าไคลเอนต์ที่แยกต่างหากจะอ่านเท็จเช่นกัน

การจัดการกับความหน่วงแฝง

การประทับเวลาของเซิร์ฟเวอร์

เซิร์ฟเวอร์ฐานข้อมูล Firebase Realtime มีกลไกในการแทรกการประทับเวลาที่สร้างบนเซิร์ฟเวอร์เป็นข้อมูล คุณลักษณะนี้เมื่อรวมกับ onDisconnect จะให้วิธีง่ายๆ ในการจดบันทึกเวลาที่ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ตัดการเชื่อมต่ออย่างน่าเชื่อถือ:

Web modular API

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

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

Web namespaced API

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 เพิ่มลงในเวลาที่รายงานในเครื่อง (เวลายุคในหน่วยมิลลิวินาที) เพื่อประมาณเวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของการชดเชยนี้อาจได้รับผลกระทบจากเวลาแฝงของเครือข่าย และมีประโยชน์ในการค้นหาความคลาดเคลื่อนขนาดใหญ่ (> 1 วินาที) ในเวลาสัญญาณนาฬิกาเป็นหลัก

Web modular API

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 namespaced API

var offsetRef = firebase.database().ref(".info/serverTimeOffset");
offsetRef.on("value", (snap) => {
  var offset = snap.val();
  var estimatedServerTimeMs = new Date().getTime() + offset;
});

แอปการแสดงตนตัวอย่าง

ด้วยการรวมการดำเนินการยกเลิกการเชื่อมต่อเข้ากับการตรวจสอบสถานะการเชื่อมต่อและการประทับเวลาของเซิร์ฟเวอร์ คุณสามารถสร้างระบบการแสดงตนของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละคนจัดเก็บข้อมูลไว้ที่ตำแหน่งฐานข้อมูลเพื่อระบุว่าไคลเอ็นต์ฐานข้อมูลเรียลไทม์ออนไลน์อยู่หรือไม่ ไคลเอนต์ตั้งค่าตำแหน่งนี้เป็นจริงเมื่อออนไลน์และประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้ระบุครั้งสุดท้ายที่ผู้ใช้รายนั้นออนไลน์

โปรดทราบว่าแอปของคุณควรจัดคิวการดำเนินการตัดการเชื่อมต่อก่อนที่ผู้ใช้จะถูกทำเครื่องหมายออนไลน์ เพื่อหลีกเลี่ยงสภาวะการแข่งขันในกรณีที่การเชื่อมต่อเครือข่ายของลูกค้าขาดหายไปก่อนที่ทั้งสองคำสั่งจะถูกส่งไปยังเซิร์ฟเวอร์

นี่คือระบบการแสดงตนของผู้ใช้อย่างง่าย:

Web modular API

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 namespaced API

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