使用雲端通訊和 Cloud Functions 傳送網頁應用程式的通知

1. 總覽

在本程式碼研究室中,您將瞭解如何使用 Cloud Functions for Firebase,向即時通訊應用程式的使用者傳送通知,為即時通訊網頁應用程式增添功能。

3b1284f5144b54f6.png

課程內容

  • 使用 Firebase SDK 建立 Google Cloud Functions。
  • 依據驗證、Cloud Storage 和 Cloud Firestore 事件觸發 Cloud Functions。
  • 在網頁應用程式中加入 Firebase 雲端通訊支援功能。

事前準備

  • 信用卡。您必須訂閱 Firebase Blaze 方案,才能使用 Cloud Functions for Firebase,也就是說,你必須使用信用卡為 Firebase 專案啟用計費功能。
  • 您選擇的 IDE/文字編輯器,例如 WebStormAtomSublime
  • 這個終端機可在已安裝 NodeJS v9 的情況下執行殼層指令。
  • 瀏覽器,例如 Chrome。
  • 程式碼範例。請參閱下一個步驟。

2. 取得程式碼範例

從指令列複製 GitHub 存放區

git clone https://github.com/firebase/friendlychat

匯入範例應用程式

使用 IDE,開啟或匯入程式碼範例目錄中的 android_studio_folder.pngcloud-functions-start 目錄。這個目錄包含程式碼研究室的起始程式碼,內含功能完整的 Chat 網頁應用程式。

3. 建立 Firebase 專案並設定應用程式

建立專案

Firebase 控制台,按一下「新增專案」,並命名為 friendlyChat

按一下 [Create Project] (建立專案)

升級至 Blaze 方案

如要使用 Cloud Functions for Firebase,您必須將 Firebase 專案升級為 Blaze 計費方案。你必須在 Google Cloud 帳戶中新增信用卡或其他付款方式。

所有 Firebase 專案 (包括 Blaze 方案中的專案) 仍可存取 Cloud Functions 的免費用量配額。本程式碼研究室列出的步驟,不會超過免費方案的用量限制。不過,您會看見用於託管 Cloud Functions 建構映像檔的 Cloud Storage 小額費用 (約 $0.03 美元)。

如果您無法使用信用卡,或者不喜歡繼續採用 Blaze 方案,請考慮使用 Firebase 模擬器套件,這可讓您在本機電腦上免費模擬 Cloud Functions。

啟用 Google Auth

為了讓使用者登入應用程式,我們會使用需要啟用的 Google Auth。

在 Firebase 控制台中,開啟「建構」部分 >驗證 >登入方式分頁 (或按這裡前往)。然後啟用 Google 登入提供者,並按一下 [儲存]。這可讓使用者使用自己的 Google 帳戶登入網頁應用程式。

此外,您也可以將應用程式的公開名稱設為友善即時通訊

8290061806aacb46.png

啟用 Cloud Storage

應用程式使用 Cloud Storage 上傳圖片。如要在 Firebase 專案中啟用 Cloud Storage,請前往「儲存空間」部分,然後按一下「開始使用」按鈕。請前往這裡的步驟,在 Cloud Storage 位置會有要使用的預設值。完成後,按一下「完成」。

新增網頁應用程式

在 Firebase 控制台中新增網頁應用程式。如要這麼做,請前往「專案設定」,並向下捲動至「新增應用程式」。選擇「網站」平台並勾選設定 Firebase 託管的方塊,然後註冊應用程式並點按「下一步」完成後續步驟,最後點按「前往控制台」

4. 安裝 Firebase 指令列介面

Firebase 指令列介面 (CLI) 可讓您在本機提供網頁應用程式,並部署網頁應用程式和 Cloud Functions。

如要安裝或升級 CLI,請執行下列 npm 指令:

npm -g install firebase-tools

如要確認是否已正確安裝 CLI,請開啟主控台並執行下列指令:

firebase --version

確認 Firebase CLI 版本高於 4.0.0,因此具備 Cloud Functions 所需的所有最新功能。如果沒有,請執行 npm install -g firebase-tools 來升級,如上所示。

執行下列指令來授權 Firebase CLI:

firebase login

請確認目前位於 cloud-functions-start 目錄中,然後設定 Firebase CLI 以使用 Firebase 專案:

firebase use --add

接下來,選取專案 ID,然後按照指示操作。系統提示時,您可以選擇任何別名,例如 codelab

5. 部署及執行網頁應用程式

現在您已匯入及設定專案,可以開始首次執行網頁應用程式!開啟終端機視窗,前往 cloud-functions-start 資料夾,然後使用以下程式碼將網頁應用程式部署至 Firebase 託管:

firebase deploy --except functions

以下是控制台輸出內容:

i deploying database, storage, hosting
  database: rules ready to deploy.
i  storage: checking rules for compilation errors...
  storage: rules file compiled successfully
i  hosting: preparing ./ directory for upload...
  hosting: ./ folder uploaded successfully
 storage: rules file compiled successfully
 hosting: 8 files uploaded successfully
i starting release process (may take several minutes)...

 Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview
Hosting URL: https://friendlychat-1234.firebaseapp.com

開啟網頁應用程式

最後一行會顯示代管網址。現在,網頁應用程式應可透過這個網址提供,格式如下:https://<project-id>.firebaseapp.com。開啟應用程式。您應該會看到即時通訊應用程式的正常運作 UI。

使用「使用 Google 帳戶登入」按鈕登入應用程式,即可新增訊息及張貼圖片:

3b1284f5144b54f6.png

如果你是第一次使用新瀏覽器登入應用程式,請務必在系統提示時允許通知:8b9d0c66dc36153d.png

我們稍後需要啟用通知功能。

如果您不小心點選了 [封鎖],可以在 Chrome 網址列中點選網址左側的 [🔒? Secure] 按鈕,並切換「通知」旁的長條,即可變更這項設定:

e926868b0546ed71.png

現在,我們將使用 Cloud Functions 專用的 Firebase SDK 新增部分功能。

6. 函式目錄

Cloud Functions 可讓您輕鬆讓程式碼在 Cloud 中執行,無需設定伺服器。我們會逐步說明如何建構函數,以因應 Firebase Auth、Cloud Storage 和 Firebase Firestore 資料庫事件。首先說明驗證

使用 Cloud Functions 專用的 Firebase SDK 時,函式程式碼預設會位於 functions 目錄下。您的 Functions 程式碼也是 Node.js 應用程式,因此需要 package.json,可提供應用程式的部分相關資訊並列出依附元件。

為了方便起見,我們已建立 functions/index.js 檔案來存放程式碼。你可以先檢查這個檔案,再繼續操作。

cd functions
ls

如果您不熟悉 Node.js,請先進一步瞭解相關資訊,再繼續本程式碼研究室。

package.json 檔案已列出兩個必要的依附元件:適用於 Cloud Functions 的 Firebase SDKFirebase Admin SDK。如要在本機安裝,請前往 functions 資料夾,然後執行下列指令:

npm install

現在,我們來看看 index.js 檔案:

index.js

/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 * ...
 */

// TODO(DEVELOPER): Import the Cloud Functions for Firebase and the Firebase Admin modules here.

// TODO(DEVELOPER): Write the addWelcomeMessage Function here.

// TODO(DEVELOPER): Write the blurImages Function here.

// TODO(DEVELOPER): Write the sendNotification Function here.

我們會匯入必要模組,然後編寫三個函式來取代 TODO。讓我們開始匯入必要的節點模組。

7. 匯入 Cloud Functions 和 Firebase 管理員模組

本程式碼研究室需要使用兩個模組:firebase-functions 可編寫 Cloud Functions 觸發條件和記錄檔,firebase-admin 則能在具有管理員存取權的伺服器上使用 Firebase 平台,以便執行寫入 Cloud Firestore 或傳送 FCM 通知等動作。

index.js 檔案中的第一個 TODO 替換為下列內容:

index.js

/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 * ...
 */

// Import the Firebase SDK for Google Cloud Functions.
const functions = require('firebase-functions');
// Import and initialize the Firebase Admin SDK.
const admin = require('firebase-admin');
admin.initializeApp();

// TODO(DEVELOPER): Write the addWelcomeMessage Function here.

// TODO(DEVELOPER): Write the blurImages Function here.

// TODO(DEVELOPER): Write the sendNotification Function here.

部署至 Cloud Functions 環境或其他 Google Cloud Platform 容器時,系統就會自動設定 Firebase Admin SDK,當我們呼叫 admin.initializeApp() (不含引數) 時就會發生這種情況。

現在,我們要新增一個函式,這個函式會在使用者首次在即時通訊應用程式中登入時執行,並加上一則訊息來歡迎使用者。

8. 歡迎新使用者

Chat 訊息結構

發布至 PreferenceChat 聊天動態饋給的訊息會儲存在 Cloud Firestore 中。我們來看看訊息所使用的資料結構。方法是在即時通訊中張貼新訊息,並顯示「Hello World」:

11f5a676fbb1a69a.png

如下所示:

fe6d1c020d0744cf.png

在 Firebase 控制台中,按一下「Build」 部分下方的「Firestore Database」。您應該會看到郵件集合以及一份包含您所撰寫郵件的文件:

442c9c10b5e2b245.png

如您所見,系統會將即時通訊訊息儲存在 Cloud Firestore 中,並將 nameprofilePicUrltexttimestamp 屬性新增至 messages 集合。

新增歡迎訊息

第一個 Cloud 函式會在即時通訊中加入歡迎新使用者的訊息。為此,我們可以使用觸發條件 functions.auth().onCreate,它會在使用者每次在 Firebase 應用程式中首次登入時執行此函式。在 index.js 檔案中新增 addWelcomeMessages 函式:

index.js

// Adds a message that welcomes new users into the chat.
exports.addWelcomeMessages = functions.auth.user().onCreate(async (user) => {
  functions.logger.log('A new user signed in for the first time.');
  const fullName = user.displayName || 'Anonymous';

  // Saves the new welcome message into the database
  // which then displays it in the FriendlyChat clients.
  await admin.firestore().collection('messages').add({
    name: 'Firebase Bot',
    profilePicUrl: '/images/firebase-logo.png', // Firebase logo
    text: `${fullName} signed in for the first time! Welcome!`,
    timestamp: admin.firestore.FieldValue.serverTimestamp(),
  });
  functions.logger.log('Welcome message written to database.');
});

將此函式新增至特殊的 exports 物件,是節點可讓您在目前檔案外存取函式的方法,而 Cloud Functions 也是必要做法。

在上述函式中,我們要新增由「Firebase Bot」發布的新歡迎訊息聊天室訊息清單。方法是對 Cloud Firestore 的 messages 集合使用 add 方法,也就是儲存聊天訊息的位置。

由於這是非同步作業,因此我們必須傳回 Promise,表示 Cloud Firestore 寫入完成的時間,因此 Cloud Functions 不會太早執行。

部署 Cloud Functions

您必須先部署 Cloud Functions,系統才會啟用 Cloud Functions。如要這麼做,請在指令列中執行下列指令:

firebase deploy --only functions

以下是控制台輸出內容:

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
  functions: missing necessary APIs. Enabling now...
i  env: ensuring necessary APIs are enabled...
  env: missing necessary APIs. Enabling now...
i  functions: waiting for APIs to activate...
i  env: waiting for APIs to activate...
  env: all necessary APIs are enabled
  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (X.XX KB) for uploading
  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: creating function addWelcomeMessages...
  functions[addWelcomeMessages]: Successful create operation. 
  functions: all functions deployed successfully!

  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlypchat-1234/overview

測試函式

成功部署函式後,您必須擁有初次登入的使用者。

  1. 使用代管網址,在瀏覽器中開啟應用程式 (格式為 https://<project-id>.firebaseapp.com)。
  2. 新使用者首次在應用程式中使用「登入」按鈕登入。
  • 如果您已登入該應用程式,請開啟 Firebase 主控台驗證,然後將您的帳戶從使用者清單中刪除。然後再次登入。

262535d1b1223c65.png

  1. 登入後,系統應會自動顯示歡迎訊息:

1c70e0d64b23525b.png

9. 圖片審核

使用者可以在聊天室中上傳所有類型的圖片。請謹慎管理令人反感的圖片,尤其是在公開社群平台上。在 chromiumChat 中,要發布至即時通訊的圖片會儲存至 Google Cloud Storage

透過 Cloud Functions,您可以使用 functions.storage().onFinalize 觸發條件偵測新上傳的圖片。每當在 Cloud Storage 中上傳或修改新檔案時,就會執行這項作業。

若要審核圖片,我們會執行下列程序:

  1. 使用 Cloud Vision API 檢查圖片是否被標記為「成人」或「暴力內容」。
  2. 如果映像檔已標記,請在執行中的函式執行個體中下載。
  3. 使用 ImageMagick 將圖片模糊處理。
  4. 將經過模糊處理的圖片上傳至 Cloud Storage。

啟用 Cloud Vision API

我們會在這個函式中使用 Google Cloud Vision API,因此您必須在 Firebase 專案中啟用 API。請按照這個連結操作,然後選取 Firebase 專案並啟用 API:

5c77fee51ec5de49.png

安裝依附元件

如要審核圖片,我們會使用 Node.js 適用的 Google Cloud Vision 用戶端程式庫 (@google-cloud/vision),透過 Cloud Vision API 執行圖片以偵測不當圖片。

如要將這個套件安裝至 Cloud Functions 應用程式,請執行以下 npm install --save 指令。請務必從 functions 目錄執行操作。

npm install --save @google-cloud/vision@2.4.0

這項操作會在本機安裝套件,並將其新增為 package.json 檔案中的宣告依附元件。

匯入及設定依附元件

如要匯入已安裝的依附元件和本章節需要的 Node.js 核心模組 (pathosfs),請在 index.js 檔案頂端加入以下幾行程式碼:

index.js

const Vision = require('@google-cloud/vision');
const vision = new Vision.ImageAnnotatorClient();
const {promisify} = require('util');
const exec = promisify(require('child_process').exec);

const path = require('path');
const os = require('os');
const fs = require('fs');

由於您的函式會在 Google Cloud 環境中執行,因此不需設定 Cloud Storage 和 Cloud Vision 程式庫,系統會自動將其設為使用您的專案。

偵測不當圖片

您將使用 functions.storage.onChange Cloud Functions 觸發條件,只要在 Cloud Storage 值區中建立或修改檔案或資料夾,就會立即執行程式碼。在 index.js 檔案中加入 blurOffensiveImages 函式:

index.js

// Checks if uploaded images are flagged as Adult or Violence and if so blurs them.
exports.blurOffensiveImages = functions.runWith({memory: '2GB'}).storage.object().onFinalize(
    async (object) => {
      const imageUri = `gs://${object.bucket}/${object.name}`;
      // Check the image content using the Cloud Vision API.
      const batchAnnotateImagesResponse = await vision.safeSearchDetection(imageUri);
      const safeSearchResult = batchAnnotateImagesResponse[0].safeSearchAnnotation;
      const Likelihood = Vision.protos.google.cloud.vision.v1.Likelihood;
      if (Likelihood[safeSearchResult.adult] >= Likelihood.LIKELY ||
          Likelihood[safeSearchResult.violence] >= Likelihood.LIKELY) {
        functions.logger.log('The image', object.name, 'has been detected as inappropriate.');
        return blurImage(object.name);
      }
      functions.logger.log('The image', object.name, 'has been detected as OK.');
    });

請注意,我們已新增一些 Cloud Functions 執行個體設定,用來執行函式。如果使用 .runWith({memory: '2GB'}),我們會要求執行個體取得 2 GB 的記憶體,而非預設記憶體,因為這個函式會耗用大量記憶體。

當函式觸發時,系統會透過 Cloud Vision API 執行圖片,偵測其是否標記為成人或暴力。如果系統根據這些條件判定圖片不適當,我們會對圖片進行模糊處理,這項作業將於 blurImage 函式中完成。

模糊處理圖片

index.js 檔案中新增下列 blurImage 函式:

index.js

// Blurs the given image located in the given bucket using ImageMagick.
async function blurImage(filePath) {
  const tempLocalFile = path.join(os.tmpdir(), path.basename(filePath));
  const messageId = filePath.split(path.sep)[1];
  const bucket = admin.storage().bucket();

  // Download file from bucket.
  await bucket.file(filePath).download({destination: tempLocalFile});
  functions.logger.log('Image has been downloaded to', tempLocalFile);
  // Blur the image using ImageMagick.
  await exec(`convert "${tempLocalFile}" -channel RGBA -blur 0x24 "${tempLocalFile}"`);
  functions.logger.log('Image has been blurred');
  // Uploading the Blurred image back into the bucket.
  await bucket.upload(tempLocalFile, {destination: filePath});
  functions.logger.log('Blurred image has been uploaded to', filePath);
  // Deleting the local file to free up disk space.
  fs.unlinkSync(tempLocalFile);
  functions.logger.log('Deleted local file.');
  // Indicate that the message has been moderated.
  await admin.firestore().collection('messages').doc(messageId).update({moderated: true});
  functions.logger.log('Marked the image as moderated in the database.');
}

在上述函式中,映像檔二進位檔是從 Cloud Storage 下載。接著,系統會使用 ImageMagick 的 convert 工具將圖片模糊處理,再將經過模糊處理的版本重新上傳至 Storage 值區。接著,我們會刪除 Cloud Functions 執行個體中的檔案,藉此釋出一些磁碟空間,而這麼做是因為相同的 Cloud Functions 執行個體可能會重新使用,而且如果未清理檔案,可能會導致磁碟空間用盡。最後,我們會在即時通訊訊息中加入布林值,指出圖片經過審核,因此用戶端會重新整理訊息。

部署函式

您必須先部署函式,函式才會啟用。在指令列中執行 firebase deploy --only functions

firebase deploy --only functions

以下是控制台輸出內容:

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (X.XX KB) for uploading
  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: updating function addWelcomeMessages...
i  functions: creating function blurOffensiveImages...
  functions[addWelcomeMessages]: Successful update operation.
  functions[blurOffensiveImages]: Successful create operation.
  functions: all functions deployed successfully!

  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview

測試函式

成功部署函式後:

  1. 使用代管網址,在瀏覽器中開啟應用程式 (格式為 https://<project-id>.firebaseapp.com)。
  2. 登入應用程式後,請上傳圖片:4db9fdab56703e4a.png
  3. 選擇你最令人反感的圖片進行上傳 (或使用這個品嚐殭屍!),你應該很快就會看到你的貼文經過模糊處理,且圖片將經過模糊處理:83dd904fbaf97d2b.png

10. 新訊息通知

在本節中,您將新增 Cloud 函式,以便在有人發布新訊息時傳送通知給即時通訊的參與者。

您可以使用 Firebase 雲端通訊 (FCM),以可靠的方式傳送通知給各平台的使用者。如要傳送通知給使用者,您需要他們的 FCM 裝置權杖。使用者首次在新瀏覽器或裝置上開啟應用程式時,我們目前使用的即時通訊網頁應用程式會自動收集裝置權杖。這些權杖會儲存在 Cloud Firestore 的 fcmTokens 集合中。

如要瞭解如何在網頁應用程式上取得 FCM 裝置權杖,可以前往 Firebase 網頁程式碼研究室

傳送通知

為偵測新訊息的發布時間,您將使用 functions.firestore.document().onCreate Cloud Functions 觸發條件。這個觸發條件會在 Cloud Firestore 的特定路徑建立新物件時執行程式碼。在 index.js 檔案中新增 sendNotifications 函式:

index.js

// Sends a notifications to all users when a new message is posted.
exports.sendNotifications = functions.firestore.document('messages/{messageId}').onCreate(
  async (snapshot) => {
    // Notification details.
    const text = snapshot.data().text;
    const payload = {
      notification: {
        title: `${snapshot.data().name} posted ${text ? 'a message' : 'an image'}`,
        body: text ? (text.length <= 100 ? text : text.substring(0, 97) + '...') : '',
        icon: snapshot.data().profilePicUrl || '/images/profile_placeholder.png',
        click_action: `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com`,
      }
    };

    // Get the list of device tokens.
    const allTokens = await admin.firestore().collection('fcmTokens').get();
    const tokens = [];
    allTokens.forEach((tokenDoc) => {
      tokens.push(tokenDoc.id);
    });

    if (tokens.length > 0) {
      // Send notifications to all tokens.
      const response = await admin.messaging().sendToDevice(tokens, payload);
      await cleanupTokens(response, tokens);
      functions.logger.log('Notifications have been sent and tokens cleaned up.');
    }
  });

在上述函式中,我們要收集所有使用者的裝置權杖,並透過 admin.messaging().sendToDevice 函式傳送通知給各個權杖。

清除權杖

最後,我們想要移除已失效的權杖。當使用者取得的使用者權杖不再由瀏覽器或裝置使用時,就會發生這種情形。舉例來說,如果使用者撤銷瀏覽器工作階段的通知權限,就會發生這種情況。如要這麼做,請在 index.js 檔案中新增下列 cleanupTokens 函式:

index.js

// Cleans up the tokens that are no longer valid.
function cleanupTokens(response, tokens) {
 // For each notification we check if there was an error.
 const tokensDelete = [];
 response.results.forEach((result, index) => {
   const error = result.error;
   if (error) {
     functions.logger.error('Failure sending notification to', tokens[index], error);
     // Cleanup the tokens that are not registered anymore.
     if (error.code === 'messaging/invalid-registration-token' ||
         error.code === 'messaging/registration-token-not-registered') {
       const deleteTask = admin.firestore().collection('fcmTokens').doc(tokens[index]).delete();
       tokensDelete.push(deleteTask);
     }
   }
 });
 return Promise.all(tokensDelete);
}

部署函式

這個函式必須在部署完成後才會啟用,如要部署,請在指令列中執行下列指令:

firebase deploy --only functions

以下是控制台輸出內容:

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (X.XX KB) for uploading
  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: updating function addWelcomeMessages...
i  functions: updating function blurOffensiveImages...
i  functions: creating function sendNotifications...
  functions[addWelcomeMessages]: Successful update operation.
  functions[blurOffensiveImages]: Successful updating operation.
  functions[sendNotifications]: Successful create operation.
  functions: all functions deployed successfully!

  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview

測試函式

  1. 函式部署成功後,請使用代管網址 (格式為 https://<project-id>.firebaseapp.com) 在瀏覽器中開啟應用程式。
  2. 如果你是第一次登入應用程式,請務必在系統提示時允許接收通知:8b9d0c66dc36153d.png
  3. 關閉即時通訊應用程式分頁或顯示其他分頁:只有在應用程式於背景執行時,才會顯示通知。如果您想瞭解如何在應用程式於前景運作時接收訊息,請參閱說明文件
  4. 使用其他瀏覽器 (或無痕式視窗) 登入應用程式,然後發布訊息。第一個瀏覽器應該會顯示通知:45282ab12b28b926.png

11. 恭喜!

您已使用 Cloud Functions 專用的 Firebase SDK,並在即時通訊應用程式中新增伺服器端元件。

涵蓋內容

  • 使用 Cloud Functions 專用的 Firebase SDK 編寫 Cloud Functions。
  • 依據驗證、Cloud Storage 和 Cloud Firestore 事件觸發 Cloud Functions。
  • 在網頁應用程式中加入 Firebase 雲端通訊支援功能。
  • 使用 Firebase CLI 部署 Cloud Functions。

後續步驟

瞭解詳情