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

1. 事前準備

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

建構項目

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

Firebase 控制台中顯示的 Geohash 擴充功能

課程內容

  • 如何將現有的 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 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/ 集合的文件,然後處理這些文件的地理雜湊。然後將雜湊輸出到同一份文件中的雜湊欄位。

可呼叫函式

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 程式碼後,您就可以開始封裝 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,因為所有擴充功能都需要搭配 Blaze 方案使用 Cloud Functions。

這涵蓋 extension.yaml 檔案中必要的欄位數量下限,以識別這個副檔名。如要進一步瞭解您可以在擴充功能中指定的其他識別資訊,請參閱說明文件

5. 將 Cloud Functions 程式碼轉換為擴充功能資源

擴充功能資源是指 Firebase 在安裝擴充功能時在專案中建立的項目。擴充功能接著會擁有這些資源,並擁有在資源上運作的特定服務帳戶。在這項專案中,這些資源為 Cloud Functions,您必須在 extension.yaml 檔案中定義這些資源,因為擴充功能不會自動透過函式資料夾中的程式碼建立資源。如果 Cloud Functions 未明確宣告為資源,就無法在部署擴充功能時加以部署。

使用者定義的部署位置

  1. 讓使用者指定要在哪個位置部署這個擴充功能,並決定以更靠近使用者或資料庫更方便的方式代管擴充功能。在 extension.yaml 檔案中,加入挑選位置的選項。

extension.yaml

您現在可以開始編寫函式資源的設定。

  1. extension.yaml 檔案中,建立 locationUpdate 函式的資源物件。將以下內容附加至 extension.yaml 檔案:
resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}

name 定義為專案 index.ts 檔案中定義的函式名稱。您要指定正在部署的函式的 type,目前應一律為 firebaseextensions.v1beta.function。接著,請定義此函式的 properties。您定義的第一個屬性是與這個函式相關聯的 eventTrigger。如要反映這項擴充功能目前支援的版本,請使用 providers/cloud.firestore/eventTypes/document.writeeventType,詳情請參閱為擴充功能編寫 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 後,前往模擬器 WebView 的「Firestore」分頁。
  2. 將文件新增至 users 集合,其中含有 xv 數字欄位和 yv 數字欄位。

Firebase 模擬器中顯示的對話方塊,會以含有詞組的集合 ID 啟動集合

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

使用者集合含有 xv、yv 和雜湊欄位的使用者文件。

清除所用資源,以免衝突

  • 測試完成後,請解除安裝擴充功能,這樣一來,您就可以更新擴充功能程式碼,而且之後不會再與目前的擴充功能相衝突。

擴充功能可讓您一次安裝同一擴充功能的多個版本,因此解除安裝擴充功能可確保與之前安裝的擴充功能沒有衝突。

firebase ext:uninstall geohash-ext

目前的解決方案雖然有效,但如專案一開始所述,有一個硬式編碼的 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 會以擴充功能生產端 (擴充功能製作者) 能看到的方式命名參數。稍後指定參數值時,請使用這個值。
  • label 是供開發人員理解的 ID,可讓開發人員瞭解參數的用途。
  • description 提供了值的詳細說明。這項功能支援 Markdown,因此可以連結至其他說明文件,或醒目顯示開發人員可能重視的字詞。
  • type 定義了使用者設定參數值的輸入機制。現有的類型有很多,包括 stringselectmultiSelectselectResourcesecret。如要進一步瞭解這些選項,請參閱說明文件
  • validationRegex 會將開發人員項目限制在特定規則運算式值 (在此範例中,其根據這裡提供的簡單欄位名稱規範);如果失敗的話...
  • validationErrorMessage 會通知開發人員檢查失敗值。
  • default 是開發人員未輸入任何文字時的對應值。
  • 必要 表示開發人員無須輸入任何文字。
  • immutable 可讓開發人員更新這項擴充功能並變更這個值。在此情況下,開發人員應能隨需求變化變更欄位名稱。
  • example:讓您瞭解有效的輸入格式。

實在難以理解!

  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 模擬器套件尚未支援擴充功能的觸發條件,請使用這個測試工作流程 (請參閱「擴充功能模擬器選項」)。模擬器目前支援 Cloud Firestore、即時資料庫和 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 檔案所屬目錄的絕對路徑。這項操作會啟動擴充功能的安裝程序,並建立包含設定的 .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 主控台或模擬器 WebView 的 Firestore 分頁。
  2. 將文件新增至 x 欄位和 y 欄位指定的集合。在本例中,更新的文件位於 u/{uid},而 x 欄位為 xvy 欄位則為 yv

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

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

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

8. 恭喜!

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

您新增了 extension.yaml 檔案並進行設定,方便開發人員選取擴充功能的部署方式。接著,您建立了使用者說明文件,說明擴充功能的開發人員在設定擴充功能前應採取的措施,以及他們成功安裝擴充功能後需採取的步驟。

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

後續步驟