Cloud Functions のコードを Firebase 拡張機能として再利用する

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.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 フィールドを検索してイベントに対応します。両方のフィールドが存在する場合は、ジオハッシュを計算し、指定されたドキュメント出力場所に結果を書き込みます。入力ドキュメントは 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 パラメータを受け取り、ジオハッシュを返します。この関数は、この Codelab では直接呼び出されませんが、Firebase 拡張機能で構成するものの例としてここに含めています。

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

拡張機能の Cloud Functions コードの機能がわかったので、配布用にパッケージ化します。すべての Firebase 拡張機能には、拡張機能の機能と動作を記述する 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 拡張機能の仕様に従うかを判断するために使用されます。この場合、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 は必須パラメータであり、すべての拡張機能は Cloud Functions に依存しているため、true に設定する必要があります。Cloud Functions には Blaze プランが必要です。

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

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

拡張機能リソースとは、拡張機能のインストール中に Firebase がプロジェクト内に作成するアイテムです。拡張機能はこれらのリソースを所有し、それらを操作する特定のサービス アカウントを持ちます。このプロジェクトでは、これらのリソースは Cloud Functions です。拡張機能は 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 データベースに対する読み取り権限と書き込み権限が必要です。extension.yaml ファイルの最後に、デベロッパーの Firebase プロジェクトのデータベースを操作するために拡張機能がアクセスする必要がある IAM ロールを指定します。
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 を使用しましたが、ここでは呼び出し可能関数と HTTPS 関数の両方をカバーする httpsTrigger を使用します。

コードチェック

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. まだ行っていない場合は、ダウンロードした拡張機能プロジェクトの functions フォルダで 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 を実行したら、エミュレータのウェブビューで Firestore タブに移動します。
  2. xv 数値フィールドと yv 数値フィールドを使用して、ドキュメントを users コレクションに追加します。

Firebase エミュレータに表示されるダイアログ ボックス。コレクション ID に「

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

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

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

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

拡張機能では、同じ拡張機能の複数のバージョンを同時にインストールできます。アンインストールすることで、以前にインストールした拡張機能との競合を回避できます。

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. 特別なパラメータを追加する前に、extension.yaml ファイルに追加するパラメータが 3 つあります。
  - 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 拡張機能マーケットプレイスでコードをテストする前に、拡張機能を使用する際に何が得られるかをデベロッパーが把握できるように、拡張機能を文書化する必要があります。

  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 またはエミュレータに push する前に、構成を含む .env ファイルが作成されます。
firebase ext:install /path/to/extension
  • プロジェクトをローカルにデプロイするため、Google Cloud Secret Manager ではなくローカル ファイルを使用するように指定します。

da928c65ffa8ce15.png

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

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

実際の Firebase プロジェクトに拡張機能をインストールできます。テストにはテスト プロジェクトを使用することをおすすめします。拡張機能のエンドツーエンドのフローをテストする場合や、使用する拡張機能のトリガーが Firebase Emulator Suite でまだサポートされていない場合は、このテスト ワークフローを使用します(拡張機能のエミュレータ オプションをご覧ください)。エミュレータは現在、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 またはエミュレータに push する前に、構成を含む .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 タブに移動します。
  2. x フィールドと y フィールドで指定されたコレクションにドキュメントを追加します。この場合、更新されたドキュメントは u/{uid} にあり、x フィールドは xvy フィールドは yv です。

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

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

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

8. 完了

最初の Cloud Functions を Firebase 拡張機能に変換できました。

extension.yaml ファイルを追加し、デベロッパーが拡張機能のデプロイ方法を選択できるように構成しました。次に、拡張機能のデベロッパーが拡張機能を設定する前にすべきことや、拡張機能のインストール後に必要な手順について説明するユーザー向けドキュメントを作成しました。

これで、Firebase 関数を配布可能な Firebase 拡張機能に変換するために必要な主な手順を理解できました。

次のステップ