Cloud Functionsは、アプリ開発者の僕にとって、普段の開発環境と全然違っていて馴染みにくいものだったので、Mac環境で、Cloud Functionsを導入し、FirebaseAuthやCloud Firestoreと連携していくまでの手順を丁寧に残しておきます。
前準備
前準備として、firebaseにプロジェクトを作成して、プランをBlazeに変更してください。
Cloud Functionsのインストール
npmを使ってCloud Functionsを実行するプログラム、firebase-toolsをインストールします。
「npm」は、Node.jsの追加機能をインストールできるプログラムで、Node.jsはJavaScriptをサーバーサイド実行するプログラムです。
Node.jsとnpmは、Macに機能を追加できるプログラム、homebrewで追加します。
という訳で段階に分けてインストール手順を解説します。
homebrewのインストール
homebrewをインストールしていない場合は、公式サイトにある次のコマンドをターミナルで実行してインストールしてください。
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
成功したら、brewコマンドが使えるようになります。
node.jsのインストール
node.jsをインストールしていない場合は、まずは複数のバージョンのnode.jsを管理できる「nodebrew」をhomebrewでインストールします。
brew install nodebrew
nodebrewのインストールが完了したら、nodebrewのパスを通します。
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
次に最新安定版のNode.jsをインストールします。
nodebrew install stable
次のようなエラーが出てインストールに失敗した場合は
Fetching: https://nodejs.org/dist/v20.3.1/node-v20.3.1-darwin-x64.tar.gz
Warning: Failed to open the file
Warning: /Users/tomohiroataka/.nodebrew/src/v20.3.1/node-v20.3.1-darwin-x64.tar
Warning: .gz: No such file or directory
0.0%curl: (23) Failure writing output to destination
download failed: https://nodejs.org/dist/v20.3.1/node-v20.3.1-darwin-x64.tar.gz
次のコマンドを実行してディレクトリを作ってあげてから改めてNode.jsのインストールを試して見てください。
mkdir -p ~/.nodebrew/src
次のコマンドでインストールされたNode.jsのバージョンが確認できます。
nodebrew ls
次のコマンドで、使うNode.jsを指定してNode.jsの設定は完了です。
nodebrew use [Node.jsのバージョン: v20.4.0とか]
npmのインストール
npmは、nodebrewのフォルダーの中に入っているので、パスを通した時点で使えるようになるようです。
次のコマンドでnpmのバージョンが確認できます。
npm -v
firebase-toolsのインストール
Node.jsもnpmも準備できたら、Cloud Functionsをローカル環境で実行するためのプログラム、firebase-toolsをnpmでインストールします。
npm install -g firebase-tools
これでCloud Functionsを利用する環境が整いました。
プロジェクトの作成
Firebaseで作成したプロジェクトと紐づいた、Cloud Functionsのプロジェクトをまずは作成します。
ターミナルでcdコマンドを使って、Cloud Functionsのプロジェクトファイルを作成したい場所まで移動したら、まずはfirebaseにログインします。
firebase login
その後、Cloud Functionsを実行する環境を新規作成するために、次のコマンドを実行します。
firebase init functions
選択肢がでるので「Use an existing project」を選択。
Firebaseに登録してあるプロジェクト一覧が出てくるので、Cloud Functionsを使いたいプロジェクトを選択。
使う言語の選択肢(JavaScriptかTypeScript)が出てくるので「JavaScript」を選択。
ESLintでコーディング規約を矯正させるかどうかの選択肢が出てくるので、今回は「N」を選択。
最後に、npmで関連するライブラリーをインストールするか聞かれるので、もちろん「Y」
以上で、次のようなファイルが作成されます。
サンプルコードのエミュレータ実行
処理は、functionsフォルダに入っている、index.jsに書いていきます。
index.jsを開くとサンプルコードが最初から書いてあります。
こちらのサンプルコードをローカルで実行してみます。
const functions = require("firebase-functions");
// // Create and deploy your first functions
// // https://firebase.google.com/docs/functions/get-started
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// functions.logger.info("Hello logs!", {structuredData: true});
// response.send("Hello from Firebase!");
// });
まずは、コメントアウトを消して保存。
const functions = require("firebase-functions");
// Create and deploy your first functions
// https://firebase.google.com/docs/functions/get-started
exports.helloWorld = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", {structuredData: true});
response.send("Hello from Firebase!");
});
ターミナルでfunctionsフォルダに移動したら、次のコマンドを実行して、エミュレータを起動します。
npm run serve
こんな表示がでたら、エミュレータの起動成功です。
上の起動表示の中に「http function initialized」とう文字のあとに、URLが書いています。
http://127.0.0.1:5001/test-eureka5/us-central1/helloWorld
こちらが、先程コメントアウトして有効にしたサンプルソースのエンドポイントになるので、コピーしてブラウザーで表示してみるとこうなります。
サンプルソースに書いてあったログ出力とレスポンス処理がちゃんと実行されました。
// ログ出力
functions.logger.info("Hello logs!", {structuredData: true});
// レスポンス処理
response.send("Hello from Firebase!");
サンプルコードの本番配信(デプロイ)と本番実行
次は、先程エミュレータで実行したサンプルを本番環境に配信して、本番で実行させてみます。
本番配信は次のコマンドで実行できます。
firebase deploy --only functions
Deploy complete!と表示されたら、配信完了。
complete!という表示のすぐ上に、本番のエンドポイントのURLが書いてあるので、そちらを実行すると、シミュレータで実行した時と同じ表示が確認できます。
本番のログは、Google Cloud Console ( https://console.cloud.google.com/ ) にログインして、上の検索窓で「Cloud Functions」を検索し配信したFunctions一覧から、ログをみたい項目を選ぶと
確認できます。
Cloud Firestoreとの連携
Cloud Functions内からFirestoreを読み込んでみます。
まずは読み込むデータをFirestoreに作成します。
今回は「tests」コレクションを作って、次の2つのドキュメントを追加しておきます。
name: “ataka”
job: “developer”
name: “tanaka”
job: “gamer”
次に、index.jsのサンプルソースを消して、Firestoreを読み込むコードに書き換えます。
Cloud Functionsのコードは、Visual Studio Codeで編集するとコードヒントも出て楽なのでオススメです。
const functions = require("firebase-functions");
// firestoreを読み込むにはfirebase-adminを使う。
// adminにinitializeApp()しないと使えません。
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
// awaitを使いたいので、onRequestの後にasync入れます。
exports.firestoreTest = functions.https.onRequest(async (request, response) => {
var res = "";
const testsSnapshot = await db.collection('tests').get();
testsSnapshot.forEach( doc => {
res += "名前:" + doc.data().name + " 職業:" + doc.data().job + "</br>";
});
response.send(res);
});
「npm run serve」コマンドでシミュレータを起動して、ターミナルに表示されるURLをブラウザで表示すると、Firestoreを読み込めていることがわかります。
次にFirestoreを更新してみます。
名前が「ataka」の人の職業を「youtuber」にしてみます。
where句でデータの取得範囲を絞っていますが、詳しい内容は公式のコチラ参照で。
exports.firestoreUpdateTest = functions.https.onRequest(async (request, response) => {
const testsSnapshot = await db.collection('tests').where("name", "==", "ataka").get();
testsSnapshot.forEach( doc => {
doc.ref.update({
job: "youtuber"
});
});
response.send("complete");
});
実行した後、testsの中身を表示するエンドポイントをブラウザーで表示すると、無事、atakaが、youtuberになりました。
アプリからCloud Functionsを呼び出す
アプリからCloud Functionsを呼び出してみます。
次のような「HTTP 呼び出し可能関数」をindex.jsに追加してアプリから呼び出せるようにします。
サーバーからはtestsコレクションの内容を全部まとめて送ります。
exports.firestoreTestForApp = functions.https.onCall(async (data, context) => {
var res = "";
const testsSnapshot = await db.collection('tests').get();
testsSnapshot.forEach( doc => {
res += "名前:" + doc.data().name + " 職業:" + doc.data().job + "\n";
});
return {
result: res
};
});
サーバーのソースをデプロイした後、アプリ側ではCloud Functionsのライブラリーを追加して、Cloud Functionsに定義した関数を呼呼び出します。
Unityから呼び出す場合は以下からFirebaseのライブラリー群をダウンロードします。
今回は、現時点で最新のver11.2.0をダウンロードしました。
zipを解凍して、中にある「FirebaseFunctions.unitypackage」をimportします。
そして、次のコードでUnityからCloud Functionsの関数を呼び出し…
using Firebase.Functions;
public void testFunctions()
{
FirebaseFunctions.DefaultInstance.GetHttpsCallable("firestoreTestForApp").CallAsync().ContinueWith(task =>
{
if (task.IsCompleted)
{
// 結果を取得します。
var response = (IDictionary)task.Result.Data;
Debug.Log(response["result"]);
}
});
}
このようにFireStoreのコレクション内容を取得できます。
iOSやAndroidネイティブからCloud Functionsの関数を呼び出す方法は、公式のコチラを参考にしてみてください。
FirebaseAuthとの連携
実際のサービスでアプリからCloud Functionsの関数を呼び出す場合、FirebaseAuthと連携して、ユーザーに関連したデータの更新などを行うと思いますので、FirebaseAuthと連携したCloud Functionsの関数を呼び出しの例を紹介します。
シンプルに説明するため、ユーザーがCloud Functionsの関数を呼んだら、サーバー側では認証情報を元にユーザー名を取得しただ返すという、実用性のあまりない内容とします。
サーバー側のソースはこんな感じ。
ログイン状態をチェックして、認証されていない場合はエラー、認証されている場合はusersコレクションから名前を取得して返します。
exports.firestoreTestForAppWithAuth = functions.https.onCall(async (data, context) => {
// 認証情報を取得
const auth = context.auth;
if (!auth) {
// 認証情報が存在しない場合のエラーハンドリング
throw new functions.https.HttpsError('unauthenticated', '認証されていません。');
}
// ユーザーIDを取得
const userId = auth.uid;
const userDoc = await db.collection('users').doc(userId).get();
return userDoc.data().name;
});
Unity側ではCloud Functionsの関数を呼び、認証されていなかった場合はtask.IsFaultedに、認証されている場合は名前が表示します。
public void testFunctions2()
{
FirebaseFunctions.DefaultInstance.GetHttpsCallable("firestoreTestForAppWithAuth").CallAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.Log($"認証エラー {task.Exception}");
}
else if (task.IsCompleted)
{
Debug.Log(task.Result.Data);
}
});
}
認証されている場合は、こんな感じにusersコレクションに登録していた自分の名前が取れます。
認証されていない場合は、こんな感じにエラー情報が取れます。
一定間隔でCloud Functionsを呼び出す
now printing…
FireStoreの変更に合わせてCloud Functionsを呼び出す
now printing…
Cloud Storageの変更に合わせてCloud Functionsを呼び出す
now printing…