تمكين إمكانيات وضع عدم الاتصال في 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);
  }
});

يمكن أيضًا إلغاء حدث onDisconnect من خلال استدعاء .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();

رصد حالة الاتصال

بالنسبة إلى العديد من الميزات المرتبطة بحالة الاتصال، من المفيد أن يعرف تطبيقك ما إذا كان متصلاً بالإنترنت أو غير متصل. Firebase Realtime Database توفر موقعًا خاصًا على /.info/connected والذي يتم تحديثه في كل مرة تتغير فيها حالة اتصال العميل Firebase Realtime Database. في ما يلي مثال:

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

التعامل مع وقت الاستجابة

الطوابع الزمنية للخادم

توفر خوادم 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 إلى الوقت المحلي المُبلغ عنه (الوقت المرجعي بالملّي ثانية) لتقدير وقت الخادم. يُرجى العِلم أنّ دقة هذا الإزاحة يمكن أن تتأثر بوقت استجابة الشبكة، لذا من المفيد بشكل أساسي رصد الاختلافات الكبيرة (> ثانية واحدة) في وقت الساعة.

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 متصلاً بالإنترنت أم لا. تضبط أجهزة العملاء هذا الموقع على "صحيح" عندما تتصل بالإنترنت، وتضبط طابعًا زمنيًا عند انقطاع الاتصال. يشير هذا الطابع الزمني إلى آخر مرة كان فيها المستخدم المحدّد متصلاً بالإنترنت.

يُرجى العِلم أنّ تطبيقك يجب أن يضع عمليات انقطاع الاتصال في قائمة الانتظار قبل وضع علامة "متصل" على المستخدم، وذلك لتجنُّب أي حالات تعارض في حال فقدان اتصال شبكة جهاز العميل قبل إرسال كلا الأمرين إلى الخادم.

في ما يلي نظام بسيط لتتبُّع حالة اتصال المستخدم:

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