تمكين إمكانيات وضع عدم الاتصال في JavaScript

تعمل تطبيقات Firebase حتى إذا انقطع اتصال شبكة تطبيقك مؤقتًا. نوفّر عدة أدوات لرصد حالة الاتصال ومزامنة الحالة المحلية مع حالة الخادم، ونقدّمها في هذا المستند.

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

في التطبيقات في الوقت الفعلي، من المفيد غالبًا رصد حالات اتصال العملاء وانقطاعهم. على سبيل المثال، قد تريد وضع علامة "غير متصل" على مستخدم عندما ينقطع اتصال عميله.

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

في ما يلي مثال بسيط على كتابة البيانات عند انقطاع الاتصال باستخدام العنصر الأساسي 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);
  }
});