JavaScript でのオフライン機能の有効化

Firebase アプリケーションは、ご使用のアプリのネットワーク接続が一時的に切断された場合でも機能します。Firebase では、プレゼンスをモニタリングしたり、ローカルの状態をサーバーの状態と同期するためのツールをいくつか用意しています。このドキュメントでは、これらのツールについて説明します。

プレゼンスの管理

リアルタイム アプリケーションでは、クライアントが接続したときや接続を解除したときを検出できると役に立つことがよくあります。たとえば、クライアントが接続を解除したら、そのユーザーを「offline」としてマークするなどです。

Firebase Database クライアントでは、クライアントが Firebase Database サーバーから接続を解除されたときにデータベースにデータを書き込むことができるようにする、シンプルなプリミティブを提供しています。このような更新はクライアントがクリーンに接続を解除したかどうかに関係なく発生するので、接続が削除された場合やクライアントがクラッシュした場合でも、更新でデータを確実にクリーンアップすることができます。接続が解除された時点で、設定、更新、削除など、あらゆる書き込み操作を実行できます。

onDisconnect プリミティブを使用して、接続の解除と同時にデータを書き込むシンプルな例を次に示します。

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 が正しくアタッチされたことを確認できます。

presenceRef.onDisconnect().remove(function(err) {
  if (err) {
    console.error('could not establish onDisconnect event', err);
  }
});

.cancel() を呼び出して onDisconnect イベントをキャンセルすることも可能です。

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

接続状態の検出

数あるプレゼンス関連の機能の中でも、オンラインまたはオフラインになったときをクライアント側で認識できる機能は有用です。Firebase Realtime Database クライアントには、クライアントの接続状態が変化すると、そのたびに更新される /.info/connected という特別な場所が用意されています。次に例を示します。

var connectedRef = firebase.database().ref(".info/connected");
connectedRef.on("value", function(snap) {
  if (snap.val() === true) {
    alert("connected");
  } else {
    alert("not connected");
  }
});

/.info/connected は、値がクライアントの状態に依存するため、クライアント間で同期されないブール値です。言い換えると、あるクライアントが読み取った /.info/connected の値が false である場合に、別のクライアントが読み取った値も false であるという保証はありません。

レイテンシの処理

サーバーのタイムスタンプ

Firebase Realtime Database サーバーには、サーバー上で生成されたタイムスタンプをデータとして挿入するメカニズムが用意されています。この機能を onDisconnect と組み合わせることで、クライアントが接続を解除した時刻を確実かつ簡単に記録できるようになります。

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 クライアントにより、サーバー時刻を推定するためにローカルのレポート時刻(ミリ秒単位のエポックタイム)に加算されます。このオフセットの精度はネットワーク処理のレイテンシによる影響を受けることがあるので、主にクロック時刻の大きな(1 秒を超える)不一致を検出するのに役立つことに注意してください。

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

プレゼンスのサンプルアプリ

接続解除の操作を接続状態のモニタリングやサーバーのタイムスタンプと組み合わせることで、ユーザー プレゼンス システムを構築できます。このシステムでは、各ユーザーが、クライアントがオンラインであるかどうかを示すデータをデータベースの場所に保存します。クライアントは、オンラインになったときにこの場所を true に設定し、接続を解除したときにタイムスタンプを設定します。このタイムスタンプは、指定されたユーザーがオンラインであった最後の時刻を示します。

両方のコマンドをサーバーに送信できるようになる前にクライアントのネットワーク接続が切断された場合に、競合状態が発生するのを回避するため、接続解除の操作をキューに入れてから、ユーザーをオンラインとしてマークする必要があるので注意してください。

次に、シンプルなユーザー プレゼンス システムを示します。

// 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', function(snap) {
  if (snap.val() === true) {
    // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect)

    // add this device to my connections list
    // this value could contain info about the device or a timestamp too
    var con = myConnectionsRef.push(true);

    // when I disconnect, remove this device
    con.onDisconnect().remove();

    // when I disconnect, update the last time I was seen online
    lastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);
  }
});

フィードバックを送信...