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

1. 始める前に

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

作成するアプリの概要

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

Firebase コンソールに表示されるジオハッシュ拡張機能

ラボの内容

  • 既存の 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/ コレクションに書き込まれたすべてのドキュメントを読み取り、それらのドキュメントのジオハッシュを処理します。次に、ハッシュを同じドキュメント内のハッシュ フィールドに出力します。

呼び出し可能関数

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. 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 は必須のパラメータです。すべての拡張機能は Cloud Functions に依存しており、Blaze プランが必要となるため、true に設定する必要があります。

これは、この拡張機能を識別するために 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 データベースに対する読み取りと書き込みの権限が必要です。デベロッパーの 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 コレクションに追加します。

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

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

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

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

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

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

firebase ext:uninstall geohash-ext

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

6. 拡張機能をユーザーが構成できるようにする

この時点で、すでに作成した関数の独自の設定で使用するように構成された拡張機能が作成されていますが、ユーザーが座標平面上の位置を示すフィールドに 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 拡張機能マーケットプレイスでコードをテストする前に、拡張機能のドキュメントを作成して、デベロッパーが拡張機能を使用する際に何を得られるかを把握できるようにする必要があります。

  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 ファイルを含むディレクトリの絶対パスに置き換えます。これにより、拡張機能のインストール プロセスが開始され、構成を含む .env ファイルが作成されます。その後、Firebase またはエミュレータに構成が push されます。
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 タブに移動します。
  2. x フィールドと y フィールドで指定されたコレクションにドキュメントを追加します。この場合、更新されたドキュメントは u/{uid} にあり、x フィールドが xvy フィールドが yv になっています。

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

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

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

8. 完了

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

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

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

次のステップ