[Firebase] Firestoreでリアルタイムなチャットを作る (Web編) その1

  • このエントリーをはてなブックマークに追加
  • LINEで送る

今回はFirestoreでリアルタイムに情報のやりとりをしてみます。こういったときのサンプルは概ねチャットと相場が決まっていますので、ひとまずデータの授受ができる最低限のコードを書いてみます。

Firestoreの準備と基本的な使い方

詳しくは以下のページをご覧くださいませ。

今回はひとまず動くものを作るのを目標としますので、ユーザー管理は行わず一つの部屋にひたすらメッセージがたまっていく作りにします。

また「ルール」は今回も全開放しています。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}
- Sponsored Link -

サンプル

実行結果

  • ユーザー名は10種類の中からアクセスする度にランダムに決定されます(選べません)
  • 自由に投稿していただいて問題ありませんが、公序良俗に反する書き込みはご遠慮ください。
  • 適当なタイミングでお掃除します。

ソースコード

config.js

Firebaseのコンソールで表示される内容をそのままコピペしconfig.jsというファイル名で保存しました。

// コンソールの内容をそのままコピペ
var config = {
  apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  authDomain: "test-f76bc.firebaseapp.com",
  databaseURL: "https://test-f76bc.firebaseio.com",
  projectId: "test-f76bc",
  storageBucket: "test-f76bc.appspot.com",
  messagingSenderId: "1111111111111111"
};
firebase.initializeApp(config);

HTML/JavaScript

こちらが本体です。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>FireStore Chat</title>
  <style>
    #chatlog{ width:500px; height:300px; border:1px solid gray; overflow-y:scroll; }
    #uname{ width:80px; float:left; margin-right:10px; padding-top:5px; text-align:center}
    #msg{ width:330px; height:30px; margin-right:10px; font-size:12pt;}
    #sbmt{ width:100px; height:30px; }
  </style>
</head>
<body>
  <h1>FireStore Chat</h1>

  <!-- 発言が表示される領域 -->
  <ul id="chatlog"></ul>

  <!-- 入力フォーム -->
  <form id="form1">
    <div id="uname"></div>
    <input type="text" id="msg"><button type="button" id="sbmt">送信</button>
  </form>

  <script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-firestore.js"></script>
  <script src="/js/config.js"></script>
  <script>
    //---------------------------------------
    // チャット初期処理
    //---------------------------------------
    // ユーザー名をランダムに決める
    var uname = getUName();
    document.getElementById("uname").innerHTML = uname;

    // テキストボックスにfocus
    // document.getElementById("msg").focus();

    //---------------------------------------
    // Firestoreの準備
    //---------------------------------------
    // Firestoreのインスタンス作成
    var db = firebase.firestore();

    // チャットルームのリファレンス取得
    var messagesRef = db.collection("chatroom").doc("room1").collection("messages");

    /**
     * 同期処理
     **/
    messagesRef.orderBy("date", "asc").limit(20).onSnapshot( (snapshot) => {
      snapshot.docChanges().forEach((change) => {
        // 追加
        if ( change.type === 'added' ) {
          addLog(change.doc.id, change.doc.data());
        }
        // 更新
        else if( change.type === 'modified' ){
          modLog(change.doc.id, change.doc.data());
        }
        // 削除
        else if ( change.type === 'removed' ) {
          removeLog(change.doc.id);
        }
      });
    });

    /**
     * 送信ボタン押下
     **/
    document.getElementById("sbmt").addEventListener("click", ()=>{
      let msg = document.getElementById("msg").value;
      if( msg.length === 0 ){
        return(false);
      }
      // メッセージをfirestoreへ送信
      messagesRef.add({
        name: uname,
        msg: msg,
        date: new Date().getTime()
      })
      .then(()=>{
        let msg = document.getElementById("msg");
        msg.focus();
        msg.value = "";
      })
    });
    // submitイベントは(いったん)無視する
    document.getElementById("form1").addEventListener("submit", (e)=>{
      e.preventDefault();
    });


    /**
     * ログに追加
     */
    function addLog(id, data){
      // 追加するHTMLを作成
      let log = `${data.name}: ${data.msg} (${getStrTime(data.date)})`;
      let li  = document.createElement('li');
      li.id   = id;
      li.appendChild(document.createTextNode(log));

      // 表示エリアへ追加
      let chatlog = document.getElementById("chatlog");
      chatlog.insertBefore(li, chatlog.firstChild);
    }

    /**
     * ログを更新
     */
    function modLog(id, data){
      let log = document.getElementById(id);
      if( log !== null ){
        log.innerText = `${data.name}: ${data.msg} (${getStrTime(data.date)})`;
      }
    }

    /**
     * ログを削除
     **/
    function removeLog(id){
      let log = document.getElementById(id);
      if( log !== null ){
        log.parentNode.removeChild(log);
      }
    }


    /**
     * ユーザー名をランダムに決定
     **/
     function getUName(){
      let master = ["キティ", "マイメロ", "プリン", "ぐでたま", "烈子", "シナモン", "たあ坊", "キキ", "ララ", "切り身"];
      let i      = Math.floor( Math.random() * master.length );
      return( master[i] );
    }

    /**
     * UNIX TIME => MM-DD hh:mm
     **/
    function getStrTime(time){
      let t = new Date(time);
      return(
        ("0" + (t.getMonth() + 1)).slice(-2) + "-" +
        ("0" + t.getDay()        ).slice(-2) + " " +
        ("0" + t.getHours()      ).slice(-2) + ":" +
        ("0" + t.getMinutes()    ).slice(-2)
      );
    }
  </script>
</body>
</html>

解説

同期する

以下の例ではコレクション/chatroom/room1/messagesと同期をしています。
何らかの変更(追加、更新、削除)が発生した際にdocChanges()が実行されます。変化があったドキュメントが渡されますのでそれをforEachでぐるぐる回しながら処理をしていきます。

var messagesRef = db.collection("chatroom").doc("room1").collection("messages");

messagesRef.onSnapshot( (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    // 変更があった際の処理をここに記述
  });
});

イベントの種類

forEachの中身ですが、added, modified, removedのいずれかのイベントが返ってきますので、イベントの種類に応じて処理を分岐してあげるのが良いでしょう。

  snapshot.docChanges().forEach((change) => {
    // 追加
    if ( change.type === 'added' ) {
      addLog(change.doc.id, change.doc.data());
    }
    // 更新
    else if( change.type === 'modified' ){
      modLog(change.doc.id, change.doc.data());
    }
    // 削除
    else if ( change.type === 'removed' ) {
      removeLog(change.doc.id);
    }
  });

データ量を制限する

以下のようにlimit()で返却される件数を制限することができます。同時にorderBy()でソートをかけておくと新しい物(古い物)から順番に返してくえるようになります。

messagesRef.orderBy("date", "asc").limit(20).onSnapshot( (snapshot) => {

更新と削除

削除や更新はUIを作るのが面倒だったのと、「ルール」が全開放となっている関係で今回は作成しませんでしたが、Firebaseのコンソールから試していただければ、正常に動作することを確認できると思います。
 ※=ご自身で環境を作った上でお試しください

レイテンシもそれほど感じないですし、コードも非常に簡潔にかけるのがわかりますね。

続き

参考ページ

- Sponsored Link -

同じカテゴリの記事

Donate

投げ銭お待ちしております!

BTC3A9nH1j7qQdKrSTrmnEdweo6zPqpHBmkxC
ETH0x1aE0541198D1F9f2908a25C35032A473e74D3731
XPXaQ9zv65F9ovfoMBrFGiPRG47aSHFhy8SX
MONAMTKgzSiS5BDueZkRCHySih24TGFwHThaDQ (MonaCoin)
ZNYZhnpf4RFYVQTAQiyoJg9dGoeC4bgT3BoSy (BitZeny)

ご質問やリクエストなどお気軽に。メールアドレスの入力は任意です。書き込みが反映されるまで時間がかかります。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください