認識 Firebase 網頁版

1. 總覽

在本程式碼研究室中,您將學習一些 Firebase 的基本概念,以便建立互動式網頁應用程式。您將使用多項 Firebase 產品建構活動回覆和留言板聊天應用程式。

這個步驟的螢幕截圖

課程內容

  • 使用 Firebase 驗證和 FirebaseUI 驗證使用者。
  • 使用 Cloud Firestore 同步處理資料。
  • 編寫 Firebase 安全性規則,保護資料庫安全。

事前準備

  • 你偏好的瀏覽器,例如 Chrome。
  • stackblitz.com 存取權 (無需帳戶或登入)。
  • 擁有 Google 帳戶,例如 Gmail 帳戶。建議您使用已用於 GitHub 帳戶的電子郵件地址。這樣一來,您就能在 StackBlitz 中使用進階功能。
  • 程式碼研究室的程式碼範例。請參閱下一個步驟,瞭解如何取得程式碼。

2. 取得範例程式碼

在本程式碼研究室中,您將使用 StackBlitz 建構應用程式,這是整合了多個 Firebase 工作流程的線上編輯器。您不需要安裝軟體或建立特殊的 StackBlitz 帳戶,即可使用 Stackblitz。

您可以透過 StackBlitz 與他人共用專案。其他人只要有您的 StackBlitz 專案網址,就能查看您的程式碼並分支您的專案,但無法編輯您的 StackBlitz 專案。

  1. 前往這個網址取得啟動程式碼:https://stackblitz.com/edit/firebase-gtk-web-start
  2. 按一下 StackBlitz 頁面頂端的「Fork」

這個步驟的螢幕截圖

您現在擁有起始程式碼的副本,也就是您自己的 StackBlitz 專案,該專案具有專屬名稱和專屬網址。所有檔案和變更都會儲存在這個 StackBlitz 專案中。

3. 編輯事件資訊

這個程式碼研究室的起始素材會為網頁應用程式提供一些結構,包括一些樣式表單和應用程式的幾個 HTML 容器。在本程式碼研究室的後續內容中,您將連結這些容器至 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 Analytics 畫面上,點選「不啟用」,因為這個應用程式不會用到 Analytics。

如要進一步瞭解 Firebase 專案,請參閱「瞭解 Firebase 專案」一文。

在控制台中啟用及設定 Firebase 產品

您建構的應用程式會使用幾項 Firebase 產品,這些產品都可用於網頁應用程式:

  • Firebase 驗證Firebase UI:可讓使用者輕鬆登入您的應用程式。
  • Cloud Firestore:在雲端儲存結構化資料,並在資料變更時立即收到通知。
  • Firebase 安全性規則:可保護資料庫的安全。

部分產品需要特殊設定或透過 Firebase 控制台啟用。

為 Firebase 驗證啟用電子郵件登入功能

為了讓使用者登入網頁應用程式,請在本程式碼研究室中使用「電子郵件/密碼」登入方式:

  1. 在 Firebase 控制台左側面板中,依序按一下「建構」 >「驗證」。然後按一下「開始使用」。您現在已進入驗證資訊主頁,可以查看註冊的使用者、設定登入提供者,以及管理設定。

    這個步驟的螢幕截圖

  2. 選取「登入方式」分頁標籤 (或按這裡直接前往該分頁)。

    這個步驟的螢幕截圖

  3. 在供應商選項中按一下「電子郵件/密碼」,將切換鈕切換至「啟用」,然後按一下「儲存」

    這個步驟的螢幕截圖

設定 Cloud Firestore

這個網頁應用程式會使用 Cloud Firestore 儲存即時通訊訊息,並接收新的即時通訊訊息。

以下說明如何在 Firebase 專案中設定 Cloud Firestore:

  1. 在 Firebase 主控台的左側面板中展開「Build」,然後選取「Firestore database」
  2. 按一下 [Create database] (建立資料庫)。
  3. 將「資料庫 ID」設為 (default)
  4. 選取資料庫的位置,然後按一下「Next」
    如果是實際應用程式,請選擇距離使用者較近的位置。
  5. 按一下「以測試模式啟動」。請詳閱安全性規則免責事項。
    在本程式碼研究室的後續部分,您將新增安全性規則來保護資料。請勿發布或公開應用程式,除非您已為資料庫新增安全性規則。
  6. 按一下「建立」

5. 新增及設定 Firebase

Firebase 專案已建立,部分服務也已啟用,現在您需要告訴程式碼要使用 Firebase,以及要使用的 Firebase 專案。

新增 Firebase 程式庫

如要讓應用程式使用 Firebase,您必須將 Firebase 程式庫新增至應用程式。如需詳細資訊,請參閱 Firebase 說明文件。舉例來說,您可以從 Google 的 CDN 新增程式庫,也可以使用 npm 在本機安裝程式庫,然後在應用程式中打包 (如果您使用 Browserify)。

StackBlitz 提供自動封裝功能,因此您可以使用匯入陳述式新增 Firebase 程式庫。您將使用模組化 (v9) 版本的程式庫,透過稱為「樹狀圖搖動」的程序,有助於縮減網頁的整體大小。如要進一步瞭解模組 SDK,請參閱說明文件

如要建構這個應用程式,您必須使用 Firebase 驗證、FirebaseUI 和 Cloud Firestore 程式庫。在本程式碼研究室中,我們已在 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. 在本程式碼研究室中,請勿勾選「Also set up Firebase Hosting for this app」旁的方塊。您目前會使用 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 驗證功能註冊。

使用電子郵件登入功能和 FirebaseUI 驗證使用者

您需要回覆按鈕,提示使用者以電子郵件地址登入。您可以將 FirebaseUI 連結至 RSVP 按鈕。FirebaseUI 是可在 Firebase Auth 上提供預先建構 UI 的程式庫。

FirebaseUI 需要設定 (請參閱說明文件中的選項),以便執行以下兩項操作:

  • 告訴 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 後立即儲存對驗證物件的參照,如下所示:
    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 中新增 RSVP 按鈕

  1. 在 StackBlitz 中,前往 index.html 檔案。
  2. event-details-container 中加入回覆按鈕的 HTML,如以下範例所示。

    請務必使用下方所示的 id 值,因為在本程式碼研究室中,index.js 檔案中已為這些特定 ID 建立鉤子。

    請注意,在 index.html 檔案中,有一個 ID 為 firebaseui-auth-container 的容器。這是您會傳遞至 FirebaseUI 的 ID,用於保存登入資訊。
    <!-- ... -->
    
    <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 的預覽視窗中,按一下「回覆」按鈕登入應用程式。
    • 在本程式碼研究室中,您可以使用任何電子郵件地址,甚至是假的電子郵件地址,因為您不會為本程式碼研究室設定電子郵件驗證步驟。
    • 如果您看到顯示 auth/operation-not-allowedThe given sign-in provider is disabled for this Firebase project 的錯誤訊息,請確認您已在 Firebase 控制台中啟用「電子郵件/密碼」做為登入提供者。
    應用程式預覽

    這個步驟的螢幕截圖

  2. 前往 Firebase 主控台的「驗證資訊主頁。在「使用者」分頁中,您應該會看到登入應用程式時輸入的帳戶資訊。

    這個步驟的螢幕截圖

在 UI 中新增驗證狀態

接著,請確認 UI 能反映您已登入的事實。

您將使用 Firebase 驗證狀態事件監聽器回呼,當使用者的登入狀態發生變更時,系統會通知您。如果目前有登入的使用者,應用程式會將「回覆」按鈕切換為「登出」按鈕。

  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);
      }
    });
    

現在,應用程式中的按鈕應顯示「LOGOUT」,點選後應會切換回「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>
    
    <!-- ... -->
    

應用程式預覽

這個步驟的螢幕截圖

使用者按下「SEND」按鈕時,系統會觸發下列程式碼片段。這會將訊息輸入欄位的內容新增至資料庫的 guestbook 集合。具體來說,addDoc 方法會將訊息內容新增至 guestbook 集合中的新文件 (附有自動產生的 ID)。

  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. 現在,我們會在 initializeApp 後立即儲存 Firestore db 物件的參照:
    initializeApp(firebaseConfig);
    auth = getAuth();
    db = getFirestore();
    
  4. main() 函式的最下方加入以下程式碼。

    請注意,auth.currentUser.uid 是指 Firebase 驗證服務為所有登入使用者自動產生的不重複 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 安全性規則保護資料庫。(後續的程式碼研究室會進一步說明安全性規則)。

  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. 輸入訊息 (例如「嗨!」),然後按一下「傳送」

這項動作會將訊息寫入 Cloud Firestore 資料庫。不過,您還無法在實際的網頁應用程式中看到這則訊息,因為您還需要實作資料擷取作業。這是您接下來要完成的工作。

不過,您可以在 Firebase 控制台中看到新加入的訊息。

在 Firebase 控制台的 Firestore 資料庫資訊主頁中,您應該會看到 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 函式會採用兩個參數:要使用的查詢和回呼函式。當符合查詢的文件發生任何變更時,系統就會觸發回呼函式。例如刪除、修改或新增訊息。詳情請參閱 Cloud Firestore 說明文件

測試同步處理訊息

Cloud Firestore 會自動即時同步處理資料,並與訂閱資料庫的用戶端同步處理。

  • 您先前在資料庫中建立的訊息應該會顯示在應用程式中。歡迎您編寫新訊息,這些訊息應該會立即顯示。
  • 如果在多個視窗或分頁中開啟工作區,系統會即時同步處理分頁中的訊息。
  • (選用) 您可以嘗試直接在 Firebase 主控台的「資料庫」部分手動刪除、修改或新增訊息,任何變更都會顯示在 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.
      }
    }
    

新增安全防護規則

由於您在每份留言板文件中使用驗證 UID 做為欄位,因此可以取得驗證 UID,並驗證任何嘗試寫入文件的使用者是否有相符的驗證 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 查詢移至 Authentication 事件監聽器中。否則會發生權限錯誤,且在使用者登出時,應用程式會中斷連線。

  1. 在 StackBlitz 中,前往 index.js 檔案。
  2. 將留言板收集 onSnapshot 事件監聽器拉入名為 subscribeGuestbook 的新函式。此外,請將 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 變數是否非空值,然後呼叫函式取消事件監聽器。
    // ...
    // 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() 函式的底部加入下列程式碼,以便監聽 RSVP 狀態:
    async function main() {
      // ...
    
      // Listen to RSVP responses
      rsvpYes.onclick = async () => {
      };
      rsvpNo.onclick = async () => {
      };
    }
    main();
    
    
  4. 接著,請建立名為 attendees 的新集合,然後在點選任一 RSVP 按鈕時註冊文件參照。視按下的按鈕而定,將該參照設為 truefalse

    首先,針對 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 狀態並計算「是」點擊次數。
    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. 從驗證事件監聽器呼叫函式。
    // ...
    // 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. 嘗試以多位使用者的身分登入,並觀察每次按下「是」按鈕時,計數器的數字會增加。

應用程式預覽

這個步驟的螢幕截圖

11. 恭喜!

您已使用 Firebase 建構互動式即時網頁應用程式!

涵蓋內容

  • Firebase 驗證
  • FirebaseUI
  • Cloud Firestore
  • Firebase 安全性規則

後續步驟

瞭解詳情

結果如何?

歡迎提供意見回饋!請點按這裡,填寫 (非常) 簡短的表單。