拡張機能を作成する

このページでは、シンプルな Firebase 拡張機能を作成するのに必要な手順について説明します。拡張機能は、プロジェクトにインストールしたり、他のユーザーと共有したりすることができます。このページで扱うシンプルな Firebase 拡張機能では、Realtime Database でメッセージを監視し、そのメッセージを大文字に変換します。

1. 環境を設定してプロジェクトを初期化する

拡張機能の作成を始める前に、必要なツールを使用してビルド環境を設定する必要があります。

  1. Node.js 16 以降をインストールします。Node のインストール方法としては、nvm(または nvm-windows)を使用する方法があります。

  2. Firebase CLI の最新バージョンをインストールするか、最新バージョンに更新します。npm を使用してインストールまたは更新する場合は、次のコマンドを実行します。

    npm install -g firebase-tools
    

Firebase CLI を使用して新しい拡張機能プロジェクトを初期化します。

  1. 拡張機能のディレクトリを作成し、cd でそのディレクトリに移動します。

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
    
  2. Firebase CLI の ext:dev:init コマンドを実行します。

    firebase ext:dev:init
    

    プロンプトが表示されたら、関数の言語として JavaScript を選択します(独自の拡張機能を開発する場合は TypeScript も使用できます)。依存関係をインストールするよう求められたら、「yes」と答えます(その他のオプションのデフォルトの値を使用します)。このコマンドにより、新しい拡張機能のスケルトン コードベースが設定され、そこから拡張機能の開発を始めることができます。

2. エミュレータを使用してサンプルの拡張機能を試す

Firebase CLI によって新しい拡張機能のディレクトリが初期化され、簡単なサンプル関数と integration-tests ディレクトリが作成されました。このディレクトリには、Firebase Emulator Suite で拡張機能を実行するために必要なファイルが含まれています。

エミュレータを使用して、サンプルの拡張機能を実行してみましょう。

  1. integration-tests ディレクトリに移動します。

    cd functions/integration-tests
    
  2. デモ プロジェクトでエミュレータを起動します。

    firebase emulators:start --project=demo-test
    

    エミュレータが、事前定義のダミー プロジェクト(demo-test)に拡張機能を読み込みます。この拡張機能は単一の HTTP トリガー関数 greetTheWorld で構成されています。この関数は、アクセスされると「hello world」というメッセージを返します。

  3. エミュレータを実行したまま、エミュレータの起動時に出力された URL にアクセスして、拡張機能の greetTheWorld 関数を実行します。

    ブラウザに「Hello World from greet-the-world」というメッセージが表示されるはずです。

  4. この関数のソースコードは、拡張機能の functions ディレクトリにあります。このソースを任意のエディタまたは IDE で開きます。

    functions/index.js

    const functions = require("firebase-functions");
    
    exports.greetTheWorld = functions.https.onRequest((req, res) => {
      // Here we reference a user-provided parameter
      // (its value is provided by the user during installation)
      const consumerProvidedGreeting = process.env.GREETING;
    
      // And here we reference an auto-populated parameter
      // (its value is provided by Firebase after installation)
      const instanceId = process.env.EXT_INSTANCE_ID;
    
      const greeting = `${consumerProvidedGreeting} World from ${instanceId}`;
    
      res.send(greeting);
    });
    
  5. エミュレータの実行中は、Functions コードに行った変更が自動的に再読み込みされます。greetTheWorld 関数を少し変更してみましょう。

    functions/index.js

    const greeting = `${consumerProvidedGreeting} everyone, from ${instanceId}`;
    

    変更を保存します。エミュレータでコードが再読み込みされます。関数の URL にアクセスしてみてください。更新された応答メッセージが表示されるはずです。

3. extension.yaml に基本的な情報を追加する

開発環境が設定され、拡張機能エミュレータが実行されています。では、独自の拡張機能の作成してみましょう。

まず、事前定義された拡張機能のメタデータを編集します。greet-the-world ではなく、作成する拡張機能を反映します。このメタデータは extension.yaml ファイルに格納されます。

  1. エディタで extension.yaml を開き、ファイル全体を次のように置き換えます。

    name: rtdb-uppercase-messages
    version: 0.0.1
    specVersion: v1beta  # Firebase Extensions specification version; don't change
    
    # Friendly display name for your extension (~3-5 words)
    displayName: Convert messages to upper case
    
    # Brief description of the task your extension performs (~1 sentence)
    description: >-
      Converts messages in RTDB to upper case
    
    author:
      authorName: Your Name
      url: https://your-site.example.com
    
    license: Apache-2.0  # Required license
    
    # Public URL for the source code of your extension
    sourceUrl: https://github.com/your-name/your-repo
    

    name フィールドに適用される命名規則に注意してください。公式の Firebase 拡張機能の名前には、拡張機能が動作するプライマリ Firebase プロダクトを示す接頭辞が付き、その後に拡張機能の説明が続きます。独自の拡張機能でも同じ規則に従う必要があります。

  2. 拡張機能の名前を変更したので、エミュレータの構成も新しい名前で更新する必要があります。

    1. functions/integration-tests/firebase.json で、greet-the-worldrtdb-uppercase-messages に変更します。
    2. functions/integration-tests/extensions/greet-the-world.envfunctions/integration-tests/extensions/rtdb-uppercase-messages.env という名前に変更します。

この段階では、拡張機能コードに含まれている greet-the-world 拡張機能の残りの部分はそのままにしておきます。この部分は後で更新します。

4. Cloud Functions の関数を記述して拡張リソースとして宣言する

ここまでで、コードの作成を始める準備ができました。このステップでは、拡張機能のコアタスクを実行する Cloud Functions の関数を作成します。この関数は、Realtime Database のメッセージを監視し、大文字に変換します。

  1. 任意のエディタまたは IDE で、拡張機能の関数のソースを開きます(functions ディレクトリにあります)。次のように置き換えます。

    functions/index.js

    import { database, logger } from "firebase-functions/v1";
    
    const app = initializeApp();
    
    // Listens for new messages added to /messages/{pushId}/original and creates an
    // uppercase version of the message to /messages/{pushId}/uppercase
    // for all databases in 'us-central1'
    export const makeuppercase = database
      .ref("/messages/{pushId}/uppercase")
      .onCreate(async (snapshot, context) => {
        // Grab the current value of what was written to the Realtime Database.
        const original = snapshot.val();
    
        // Convert it to upper case.
        logger.log("Uppercasing", context.params.pushId, original);
        const uppercase = original.toUpperCase();
    
        // Setting an "uppercase" sibling in the Realtime Database.
        const upperRef = snapshot.ref.parent.child("upper");
        await upperRef.set(uppercase);
    });
    

    置き換える前の関数は、HTTP エンドポイントからアクセスされたときに実行される HTTP トリガー関数でした。新しい関数はリアルタイムのデータベース イベントによってトリガーされ、特定のパスにある新しいアイテムを監視します。検出されると、大文字の値をデータベースに書き込みます。

    この新しいファイルでは、CommonJS(require)ではなく ECMAScript モジュール構文(importexport)が使用されています。Node で ES モジュールを使用する場合は、functions/package.json"type": "module" を指定します。

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      …
    }
    
  2. 拡張機能のすべての関数は extension.yaml ファイルで宣言する必要があります。このサンプルの拡張機能で宣言されていた Cloud Functions の関数は greetTheWorld だけで、これを makeuppercase に置き換えたので、その宣言も更新する必要があります。

    extension.yaml を開き、resources フィールドを追加します。

    resources:
      - name: makeuppercase
        type: firebaseextensions.v1beta.function
        properties:
          eventTrigger:
            eventType: providers/google.firebase.database/eventTypes/ref.create
            # DATABASE_INSTANCE (project's default instance) is an auto-populated
            # parameter value. You can also specify an instance.
            resource: projects/_/instances/${DATABASE_INSTANCE}/refs/messages/{pushId}/original
          runtime: "nodejs18"
    
  3. 変更後の拡張機能はトリガーとして Realtime Database を使用しているため、Cloud Functions エミュレータとともに RTDB エミュレータを実行するには、エミュレータの構成を更新する必要があります。

    1. エミュレータが実行中の場合は、Ctrl+C キーを押して停止します。

    2. functions/integration-tests ディレクトリから次のコマンドを実行します。

      firebase init emulators
      

      プロンプトが表示されたら、デフォルト プロジェクトの設定をスキップして、Functions エミュレータと Database エミュレータを選択します。デフォルトのポートを受け入れ、設定ツールが必要なファイルをダウンロードできるようにします。

    3. エミュレータを再起動します。

      firebase emulators:start --project=demo-test
      
  4. 更新後の拡張機能を試してみましょう。

    1. エミュレータの起動時に出力されたリンクを使用して、Database Emulator UI を開きます。

    2. データベースのルートノードを編集します。

      • フィールド: messages
      • タイプ: json
      • 値: {"11": {"original": "recipe"}}

      すべてが正しく設定されていれば、データベースの変更を保存したときに、拡張機能の makeuppercase 関数によって子レコードがトリガーされ、メッセージ 11 にコンテンツ "upper": "RECIPE" が追加されます。エミュレータ UI のログとデータベース タブを調べて、期待される結果になっているかどうか確認します。

    3. messages ノード({"original":"any text"})にさらに子を追加してみましょう。新しいレコードを追加するたびに拡張機能が uppercase フィールドを追加し、そのフィールドに original フィールドの内容を大文字にした結果が保存されているはずです。

これで、RTDB インスタンス上で動作するシンプルな拡張機能が完成しました。以降のセクションでは、この拡張機能に機能を追加して改良していきます。さらに、拡張機能を他のユーザーに配布できるようにし、最後に Extensions Hub で公開します。

5. API とロールを宣言する

Firebase は、インストールされた拡張機能の各インスタンスに対して、インスタンスごとのサービス アカウントを使用してプロジェクトとそのデータへのアクセスを制限付きで許可します。各アカウントには、オペレーションに必要な最小限の権限セットが含まれています。このため、拡張機能に必要な IAM ロールを明示的に宣言する必要があります。ユーザーが拡張機能をインストールすると、Firebase はこれらのロールが付与されているサービス アカウントを作成し、そのアカウントを使用して拡張機能を実行します。

プロダクトのイベントでトリガーするためにロールを宣言する必要はありませんが、他の方法で操作する場合はロールを宣言する必要があります。前の手順で追加した関数は Realtime Database に書き込みを行うため、次の宣言を extension.yaml に追加する必要があります。

roles:
  - role: firebasedatabase.admin
    reason: Allows the extension to write to RTDB.

同様に、拡張機能が使用する Google API を apis フィールドで宣言します。ユーザーが拡張機能をインストールすると、プロジェクトでこれらの API を自動的に有効にするかどうかを尋ねるメッセージが表示されます。これは Firebase 以外の Google API で必要になる操作で、このガイドでは行う必要はありません。

6. ユーザー構成可能なパラメータを定義する

前の 2 つの手順で作成した関数は、特定の RTDB の場所で受信メッセージを監視する関数です。拡張機能専用のデータベース構造で拡張機能が動作している場合などは、所定の場所での監視が必要になりますが、ほとんどの場合は、プロジェクトに拡張機能をインストールするユーザーがこれらの値を構成できるようにする必要があります。これにより、ユーザーが既存のデータベース設定で拡張機能を使用できるようになります。

拡張機能が新しいメッセージを監視するパスをユーザーが構成できるようにします。

  1. extension.yaml ファイルに params セクションを追加します。

    - param: MESSAGE_PATH
      label: Message path
      description: >-
        What is the path at which the original text of a message can be found?
      type: string
      default: /messages/{pushId}/original
      required: true
      immutable: false
    

    これにより、拡張機能のインストール時にユーザーに設定を求める新しい文字列パラメータが定義されます。

  2. extension.yaml ファイルの makeuppercase 宣言に戻り、resource フィールドを次のように変更します。

    resource: projects/_/instances/${DATABASE_INSTANCE}/refs/${param:MESSAGE_PATH}
    

    ${param:MESSAGE_PATH} トークンは、先ほど定義したパラメータの参照です。拡張機能の実行時に、このトークンはユーザーが構成したパラメータの値に置き換えられ、makeuppercase 関数はユーザーが指定したパスをリッスンします。この構文を使用すると、extension.yaml 内にあるどのユーザー定義パラメータでも参照することができます(POSTINSTALL.md 内のユーザー定義パラメータも参照できます。こちらについて後で詳しく説明します)。

  3. また、関数コードからユーザー定義パラメータにアクセスすることもできます。

    前のセクションで作成した関数では、変更を監視するパスがハードコードされています。ユーザー定義の値を参照するようにトリガーの定義を変更します。

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate
    

    この変更は説明を目的としたものです。Firebase Extensions では、Cloud Functions の関数を拡張機能の一部としてデプロイすると、extension.yaml ファイルのトリガー定義が使用されます。関数定義で指定された値は無視されます。それでも、この値の出所をコードに記述しておくことをおすすめします。

  4. ランタイムに影響のないコード変更は無駄に感じるかもしれませんが、関数コード内のユーザー定義パラメータにアクセスして、関数のロジックで通常の値として使用できることを覚えておいてください。この点は重要です。次のログ ステートメントを追加して、ユーザーが定義した値に実際にアクセスできることを確認してみましょう。

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate(
      async (snapshot, context) => {
        logger.log("Found new message at ", snapshot.ref);
    
        // Grab the current value of what was written to the Realtime Database.
        ...
    
  5. 通常、拡張機能のインストール時に、ユーザーはパラメータの値を指定するように求められます。テストと開発でエミュレータを使用している場合は、インストール プロセスをスキップし、代わりに env ファイルにユーザー定義パラメータの値を指定します。

    functions/integration-tests/extensions/rtdb-uppercase-messages.env を開き、GREETING 定義を次のように置き換えます。

    MESSAGE_PATH=/msgs/{pushId}/original
    

    上記のパスはデフォルトのパスや以前に定義したパスとは異なります。これは、更新後の拡張機能で定義が機能していることを確認するためのものです。

  6. エミュレータを再起動して、データベースのエミュレータ UI にアクセスします。

    上で定義したパスを使用して、データベースのルートノードを編集します。

    • フィールド: msgs
    • タイプ: json
    • 値: {"11": {"original": "recipe"}}

    データベースの変更を保存すると、前と同じように拡張機能の makeuppercase 関数がトリガーされますが、ユーザー定義パラメータもコンソールログに出力されているはずです。

7. ユーザー定義ロジックのイベントフックを提供する

拡張機能を作成しながら、Firebase プロダクトが拡張機能提供のロジックをトリガーする仕組みも確認しました。この例では、Realtime Database で新しいレコードを作成すると、makeuppercase 関数がトリガーされます。この仕組みは拡張機能とそれをインストールしたユーザーの関係と似ています。拡張機能はユーザーが定義したロジックをトリガーできます。

拡張機能では、同期フック、非同期フック、またはその両方を使用できます。同期フックを使用すると、拡張機能の 1 つの関数の完了をブロックするタスクを実行できます。これは、拡張機能が機能する前にユーザーがカスタム前処理を実行できるようにする場合に役立ちます。

このガイドでは、拡張機能に非同期フックを追加し、大文字のメッセージが Realtime Database に書き込まれた後に実行される独自の処理ステップをユーザーが定義できるようにします。非同期フックは、Eventarc を使用してユーザー定義関数をトリガーします。拡張機能は出力するイベントのタイプを宣言し、ユーザーは拡張機能のインストール時に目的のイベントタイプを選択します。イベントを少なくとも 1 つ選択すると、Firebase はインストール プロセスの一環として、拡張機能の Eventarc チャネルをプロビジョニングします。ユーザーは、そのチャネルをリッスンし、拡張機能が新しいイベントを公開したときにトリガーされる独自の Cloud Functions の関数をデプロイできます。

非同期フックを追加する手順は次のとおりです。

  1. extension.yaml ファイルに次のセクションを追加します。このセクションでは、拡張機能が出力するイベントのタイプを 1 つ宣言します。

    events:
      - type: test-publisher.rtdb-uppercase-messages.v1.complete
        description: >-
          Occurs when message uppercasing completes. The event subject will contain
          the RTDB URL of the uppercase message.
    

    イベントのタイプはユニバーサルに一意である必要があります。一意性を確保するため、イベントの名前は常に <publisher-id>.<extension-id>.<version>.<description> という形式にします(パブリッシャー ID はまだないため、ここでは test-publisher を使用してください)。

  2. makeuppercase 関数の最後に、宣言したタイプのイベントを公開するコードを追加します。

    functions/index.js

    // Import the Eventarc library:
    import { initializeApp } from "firebase-admin/app";
    import { getEventarc } from "firebase-admin/eventarc";
    
    const app = initializeApp();
    
    // In makeuppercase, after upperRef.set(uppercase), add:
    
    // Set eventChannel to a newly-initialized channel, or `undefined` if events
    // aren't enabled.
    const eventChannel =
      process.env.EVENTARC_CHANNEL &&
      getEventarc().channel(process.env.EVENTARC_CHANNEL, {
        allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
      });
    
    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel &&
      eventChannel.publish({
        type: "test-publisher.rtdb-uppercase-messages.v1.complete",
        subject: upperRef.toString(),
        data: {
          "original": original,
          "uppercase": uppercase,
        },
      });
    

    このサンプルコードでは、ユーザーが少なくとも 1 つのイベントタイプを有効にしたときにのみ EVENTARC_CHANNEL 環境変数が定義される点を利用しています。EVENTARC_CHANNEL が定義されていない場合、イベントは公開されません。

    Eventarc イベントには追加情報を追加できます。上の例では、イベントの subject フィールドに新しく作成した値への参照と data ペイロードが含まれています。このペイロードには、元のメッセージと大文字に変換したメッセージが含まれています。イベントをトリガーするユーザー定義関数でこの情報を利用できます。

  3. 通常、EVENTARC_CHANNEL 環境変数と EXT_SELECTED_EVENTS 環境変数は、ユーザーがインストール時に選択したオプションに基づいて定義されます。エミュレータを使用してテストする場合は、rtdb-uppercase-messages.env ファイルで次の変数を定義します。

    EVENTARC_CHANNEL=locations/us-central1/channels/firebase
    EXT_SELECTED_EVENTS=test-publisher.rtdb-uppercase-messages.v1.complete
    

これで、拡張機能に非同期イベントフックが追加されました。

実装した新しい機能を試してみましょう。次のステップでは、拡張機能をインストールするユーザーのロールを前提としています。

  1. functions/integration-tests ディレクトリから、新しい Firebase プロジェクトを初期化します。

    firebase init functions
    

    プロンプトが表示されたら、デフォルト プロジェクトの設定を拒否します。Cloud Functions の言語として JavaScript を選択し、必要な依存関係をインストールします。このプロジェクトは、拡張機能をインストールしたユーザーのプロジェクトです。

  2. integration-tests/functions/index.js を編集して、次のコードを貼り付けます。

    import { logger } from "firebase-functions/v1";
    import { onCustomEventPublished } from "firebase-functions/v2/eventarc";
    
    import { initializeApp } from "firebase-admin/app";
    import { getDatabase } from "firebase-admin/database";
    
    const app = initializeApp();
    
    export const extraemphasis = onCustomEventPublished(
      "test-publisher.rtdb-uppercase-messages.v1.complete",
      async (event) => {
        logger.info("Received makeuppercase completed event", event);
    
        const refUrl = event.subject;
        const ref = getDatabase().refFromURL(refUrl);
        const upper = (await ref.get()).val();
        return ref.set(`${upper}!!!`);
      }
    );
    

    これは、ユーザーが作成する後処理関数の例です。この例では、拡張機能による complete イベントの公開をリッスンし、トリガーされると、大文字に変換されたメッセージに感嘆符を 3 つ追加します。

  3. エミュレータを再起動します。エミュレータは、拡張機能の関数とユーザー定義の後処理関数を読み込みます。

  4. データベース エミュレータ UI にアクセスして、上で定義したパスを使用してデータベースのルートノードを編集します。

    • フィールド: msgs
    • タイプ: json
    • 値: {"11": {"original": "recipe"}}

    データベースの変更を保存すると、拡張機能の makeuppercase 関数とユーザー定義の extraemphasis 関数が順番にトリガーされ、upper フィールドの値が RECIPE!!! になるはずです。

8. ライフサイクル イベント ハンドラを追加する

ここで作成しているのは、メッセージが作成されたときにそのメッセージを処理する拡張機能です。では、ユーザーが拡張機能をインストールするときにすでに、データベースにメッセージが存在する場合はどうなるでしょうか。Firebase Extensions にはライフサイクル イベント フックという機能があります。この機能を利用すると、拡張機能のインストール、更新、再構成時にアクションをトリガーできます。このセクションでは、ライフサイクル イベントフックを使用して、拡張機能のインストール時にプロジェクトにすでに存在しているメッセージを大文字に変換し、変換後のメッセージをデータベースにバックフィルします。

Firebase Extensions は、Cloud Tasks を使用してライフサイクル イベント ハンドラを実行します。イベント ハンドラは Cloud Functions を使用して定義します。ハンドラを定義すると、拡張機能のインスタンスがサポートされているライフサイクル イベントのいずれかに到達すると、ハンドラが Cloud Tasks キューに追加されます。Cloud Tasks はハンドラを非同期的に実行します。ライフサイクル イベント ハンドラの実行中に、処理中のタスクの存在が Firebase コンソールから拡張機能インスタンスに報告されます。タスクの進行状況と完了をユーザーに報告するかどうかは、ハンドラ関数で判断します。

既存のメッセージをバックフィルするライフサイクル イベント ハンドラを追加するには、次の操作を行います。

  1. タスクキュー イベントによってトリガーされる新しい Cloud Functions の関数を定義します。

    functions/index.js

    import { tasks } from "firebase-functions/v1";
    
    import { getDatabase } from "firebase-admin/database";
    import { getExtensions } from "firebase-admin/extensions";
    import { getFunctions } from "firebase-admin/functions";
    
    export const backfilldata = tasks.taskQueue().onDispatch(async () => {
      const batch = await getDatabase()
        .ref(process.env.MESSAGE_PATH)
        .parent.parent.orderByChild("upper")
        .limitToFirst(20)
        .get();
    
      const promises = [];
      for (const key in batch.val()) {
        const msg = batch.child(key);
        if (msg.hasChild("original") && !msg.hasChild("upper")) {
          const upper = msg.child("original").val().toUpperCase();
          promises.push(msg.child("upper").ref.set(upper));
        }
      }
      await Promise.all(promises);
    
      if (promises.length > 0) {
        const queue = getFunctions().taskQueue(
          "backfilldata",
          process.env.EXT_INSTANCE_ID
        );
        return queue.enqueue({});
      } else {
        return getExtensions()
          .runtime()
          .setProcessingState("PROCESSING_COMPLETE", "Backfill complete.");
      }
    });
    

    この関数は、タスクキューに自身を追加する前にいくつかのレコードを処理します。これは、Cloud Functions のタイムアウト期間内に完了できないタスクに対応する際によく使用される手法です。ユーザーが拡張機能をインストールするときに、どのくらいの数のメッセージがデータベース内に存在するかは予測が難しいため、このような場合には適した方法です。

  2. extension.yaml ファイルで、taskQueueTrigger プロパティを持つ拡張リソースとしてバックフィル関数を宣言します。

    resources:
      - name: makeuppercase
        ...
      - name: backfilldata
        type: firebaseextensions.v1beta.function
        description: >-
          Backfill existing messages with uppercase versions
        properties:
          runtime: "nodejs18"
          taskQueueTrigger: {}
    

    次に、この関数を onInstall ライフサイクル イベントのハンドラとして宣言します。

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. 既存のメッセージのバックフィルは重要ですが、それがなくても拡張機能が機能することもあります。このような場合は、ライフサイクル イベント ハンドラの実行をオプションにする必要があります。

    その場合は extension.yaml に新しいパラメータを追加します。

    - param: DO_BACKFILL
      label: Backfill existing messages
      description: >-
        Generate uppercase versions of existing messages?
      type: select
      required: true
      options:
        - label: Yes
          value: true
        - label: No
          value: false
    

    次に、バックフィル関数の先頭で、DO_BACKFILL パラメータの値を確認し、値が設定されていない場合は早めに終了します。

    functions/index.js

    if (!process.env.DO_BACKFILL) {
      return getExtensions()
        .runtime()
        .setProcessingState("PROCESSING_COMPLETE", "Backfill skipped.");
    }
    

上記の変更により、拡張機能のインストール時に既存のメッセージが大文字に変換されるようになりました。

ここまでは拡張機能エミュレータを使用して拡張機能を作成し、変更をテストしてきました。ただし、拡張機能エミュレータはインストール プロセスをスキップするため、onInstall イベント ハンドラをテストするには、実際のプロジェクトに拡張機能をインストールする必要があります。自動バックフィル機能を追加したので、このチュートリアルの拡張機能のコードはこれで完成です。

9. 実際の Firebase プロジェクトにデプロイする

拡張機能エミュレータは、拡張機能の迅速なイテレーション開発に優れたツールですが、ある時点で、実際のプロジェクトで試してみることも必要です。

そのために、まず新しいプロジェクトを設定して、いくつかのサービスを有効にします。

  1. Firebase コンソールで、新しいプロジェクトを追加します。
  2. プロジェクトを従量課金制の Blaze プランにアップグレードします。Cloud Functions for Firebase ではプロジェクトに請求先アカウントが必要であるため、拡張機能をインストールするには請求先アカウントが必要になります。
  3. 新しいプロジェクトで、Realtime Database を有効にします
  4. 拡張機能のインストール時に既存のデータをバックフィルする機能をテストするため、Realtime Database インスタンスにサンプルデータをインポートします。
    1. シード RTDB データをいくつかダウンロードします。
    2. Firebase コンソールの [Realtime Database] ページで (展開)> [JSON をインポート] をクリックして、ダウンロードしたファイルを選択します。
  5. バックフィル関数を有効にして orderByChild メソッドを使用するには、upper の値でメッセージのインデックスを作成するようにデータベースを構成します。

    {
      "rules": {
        ".read": false,
        ".write": false,
        "messages": {
          ".indexOn": "upper"
        }
      }
    }
    

次に、ローカルソースから新しいプロジェクトに拡張機能をインストールします。

  1. Firebase プロジェクト用の新しいディレクトリを作成します。

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. 作業ディレクトリで Firebase プロジェクトを初期化します。

    firebase init database
    

    プロンプトが表示されたら、作成したプロジェクトを選択します。

  3. ローカルの Firebase プロジェクトに拡張機能をインストールします。

    firebase ext:install /path/to/rtdb-uppercase-messages
    

    ここでは、Firebase CLI ツールを使用して拡張機能をインストールした場合のユーザー エクスペリエンスを確認できます。構成ツールが既存のデータベースをバックフィルするかどうかを尋ねられたら、必ず「yes」を選択してください。

    構成オプションを選択すると、Firebase CLI により構成が extensions ディレクトリに保存されます。また、拡張機能のソースの場所が firebase.json ファイルに記録されます。この 2 つのレコードを総称して拡張機能のマニフェストといいます。ユーザーは、マニフェストを使用して拡張機能の構成を保存し、別のプロジェクトにデプロイできます。

  4. 拡張機能の構成を実際のプロジェクトにデプロイします。

    firebase deploy --only extensions
    

問題がなければ、Firebase CLI によって拡張機能がプロジェクトにアップロードされ、インストールされます。インストールが完了すると、バックフィル タスクが実行され、数分以内に大文字のメッセージでデータベースが更新されます。いくつかの新しいノードをメッセージ データベースに追加して、拡張機能が新しいメッセージでも機能していることを確認します。

10. ドキュメントを作成する

拡張機能をユーザーと共有する前に、ユーザーが正常に使用できるように、十分なドキュメントを用意してください。

拡張機能のプロジェクトを初期化したときに、Firebase CLI により必要最小限のドキュメントのスタブ バージョンが作成されました。作成した拡張機能が正確に反映されているように、これらのファイルを更新します。

extension.yaml

このファイルは拡張機能の開発中に更新しているので、これ以上更新する必要はありません。

ただし、このファイルに含まれるドキュメントの重要性は忘れないでください。extension.yaml ファイルには、拡張機能の重要な識別情報(名前、説明、作成者、公式リポジトリの場所)に加えて、リソースやユーザーが構成できるパラメータに関するユーザー向けのドキュメントが含まれています。この情報は、Firebase コンソール、Extensions Hub、Firebase CLI でユーザーに表示されます。

PREINSTALL.md

このファイルでは、ユーザーが拡張機能をインストールする前に必要な情報を提供します。拡張機能の内容、前提条件、拡張機能のインストールによって発生する料金などについて簡単な説明を記述してください。ウェブサイトに追加情報がある場合は、ここにリンクを含めます。

このファイルのテキストは、Extensions Hub と firebase ext:info コマンドでユーザーに表示されます。

PREINSTALL ファイルの例を示します。

Use this extension to automatically convert strings to upper case when added to
a specified Realtime Database path.

This extension expects a database layout like the following example:

    "messages": {
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
    }

When you create new string records, this extension creates a new sibling record
with upper-cased text:

    MESSAGE_ID: {
      "original": MESSAGE_TEXT,
      "upper": UPPERCASE_MESSAGE_TEXT,
    }

#### Additional setup

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

#### 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:
  - Realtime Database
  - Cloud Functions (Node.js 10+ runtime)
    [See FAQs](https://firebase.google.com/support/faq#extensions-pricing)
- If you enable events,
  [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

POSTINSTALL.md

このファイルには、ユーザーが拡張機能を正常にインストールした後に役立つ情報(フォローアップの設定手順、拡張機能の動作例など)が含まれています。

POSTINSTALL.md の内容は、拡張機能を構成してインストールした後、Firebase コンソールに表示されます。このファイルのユーザー パラメータは参考情報です。これらは構成した値に置き換えられます。

チュートリアルの拡張機能をインストールした後のファイルの例を示します。

### See it in action

You can test out this extension right away!

1.  Go to your
    [Realtime Database dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/database/${param:PROJECT_ID}/data) in the Firebase console.

1.  Add a message string to a path that matches the pattern `${param:MESSAGE_PATH}`.

1.  In a few seconds, you'll see a sibling node named `upper` that contains the
    message in upper case.

### Using the extension

We recommend adding data by pushing -- for example,
`firebase.database().ref().push()` -- because pushing assigns an automatically
generated ID to the node in the database. During retrieval, these nodes are
guaranteed to be ordered by the time they were added. Learn more about reading
and writing data for your platform (iOS, Android, or Web) in the
[Realtime Database documentation](https://firebase.google.com/docs/database/).

### Monitoring

As a best practice, you can
[monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor)
of your installed extension, including checks on its health, usage, and logs.

CHANGELOG.md

拡張機能のリリース間で行った変更は CHANGELOG.md ファイルに文書化する必要があります。

このサンプルの拡張機能はまだ公開されていないため、変更ログのエントリは 1 つだけです。

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

ほとんどの拡張機能は、ユーザーが拡張機能のリポジトリにアクセスしたときに役立つように、readme ファイルを提供しています。このファイルは手動で作成することも、コマンドで生成することもできます。

このガイドでは、readme ファイルの作成については説明しません。

その他のドキュメント

前述のドキュメントは、ユーザーに提供する必要のあるドキュメントの最小セットです。多くの場合、ユーザーが拡張機能を正常に使用できるように、より詳細なドキュメントを用意する必要があります。そのような場合は、追加のドキュメントを作成して、ユーザーが参照できる場所にホストしてください。

このガイドでは、詳細なドキュメントの作成について説明しません。

11. Extensions Hub で公開する

拡張機能のコードが完成してドキュメントを用意したので、拡張機能を Extensions Hub で公開する準備ができました。ただし、これはチュートリアルですので、公開は行わないでください。このチュートリアルと Firebase Extensions パブリッシャー向けドキュメントの残りの部分で学んだことを基に、Firebase で作成された公式の拡張機能のソースを調べて、独自の拡張機能を作成してください。

Extensions Hub で公開する準備ができたら、以下の操作を行います。

  1. 拡張機能を初めて公開する場合は、拡張機能のパブリッシャーとして登録します。拡張機能のパブリッシャーとして登録する際には、拡張機能の作成者であることをユーザーが簡単に特定できるパブリッシャー ID を作成します。
  2. 公開されている検証可能な場所に、拡張機能のソースコードをホストします。検証可能なソースからコードを提供すると、Firebase は拡張機能をその場所から直接公開できます。これにより、現在リリースされている拡張機能のバージョンが確実に公開されるようになります。また、ユーザーがプロジェクトにインストールするコードを確認できるようになります。

    現時点では、拡張機能を公開 GitHub リポジトリで提供できます。

  3. firebase ext:dev:upload コマンドを使用して、Extensions Hub に拡張機能をアップロードします。

  4. Firebase コンソールのパブリッシャー ダッシュボードに移動し、アップロードした拡張機能を見つけて、[Extensions Hub で公開] をクリックします。審査担当者による審査を申請します。審査には数日かかることがあります。承認されると、拡張機能が Extensions Hub に公開されます。拒否された場合は、その理由を説明するメッセージが届きます。報告された問題を解決して再度審査を依頼できます。