FireBaseを使う(Cloud Firestore)

FireBaseの導入ー>Authenticationと進んで来ました。今回は、Firestoreです。FirebaseはデータBASE。リアルタイム リスナーを介してクライアント アプリ間でデータを同期出来るのが特徴です。

Firestoreの概要

Cloud Firestore データモデルにFirestoreの説明が有ります。

  • ドキュメント: ストレージの単位。
    • 各ドキュメントは名前で識別される。
    • 例えば、ユーザー alovelace を表すドキュメント
  • マップ: ドキュメント内のネストされた複雑なオブジェクト。
    • 上記の例のユーザー名をマップで構造化すると、
  • コレクション: ドキュメントのコンテナ。ドキュメントはコレクションの中にあります。
    • 例: users コレクションを作成して、さまざまなドキュメントを格納
  • リファレンス: データベース内の場所を示す軽量なオブジェクト
    • 例: コレクション users 内のドキュメント alovelace への「リファレンス」
      • var alovelaceDocumentRef = db.collection(‘users’).doc(‘alovelace’);
    • データがあるかどうかにかかわらず作成可能
  • 階層データ: 特定のドキュメントにコレクションを作成する事が出来る。
    • サブコレクション:データを階層的に構造化できる。データへのアクセスが容易。
    • サブコレクション内のドキュメントにもサブコレクションを格納できる。
    • データは最大 100 レベルまでネストできます。
    • 例:rooms コレクション内のroomAドキュメントに、messages というサブコレクションを作成
    • 例: サブコレクション内のメッセージへのリファレンスを作成
      •  var messageRef = db.collection(‘rooms’).doc(‘roomA’).collection(‘messages’).doc(‘message1’);

PCのファイルシステムと比較してイメージ的に下記の感じ。

  • Directory -> collection
  • File -> document
  • data -> data

先ずはFirebase側の設定

Firestoreを使用するためにFirebase側のセットアップを行います。

  • 1. Firebaseコンソールで前回のプロジェクト(”test”)を選択し、画面左側のリストの構築をクリック。
       Firestore Database をクリックします。
  • 2. この画面で”データベース”の作成をクリック。
  • 3. ここではデータベースを何処に作成するか聞かれます。好きな所で良いと思いますが、取り敢えず
       東京を選択しました。
  • 4. データベースのセキュリティーについて聞かれます。各々のモードの初期設定は
         本番環境モード:全てのクライアントがアクセス不可。
         テストモード:全てのクライアントが期間限定でアクセス可能。
       となります。今回は承認されたクライアントのみアクセス出来るように設定して行きます。
       どちらを選んでも再設定を行うので、本番環境モードを選択しました。
  • 5.しばらく待ってこの画面が表示されればFirestoreの導入完了です。

Firestoreセキュリティーの設定。

今回はFirestoreの導入で本番環境モードを選択しているのでセキュリティーを以下の様に行いました。

  1.  コンソールの画面でルールのタブをクリックするとこの画面が表示されます。ここで
     match /{document=**}とallow read, write: if False;の2行を編集します。
    • match /{document=**} ー> documentのpathを記入
      • **の部分にセキュリティを設定するdocumentのpathを記入します。
        現在の**は全てのdocument指定となります。
    • allow read, write: if false; -> 設定条件の指定
      • ここで許可するオペレーションと条件を指定します
      • 指定方法 ー> allow (許可するオペレーション): if (条件)
      • 現状の条件if falseは全て不可となります。逆にif trueとすれば全て可。
  2.  今回は下記の操作を行った後、公開ボタンを押して下さい。
    • document: 何処に保存するか未定なので変更無し。
    • allow: 許可するオペレーションは read, write。
    • 条件: Authenticationされたクライアントの読み、書き。 
      • request.auth: 承認権限
      • null:  承認権限無し
      • if : request.auth != null;  ー> 承認権限の有るクライアント全て
    • allow read, write: if request.auth != null; に変更する。

これでFirebase側の設定は終了です。

次はPC側の設定

開発を行っている作業用フォルダに移動します。先ずfirebase initを行います。実行後、選択 ? Which Firebase features do you want….の箇所で
  ◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
  ◉ Emulators: Set up local emulators for Firebase products
  ◉ Firestore: Configure security rules and indexes files for Firestore


の3項目を選択します。前々回前回でfirebase initを行っていれば、これ以降の質問はデフォルトで回答します。最後の=== Emulators Setupの質問は
  ◉ Hosting Emulator
のみを選択して下さい。これで準備完了です。

アプリからFirestoreへのアクセス方法。

Webページに説明が有ります。書き込み: Cloud Firestore にデータを追加する 読み込み: Cloud Firestore でデータを取得する 今回は以下の機能を使用してデモアプリを作成したいと思います。

  •  Cloud Firestore を初期化 : const db = firebase.firestore();
  •  書き込み:
    • set():ドキュメントの作成または上書きする。
          db.collection(“collection”).doc(“document”).set({ data });
    • update():ドキュメント全体を上書きすることなく一部のフィールドを更新する
          db.collection(“collection”).doc(“document”).update({ data });
    • add():ドキュメントを指定せず任意のIDで作成する場合
          db.collection(“collection”).doc(“document”).add({ data });
  • 読み込み:
    • get():単一のドキュメントの内容を取得する
          var data = db.collection(“collection”).doc(“document”).get();
  • リアルタイム アップデート
    • onSnapshot():アプリでローカル書き込みが行われるとスナップショットをリスナーに通知
          db.collection(“collection”).doc(“document”)
            .onSnapshot((doc) => {
              console.log(“Current data: “, doc.data());
          });

デモアプリの作製。

前回の login.html を変更して行きます。

login.html

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Firebase</title>

    <!-- update the version number as needed -->
    <script src="/__/firebase/10.11.1/firebase-app-compat.js"></script>
    <!-- include only the Firebase features as you need -->
    <script src="/__/firebase/10.11.1/firebase-auth-compat.js"></script>
    <script src="/__/firebase/10.11.1/firebase-firestore-compat.js"></script>
    <!-- 
      initialize the SDK after all desired features are loaded, set useEmulator to false
      to avoid connecting the SDK to running emulators.
    -->
    <script src="/__/firebase/init.js?useEmulator=true"></script>
  </head>
  <body>
      <h2>Hello Firebase </h2>
        <b><label>Now login: </label> <label id="state" style="padding-right:15px;"></label></b>
        <button type='button' onclick="signOut()" >sinOut</button><br><br>

      <label ><b>----- Set Collection -----------------</b></label><br>
          <label >0. Collection : </label>
          <input type="text" id="doc_00" size="4" />
          <label style="padding-right: 10px;"></label>
          <button onclick="set_store(0)">Go</button><br><br> 
     
      <label ><b>----- Set Data ------------------------</b></label><br>
          <label >1. db.collection("</label><label id="col_1"></label>").doc("</label>
          <input type="text" id="doc_01" size="4" style="font-size:16px;"/>
          <label >").set({</label>
          <input type="text" id="dat_01" size="14" style="font-size:16px;"/>
          <label style="padding-right: 10px;">});</label>
          <button onclick="set_store(1)" id="bt_1" disabled="true">Go</button><br>  
          <label style="padding-left: 20px;">-></label><label id="data_01" style="padding-left: 10px;">-</label><br>

          <label id="2nd" >2. db.collection("</label><label id="col_2"></label>").add({</label>
          <input type="text" id="dat_02" size="14" style="font-size:16px;"/>
          <label style="padding-right: 10px;">});</label>
          <button onclick="set_store(2)" id="bt_2" disabled="true">Go</button><br>  
          <label style="padding-left: 20px;">-></label><label id="data_02" style="padding-left: 10px;">-</label><br>

          <label id="3rd">3. db.collection("</label><label id="col_3"></label>").doc("</label>
            <select id='pd_03' onchange="make_data()">
              <option value='0'> </option>
            </select>
          <label>").update({</label>
          <input type="text" id="dat_03" size="14" style="font-size:16px;"/>
          <label style="padding-right: 10px;">});</label>
          <button onclick="set_store(3)" id="bt_3" disabled="true">Go</button><br>
          <label style="padding-left: 20px;">-></label><label id="data_03" style="padding-left: 10px;">-</label><br><br>

      <label><b>----- Get Data ----------------------</b></label><br>
          <label>4. db.collection("</label><label id="col_4"></label>").get();</label>
          <label style="padding-right: 10px;"></label>
          <button onclick="set_store(4)" id="bt_4" disabled="true">Go</button><br>
          <label style="padding-left: 20px;">-></label><label id="data_04" style="padding-left: 10px;">-</label><br>

          <label>5. db.collection("</label><label id="col_5"></label>").doc("</label>
          <select id='pd_05' onchange="make_data()">
            <option value='0'> </option>
          </select>
          <label>").get();</label>
          <label style="padding-right: 10px;"></label>
          <button onclick="set_store(5)" id="bt_5" disabled="true">Go</button><br>
          <label style="padding-left: 20px;">-></label><label id="data_05" style="padding-left: 10px;">-</label><br><br>

      <label><b>----- Delete Data ------------------</b></label><br>
          <label id="6th">6. db.collection("</label><label id="col_6"></label>").doc("</label>
              <select id='pd_60' onchange="make_data()">
                  <option value='0'> </option>
              </select>
          <label>").update({"</label>
              <select id='pd_61' >
                  <option value='0'> </option>
              </select>
          <label>: firebase.firestore.FieldValue.delete()});</label>
          <label style="padding-right: 10px;"></label>
          <button onclick="set_store(6)" id="bt_6" disabled="true">Go</button><br>
          <label style="padding-left: 20px;">-></label><label id="data_06" style="padding-left: 10px;">-</label><br>

          <label id="7th">7. db.collection("</label><label id="col_7"></label>").doc("</label>
              <select id='pd_07' >
                  <option value='0'> </option>
              </select>
          <label>").delete();</label>
          <label style="padding-right: 10px;"></label>
          <button onclick="set_store(7)" id="bt_7" disabled="true">Go</button><br>
          <label style="padding-left: 20px;">-></label><label id="data_07" style="padding-left: 10px;">-</label><br><br>

      <label><b>----- onSnapshot ----------------</b></label><br>
        <label >8. db.collection("user").doc("snap").onSnapshot()</label><br>
        <label style="padding-left: 20px;">-></label><label id="data_08" style="padding-left: 10px;">-</label>
  </body>
  <script>
    // Initialize Cloud Firestore and get a reference to the service
    const db = firebase.firestore();

    function signOut() {
        firebase.auth().signOut().then(() => {
              console.log('ログアウトしました');
              location.reload();
            })
            .catch((error) => {
              console.log(`ログアウト時にエラーが発生しました (${error})`);
            });
    }

    firebase.auth().onAuthStateChanged((user) => {
        var str = 'none';
        if (user) {
            str = user.email;
        }
        document.getElementById('state').innerHTML = str;
    });

    window.addEventListener('beforeunload', function (e) {
        signOut();
    });

    var _collect, doc_st;
    async function set_store(cmd) {
      var a, b, _doc, _dat, str, _temp;

      switch(cmd){
        case 0: // create 
                _collect = document.getElementById('doc_00').value;
                document.getElementById('col_1').innerHTML = _collect;
                document.getElementById('col_2').innerHTML = _collect;
                document.getElementById('col_3').innerHTML = _collect;
                document.getElementById('col_4').innerHTML = _collect;
                document.getElementById('col_5').innerHTML = _collect;
                document.getElementById('col_6').innerHTML = _collect;
                document.getElementById('col_7').innerHTML = _collect;

                document.getElementById('doc_01').value = '';
                document.getElementById('dat_01').value = '';
                document.getElementById('dat_03').value = '';

                set_button(['bt_1', 'bt_2'], 1);
                set_button(['bt_3', 'bt_4', 'bt_5', 'bt_6', 'bt_7'], 0);
                a = await db.collection(_collect).get();
                if(a.size){
                  make_doc();
                  set_button(['bt_3', 'bt_4', 'bt_5', 'bt_6', 'bt_7'], 1);
                }
                else{
                  a = ['pd_03', 'pd_05', 'pd_60', 'pd_61', 'pd_07'];
                  a.forEach((b) => {
                    str = document.getElementById(b);
                    while(str.item(0) != null) str.remove(0);
                  });
                }
                break;

        case 1: // create set
                _doc = document.getElementById('doc_01').value;
                _dat = document.getElementById('dat_01').value;
                _dat = check_00('doc_st = {' + _dat + '}');
                if(_dat){
                    try{
                      await db.collection(_collect).doc(_doc).set(_dat);
                    }catch(e){
                      document.getElementById('data_01').innerHTML = 'Error. Check data format.';
                      break;    
                    }

                    str = await db.collection(_collect).doc(_doc).get();
                    doc_st = _doc;
                    get_data(str, 'data_01');
                    make_doc();
                    set_button(['bt_3', 'bt_4', 'bt_5', 'bt_6', 'bt_7'], 1);
                }
                else 
                    document.getElementById('data_01').innerHTML = 'Error. Check data format.';
                break;

        case 2: // create add
                _dat = document.getElementById('dat_02').value;

                _dat = check_00('doc_st= {' + _dat + '}');
                if(_dat){
                    try{
                      _doc = await db.collection(_collect).add(_dat);
                    }catch(e){
                      document.getElementById('data_02').innerHTML = 'Error. Check data format.';
                      break;
                    }

                    str = await db.collection(_collect).doc(_doc.id).get();
                    doc_st = _doc.id;
                    get_data(str, 'data_02');
                    make_doc();
                    set_button(['bt_3', 'bt_4', 'bt_5', 'bt_6', 'bt_7'], 1);
                }
                else
                  document.getElementById('data_02').innerHTML = 'Error. Check data format.';
                break;

        case 3: // update 
                _dat = document.getElementById('dat_03').value;
 
                _dat = check_00('doc_st= {' + _dat + '}');
                if(_dat){
                    a = document.getElementById('pd_03');
                    b = a.selectedIndex;
                    _doc = a.options[b].value;
                    try{
                      await db.collection(_collect).doc(_doc).update(_dat);
                    }catch(e){
                      document.getElementById('data_03').innerHTML = 'Error. Check data format.';
                    }

                    str = await db.collection(_collect).doc(_doc).get();
                    get_data(str, 'data_03');
                    a = document.getElementById('pd_60');
                    a = a.selectedIndex;
                    if(a == b) make_data();
                }
                else
                  document.getElementById('data_03').innerHTML = 'Error. Check data format.';
                break;

        case 4: // get Document
                get_doc('data_04');
                break;

        case 5: // get data
                a = document.getElementById('pd_05');
                b = a.selectedIndex;
                _doc = a.options[b].value;
                str = await db.collection(_collect).doc(_doc).get();
                get_data(str, 'data_05');
                break;

        case 6: // delete data
                a = document.getElementById('pd_60');
                b = a.selectedIndex;
                _doc = a.options[b].value;
                a = document.getElementById('pd_61');
                b = a.selectedIndex;
                _dat = a.options[b].value;
                str = {[_dat]: firebase.firestore.FieldValue.delete()};
                await db.collection(_collect).doc(_doc).update(str);
                str = await db.collection(_collect).doc(_doc).get();
                get_data(str, 'data_06');
                a.remove(b);
                break;

        case 7: // delete document
                a = document.getElementById('pd_07');
                b = a.selectedIndex;
                _doc = a.options[b].value;
                await db.collection(_collect).doc(_doc).delete();
                get_doc('data_07');
                a.remove(b);
                a = document.getElementById('pd_03');
                a.remove(b);
                a = document.getElementById('pd_05');
                a.remove(b);
                a = document.getElementById('pd_60');
                await a.remove(b);
                if(a.item(0) == null) 
                  set_button(['bt_3', 'bt_4', 'bt_5', 'bt_6', 'bt_7'], 0);
                make_data();
                break;
      }
      clear_cmt(cmd);
    }

    db.collection("user").doc("snap")
    .onSnapshot((doc) => {
         get_data(doc, 'data_08');
    });

    function check_00(str){
        var _collect, doc_st;
        try {
            eval(str);
        }catch(e){
            return 0;
        }
        return doc_st;
    }
    
    async function make_doc(){
      make_doc_01('pd_03');
      make_doc_01('pd_05');
      make_doc_01('pd_07');
      await make_doc_01('pd_60');
      make_data();
    }

    async function make_doc_01(_list) {
      var a, b, _temp;
      
      a = document.getElementById(_list);
      while(a.item(0) != null) a.remove(0);
      _temp = await db.collection(_collect).get();
      _temp.forEach((doc) => {
        b = document.createElement("option");
        b.value = doc.id;
        b.text = doc.id;
        a.add(b,null);
      });
    }

    async function make_data() {
      var a, b, temp;

      a = document.getElementById('pd_61');
      while(a.item(0) != null) a.remove(0);

      a = document.getElementById('pd_60');
      b = a.selectedIndex;
      if(b != -1){
        temp = db.collection(_collect).doc(a.options[b].value);
        temp = await temp.get();
        temp = temp.data();
        if(Object.keys(temp).length){
          a = document.getElementById('pd_61');
          Object.keys(temp).forEach(function (key) {
            b = document.createElement("option");
            b.value = key;
            b.text = key;
            a.add(b,null);
          });
        }
      }
    }

    async function get_data(_temp, _label) {
      var a, b, str;
      
      str = _temp.data();
      if(str != undefined){
          str = JSON.stringify(str);
          str = check(str, "{");
          str = check(str, ",");
          _temp =  _collect + "/" + doc_st + "/";
          if(_label == 'data_01' ) str = _temp + str;
          if(_label == 'data_02' ) str = _temp + str;
      }
      else str ='-';
      document.getElementById(_label).innerHTML = str;
    }

    async function get_doc(_list) {
      var a, str, _temp;

      _temp = await db.collection(_collect).get();
      str = '';
      a = 0;
      _temp.forEach((doc) => {
        if(a) str += ' , ';
        else a = 1;
        str += ('"' + doc.id + '"');
      });
      document.getElementById(_list).innerHTML = str;
    }

    function set_button(btn, flg){
      var state;

      btn.forEach(function(element){
        state = true;
        if(flg) state = false;
        document.getElementById(element).disabled = state;
      });    
    }
    
    function clear_cmt(num){
      var a = ['data_01', 'data_02', 'data_03', 'data_04', 'data_05', 'data_06', 'data_07'];
      c = 1;
      a.forEach(function(b){
        if(num != c) document.getElementById(b).innerHTML = '-';
        c ++;
      });    
    }
   
    function check(str, data){
        var a,b, new_str;

        new_str = '';
        a = str.search(data);
        b = 1;
        while(b){
            if(a != -1){
                new_str += str.substr(0, a + 1);
                str = str.substr(a + 2);
                a = str.search('"');
                if(a != -1){
                new_str += str.substr(0, a);
                str = str.substr(a + 1);
                a = str.search(data);
                }
                else b = 0;
           }
           else b = 0;
        }
        new_str += str;
        return new_str;
    }

  </script>
</html>
  • 10行: <script src=”/__/firebase/10.11.1/firebase-firestore-compat.js”></script>
    • Firestoreを使用する為にこの行を追加します。
  • 18から20行、97から119行は前回のコードです。

FirebaseCLIで ”firebase server –only hosting”を実行しブラウザにhttp://localhost:5000″を入力すると前回と同様のLOGIN画面が表示されます。

ここで前回登録した Email: test@aaa.com Password:123abc を入力すると画面に以下が表示されます。

これが今回作製したデモプログラムです。Firestoreの機能をデモするプログラムです。実際の動作をFirebaseコンソールで確認しながらデモを進めて行きます。便宜上Firestoreには何もデータ無い状態から始めます。

  1.  Collection :〜
    • ここで操作するCollectionを指定します。”test” と入力します。
    • 入力された値が各行のCollection指定部に反映されます。
    • ここはデモ使用するCollectionを指定してするのみでFirestoreへの操作は行っていません
  2.  set()機能のデモ
    • ここからFirestoreにアクセスします。
    • doc(”  ”)とset({  });の入力欄に、TOK、name:”Tokyo”と入力し実行。
    • 実行コマンド db.collection(“test”).doc(” TOK”).set({name:”Tokyo” });となり
      Firestoreにデータが書き込まれます。
  3.  add()機能のデモ2.Authenticationの追加
    • これはDocument IDを指定せずにデータを書き込む機能です。
    • .add({  });に name:”Kanagawa”  と入力して下さい。
    • 実行コマンドは db.collection(“test”).add({name:”Kanagawa”}); となります。
    •  iEVHPBML1VG75ofMwXMbがDocument IDとして振らた事が分かります。
    •  このIDはシステムが自動で割り振るので実行される環境で違ったIDになります。
  4.  update()機能のデモ
    • データの変更または追加機能です。.doc(” “)のプルダウンリストからTOKを選び
      .update({ });に population: 100 と入力して下さい。
    • Document TOKにpopulation項目が無いので新たに population: 100が追加されています。
    • 仮にdb.collection(“test”).doc(” TOK”).set({population: 100});とset()機能を使うと、
      TOKのデータはname:”Tokyo”は削除されpopulation: 100のみになります。
    • .update({ });に population: 9000000 とし実行するとpopulation:の値のみ更新されます
  5.  get()機能のデモ(Documentの取得)
    • 現在のCollection ”test” に登録されている Document を表示します。
    • 実行されるコマンドは db.collection(“test”).get();。
  6.  get()機能のデモ(Dataの取得)
    • Documentを指定してそのデータを表示します。
    • .doc(” “)のプルダウンリストからTOK選び実行します
    • 実行されるコマンドは db.collection(“test”).doc(“TOK “).get();
  7.  update()機能のデモ(Dataの削除)
    • 指定したDocumentのデータを削除します。
    • .doc(” “)で TOK、.update({” “})で name 選んで実行して下さい
    • 実行されるコマンドdb.collection(“test”).doc(“TOK”).update({“name : firebase.firestore.FieldValue.delete()});
  8.  delete()機能のデモ(Documentの削除)
    • 指定したDocumentを削除します。
    • .doc(” “)で iEVHPBML1VG75ofMwXMbを選び実行
    • 実行コマンドは、db.collection(“test”).doc(” iEVHPBML1VG75ofMwXMb”).delete();
  9.  onSnapshot()機能のデモ
    • リアルタイム アップデートのデモです。
    • collection “user” 、Document “snap” を更新するとこの行の下にそれを表示します。
    • 0のデモで Collectionに”user”を指定して実行。
    • 1のデモで doc(”  ”)とset({  })に、snap 、1:”OK”と入力して実行。
    • 結果欄に{1:”OK”}とデータの値が表示されます。
    • デモ1,3でDocument に”snap”を指定し、データを更新するとその度にデモ8の結果欄に
      更新されたデータが表示されます。

次回は

今回までに

について説明して来ました。これでHPを公開し認証されたクライアントのみLogin出来、クラウドデータベースにアクセス出来る様になりました。これにデモ8のリアルタイム アップデート機能を使えば簡単なチャットプログラムを書く事も可能です。次回は、ここまでの機能を使ってWebRTCをやって見ます。