1. 概要
この Codelab では、Cloud Functions for Firebase を使用してチャットアプリのユーザーに通知を送信し、チャット ウェブアプリに機能を追加する方法を学びます。
学習内容
- Firebase SDK を使用して Google Cloud Functions を作成する。
- Auth、Cloud Storage、Cloud Firestore イベントに基づいて Cloud Functions をトリガーする。
- ウェブアプリに Firebase Cloud Messaging サポートを追加する。
必要なもの
2. サンプルコードを取得する
コマンドラインから GitHub リポジトリのクローンを作成します。
git clone https://github.com/firebase/friendlychat
スターター アプリをインポートする
IDE を使用して、cloud-functions-start
ディレクトリを開くか、サンプルコード ディレクトリからインポートします。このディレクトリには、完全に機能する Chat ウェブアプリで構成される Codelab の開始用コードが含まれています。
3. Firebase プロジェクトを作成してアプリを設定する
プロジェクトの作成
Firebase コンソールで [プロジェクトを追加] をクリックし、「FriendlyChat」という名前を付けます。
[プロジェクトを作成] をクリックします。
Blaze プランにアップグレードする
Cloud Functions for Firebase を使用するには、Firebase プロジェクトを Blaze 料金プランにアップグレードする必要があります。クレジット カードなどのお支払い方法を Google Cloud アカウントに追加する必要があります。
Blaze プランのプロジェクトを含むすべての Firebase プロジェクトで、Cloud Functions の無料の使用量割り当てを引き続きご利用いただけます。この Codelab で概説している手順は、無料枠の使用量上限に収まります。ただし、Cloud Functions のビルドイメージをホストするために使用される Cloud Storage から少額の料金(約 $0.03)が請求されます。
クレジット カードを利用できない場合や、Blaze プランの続行に不安がある場合は、ローカルマシンで Cloud Functions を無料でエミュレートできる Firebase Emulator Suite の使用を検討してください。
Google 認証を有効にする
ユーザーがアプリにログインできるように、有効にする必要がある Google 認証を使用します。
Firebase コンソールで、[Build] セクション > [Authentication] > [Sign-in method] タブを開きます(または、こちらをクリックして移動します)。次に、Google ログイン プロバイダを有効にして、[保存] をクリックします。これにより、ユーザーは Google アカウントを使用してウェブアプリにログインできるようになります。
また、アプリの公開名を [Friendly Chat] に設定してもかまいません。
Cloud Storage を有効にする
このアプリは Cloud Storage を使用して画像をアップロードします。Firebase プロジェクトで Cloud Storage を有効にするには、[Storage] セクションに移動して [始める] ボタンをクリックします。そこに記載されている手順を行うと、Cloud Storage のロケーションに使用するデフォルト値が表示されます。その後、[完了] をクリックします。
ウェブアプリを追加する
Firebase コンソールでウェブアプリを追加します。そのためには、[プロジェクトの設定] に移動し、[アプリを追加] までスクロールします。プラットフォームとしてウェブを選択し、Firebase Hosting を設定するボックスをオンにしてからアプリを登録し、残りの手順で [次へ] をクリックし、最後に [コンソールに進む] をクリックします。
4. Firebase コマンドライン インターフェースをインストールする
Firebase コマンドライン インターフェース(CLI)を使用すると、ウェブアプリをローカルで提供し、ウェブアプリと Cloud Functions をデプロイできます。
CLI をインストールまたはアップグレードするには、次の npm コマンドを実行します。
npm -g install firebase-tools
CLI が正しくインストールされたことを確認するには、コンソールを開いて次のコマンドを実行します。
firebase --version
Cloud Functions に必要な最新の機能がすべて含まれるように、Firebase CLI のバージョンが 4.0.0 以降であることを確認してください。そうでない場合は、上記のように npm install -g firebase-tools
を実行してアップグレードします。
次のコマンドを実行して Firebase CLI を承認します。
firebase login
現在のディレクトリが cloud-functions-start
ディレクトリであることを確認してから、Firebase プロジェクトを使用するように Firebase CLI を設定します。
firebase use --add
次に、プロジェクト ID を選択し、手順に沿って操作します。プロンプトが表示されたら、codelab
などの任意のエイリアスを選択できます。
5. ウェブアプリをデプロイして実行する
これでプロジェクトのインポートと構成が完了し、ウェブアプリを初めて実行する準備ができました。ターミナル ウィンドウを開き、cloud-functions-start
フォルダに移動し、次のコマンドを使用してウェブアプリを Firebase Hosting にデプロイします。
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
ウェブアプリを開く
最後の行に [Hosting URL] が表示されます。ウェブアプリが、https://<project-id>.firebaseapp.com という形式の URL から提供されるはずです。チャットアプリの機能する UI が表示されます。
[Google でログイン] ボタンを使用してアプリにログインし、必要に応じてメッセージを追加したり、画像を投稿したりしてください。
新しいブラウザで初めてアプリにログインする場合は、プロンプトが表示されたら通知を許可してください:
後で通知を有効にする必要があります。
誤って [ブロック] をクリックしてしまった場合は、Chrome のアドレスバーの URL の左側にある [🔒? 安全] ボタンをクリックし、[通知] の横にあるバーを切り替えることで、この設定を変更できます。
今回は、Firebase SDK for Cloud Functions を使用して機能を追加します。
6. Functions ディレクトリ
Cloud Functions を使用すると、サーバーをセットアップしなくても、クラウドでコードを簡単に実行できます。Firebase Auth、Cloud Storage、Firebase Firestore のデータベース イベントに反応する関数を作成する方法について説明します。認証から始めましょう。
Firebase SDK for Cloud Functions を使用する場合、関数のコードは functions
ディレクトリにあります(デフォルト)。関数のコードも Node.js アプリであるため、アプリに関する情報と依存関係を一覧表示する package.json
が必要です。
作業を容易にするため、コードを配置する functions/index.js
ファイルはすでに作成されています。次に進む前に、このファイルを確認してください。
cd functions
ls
Node.js になじみがない場合は、この Codelab を続ける前に詳細を学んでおくと便利です。
package.json
ファイルには、Firebase SDK for Cloud Functions と Firebase Admin SDK の 2 つの必要な依存関係がすでにリストされています。ローカルにインストールするには、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 の代わりに 3 つの関数を記述します。まず、必要な Node モジュールをインポートします。
7. Cloud Functions モジュールと Firebase Admin モジュールをインポートする
この Codelab には 2 つのモジュールが必要です。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.
Firebase Admin SDK は、Cloud Functions 環境または他の Google Cloud Platform コンテナにデプロイするときに自動的に構成できます。これは、引数なしで admin.initializeApp()
を呼び出すと行われます。
次に、ユーザーがチャットアプリに初めてログインしたときに実行される関数を追加し、ユーザーを歓迎するチャット メッセージを追加します。
8. 新規ユーザーを歓迎する
チャット メッセージの構造
FriendlyChat チャット フィードに投稿されたメッセージは Cloud Firestore に保存されます。メッセージに使用するデータ構造を見てみましょう。そのためには、「Hello World」という新しいメッセージをチャットに投稿します。
これは次のように表示されます。
Firebase コンソールで、[構築] セクションの下にある [Firestore Database] をクリックします。メッセージ コレクションと、作成したメッセージを含む 1 つのドキュメントが表示されます。
ご覧のとおり、チャット メッセージは、name
、profilePicUrl
、text
、timestamp
の各属性が messages
コレクションに追加されたドキュメントとして Cloud Firestore に保存されます。
ウェルカム メッセージの追加
最初の Cloud Functions の関数は、新規ユーザーをチャットに歓迎するメッセージを追加します。そのためには、functions.auth().onCreate
トリガーを使用します。このトリガーは、ユーザーが Firebase アプリに初めてログインするたびに関数を実行します。addWelcomeMessages
関数を index.js
ファイルに追加します。
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
オブジェクトに追加することで、Node で現在のファイルの外部から関数にアクセスできるようにします。これは Cloud Functions では必須です。
上記の関数では、「Firebase Bot」によって投稿された新しいウェルカム メッセージをチャット メッセージのリストに追加します。これを行うには、チャットのメッセージが保存される Cloud Firestore の messages
コレクションで add
メソッドを使用します。
これは非同期オペレーションであるため、Cloud Functions の関数の実行が早すぎないように、Cloud Firestore がいつ書き込みを完了したのかを示す Promise を返す必要があります。
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
関数をテストする
関数が正常にデプロイされたら、初めてログインするユーザーが必要です。
- ホスティング URL(
https://<project-id>.firebaseapp.com
の形式)を使用して、ブラウザでアプリを開きます。 - 新規ユーザーとして、[Sign In] ボタンを使用してアプリで初めてログインします。
- すでにアプリにログインしている場合は、Firebase コンソールの認証を開き、ユーザーのリストからアカウントを削除できます。もう一度ログインします。
- ログインすると、ウェルカム メッセージが自動的に表示されます。
9. 画像の管理
ユーザーはチャットにあらゆる種類の画像をアップロードできます。特に公開されているソーシャル プラットフォームでは、攻撃的な画像を管理することが常に重要です。FriendlyChat では、チャットに公開されている画像は Google Cloud Storage に保存されます。
Cloud Functions では、functions.storage().onFinalize
トリガーを使用して新しい画像のアップロードを検出できます。これは、Cloud Storage で新しいファイルがアップロードされるかファイルが変更されるたびに実行されます。
画像を管理するには、以下の処理を行います。
- Cloud Vision API を使用して、画像にアダルト コンテンツや暴力的コンテンツが含まれていないか確認します。
- 画像にフラグが付いている場合は、実行中の Functions インスタンスにダウンロードします。
- ImageMagick を使用して画像にぼかしを入れます。
- ぼかしを入れた画像を Cloud Storage にアップロードします。
Cloud Vision API を有効にする
この関数では Google Cloud Vision API を使用するため、Firebase プロジェクトで API を有効にする必要があります。こちらのリンクをクリックし、Firebase プロジェクトを選択して API を有効にします。
依存関係をインストールする
画像を管理するには、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 コアモジュール(path
、os
、fs
)をインポートするには、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 バケット内でファイルまたはフォルダが作成または変更されるとすぐにコードを実行します。blurOffensiveImages
関数を index.js
ファイルに追加します。
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
ツールを使用して画像にぼかしを入れ、ぼかしを入れた画像がストレージ バケットに再アップロードされます。次に、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
関数をテストする
関数が正常にデプロイされたら、次の手順を行います。
- ホスティング URL(
https://<project-id>.firebaseapp.com
の形式)を使用して、ブラウザでアプリを開きます。 - アプリにログインしたら、画像をアップロードします。
- 不適切な画像の中から最も適した画像をアップロード(または肉食のゾンビ画像)を選ぶと、しばらくすると、ぼかしを入れた画像で投稿が更新されます。
10. 新しいメッセージの通知
このセクションでは、新しいメッセージが投稿されたときにチャットの参加者に通知を送信する Cloud Functions の関数を追加します。
Firebase Cloud Messaging(FCM)を使用すると、プラットフォームをまたいでユーザーに通知を確実に送信できます。ユーザーに通知を送信するには、ユーザーの FCM デバイス トークンが必要です。使用しているチャット ウェブアプリは、ユーザーが新しいブラウザまたはデバイスで初めてアプリを開いたときに、すでにデバイス トークンを収集します。これらのトークンは、Cloud Firestore の fcmTokens
コレクションに保存されます。
ウェブアプリで FCM デバイス トークンを取得する方法については、Firebase ウェブ Codelab をご覧ください。
通知を送信する
新しいメッセージが送信されたタイミングを検出するには、functions.firestore.document().onCreate
Cloud Functions トリガーを使用します。このトリガーは、Cloud Firestore の特定のパスで新しいオブジェクトが作成されるとコードを実行します。sendNotifications
関数を index.js
ファイルに追加します。
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.');
}
});
上記の関数では、Cloud Firestore データベースからすべてのユーザーのデバイス トークンを収集し、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
関数をテストする
- 関数が正常にデプロイされたら、ホスティング URL(
https://<project-id>.firebaseapp.com
の形式)を使用してブラウザでアプリを開きます。 - 初めてアプリにログインする場合は、プロンプトが表示されたら通知を許可してください:
- チャットアプリのタブを閉じるか、別のタブを表示する: アプリがバックグラウンドで実行されている場合にのみ通知が表示されます。アプリがフォアグラウンドで動作しているときにメッセージを受信する方法については、こちらのドキュメントをご覧ください。
- 別のブラウザ(またはシークレット ウィンドウ)でアプリにログインし、メッセージを投稿します。最初のブラウザで通知が表示されます。
11. お疲れさまでした
Firebase SDK for Cloud Functions を使用してチャットアプリにサーバー側コンポーネントを追加しました。
学習した内容
- Firebase SDK for Cloud Functions を使用して Cloud Functions を作成する。
- Auth、Cloud Storage、Cloud Firestore イベントに基づいて Cloud Functions をトリガーする。
- ウェブアプリに Firebase Cloud Messaging サポートを追加する。
- Firebase CLI を使用して Cloud Functions をデプロイします。
次のステップ
- 他の Cloud Functions の関数のトリガータイプについて確認する。
- 作成するアプリで Firebase と Cloud Functions を使ってみてください。