🦄

Unityから Firebase Functions を使う - UnityでSDKなしでFirebaseを使う

2021/07/24に公開

サーバー側で何か処理させたい場合にAPIを作成しますが、FirebaseではFunctionsというAPIを簡単に作成できる機能があります。FunctionsにはSDK向けのonCallとより一般的なAPIを作成するonRequestという2つの関数があるのでそれらの違いと使い方について解説します。

この投稿はUnityでSDKなしでFirebaseを使うの一部です。

途中で出てくるソースコードの全体は以下にあります。
Firebase側:
https://github.com/satouso0401/firebase-unity-not-use-sdk-firebase/tree/main/functions

Unity側:
https://github.com/satouso0401/firebase-unity-not-use-sdk/tree/main/Assets/Scenes/Functions

onCallとonRequestの違い

functions.https.onCallはFirebase SDK向けのFunctionでFirebase、SDK両方で連携して認証などを行ってくれるので、SKDの利用者はFunctionsを安全で簡単に利用する事ができます。
また、onCallはプロトコル仕様が公開されているのでクライアント側を自分で実装することでSDK以外からも呼び出すことができます。

functions.https.onRequestはより一般的なWebAPIを作成するFunctionでFirebase SDKからは呼び出せないもののhttpが使えるクライアントであれば環境を選ばず使用することができます。また、onCallはPOSTメソッドしか作成できないためRESTfulなAPIを作成する場合はonRequestを使用する必要があります。欠点としてonCallで自動的に行ってくれていた認証などは自前で実装する必要があります。

まとめると以下の通りです。

次の項では実際の実装方法について解説していきますが、Unity SDKからFunctionsを呼び出す方法は公式ドキュメントやネット上に様々な紹介記事があるので省略します。

Functionsを利用する準備

Firebasseプロジェクト作成Firebasseローカル開発環境作成を実施します。

また、前述の通りonCallは認証が必要なので、いずれかの方法で認証を行ってidTokenが取得できるようにおく必要があります。

Firebasseローカル開発環境のfunctionsフォルダ配下に後述するFunctions用のtsファイルを作成して プロジェクトのルードディレクトリ(functionsフォルダの一つ上のフォルダ)でfirebase deploy --only functions コマンドを実行することでAPIを作成することができます。
またAPIのエンドポイントのURLはFirebaseのWebの管理画面のサイドメニューのFunctionsから確認することができます。

SDKを使わずonCallのFunctionを呼び出す

サンプルとしてメッセージを送ると「!」をつけてメッセージを返してくるstrongEchoOnCallAPIを作成しUnityから呼び出す方法を解説します。

Firebasse Functions側

Firebaseのローカル開発環境のfunctions/src配下のtsファイルににAPIを実装します。
index.tsにAPIの処理内容を直接書くこともできますが、複数のAPIを作る際にはindex.tsに全てのエンドポイントを書く必要があるので、コードの見通しをよくするため、処理本体は別のファイルにhandlerとして実装してindex.tsではhandlerを呼び出すだけにします。

functions/src/index.ts
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";

const strongEchoOnCall = require("./handlers/strong-echo-on-call");

admin.initializeApp();

exports.strongEchoOnCall = functions
    .region("asia-northeast1").https.onCall(strongEchoOnCall.handler);

前述の通りonCallの場合は認証部分は自分で実装する必要がないので、受け取ったリクエスト中からmessageプロパティからメッセージを取り出して!を付けた文字列をechoMessageとして返す処理を実装します。

functions/src/handlers/strong-echo-on-call.ts
import {CallableContext} from "firebase-functions/lib/providers/https";

exports.handler = (requestData: any, context: CallableContext) => {
  if (context.auth) { // 認証済みであれば認証情報がcontext内に存在する
    return {echoMessage: requestData.message + "!"};
  } else {
    return {errorMessage: "auth error"};
  }
};

onCallのhandlerの実装のポイントは以下の2点です。

  • requestDataにクライアントが送ってきたjsonが渡ってくる
  • returnでクライアントに送り返すjsonを返す

実装でできたらfirebase deploy --only functionsでデプロイします。

Unity側

Unity側ではメッセージを送って、帰ってきたエコーメッセージをログに出力する処理を実装をします。
urlはFirebaseのWeb管理画面のFuncitonsから確認できます。
idTokenこの投稿を参考に取得してください。

SampleFunctionsScript.sc
void StrongEchoOnCall()
{
    var url = "https://asia-northeast1-[プロジェクトID].cloudfunctions.net/strongEchoOnCall";
    var idToken = "****";

    WebClient wc = new WebClient();
    wc.Headers[HttpRequestHeader.ContentType] = "application/json";
    wc.Headers[HttpRequestHeader.Authorization] = $"Bearer {idToken}";
    var requestJson = new StrongEchoOnCallRequest("おはようございます").ToJson();
    var responseJson = wc.UploadString(new Uri(url), requestJson);
    var response = JsonUtility.FromJson<StrongEchoOnCallResponse>(responseJson);
    Debug.Log(response.result.echoMessage);
}

// 以下Jsonの変換用クラス
[Serializable]
class StrongEchoOnCallRequest
{
    public StrongEchoOnCallRequestData data;
    public StrongEchoOnCallRequest(string message)
    {
	this.data = new StrongEchoOnCallRequestData(message);
    }

    public String ToJson()
    {
	return JsonUtility.ToJson(this);
    }
}

[Serializable]
class StrongEchoOnCallRequestData
{
    public string message;
    public StrongEchoOnCallRequestData(string message)
    {
	this.message = message;
    }

    public String ToJson()
    {
	return JsonUtility.ToJson(this);
    }
}

[Serializable]
class StrongEchoOnCallResponse
{
    public StrongEchoOnCallResponseResult result;
    public static StrongEchoOnCallResponse FromJson(string json)
    {
	return JsonUtility.FromJson<StrongEchoOnCallResponse>(json);
    }
}

[Serializable]
class StrongEchoOnCallResponseResult
{
    public string echoMessage;
}

Unity側の実装のポイントは以下の3点です

  • 認証に使うidTokenはAuthorizationヘッダーに設定する
  • リクエストはFunctionsに送りたい情報をjsonのdataプロパティーの値にする
  • レスポンスはFunctionsから受け取りたい情報をjsonのresultプロパティーの値にする

SDKを使わずonRequestのFunctionを呼び出す

onCallの場合と同様にメッセージを送ると「!」をつけてメッセージを返してくるstrongEchoOnCallAPIを作成しUnityから呼び出す方法を解説します。

Firebasse Functions側

index.tsの実装方針についてはonCallと同様です。

functions/src/index.ts
import * as functions from "firebase-functions";
import * as express from "express";
import * as admin from "firebase-admin";

const strongEchoOnRequest = require("./handlers/strong-echo-on-request");

admin.initializeApp();
const expressApp = express();

expressApp.post("/", strongEchoOnRequest.handler);
exports.strongEchoOnRequest = functions
    .region("asia-northeast1").https.onRequest(expressApp);

以下がhandlerの実装です。
前述の通りonRequestは自動的に認証されないため、もし認証が必要な場合は何らかの方法で認証の処理を行います。今回のサンプルではクライアントからのリクエストにidTokenを含めてもらいadmin.auth().verifyIdToken()を使って認証しています。
認証が不要なAPIの場合はres.status(200).json({echoMessage: strongEcho});の部分だけ実装すれば良いです。

functions/src/handlers/strong-echo-on-request.ts
import * as express from "express";
import * as admin from "firebase-admin";

exports.handler = async (req: express.Request, res: express.Response) => {
  admin
      .auth().verifyIdToken(req.body.idToken).then((decodedIdToken) => {
        console.log("uid: ", decodedIdToken.uid);
        const strongEcho = req.body.message + "!";
        res.status(200).json({echoMessage: strongEcho});
      }).catch((error) => {
        console.log("error: ", error);
        res.status(400).json({message: "auth error"});
      });
};

onRequestのhandlerの実装のポイントは以下の2点です。

  • req.bodyにクライアントが送ってきたjsonが渡ってくる
  • クライアントへのレスポンスはresにstatusとjsonで設定する

実装でできたらfirebase deploy --only functionsでデプロイします。

Unity側

Unity側ではメッセージを送って、帰ってきたエコーメッセージをログに出力する処理を実装をします。
urlidTokenについてはonCallと同様です。

Unity側ではonCallと違いonRequestはより一般的なAPIを扱うためdataやreulstに包まずに送受信したいjsonを直接扱います。

SampleFunctionsScript.cs
void StrongEchoOnRequest()
{
    var url = "https://asia-northeast1-[プロジェクトID].cloudfunctions.net/strongEchoOnCall";
    var idToken = "****";

    WebClient wc = new WebClient();
    wc.Headers[HttpRequestHeader.ContentType] = "application/json";
    var requestJson = new StrongEchoOnRequestRequest("おはようございます", idToken).ToJson();
    var responseJson = wc.UploadString(new Uri(url), requestJson);
    var response = JsonUtility.FromJson<StrongEchoOnRequestResponse>(responseJson);
    Debug.Log(response.echoMessage);
}

// 以下Jsonの変換用クラス
[Serializable]
class StrongEchoOnRequestRequest
{
    public string message;
    public string idToken;

    public StrongEchoOnRequestRequest(string message, string idToken)
    {
	this.message = message;
	this.idToken = idToken;
    }

    public String ToJson()
    {
	return JsonUtility.ToJson(this);
    }
}

[Serializable]
class StrongEchoOnRequestResponse
{
    public string echoMessage;

    public static StrongEchoOnRequestResponse FromJson(string json)
    {
	return JsonUtility.FromJson<StrongEchoOnRequestResponse>(json);
    }
}

参考

https://firebase.google.com/docs/functions/callable?hl=ja
https://firebase.google.com/docs/functions/http-events?hl=ja

Discussion