重新利用 Cloud Functions 程式碼做為 Firebase 擴充功能

1. 事前準備

Firebase 擴充功能會因應 HTTP 要求,或來自其他 Firebase 和 Google 產品 (例如 Firebase 雲端通訊、Cloud Firestore 或 Pub/Sub) 的觸發事件,執行特定工作或一組工作。

建構項目

在本程式碼研究室中,您將建構 geohashing 的 Firebase 擴充功能。部署完成後,擴充功能就會在回應 Firestore 事件或透過可呼叫函式叫用時,將 X 和 Y 座標轉換為地理雜湊。您可以將這項功能做為替代方案,在所有目標平台上實作 geofire 程式庫來儲存資料,節省時間。

Firebase 控制台顯示的地理雜湊擴充功能

課程內容

  • 如何將現有的 Cloud Functions 程式碼轉換為可發布的 Firebase 擴充功能
  • 如何設定 extension.yaml 檔案
  • 如何在擴充功能中儲存機密字串 (API 金鑰)
  • 如何允許擴充功能開發人員根據需求設定擴充功能
  • 如何測試及部署擴充功能

軟硬體需求

  • Firebase CLI (安裝並登入)
  • Google 帳戶,例如 Gmail 帳戶
  • Node.js 和 npm
  • 您偏好的開發環境

2. 做好準備

取得程式碼

這個擴充功能所需的一切都位於 GitHub 存放區中。如要開始進行本專案,請先取得程式碼並在慣用的開發環境中開啟。

  1. 將下載的 ZIP 檔案解壓縮。
  2. 如要安裝必要的依附元件,請在 functions 目錄中開啟終端機,然後執行 npm install 指令。

設定 Firebase

本程式碼研究室強烈建議使用 Firebase 模擬器。如要使用實際的 Firebase 專案試用擴充功能開發作業,請參閱「建立 Firebase 專案」。本程式碼研究室使用 Cloud Functions,因此如果您使用實際的 Firebase 專案 (而非模擬器),請升級至 Blaze 定價方案

想直接跳到某個單元嗎?

您可以下載完成的程式碼研究室版本。如果遇到問題,或想查看完成的擴充功能,請查看 GitHub 存放區codelab-end 分支,或下載完成的 zip 檔案。

3. 檢查程式碼

  • 開啟 zip 檔案中的 index.ts 檔案。請注意,其中包含兩個 Cloud 函式宣告。

這些函式有什麼作用?

這些示範函式用於地理雜湊。這類函式會採用座標對,並轉換為 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/ 集合的每份文件,然後處理這些文件的地理雜湊。然後將雜湊值輸出至同一份文件中的雜湊值欄位。

可呼叫函式

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 函式。這表示該函式是可呼叫的函式,可從用戶端應用程式程式碼內呼叫。這個可呼叫函式會採用 xy 參數,並傳回地理雜湊。雖然在本程式碼研究室中不會直接呼叫這個函式,但這裡會納入這個函式,做為在 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 是必要參數,且必須設為 true,因為所有擴充功能都依賴 Cloud Functions,而這項服務需要 Blaze 方案。

這涵蓋 extension.yaml 檔案中識別擴充功能所需的最少欄位數。如要進一步瞭解可在擴充功能中指定的其他識別資訊,請參閱說明文件

5. 將 Cloud Functions 程式碼轉換為 Extensions 資源

擴充功能資源是 Firebase 在擴充功能安裝期間,於專案中建立的項目。擴充功能隨後會擁有這些資源,並使用特定服務帳戶來操作資源。在這個專案中,這些資源是 Cloud Functions,必須在 extension.yaml 檔案中定義,因為擴充功能不會自動從 functions 資料夾中的程式碼建立資源。如果 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。如要反映擴充功能目前支援的內容,請使用 providers/cloud.firestore/eventTypes/document.write 中的 eventType,這可在「為擴充功能編寫 Cloud Functions」說明文件中找到。您可將 resource 定義為文件位置。由於您目前的目標是反映程式碼中的內容,因此文件路徑會監聽 users/{uid},並以預設資料庫位置為前置。

  1. 擴充功能需要 Firestore 資料庫的讀取和寫入權限。在 extension.yaml 檔案的最後,指定擴充功能應具備的 IAM 角色,以便在開發人員的 Firebase 專案中使用資料庫。
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

datastore.user 角色來自擴充功能支援的 IAM 角色清單。由於擴充功能會讀取及寫入資料,因此 datastore.user 角色很適合。

  1. 您也必須新增可呼叫的函式。在 extension.yaml 檔案中,於資源屬性下建立新資源。這些屬性是可呼叫函式專屬的屬性:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

雖然先前的資源使用 eventTrigger,但這裡使用 httpsTrigger,涵蓋可呼叫函式和 HTTPS 函式。

程式碼檢查

您需要進行許多設定,才能讓 extension.yaml 檔案中的程式碼比對 index.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 後,請前往模擬器網頁檢視中的 Firestore 分頁。
  2. users 集合中新增文件,並包含 xv 數字欄位和 yv 數字欄位。

Firebase 模擬器中顯示的對話方塊,可啟動集合,集合 ID 包含片語

  1. 如果擴充功能安裝成功,系統會在文件中建立名為 hash 的新欄位。

使用者集合,其中包含具有 xv、yv 和雜湊欄位的使用者文件。

清除項目以避免衝突

  • 測試完成後,請解除安裝擴充功能,因為您要更新擴充功能程式碼,以免日後與目前的擴充功能發生衝突。

擴充功能可同時安裝多個相同擴充功能版本,因此解除安裝可確保先前安裝的擴充功能不會發生衝突。

firebase ext:uninstall geohash-ext

目前的解決方案可以運作,但如專案一開始所述,其中有硬式編碼的 API 金鑰,用於模擬與服務通訊。如何使用原始提供的 API 金鑰,改為使用終端使用者的 API 金鑰?請繼續閱讀下文瞭解詳情。

6. 讓擴充功能可供使用者設定

在本程式碼研究室中,您已設定擴充功能,可搭配您已編寫的函式使用,但如果使用者想改用經緯度,而非 yx,來表示笛卡兒平面上的位置,該怎麼辦?此外,如何讓使用者提供自己的 API 金鑰,而不是使用您提供的 API 金鑰?您可能會快速超出該 API 的配額。在這種情況下,您需要設定及使用參數。

extension.yaml 檔案中定義基本參數

首先,請轉換開發人員可能需要自訂設定的項目。第一個是 XFIELDYFIELD 參數。

  1. extension.yaml 檔案中,加入下列程式碼,使用 XFIELDYFIELD 欄位參數。這些參數位於先前定義的 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 會以擴充功能製作人可見的方式命名參數。稍後指定參數值時,請使用這個值。
  • 標籤是開發人員可理解的 ID,可讓他們瞭解參數的用途。
  • description 則會詳細說明值。由於這項功能支援 Markdown,因此可以連結至額外說明文件,或醒目顯示對開發人員來說可能很重要的字詞。
  • type 定義使用者設定參數值時的輸入機制。包括 stringselectmultiSelectselectResourcesecret 等。如要進一步瞭解這些選項,請參閱說明文件
  • validationRegex 會將開發人員輸入的內容限制為特定 regex 值 (範例是根據這裡的簡單欄位名稱規範);如果失敗...
  • validationErrorMessage 會向開發人員發出警示,指出失敗值。
  • 如果開發人員未輸入任何文字,值會是 default
  • 必要表示開發人員不必輸入任何文字。
  • 不可變動:開發人員可以更新這個擴充功能並變更這個值。在這種情況下,開發人員應能隨著需求變更欄位名稱。
  • 範例可讓您瞭解有效輸入內容的格式。

這段內容資訊量很大!

  1. 您還需要將三個參數新增至 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!;

一般來說,您會想對環境變數值執行空值檢查,但在這個情況下,您相信參數值已正確複製。程式碼現在已設定完畢,可搭配擴充功能參數運作。

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 或模擬器。
firebase ext:install /path/to/extension
  • 由於您是在本機部署專案,請指定要使用本機檔案,而非 Google Cloud Secret Manager。

da928c65ffa8ce15.png

  1. 啟動本機模擬器套件:
firebase emulators:start

使用實際的 Firebase 專案安裝及測試

您可以在實際的 Firebase 專案中安裝擴充功能。建議您使用測試專案進行測試。如要測試擴充功能的端對端流程,或 Firebase 模擬器套件尚未支援擴充功能的觸發條件 (請參閱擴充功能模擬器選項),請使用這個測試工作流程。模擬器目前支援由 HTTP 要求觸發的函式,以及由 Cloud Firestore、即時資料庫和 Pub/Sub 的背景事件觸發的函式。

  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 檔案的目錄絕對路徑。系統會開始安裝擴充功能,並建立包含設定的「.env」檔案,然後將設定推送至 Firebase 或模擬器。
firebase ext:install /path/to/extension
  • 由於您想直接部署至 Firebase,並使用 Google Cloud Secret Manager,因此請先啟用 Secret Manager API,再安裝擴充功能。
  1. 部署至 Firebase 專案。
firebase deploy

測試擴充功能

  1. 執行 firebase deployfirebase emulators:start 後,請視需要前往 Firebase 控制台或模擬器的網頁檢視畫面,然後點選 Firestore 分頁。
  2. 將文件新增至 x 欄位和 y 欄位指定的集合。在本例中,更新後的文件位於 u/{uid},且 x 欄位為 xvy 欄位為 yv

Firebase 模擬器畫面,可新增 Firestore 記錄

  1. 如果成功安裝擴充功能,儲存這兩個欄位後,擴充功能會在文件中建立名為 hash 的新欄位。

模擬器的 Firestore 資料庫畫面,顯示已新增的雜湊

8. 恭喜!

您已成功將第一個 Cloud Functions 轉換為 Firebase 擴充功能!

您已新增 extension.yaml 檔案並完成設定,開發人員可以選擇要如何部署擴充功能。然後,您建立使用者文件,提供擴充功能開發人員在設定擴充功能前應執行的操作,以及成功安裝擴充功能後可能需要採取的步驟。

您現在已瞭解將 Firebase 函式轉換為可發布的 Firebase 擴充功能所需的重要步驟。

後續步驟