Cloud Functions のコードを Firebase Extension として再利用する

1. 始める前に

Firebase Extension は、HTTP リクエスト、または Firebase と Google の他のプロダクト(Firebase Cloud Messaging、Cloud Firestore、Pub/Sub など)からのイベントのトリガーのいずれかに応答して、特定のタスクまたは一連のタスクを実行します。

作成するアプリの概要

この Codelab では、ジオハッシュ用の Firebase 拡張機能を作成します。拡張機能をデプロイすると、Firestore イベントに応答して、または呼び出し可能関数の呼び出しを通じて、X 座標と Y 座標がジオハッシュに変換されます。すべてのターゲット プラットフォームにデータを保存するために geofire ライブラリを実装する代わりに、この方法を使用すると、時間を節約できます。

Firebase コンソールに表示されている Geohash 拡張機能

ラボの内容

  • 既存の Cloud Functions の関数コードを配布可能な Firebase Extension に変換する方法
  • extension.yaml ファイルの設定方法
  • 拡張機能に機密性の高い文字列(API キー)を保存する方法
  • デベロッパーがニーズに合わせて拡張機能を設定できるようにする方法
  • 拡張機能のテストとデプロイの方法

必要なもの

  • Firebase CLI(インストールとログイン)
  • Google アカウント(Gmail アカウントなど)
  • Node.js と npm
  • お気に入りの開発環境

2. セットアップする

コードを取得する

この拡張機能に必要なものはすべて GitHub リポジトリにあります。まず、コードを取得して、お好みの開発環境で開きます。

  1. ダウンロードした zip ファイルを解凍します。
  2. 必要な依存関係をインストールするには、functions ディレクトリでターミナルを開き、npm install コマンドを実行します。

Firebase を設定する

この Codelab では、Firebase エミュレータを使用することを強くおすすめします。実際の Firebase プロジェクトで拡張機能の開発を試してみる場合は、Firebase プロジェクトの作成をご覧ください。この Codelab では Cloud Functions を使用するため、エミュレータではなく実際の Firebase プロジェクトを使用している場合は、Blaze の料金プランにアップグレードする必要があります。

先に進む場合

この Codelab の完全版はダウンロードできます。途中で行き詰まった場合や、完成した拡張機能がどのようなものかを知りたい場合は、GitHub リポジトリcodelab-end ブランチを確認するか、完成した zip をダウンロードしてください。

3. コードを確認する

  • zip ファイル内の index.ts ファイルを開きます。その中に 2 つの Cloud Functions 宣言が含まれていることに注意してください。

これらの関数の機能

これらのデモ関数は、ジオハッシュに使用されます。座標ペアを受け取り、Firestore での地理クエリに最適化された形式に変換します。これらの関数は API 呼び出しの使用をシミュレートするため、拡張機能でのセンシティブ データタイプの処理について詳しく学ぶことができます。詳細については、Firestore のデータに対して地域クエリを実行するをご覧ください。

関数定数

定数は、早い段階で index.ts ファイルの先頭で宣言されます。これらの定数の一部は、拡張機能で定義されたトリガーで参照されます。

index.ts

import {firestore} from "firebase-functions";
import {initializeApp} from "firebase-admin/app";
import {GeoHashService, ResultStatusCode} from "./fake-geohash-service";
import {onCall} from "firebase-functions/v1/https";
import {fieldValueExists} from "./utils";

const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

initializeApp();

const service = new GeoHashService(apiKey);

Firestore トリガー

index.ts ファイルの最初の関数は次のようになります。

index.ts

export const locationUpdate = firestore.document(documentPath)
  .onWrite((change) => {
    // item deleted
    if (change.after == null) {
      return 0;
    }
    // double check that both values exist for computation
    if (
      !fieldValueExists(change.after.data(), xField) ||
      !fieldValueExists(change.after.data(), yField)
    ) {
      return 0;
    }
    const x: number = change.after.data()![xField];
    const y: number = change.after.data()![yField];
    const hash = service.convertToHash(x, y);
    // This is to check whether the hash value has changed. If
    // it hasn't, you don't want to write to the document again as it
    // would create a recursive write loop.
    if (fieldValueExists(change.after.data(), outputField)
      && change.after.data()![outputField] == hash) {
      return 0;
    }
    return change.after.ref
      .update(
        {
          [outputField]: hash.hash,
        }
      );
  });

この関数は Firestore トリガーです。データベースで書き込みイベントが発生すると、関数は xv フィールドと yv フィールドを検索してそのイベントに反応し、両方のフィールドが存在する場合は Geohash を計算し、指定されたドキュメント出力場所に出力を書き込みます。入力ドキュメントは users/{uid} 定数によって定義されます。つまり、この関数は users/ コレクションに書き込まれたすべてのドキュメントを読み取り、それらのドキュメントの Geohash を処理します。次に、そのハッシュを同じドキュメントのハッシュ フィールドに出力します。

呼び出し可能関数

index.ts ファイルの次の関数は次のようになります。

index.ts

export const callableHash = onCall((data, context) => {
  if (context.auth == undefined) {
    return {error: "Only authorized users are allowed to call this endpoint"};
  }
  const x = data[xField];
  const y = data[yField];
  if (x == undefined || y == undefined) {
    return {error: "Either x or y parameter was not declared"};
  }
  const result = service.convertToHash(x, y);
  if (result.status != ResultStatusCode.ok) {
    return {error: `Something went wrong ${result.message}`};
  }
  return {result: result.hash};
});

onCall 関数に注目してください。これは、この関数が、クライアント アプリケーション コード内から呼び出すことができる呼び出し可能関数であることを示しています。この呼び出し可能関数は、x パラメータと y パラメータを受け取り、Geohash を返します。この関数は、この Codelab で直接呼び出すことはありませんが、Firebase 拡張機能で構成する例として、ここで紹介しています。

4. Extensions.yaml ファイルを設定する

拡張機能の Cloud Functions コードの役割がわかったので、次はそれを配布用にパッケージ化します。すべての Firebase Extension には、拡張機能の機能や動作を記述した extension.yaml ファイルが用意されています。

extension.yaml ファイルには、拡張機能に関する初期メタデータが必要です。以下の手順は、すべてのフィールドの意味とフィールドが必要な理由を理解するのに役立ちます。

  1. 先ほどダウンロードしたプロジェクトのルート ディレクトリに extension.yaml ファイルを作成します。まず以下を追加します。
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

拡張機能の名前は、拡張機能のインスタンス ID のベースとして使用されます(ユーザーは、それぞれ固有の ID を持つ拡張機能のインスタンスを複数インストールできます)。Firebase は、そのインスタンス ID を使用して、拡張機能のサービス アカウントと拡張機能固有のリソースの名前を生成します。バージョン番号は、拡張機能のバージョンを示します。セマンティック バージョニングに従う必要があり、拡張機能の機能を変更する場合は常に更新する必要があります。拡張機能仕様のバージョンは、どの Firebase Extensions 仕様に従うかを決定するために使用されます。この場合は、v1beta が使用されます。

  1. YAML ファイルにユーザー フレンドリーな情報をいくつか追加します。
...

displayName: Latitude and longitude to GeoHash converter
description: A converter for changing your Latitude and longitude coordinates to geohashes.

表示名は、デベロッパーが拡張機能を操作する際に、拡張機能の名前を示すわかりやすい名前です。説明では、この拡張機能の機能について簡単に説明します。拡張機能が extensions.dev にデプロイされると、次のようになります。

Extensions.dev に表示される Geohash Converter 拡張機能

  1. 拡張機能のコードのライセンスを指定します。
...

license: Apache-2.0  # The license you want for the extension
  1. 拡張機能の作成者と、インストールに課金が必要かどうかを示します。
...

author:
  authorName: AUTHOR_NAME
  url: https://github.com/Firebase

billingRequired: true

author セクションは、拡張機能に関する問題が発生したときや、拡張機能の詳細情報を求められた場合に連絡を取るユーザーに知らせるために使用されます。billingRequired は必須パラメータであり、true に設定する必要があります。これは、すべての拡張機能が Cloud Functions に依存し、Cloud Functions には Blaze プランが必要なためです。

これは、この拡張機能を識別するために extension.yaml ファイルに必要な最小数のフィールドに対応しています。拡張機能で指定できるその他の識別情報について詳しくは、こちらのドキュメントをご覧ください。

5. Cloud Functions のコードを拡張機能リソースに変換する

拡張機能のリソースは、拡張機能のインストール中に Firebase がプロジェクトに作成するアイテムです。拡張機能はこれらのリソースを所有し、それらを操作する特定のサービス アカウントを持ちます。このプロジェクトでは、これらのリソースは Cloud Functions であり、拡張機能は関数フォルダ内のコードからリソースを自動的に作成しないため、extension.yaml ファイルで定義する必要があります。Cloud Functions の関数がリソースとして明示的に宣言されていない場合、拡張機能のデプロイ時にデプロイできません。

ユーザー定義のデプロイ ロケーション

  1. ユーザーは、この拡張機能をデプロイする場所を指定して、拡張機能をホストする場所(エンドユーザーの近くとデータベースの近く)を決定できます。extension.yaml ファイルに、ロケーションを選択するオプションを含めます。

extension.yaml

これで、関数リソースの構成を書き込む準備が整いました。

  1. extension.yaml ファイルで、locationUpdate 関数のリソース オブジェクトを作成します。extension.yaml ファイルに次の行を追加します。
resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}

name は、プロジェクトの index.ts ファイルで定義された関数名として定義します。デプロイする関数の type を指定します。ここでは、常に firebaseextensions.v1beta.function にする必要があります。次に、この関数の properties を定義します。最初に定義するプロパティは、この関数に関連付けられた eventTrigger です。拡張機能が現在サポートしているものをミラーリングするには、拡張機能の Cloud Functions の関数を作成するのドキュメントにある providers/cloud.firestore/eventTypes/document.writeeventType を使用します。resource をドキュメントの場所として定義します。現在の目標はコードに存在するものをミラーリングすることであるため、ドキュメント パスはデフォルトのデータベースの場所を先頭に、users/{uid} をリッスンします。

  1. この拡張機能には、Firestore データベースに対する読み取りと書き込みの権限が必要です。デベロッパーの Firebase プロジェクト内のデータベースを操作するために、拡張機能がアクセスできる IAM ロールを extension.yaml ファイルの最後に指定します。
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

datastore.user ロールは、拡張機能でサポートされている IAM ロールのリストにあります。拡張機能は読み取りと書き込みを行うため、ここでは datastore.user ロールが適しています。

  1. 呼び出し可能関数も追加する必要があります。extension.yaml ファイルで、resources プロパティに新しいリソースを作成します。以下のプロパティは、呼び出し可能関数に固有のものです。
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

前のリソースでは eventTrigger を使用していましたが、ここでは httpsTrigger を使用しています。これは、呼び出し可能関数と HTTPS 関数の両方に対応しています。

コードチェック

これは、extension.yamlindex.ts ファイル内のコードによるすべてに一致させるための多くの構成でした。完成した extension.yaml ファイルは次のようになります。

extension.yaml

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

ステータス チェック

ここまでで、拡張機能の最初の機能のセットアップが完了したので、Firebase エミュレータを使用して実際に試してみることができます。

  1. まだ行っていない場合は、ダウンロードした拡張機能プロジェクトの関数フォルダで npm run build を呼び出します。
  2. ホストシステムに新しいディレクトリを作成し、firebase init を使用してそのディレクトリを Firebase プロジェクトに接続します。
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
    This command creates a `firebase.json` file in the directory. In the following steps, you push the configuration specified in this file to Firebase.
  1. 同じディレクトリから firebase ext:install を実行します。/path/to/extension は、extension.yaml ファイルを含むディレクトリの絶対パスに置き換えます。
firebase ext:install /path/to/extension
    This command does two things:
  • 拡張機能インスタンスの構成を指定するように求められ、インスタンスの構成情報を含む *.env ファイルが作成されます。
  • 拡張機能のインスタンスを firebase.jsonextensions セクションに追加します。これは、インスタンス ID から拡張機能のバージョンへのマップとして機能します。
  • プロジェクトをローカルにデプロイするため、Google Cloud Secret Manager ではなくローカル ファイルを使用するように指定できます。

この拡張機能のインストール時に、シークレットにローカル ファイルが使用されることを示す拡張機能のインストール プロセスのスクリーンショット

  1. 新しい構成で Firebase エミュレータを起動します。
firebase emulators:start
  1. emulators:start を実行したら、エミュレータの WebView で Firestore タブに移動します。
  2. xv 数値フィールドと yv 数値フィールドを含むドキュメントを users コレクションに追加します。

フレーズを含むコレクション ID でコレクションを開始するために Firebase エミュレータに表示されるダイアログ ボックス

  1. 拡張機能のインストールに成功すると、ドキュメント内に hash という新しいフィールドが作成されます。

xv、yv、hash フィールドを持つユーザー ドキュメントを持つ users コレクション。

競合を回避するためにクリーンアップする

  • テストが完了したら、拡張機能をアンインストールします。この拡張機能のコードが更新されるので、後で現在の拡張機能と競合しないようにできます。

拡張機能では、同じ拡張機能の複数のバージョンを一度にインストールできるため、アンインストールすることで、以前にインストールした拡張機能と競合しないようにできます。

firebase ext:uninstall geohash-ext

現在のソリューションは機能しますが、プロジェクトの開始時に述べたように、ハードコードされた API キーを使用してサービスとの通信をシミュレートします。最初に提供された API キーの代わりにエンドユーザーの API キーを使用するにはどうすればよいですか。以下で詳しく見ていきましょう。

6. 拡張機能をユーザーが構成可能にする

Codelab のこの時点で、記述した関数の独自の設定で使用するように構成された拡張機能がありますが、ユーザーがデカルト平面上の位置を示すフィールドに yx の代わりに緯度と経度を使用したい場合はどうでしょうか。また、エンドユーザーに提供された API キーの使用を許可せずに、独自の API キーを提供してもらうには、どうすればよいでしょうか。その API の割り当てがすぐに超過する可能性があります。この場合は、パラメータを設定して使用します。

extension.yaml ファイルで基本的なパラメータを定義する

まず、デベロッパーがカスタム設定を使用する可能性のある項目を変換します。1 つ目は XFIELD パラメータと YFIELD パラメータです。

  1. extension.yaml ファイルに、XFIELD フィールドと YFIELD フィールド パラメータを使用する次のコードを追加します。これらのパラメータは、以前に定義された params YAML プロパティ内にあります。

extension.yaml

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If no value is specified, the extension searches for
      field 'xv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      value. If no value is specified, the extension searches for
      field 'yv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  • param は、拡張機能プロデューサーから見える方法でパラメータに名前を付けます。この値は、後でパラメータ値を指定するときに使用します。
  • label: 人間が読める形式の識別子で、デベロッパーがパラメータの機能を伝えることができます。
  • description: 値の詳細な説明を示します。マークダウンをサポートしているため、追加のドキュメントにリンクしたり、デベロッパーにとって重要な単語を強調したりできます。
  • type は、ユーザーがパラメータ値を設定する方法の入力メカニズムを定義します。stringselectmultiSelectselectResourcesecret など、多くの型が存在します。これらの各オプションの詳細については、ドキュメントをご覧ください。
  • validationRegex は、デベロッパー エントリを特定の正規表現値に制限する(この例では、こちらに記載されている単純なフィールド名のガイドラインに基づく)。失敗したら...
  • validationErrorMessage はデベロッパーに失敗の値を通知します。
  • default は、デベロッパーがテキストを入力しなかった場合の値。
  • required は、デベロッパーがテキストを入力する必要がないことを意味します。
  • immutable を使用すると、デベロッパーはこの拡張機能を更新してこの値を変更できます。この場合、デベロッパーは要件の変化に応じてフィールド名を変更できる必要があります。
  • example は、有効な入力がどのようなものかを示します。

とても理解できました。

  1. 特別なパラメータを追加する前に、さらに 3 つのパラメータを extension.yaml ファイルに追加します。
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has received a value, it notifies the extension to
      calculate a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash

機密パラメータを定義する

次は、ユーザーが指定した API キーを管理者が管理する必要があります。これは機密文字列です。関数内に書式なしテキストで保存することは避けてください。代わりに、この値を Cloud Secret Manager に保存します。これは、暗号化されたシークレットが保存され、誤って漏洩するのを防ぐ、クラウド内の特別な場所です。デベロッパーはこのサービスの使用料を支払う必要がありますが、API キーにセキュリティのレイヤが追加され、不正行為が制限される可能性があります。ユーザー ドキュメントで有料サービスであることが開発者に通知されるので、想定外の請求が発生することはありません。全体的には、使用方法は前述の他の文字列リソースと同様です。唯一の違いは、secret と呼ばれる型です。

  • extension.yaml ファイルに、次のコードを追加します。

extension.yaml

  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

resource 属性を使用してパラメータを使用する

前述のように、(関数ではなく)リソースがリソースを監視する方法を定義するため、新しいパラメータを使用するには locationUpdate リソースを更新する必要があります。

  • extension.yaml ファイルに、次のコードを追加します。

extension.yaml

## Change from this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}]

## To this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}

extension.yaml ファイルを確認します。

  • extension.yaml ファイルを確認します。次のようになります。

extension.yaml

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want to use for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If you don't provide a value for this field, the extension will use 'xv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      Value. If you don't provide a value for this field, the extension will use 'yv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has been modified, it notifies the extension to
      compute a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash
  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

コード内のパラメータにアクセスする

すべてのパラメータを extension.yaml ファイルに構成したので、それらを index.ts ファイルに追加します。

  • index.ts ファイルで、デフォルト値を process.env.PARAMETER_NAME に置き換えます。これにより、適切なパラメータ値が取得され、デベロッパーの Firebase プロジェクトにデプロイされた関数コードに挿入されます。

index.ts

// Replace this:
const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

// with this:
const documentPath = process.env.INPUTPATH!; // this value is ignored since its read from the resource
const xField = process.env.XFIELD!;
const yField = process.env.YFIELD!;
const apiKey = process.env.APIKEY!;
const outputField = process.env.OUTPUTFIELD!;

通常は、環境変数の値で null チェックを実行する必要がありますが、この場合はパラメータ値が正しくコピーされることを信頼します。これで、拡張パラメータで動作するようにコードが構成されました。

7. ユーザー ドキュメントを作成する

エミュレータまたは Firebase Extensions Marketplace でコードをテストする前に、デベロッパーが拡張機能を使用したときに何が得られるかを把握できるように、拡張機能を文書化しておく必要があります。

  1. まず PREINSTALL.md ファイルを作成します。このファイルには、機能、インストールの前提条件、料金への影響が記述されています。

PREINSTALL.md

Use this extension to automatically convert documents with a latitude and
longitude to a geohash in your database. Additionally, this extension includes a callable function that allows users to make one-time calls
to convert an x,y coordinate into a geohash.

Geohashing is supported for latitudes between 90 and -90 and longitudes
between 180 and -180.

#### Third Party API Key

This extension uses a fictitious third-party API for calculating the
geohash. You need to supply your own API keys. (Since it's fictitious,
you can use 1234567890 as an API key).

#### Additional setup

Before installing this extension, make sure that you've [set up a Cloud
Firestore database](https://firebase.google.com/docs/firestore/quickstart) in your Firebase project.

After installing this extension, you'll need to:

- Update your client code to point to the callable geohash function if you
want to perform arbitrary geohashes.

Detailed information for these post-installation tasks are provided after
you install this extension.

#### Billing
To install an extension, your project must be on the [Blaze (pay as you
go) plan](https://firebase.google.com/pricing)

- This extension uses other Firebase and Google Cloud Platform services,
which have associated charges if you exceed the service's no-cost tier:
 - Cloud Firestore
 - Cloud Functions (Node.js 16+ runtime. [See
FAQs](https://firebase.google.com/support/faq#extensions-pricing))
 - [Cloud Secret Manager](https://cloud.google.com/secret-manager/pricing)
  1. このプロジェクトの README.md を記述する時間を節約するには、次の便利なメソッドを使用します。
firebase ext:info . --markdown > README.md

これにより、PREINSTALL.md ファイルの内容と extension.yaml ファイルの拡張機能に関する追加情報が組み合わされます。

最後に、インストールした拡張機能の詳しい情報をデベロッパーに知らせます。デベロッパーは、インストールの完了後に追加の手順や情報を取得したり、クライアント コードの設定など、インストール後の詳細な作業を行ったりすることがあります。

  1. POSTINSTALL.md ファイルを作成し、次のインストール後の情報を含めます。

POSTINSTALL.md

Congratulations on installing the geohash extension!

#### Function information

* **Firestore Trigger** - ${function:locationUpdate.name} was installed
and is invoked when both an x field (${param:XFIELD}) and y field
(${param:YFIELD}) contain a value.

* **Callable Trigger** - ${function:callableHash.name} was installed and
can be invoked by writing the following client code:
 ```javascript
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions();
const geoHash = httpsCallable(functions, '${function:callableHash.name}');
geoHash({ ${param:XFIELD}: -122.0840, ${param:YFIELD}: 37.4221 })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const error = data.error;
    if (error != null) {
        console.error(`callable error : ${error}`);
    }
    const result = data.result;
    console.log(result);
  });

モニタリング

インストールされている拡張機能のアクティビティをモニタリングすることをおすすめします。これには、拡張機能の健全性、使用状況、ログの確認が含まれます。

The output rendering looks something like this when it's deployed:

<img src="img/82b54a5c6ca34b3c.png" alt="A preview of the latitude and longitude geohash converter extension in the firebase console"  width="957.00" />


## Test the extension with the full configuration
Duration: 03:00


It's time to make sure that the user-configurable extension is working the way it is intended.

* Change into the functions folder and ensure that the latest compiled version of the extensions exists. In the extensions project functions directory, call:

```console
npm run build

これにより関数が再コンパイルされ、エミュレータまたは Firebase に直接デプロイされたときに、最新のソースコードを拡張機能とともにデプロイできるようになります。

次に、拡張機能のテストに使用する新しいディレクトリを作成します。この拡張機能は既存の関数から開発されているため、拡張機能が構成されているフォルダからテストしないでください。関数と Firebase ルールも一緒にデプロイされます。

Firebase エミュレータでインストールしてテストする

  1. ホストシステムに新しいディレクトリを作成し、firebase init を使用してそのディレクトリを Firebase プロジェクトに接続します。
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. そのディレクトリで firebase ext:install を実行して拡張機能をインストールします。/path/to/extension は、extension.yaml ファイルを含むディレクトリの絶対パスに置き換えます。これにより、拡張機能のインストール プロセスが開始され、構成が Firebase またはエミュレータにプッシュされる前に、構成を含む .env ファイルが作成されます。
firebase ext:install /path/to/extension
  • プロジェクトをローカルにデプロイするため、Google Cloud Secret Manager ではなくローカル ファイルを使用することを指定します。

da928c65ffa8ce15.png

  1. ローカル エミュレータ スイートを起動します。
firebase emulators:start

実際の Firebase プロジェクトでインストールしてテストする

実際の Firebase プロジェクトに拡張機能をインストールできます。テストにはテスト プロジェクトを使用することをおすすめします。拡張機能のエンドツーエンドのフローをテストする場合や、拡張機能のトリガーが Firebase Emulator スイートでまだサポートされていない場合は、このテスト ワークフローを使用します(拡張機能のエミュレータ オプションをご覧ください)。エミュレータは現在、Cloud Firestore、Realtime Database、Pub/Sub の HTTP リクエスト トリガー関数とバックグラウンド イベント トリガー関数をサポートしています。

  1. ホストシステムに新しいディレクトリを作成し、firebase init を使用してそのディレクトリを Firebase プロジェクトに接続します。
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. 次に、そのディレクトリから firebase ext:install を実行して拡張機能をインストールします。/path/to/extension は、extension.yaml ファイルを含むディレクトリの絶対パスに置き換えます。これにより、拡張機能のインストール プロセスが開始され、構成が Firebase またはエミュレータにプッシュされる前に、構成を含む .env ファイルが作成されます。
firebase ext:install /path/to/extension
  • Firebase に直接デプロイし、Google Cloud Secret Manager を使用するため、拡張機能をインストールする前に Secret Manager API を有効にする必要があります。
  1. Firebase プロジェクトにデプロイします。
firebase deploy

拡張機能をテストする

  1. firebase deploy または firebase emulators:start を実行したら、必要に応じて Firebase コンソールの [Firestore] タブか、エミュレータの WebView に移動します。
  2. x フィールドと y フィールドで指定されたコレクションにドキュメントを追加します。この場合、更新されたドキュメントは u/{uid} にあり、x フィールドが xvy フィールドが yv になっています。

Firestore レコードを追加する Firebase エミュレータの画面

  1. 拡張機能のインストールに成功すると、2 つのフィールドを保存すると、ドキュメントに hash という新しいフィールドが作成されます。

ハッシュが追加されたことを示すエミュレータの Firestore データベース画面

8. 完了

最初の Cloud Functions の関数を Firebase Extension に変換できました。

extension.yaml ファイルを追加して構成し、拡張機能のデプロイ方法をデベロッパーが選択できるようにしました。次に、拡張機能のデベロッパーが、拡張機能をセットアップする前に行うべきことと、拡張機能が正常にインストールした後に行う必要がある手順について、ガイダンスを提供するユーザー ドキュメントを作成しました。

ここでは、Firebase Functions を配布可能な Firebase 拡張機能に変換するために必要な主な手順を学習しました。

次のステップ