ウェブ向け Firebase を理解する

1. 概要

この Codelab では、インタラクティブなウェブ アプリケーションを作成するための Firebase の基本の一部について学習します。いくつかの Firebase プロダクトを使用して、イベントの出欠確認とゲストブックのチャットアプリを作成します。

このステップのスクリーンショット

ラボの内容

  • Firebase Authentication と FirebaseUI を使用してユーザーを認証する。
  • Cloud Firestore を使用してデータを同期する。
  • Firebase セキュリティ ルールを記述してデータベースを保護する。

必要なもの

  • Chrome などの任意のブラウザ。
  • stackblitz.com へのアクセス(アカウントやログインは不要)。
  • Google アカウント(Gmail アカウントなど)。GitHub アカウントですでに使用しているメール アカウントを使用することをおすすめします。これにより、StackBlitz の高度な機能を使用できるようになります。
  • Codelab のサンプルコード。コードを取得する方法については、次のステップをご覧ください。

2. 開始用コードを取得する

この Codelab では、いくつかの Firebase ワークフローが統合されたオンライン エディタである StackBlitz を使用してアプリを作成します。Stackblitz では、ソフトウェアのインストールや特別な StackBlitz アカウントは必要ありません。

StackBlitz を使用すると、プロジェクトを他のユーザーと共有できます。StackBlitz プロジェクトの URL を知っている他のユーザーは、コードを表示したり、プロジェクトをフォークしたりできますが、StackBlitz プロジェクトを編集することはできません。

  1. 開始コードについては、次の URL にアクセスしてください。https://stackblitz.com/edit/firebase-gtk-web-start
  2. StackBlitz ページの上部にある [Fork] をクリックします。

このステップのスクリーンショット

これで、開始コードのコピーが、一意の名前と一意の URL を持つ独自の StackBlitz プロジェクトとして作成されました。すべてのファイルと変更は、この StackBlitz プロジェクトに保存されます。

3. 予定情報を編集する

この Codelab の開始用資料には、ウェブアプリの構造が用意されています。これには、いくつかのスタイルシートと、アプリ用の HTML コンテナが含まれています。この Codelab の後半で、これらのコンテナを Firebase に接続します。

まず、StackBlitz インターフェースについて、もう少し詳しく見てみましょう。

  1. StackBlitz で index.html ファイルを開きます。
  2. event-details-containerdescription-container を見つけて、予定の詳細を編集してみてください。

テキストを編集すると、StackBlitz の自動ページ再読み込みによって新しいイベントの詳細が表示されます。便利です。

<!-- ... -->

<div id="app">
  <img src="..." />

  <section id="event-details-container">
     <h1>Firebase Meetup</h1>

     <p><i class="material-icons">calendar_today</i> October 30</p>
     <p><i class="material-icons">location_city</i> San Francisco</p>

  </section>

  <hr>

  <section id="firebaseui-auth-container"></section>

  <section id="description-container">
     <h2>What we'll be doing</h2>
     <p>Join us for a day full of Firebase Workshops and Pizza!</p>
  </section>
</div>

<!-- ... -->

アプリのプレビューは次のようになります。

アプリのプレビュー

このステップのスクリーンショット

4. Firebase プロジェクトを作成して設定する

イベント情報を表示するのはゲストにとっては便利ですが、イベントを表示するだけではあまり役に立ちません。このアプリに動的な機能を追加しましょう。そのためには、アプリに Firebase を接続する必要があります。Firebase の利用を開始するには、Firebase プロジェクトを作成して設定する必要があります。

Firebase プロジェクトを作成する

  1. Firebase にログインします。
  2. Firebase コンソールで [プロジェクトを追加](または [プロジェクトを作成])をクリックし、Firebase プロジェクトに「Firebase-Web-Codelab」という名前を付けます。

    このステップのスクリーンショット

  3. プロジェクト作成オプションをクリックします。Firebase の利用規約が表示されたら同意します。このアプリではアナリティクスを使用しないため、Google アナリティクスの画面で [無効にする] をクリックします。

Firebase プロジェクトの詳細については、Firebase プロジェクトについて理解するをご覧ください。

コンソールで Firebase プロダクトを有効にして設定する

作成しているアプリでは、ウェブアプリで利用可能な次の Firebase プロダクトを使用します。

  • ユーザーがアプリに簡単にログインできるようにするための Firebase AuthenticationFirebase UI
  • 構造化されたデータをクラウドに保存し、データが変更されたときに即座に通知を受け取る Cloud Firestore
  • データベースを保護するための Firebase セキュリティ ルール

この中には、特別な設定が必要になるプロダクトや、Firebase コンソールを使用して有効化する必要があるプロダクトがあります。

Firebase Authentication でメールによるログインを有効にする

ユーザーがウェブアプリにログインできるように、この Codelab ではメール/パスワードのログイン方法を使用します。

  1. Firebase コンソールの左側のパネルで、[Build] > [Authentication] をクリックします。[開始する] をクリックします。認証ダッシュボードでは、登録ユーザーの確認、ログイン プロバイダの設定、設定の管理を行えます。

    このステップのスクリーンショット

  2. [ログイン方法] タブを選択します(または、こちらをクリックしてタブに直接移動します)。

    このステップのスクリーンショット

  3. プロバイダ オプションで [メール / パスワード] をクリックし、スイッチを [有効にする] に切り替えて、[保存] をクリックします。

    このステップのスクリーンショット

Cloud Firestore を設定する

このウェブアプリは、Cloud Firestore を使用して、チャット メッセージの保存と新しいチャット メッセージの受信を行います。

Firebase プロジェクトで Cloud Firestore を設定する方法は次のとおりです。

  1. Firebase コンソールの左側のパネルで [Build] を開き、[Firestore データベース] を選択します。
  2. [データベースを作成] をクリックします。
  3. [データベース ID] は (default) のままにします。
  4. データベースのロケーションを選択し、[Next] をクリックします。
    実際のアプリでは、ユーザーに近いロケーションを選択します。
  5. [テストモードで開始] をクリックします。セキュリティ ルールに関する免責条項を確認します。
    この Codelab の後半で、セキュリティ ルールを追加してデータを保護します。データベースのセキュリティ ルールを追加せずに、アプリを配布または公開しないでください。
  6. [作成] をクリックします。

5. Firebase を追加して構成する

Firebase プロジェクトが作成され、一部のサービスが有効になったので、Firebase を使用することと、使用する Firebase プロジェクトをコードに指示する必要があります。

Firebase ライブラリを追加する

アプリで Firebase を使用するには、Firebase ライブラリをアプリに追加する必要があります。これを行う方法は複数あります。詳しくは、Firebase のドキュメントをご覧ください。たとえば、Google の CDN からライブラリを追加することも、npm を使用してローカルにインストールしてから、Browserify を使用している場合はアプリにパッケージ化することもできます。

StackBlitz には自動バンドルが用意されているため、import ステートメントを使用して Firebase ライブラリを追加できます。ライブラリのモジュラー(v9)バージョンを使用します。「ツリー シェイキング」と呼ばれるプロセスにより、ウェブページ全体のサイズを小さくできます。モジュラー SDK について詳しくは、こちらのドキュメントをご覧ください。

このアプリを作成するには、Firebase Authentication、FirebaseUI、Cloud Firestore ライブラリを使用します。この Codelab では、次のインポート ステートメントが index.js ファイルの先頭にすでに含まれています。この後、各 Firebase ライブラリからさらにメソッドをインポートしていきます。

// Import stylesheets
import './style.css';

// Firebase App (the core Firebase SDK) is always required
import { initializeApp } from 'firebase/app';

// Add the Firebase products and methods that you want to use
import {} from 'firebase/auth';
import {} from 'firebase/firestore';

import * as firebaseui from 'firebaseui';

Firebase ウェブアプリを Firebase プロジェクトに追加する

  1. Firebase コンソールの左上にある [プロジェクトの概要] をクリックして、プロジェクトの概要ページに移動します。
  2. プロジェクトの概要ページの中央にあるウェブアイコン ウェブアプリ アイコン をクリックして、新しい Firebase ウェブアプリを作成します。

    このステップのスクリーンショット

  3. アプリを「ウェブアプリ」というニックネームで登録します。
  4. この Codelab では、[このアプリの Firebase Hosting も設定します] の横にあるチェックボックスをオンにしないでください。現時点では、StackBlitz のプレビューペインを使用します。
  5. [アプリの登録] をクリックします。

    このステップのスクリーンショット

  6. Firebase 構成オブジェクトをクリップボードにコピーします。

    このステップのスクリーンショット

  7. [コンソールに進む] をクリックします。Firebase 構成オブジェクトをアプリに追加します。
  8. StackBlitz に戻り、index.js ファイルに移動します。
  9. Add Firebase project configuration object here コメント行を見つけて、そのコメントのすぐ下に構成スニペットを貼り付けます。
  10. initializeApp 関数呼び出しを追加して、一意の Firebase プロジェクト構成を使用して Firebase を設定します。
    // ...
    // Add Firebase project configuration object here
    const firebaseConfig = {
      apiKey: "random-unique-string",
      authDomain: "your-projectId.firebaseapp.com",
      databaseURL: "https://your-projectId.firebaseio.com",
      projectId: "your-projectId",
      storageBucket: "your-projectId.firebasestorage.app",
      messagingSenderId: "random-unique-string",
      appId: "random-unique-string",
    };
    
    // Initialize Firebase
    initializeApp(firebaseConfig);
    

6. ユーザー ログイン(出欠確認)を追加する

これでアプリに Firebase が追加されたので、Firebase Authentication を使用してユーザーを登録する出欠確認ボタンを設定できます。

メールログインと FirebaseUI でユーザーを認証する

メールアドレスでログインするようユーザーに求める RSVP ボタンが必要です。そのためには、FirebaseUI を出欠確認ボタンに接続します。FirebaseUI は、Firebase Auth で構築済みの UI を提供するライブラリです。

FirebaseUI には、次の 2 つの処理を行う構成が必要です(ドキュメントのオプションを参照)。

  • メール / パスワードのログイン方法を使用することを FirebaseUI に指示します。
  • ログイン成功のコールバックを処理し、リダイレクトを回避するために false を返します。単一ページのウェブアプリを作成しているため、ページを更新したくない。

FirebaseUI Auth を初期化するコードを追加する

  1. StackBlitz で index.js ファイルに移動します。
  2. 上部で firebase/auth インポート ステートメントを見つけ、次のように getAuthEmailAuthProvider を追加します。
    // ...
    // Add the Firebase products and methods that you want to use
    import { getAuth, EmailAuthProvider } from 'firebase/auth';
    
    import {} from 'firebase/firestore';
    
  3. 次のように、initializeApp の直後に auth オブジェクトへの参照を保存します。
    initializeApp(firebaseConfig);
    auth = getAuth();
    
  4. 開始コードには FirebaseUI の構成がすでに含まれています。メール認証プロバイダを使用するようにすでに設定されています。
  5. index.jsmain() 関数の下部に、FirebaseUI 初期化ステートメントを次のように追加します。
    async function main() {
      // ...
    
      // Initialize the FirebaseUI widget using Firebase
      const ui = new firebaseui.auth.AuthUI(auth);
    }
    main();
    
    

HTML に出欠確認ボタンを追加する

  1. StackBlitz で index.html ファイルに移動します。
  2. 次の例に示すように、event-details-container 内に RSVP ボタンの HTML を追加します。

    この Codelab では、これらの特定の ID のフックがすでに index.js ファイルに存在するため、以下に示す id 値と同じ値を使用するようにしてください。

    index.html ファイルには、ID が firebaseui-auth-container のコンテナがあります。この ID を、ログインを保持するために FirebaseUI に渡します。
    <!-- ... -->
    
    <section id="event-details-container">
        <!-- ... -->
        <!-- ADD THE RSVP BUTTON HERE -->
        <button id="startRsvp">RSVP</button>
    </section>
    <hr>
    <section id="firebaseui-auth-container"></section>
    <!-- ... -->
    
    アプリのプレビュー

    このステップのスクリーンショット

  3. RSVP ボタンにリスナーを設定し、FirebaseUI の開始関数を呼び出します。これにより、ログイン ウィンドウを表示することを FirebaseUI に指示します。

    index.jsmain() 関数の末尾に次のコードを追加します。
    async function main() {
      // ...
    
      // Listen to RSVP button clicks
      startRsvpButton.addEventListener("click",
       () => {
            ui.start("#firebaseui-auth-container", uiConfig);
      });
    }
    main();
    

アプリへのログインをテストする

  1. StackBlitz のプレビュー ウィンドウで、出欠確認ボタンをクリックしてアプリにログインします。
    • この Codelab のメール確認ステップは設定しないため、架空のメールアドレスであっても任意のメールアドレスを使用できます。
    • auth/operation-not-allowed または The given sign-in provider is disabled for this Firebase project というエラー メッセージが表示された場合は、Firebase コンソールでログイン プロバイダとして [メール / パスワード] が有効になっていることを確認します。
    アプリのプレビュー

    このステップのスクリーンショット

  2. Firebase コンソールの [Authentication] ダッシュボードに移動します。[ユーザー] タブに、アプリへのログイン時に入力したアカウント情報が表示されます。

    このステップのスクリーンショット

UI に認証状態を追加する

次に、UI がログイン状態を反映していることを確認します。

Firebase Authentication の状態リスナー コールバックを使用します。このコールバックは、ユーザーのログイン ステータスが変更されるたびに通知されます。現在ログインしているユーザーがいる場合は、アプリで [出欠確認] ボタンが [ログアウト] ボタンに切り替わります。

  1. StackBlitz で index.js ファイルに移動します。
  2. 上部で firebase/auth インポート ステートメントを見つけ、次のように signOutonAuthStateChanged を追加します。
    // ...
    // Add the Firebase products and methods that you want to use
    import {
      getAuth,
      EmailAuthProvider,
      signOut,
      onAuthStateChanged
    } from 'firebase/auth';
    
    import {} from 'firebase/firestore';
    
  3. main() 関数の一番下に次のコードを追加します。
    async function main() {
      // ...
    
      // Listen to the current Auth state
      onAuthStateChanged(auth, user => {
        if (user) {
          startRsvpButton.textContent = 'LOGOUT';
        } else {
          startRsvpButton.textContent = 'RSVP';
        }
      });
    }
    main();
    
  4. ボタン リスナーで、現在のユーザーがいるかどうかを確認し、ログアウトします。これを行うには、現在の startRsvpButton.addEventListener を次のように置き換えます。
    // ...
    // Called when the user clicks the RSVP button
    startRsvpButton.addEventListener('click', () => {
      if (auth.currentUser) {
        // User is signed in; allows user to sign out
        signOut(auth);
      } else {
        // No user is signed in; allows user to sign in
        ui.start('#firebaseui-auth-container', uiConfig);
      }
    });
    

これで、アプリのボタンに [ログアウト] と表示され、クリックすると [RSVP] に戻ります。

アプリのプレビュー

このステップのスクリーンショット

7. Cloud Firestore にメッセージを書き込む

ユーザーがアクセスしていることがわかっていれば、アプリ上で他にすべきことをゲストに提供しましょう。ゲストブックにメッセージを残してもらえるとしたらどうでしょうか。来場を楽しみにしている理由や、誰に会いたいと思っているのかを共有できる。

ユーザーがアプリに書き込んだチャット メッセージを保存するには、Cloud Firestore を使用します。

データモデル

Cloud Firestore は NoSQL データベースであり、データベースに保存されるデータはコレクション、ドキュメント、フィールド、サブコレクションに分割されます。チャットの各メッセージは、guestbook というトップレベルのコレクションにドキュメントとして保存します。

複数のメッセージ ドキュメントを含むゲストブック コレクションを示す Firestore データモデルのグラフィック

Firestore にメッセージを追加する

このセクションでは、ユーザーがデータベースに新しいメッセージを書き込めるようにする機能を追加します。まず、UI 要素(メッセージ フィールドと送信ボタン)の HTML を追加します。次に、これらの要素をデータベースに接続するコードを追加します。

メッセージ フィールドと送信ボタンの UI 要素を追加するには:

  1. StackBlitz で index.html ファイルに移動します。
  2. guestbook-container を見つけて、次の HTML を追加して、メッセージ入力フィールドと送信ボタンのあるフォームを作成します。
    <!-- ... -->
    
     <section id="guestbook-container">
       <h2>Discussion</h2>
    
       <form id="leave-message">
         <label>Leave a message: </label>
         <input type="text" id="message">
         <button type="submit">
           <i class="material-icons">send</i>
           <span>SEND</span>
         </button>
       </form>
    
     </section>
    
    <!-- ... -->
    

アプリのプレビュー

このステップのスクリーンショット

ユーザーが [送信] ボタンをクリックすると、以下のコード スニペットがトリガーされます。メッセージ入力フィールドの内容がデータベースの guestbook コレクションに追加されます。具体的には、addDoc メソッドにより、メッセージ コンテンツを(自動生成された ID を持つ)新しいドキュメント guestbook コレクションに追加します。

  1. StackBlitz で index.js ファイルに移動します。
  2. 上部で firebase/firestore インポート ステートメントを見つけ、次のように getFirestoreaddDoccollection を追加します。
    // ...
    
    // Add the Firebase products and methods that you want to use
    import {
      getAuth,
      EmailAuthProvider,
      signOut,
      onAuthStateChanged
    } from 'firebase/auth';
    
    import {
      getFirestore,
      addDoc,
      collection
    } from 'firebase/firestore';
    
  3. 次に、Firestore db オブジェクトへの参照を initializeApp の直後に保存します。
    initializeApp(firebaseConfig);
    auth = getAuth();
    db = getFirestore();
    
  4. main() 関数の最後に、次のコードを追加します。

    auth.currentUser.uid は、ログインしたすべてのユーザーに Firebase Authentication が付与する自動生成の一意の ID を参照します。
    async function main() {
      // ...
    
      // Listen to the form submission
      form.addEventListener('submit', async e => {
        // Prevent the default form redirect
        e.preventDefault();
        // Write a new message to the database collection "guestbook"
        addDoc(collection(db, 'guestbook'), {
          text: input.value,
          timestamp: Date.now(),
          name: auth.currentUser.displayName,
          userId: auth.currentUser.uid
        });
        // clear message input field
        input.value = '';
        // Return false to avoid redirect
        return false;
      });
    }
    main();
    

ログインしたユーザーにのみゲストブックを表示する

ゲストのチャットをにも見せたくない場合、チャットを保護するためにできることの一つは、ログインしているユーザーのみにゲストブックの閲覧を許可することです。ただし、独自のアプリでは Firebase セキュリティ ルールを使用してデータベースを保護する必要もあります。(セキュリティ ルールについては、この Codelab の後半で詳しく説明します)。

  1. StackBlitz で index.js ファイルに移動します。
  2. ゲストブックの表示 / 非表示を切り替えるには、onAuthStateChanged リスナーを編集します。
    // ...
    
    // Listen to the current Auth state
    onAuthStateChanged(auth, user => {
      if (user) {
        startRsvpButton.textContent = 'LOGOUT';
        // Show guestbook to logged-in users
        guestbookContainer.style.display = 'block';
      } else {
        startRsvpButton.textContent = 'RSVP';
        // Hide guestbook for non-logged-in users
        guestbookContainer.style.display = 'none';
      }
    });
    

メッセージの送信をテストする

  1. アプリにログインしていることを確認します。
  2. 「Hey there!」などのメッセージを入力し、[送信] をクリックします。

このアクションにより、メッセージが Cloud Firestore データベースに書き込まれます。ただし、データの取得が実装されていないため、実際のウェブアプリにはまだメッセージが表示されません。あとでこれを行います。

ただし、新しく追加されたメッセージは Firebase コンソールで確認できます。

Firebase コンソールの Firestore Database ダッシュボードに、新しく追加したメッセージとともに guestbook コレクションが表示されます。メッセージの送信を続けると、ゲストブック コレクションには次のような多数のドキュメントが含まれます。

Firebase コンソール

このステップのスクリーンショット

8. メッセージを読む

メッセージを同期する

ゲストがデータベースにメッセージを書き込むことができるのは素晴らしいことですが、アプリではまだメッセージを確認できません。

メッセージを表示するには、データの変更時にトリガーするリスナーを追加し、新しいメッセージを表示する UI 要素を作成する必要があります。

新たに追加されたアプリからメッセージをリッスンするコードを追加します。まず、メッセージを表示するセクションを HTML に追加します。

  1. StackBlitz で index.html ファイルに移動します。
  2. guestbook-container に、ID guestbook の新しいセクションを追加します。
    <!-- ... -->
    
      <section id="guestbook-container">
       <h2>Discussion</h2>
    
       <form><!-- ... --></form>
    
       <section id="guestbook"></section>
    
     </section>
    
    <!-- ... -->
    

次に、データに加えられた変更をリッスンするリスナーを登録します。

  1. StackBlitz で index.js ファイルに移動します。
  2. 上部で firebase/firestore インポート ステートメントを見つけ、次のように queryorderByonSnapshot を追加します。
    // ...
    import {
      getFirestore,
      addDoc,
      collection,
      query,
      orderBy,
      onSnapshot
    } from 'firebase/firestore';
    
  3. main() 関数の最後に次のコードを追加して、データベース内のすべてのドキュメント(ゲストブック メッセージ)をループします。このコードの内容について詳しくは、以下のスニペットをご覧ください。
    async function main() {
      // ...
    
      // Create query for messages
      const q = query(collection(db, 'guestbook'), orderBy('timestamp', 'desc'));
      onSnapshot(q, snaps => {
        // Reset page
        guestbook.innerHTML = '';
        // Loop through documents in database
        snaps.forEach(doc => {
          // Create an HTML entry for each document and add it to the chat
          const entry = document.createElement('p');
          entry.textContent = doc.data().name + ': ' + doc.data().text;
          guestbook.appendChild(entry);
        });
      });
    }
    main();
    

データベース内のメッセージをリッスンするために、collection 関数を使用して特定のコレクションに対するクエリを作成しました。上記のコードは、チャット メッセージが保存されている guestbook コレクションの変更をリッスンします。また、メッセージは日付で並べ替えられ、orderBy('timestamp', 'desc') を使用して最新のメッセージが一番上に表示されます。

onSnapshot 関数は、使用するクエリとコールバック関数の 2 つのパラメータを受け取ります。クエリに一致するドキュメントに変更があると、コールバック関数がトリガーされます。これは、メッセージが削除、変更、追加された場合のいずれかです。詳細については、Cloud Firestore のドキュメントをご覧ください。

メッセージの同期をテストする

Cloud Firestore は、データベースにサブスクライブしているクライアントとデータを自動的かつ瞬時に同期します。

  • 先ほどデータベースに作成したメッセージがアプリに表示されます。新しいメッセージを作成してもすぐに表示されます。
  • ワークスペースを複数のウィンドウまたはタブで開くと、タブ間でメッセージがリアルタイムで同期されます。
  • (省略可)Firebase コンソールの [Database] で直接、メッセージを手動で削除、変更したり新しいメッセージを追加したりしてみてください。これらの変更も UI に反映されるはずです。

これで完了です。アプリで Cloud Firestore ドキュメントを読み取っています。

アプリのプレビュー

このステップのスクリーンショット

9. 基本的なセキュリティ ルールを設定する

最初に Cloud Firestore をセットアップしたときにテストモードを使用するように設定したため、データベースは読み取りと書き込みが可能な状態になっています。ただし、テストモードは開発の非常に初期段階でのみ使用してください。ベスト プラクティスとして、アプリの開発時にデータベースのセキュリティ ルールを設定する必要があります。セキュリティは、アプリの構造と動作に不可欠な要素です。

セキュリティ ルールを使用すると、データベース内のドキュメントおよびコレクションへのアクセスを制御できます。柔軟なルール構文により、データベース全体に対するすべての書き込みから特定のドキュメントに対するオペレーションまで、あらゆるものに一致するルールを作成できます。

Firebase コンソールで Cloud Firestore のセキュリティ ルールを作成できます。

  1. Firebase コンソールの [構築] セクションで [Firestore データベース] をクリックし、[ルール] タブを選択します(または、こちらをクリックして [ルール] タブに直接移動します)。
  2. 次のデフォルトのセキュリティ ルールが表示されます。公開アクセスの制限時間は、今日から数週間後になります。

このステップのスクリーンショット

コレクションを特定する

まず、アプリがデータを書き込むコレクションを特定します。

  1. 既存の match /{document=**} 句を削除して、ルールを次のようにします。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
      }
    }
    
  2. match /databases/{database}/documents で、保護するコレクションを特定します。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
         // You'll add rules here in the next step.
      }
    }
    

セキュリティ ルールを追加する

各ゲストブック ドキュメントで Authentication UID をフィールドとして使用したため、Authentication UID を取得して、ドキュメントへの書き込みを試みるすべてのユーザーに一致する Authentication UID があることを確認できます。

  1. 次のように、読み取りルールと書き込みルールをルールセットに追加します。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
          allow read: if request.auth.uid != null;
          allow create:
            if request.auth.uid == request.resource.data.userId;
        }
      }
    }
    
  2. [公開] をクリックして新しいルールをデプロイします。ゲストブックでは、ログインしているユーザーだけがメッセージ(すべてのメッセージ)を読むことができますが、メッセージの作成にはユーザー ID を使用する必要があります。また、メッセージの編集や削除も許可されません。

検証ルールを追加する

  1. データの検証を追加して、想定されているすべてのフィールドがドキュメントに存在することを確認します。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /guestbook/{entry} {
          allow read: if request.auth.uid != null;
          allow create:
          if request.auth.uid == request.resource.data.userId
              && "name" in request.resource.data
              && "text" in request.resource.data
              && "timestamp" in request.resource.data;
        }
      }
    }
    
  2. [公開] をクリックして、新しいルールをデプロイします。

リスナーをリセットする

認証されたユーザーのみがログインできるようになりました。そのため、ゲストブックの firestore クエリを認証リスナー内に移動する必要があります。そうしないと、権限エラーが発生し、ユーザーがログアウトするとアプリの接続が切断されます。

  1. StackBlitz で index.js ファイルに移動します。
  2. ゲストブック コレクションの onSnapshot リスナーを subscribeGuestbook という新しい関数に pull します。また、onSnapshot 関数の結果を guestbookListener 変数に割り当てます。

    Firestore onSnapshot リスナーは、後でスナップショット リスナーをキャンセルするために使用できる、登録解除関数を返します
    // ...
    // Listen to guestbook updates
    function subscribeGuestbook() {
      const q = query(collection(db, 'guestbook'), orderBy('timestamp', 'desc'));
      guestbookListener = onSnapshot(q, snaps => {
        // Reset page
        guestbook.innerHTML = '';
        // Loop through documents in database
        snaps.forEach(doc => {
          // Create an HTML entry for each document and add it to the chat
          const entry = document.createElement('p');
          entry.textContent = doc.data().name + ': ' + doc.data().text;
          guestbook.appendChild(entry);
        });
      });
    }
    
  3. その下に unsubscribeGuestbook という新しい関数を追加します。guestbookListener 変数が null でないことを確認してから、関数を呼び出してリスナーをキャンセルします。
    // ...
    // Unsubscribe from guestbook updates
    function unsubscribeGuestbook() {
      if (guestbookListener != null) {
        guestbookListener();
        guestbookListener = null;
      }
    }
    

最後に、新しい関数を onAuthStateChanged コールバックに追加します。

  1. if (user) の末尾に subscribeGuestbook() を追加します。
  2. else ステートメントの一番下に unsubscribeGuestbook() を追加します。
    // ...
    // Listen to the current Auth state
    onAuthStateChanged(auth, user => {
      if (user) {
        startRsvpButton.textContent = 'LOGOUT';
        // Show guestbook to logged-in users
        guestbookContainer.style.display = 'block';
        // Subscribe to the guestbook collection
        subscribeGuestbook();
      } else {
        startRsvpButton.textContent = 'RSVP';
        // Hide guestbook for non-logged-in users
        guestbookContainer.style.display = 'none';
        // Unsubscribe from the guestbook collection
        unsubscribeGuestbook();
      }
    });
    

10. ボーナス ステップ: 学んだことを実践する

参加者の出欠確認状況を記録する

現在のところ、お客様のアプリでは、ユーザーがイベントに興味を持った場合にチャットを開始できます。また、誰かが来ているかどうかを確認する方法は、その人がチャットに投稿することだけです。イベントを整理して、何人で来場したかを伝えましょう。

イベントに参加するユーザーを登録するための切り替えボタンを追加し、参加人数を収集します。

  1. StackBlitz で index.html ファイルに移動します。
  2. guestbook-container で、次のように [はい] と [いいえ] ボタンのセットを追加します。
    <!-- ... -->
      <section id="guestbook-container">
       <h2>Are you attending?</h2>
         <button id="rsvp-yes">YES</button>
         <button id="rsvp-no">NO</button>
    
       <h2>Discussion</h2>
    
       <!-- ... -->
    
     </section>
    <!-- ... -->
    

アプリのプレビュー

このステップのスクリーンショット

次に、ボタンのクリック リスナーを登録します。ユーザーが [はい] をクリックした場合は、認証 UID を使用してレスポンスをデータベースに保存します。

  1. StackBlitz で index.js ファイルに移動します。
  2. 上部で firebase/firestore インポート ステートメントを見つけ、次のように docsetDocwhere を追加します。
    // ...
    // Add the Firebase products and methods that you want to use
    import {
      getFirestore,
      addDoc,
      collection,
      query,
      orderBy,
      onSnapshot,
      doc,
      setDoc,
      where
    } from 'firebase/firestore';
    
  3. main() 関数の最後に次のコードを追加して、出欠確認ステータスをリッスンします。
    async function main() {
      // ...
    
      // Listen to RSVP responses
      rsvpYes.onclick = async () => {
      };
      rsvpNo.onclick = async () => {
      };
    }
    main();
    
    
  4. 次に、attendees という新しいコレクションを作成し、どちらかの RSVP ボタンがクリックされた場合にドキュメント参照を登録します。クリックされたボタンに応じて、その参照を true または false に設定します。

    まず、rsvpYes の場合:
    // ...
    // Listen to RSVP responses
    rsvpYes.onclick = async () => {
      // Get a reference to the user's document in the attendees collection
      const userRef = doc(db, 'attendees', auth.currentUser.uid);
    
      // If they RSVP'd yes, save a document with attendi()ng: true
      try {
        await setDoc(userRef, {
          attending: true
        });
      } catch (e) {
        console.error(e);
      }
    };
    
    次に、rsvpNo についても同じ手順を実行しますが、値 false を使用します。
    rsvpNo.onclick = async () => {
      // Get a reference to the user's document in the attendees collection
      const userRef = doc(db, 'attendees', auth.currentUser.uid);
    
      // If they RSVP'd yes, save a document with attending: true
      try {
        await setDoc(userRef, {
          attending: false
        });
      } catch (e) {
        console.error(e);
      }
    };
    

セキュリティ ルールを更新する

すでにルールが設定されているため、ボタンを使って追加する新しいデータは拒否されます。

attendees コレクションへの追加を許可する

attendees コレクションに追加できるようにするには、ルールを更新する必要があります。

  1. attendees コレクションでは、認証 UID をドキュメント名として使用したため、これを取得して、送信者の uid が作成しているドキュメントと同じであることを確認できます。参加者リストは(機密データがないため)すべてのユーザーが読み取れるようにしますが、更新できるのは作成者のみにします。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // ... //
        match /attendees/{userId} {
          allow read: if true;
          allow write: if request.auth.uid == userId;
        }
      }
    }
    
  2. [公開] をクリックして、新しいルールをデプロイします。

検証ルールを追加する

  1. データ検証ルールを追加して、想定されるすべてのフィールドがドキュメントに存在することを確認します。
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // ... //
        match /attendees/{userId} {
          allow read: if true;
          allow write: if request.auth.uid == userId
              && "attending" in request.resource.data;
    
        }
      }
    }
    
  2. 忘れずに [公開] をクリックしてルールをデプロイしてください。

(省略可)ボタンをクリックした場合の結果が表示されます。Firebase コンソールで Cloud Firestore ダッシュボードに移動します。

出欠確認のステータスを確認する

回答を記録したので、誰が来るかを確認し、UI に反映しましょう。

  1. StackBlitz で index.html ファイルに移動します。
  2. description-container に、ID が number-attending の新しい要素を追加します。
    <!-- ... -->
    
     <section id="description-container">
         <!-- ... -->
         <p id="number-attending"></p>
     </section>
    
    <!-- ... -->
    

次に、attendees コレクションのリスナーを登録し、YES レスポンスの数をカウントします。

  1. StackBlitz で index.js ファイルに移動します。
  2. main() 関数の一番下に、次のコードを追加して RSVP ステータスをリッスンし、[YES] のクリック数をカウントします。
    async function main() {
      // ...
    
      // Listen for attendee list
      const attendingQuery = query(
        collection(db, 'attendees'),
        where('attending', '==', true)
      );
      const unsubscribe = onSnapshot(attendingQuery, snap => {
        const newAttendeeCount = snap.docs.length;
        numberAttending.innerHTML = newAttendeeCount + ' people going';
      });
    }
    main();
    

最後に、現在のステータスに対応するボタンをハイライト表示しましょう。

  1. 現在の認証 UID に attendees コレクションにエントリがあるかどうかを確認する関数を作成し、ボタンクラスを clicked に設定します。
    // ...
    // Listen for attendee list
    function subscribeCurrentRSVP(user) {
      const ref = doc(db, 'attendees', user.uid);
      rsvpListener = onSnapshot(ref, doc => {
        if (doc && doc.data()) {
          const attendingResponse = doc.data().attending;
    
          // Update css classes for buttons
          if (attendingResponse) {
            rsvpYes.className = 'clicked';
            rsvpNo.className = '';
          } else {
            rsvpYes.className = '';
            rsvpNo.className = 'clicked';
          }
        }
      });
    }
    
  2. また、登録を解除する関数も作成しましょう。ユーザーがログアウトしたときに使用されます。
    // ...
    function unsubscribeCurrentRSVP() {
      if (rsvpListener != null) {
        rsvpListener();
        rsvpListener = null;
      }
      rsvpYes.className = '';
      rsvpNo.className = '';
    }
    
  3. Authentication リスナーから関数を呼び出します。
    // ...
    // Listen to the current Auth state
      // Listen to the current Auth state
      onAuthStateChanged(auth, user => {
        if (user) {
          startRsvpButton.textContent = 'LOGOUT';
          // Show guestbook to logged-in users
          guestbookContainer.style.display = 'block';
    
          // Subscribe to the guestbook collection
          subscribeGuestbook();
          // Subscribe to the user's RSVP
          subscribeCurrentRSVP(user);
        } else {
          startRsvpButton.textContent = 'RSVP';
          // Hide guestbook for non-logged-in users
          guestbookContainer.style.display = 'none'
          ;
          // Unsubscribe from the guestbook collection
          unsubscribeGuestbook();
          // Unsubscribe from the guestbook collection
          unsubscribeCurrentRSVP();
        }
      });
    
  4. 複数のユーザーとしてログインし、[YES] ボタンを追加でクリックするたびにカウントが増加することを確認します。

アプリのプレビュー

このステップのスクリーンショット

11. 完了

Firebase を使用して、インタラクティブなリアルタイムのウェブ アプリケーションを構築できました。

学習した内容

  • Firebase Authentication
  • FirebaseUI
  • Cloud Firestore
  • Firebase セキュリティ ルール

次のステップ

  • Firebase デベロッパー ワークフローの詳細を確認するアプリを完全にローカルでテストして実行する方法については、Firebase エミュレータの Codelab をご覧ください。
  • 他の Firebase プロダクトの詳細については、ユーザーがアップロードした画像ファイルを保存したい場合などです。またはユーザーに通知を送信するか。Firebase ウェブ Codelab で、さらに多くのウェブ向け Firebase プロダクトについて掘り下げている Codelab をご確認ください。
  • Cloud Firestore について詳しくは、以下をご覧ください。サブコレクションとトランザクションについて学びたい場合は、Cloud Firestore についてより深く学習できる Codelab については、Cloud Firestore ウェブ Codelab をご覧ください。または、こちらの Cloud Firestore についての YouTube シリーズをご覧ください。

詳細

いかがでしたか?

皆様からのご意見をお待ちしております。こちらの簡単なフォームにご記入ください。