Cloud Functions 코드를 Firebase 확장 프로그램으로 재사용

1. 시작하기 전에

Firebase 확장 프로그램은 HTTP 요청 또는 다른 Firebase 및 Firebase 클라우드 메시징, Cloud Firestore, Pub/Sub와 같은 Google 제품의 트리거 이벤트에 대한 응답으로 특정 태스크 또는 태스크 세트를 수행합니다.

빌드할 항목

이 Codelab에서는 지오해시를 위한 Firebase 확장 프로그램을 빌드합니다. 배포되면 확장 프로그램은 Firestore 이벤트에 대한 응답으로 또는 호출 가능한 함수 호출을 통해 X 및 Y 좌표를 지오해시로 변환합니다. 이는 데이터 저장을 위해 모든 타겟 플랫폼에서 Geofire 라이브러리를 구현하는 대신 사용할 수 있으므로 시간을 절약할 수 있습니다.

Firebase Console에 표시되는 지오해시 확장 프로그램

학습 내용

  • 기존 Cloud Functions 코드를 배포 가능한 Firebase Extensions로 변환하는 방법
  • 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 파일을 엽니다. 안에 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 매개변수를 사용하고 지오해시를 반환합니다. 이 함수는 이 Codelab에서 직접 호출되지 않지만 Firebase 확장 프로그램에서 구성할 항목의 예로 포함되어 있습니다.

4. extension.yaml 파일 설정

이제 확장 프로그램의 Cloud Functions 코드가 수행하는 작업을 알았으므로 배포를 위해 패키징할 준비가 되었습니다. 모든 Firebase 확장 프로그램에는 확장 프로그램의 기능과 작동 방식을 설명하는 extension.yaml 파일이 함께 제공됩니다.

extension.yaml 파일에는 확장 프로그램에 대한 초기 메타데이터가 필요합니다. 다음 각 단계는 모든 필드의 의미와 필드가 필요한 이유를 이해하는 데 도움이 됩니다.

  1. 앞서 다운로드한 프로젝트의 루트 디렉터리에 extension.yaml 파일을 만듭니다. 먼저 다음을 추가합니다.
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

확장 프로그램의 이름은 확장 프로그램 인스턴스 ID의 기본으로 사용됩니다. 사용자는 각각 자체 ID가 있는 확장 프로그램의 여러 인스턴스를 설치할 수 있습니다. 그러면 Firebase에서 인스턴스 ID를 사용하여 확장 프로그램의 서비스 계정 이름과 확장 프로그램별 리소스를 생성합니다. 버전 번호는 확장 프로그램의 버전을 나타냅니다. 이는 시맨틱 버전 관리를 따라야 하며 확장 프로그램의 기능이 변경될 때마다 업데이트해야 합니다. 확장 프로그램 사양 버전은 따라야 할 Firebase 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이며 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}

프로젝트의 index.ts 파일에 정의된 함수 이름으로 name를 정의합니다. 배포되는 함수의 type를 지정합니다. 지금은 항상 firebaseextensions.v1beta.function여야 합니다. 그런 다음 이 함수의 properties를 정의합니다. 정의하는 첫 번째 속성은 이 함수와 연결된 eventTrigger입니다. 확장 프로그램에서 현재 지원하는 항목을 미러링하려면 확장 프로그램용 Cloud Functions 작성 문서에 있는 providers/cloud.firestore/eventTypes/document.writeeventType를 사용합니다. resource를 문서의 위치로 정의합니다. 현재 목표는 코드에 있는 내용을 미러링하는 것이므로 문서 경로는 users/{uid}를 리슨하고 앞에 기본 데이터베이스 위치가 표시됩니다.

  1. 확장 프로그램에는 Firestore 데이터베이스에 대한 읽기 및 쓰기 권한이 필요합니다. extension.yaml 파일의 맨 끝에 개발자의 Firebase 프로젝트에서 데이터베이스 작업을 위해 확장 프로그램이 액세스해야 하는 IAM 역할을 지정합니다.
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

datastore.user 역할은 확장 프로그램에 지원되는 IAM 역할 목록에서 가져옵니다. 확장 프로그램은 읽기 및 쓰기를 수행하므로 datastore.user 역할이 여기에 적합합니다.

  1. 호출 가능 함수도 추가해야 합니다. extension.yaml 파일의 리소스 속성 아래에 새 리소스를 만듭니다. 다음 속성은 호출 가능 함수에만 적용됩니다.
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

이전 리소스에서는 eventTrigger를 사용했지만 여기서는 호출 가능 함수와 HTTPS 함수를 모두 다루는 httpsTrigger을 사용합니다.

코드 확인

이는 extension.yamlindex.ts 파일의 코드에서 실행하는 모든 것과 일치하도록 하기 위해 많은 구성입니다. 이 시점에서 완성된 extension.yaml 파일은 다음과 같습니다.

extension.yaml에서 실행됩니다.

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

상태 확인

이제 확장 프로그램의 초기 기능이 설정되었으므로 Firebase 에뮬레이터를 사용하여 실제로 사용해 볼 수 있습니다.

  1. 아직 호출하지 않았다면 다운로드한 확장 프로그램 프로젝트의 함수 폴더에서 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/extensionextension.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 컬렉션에 추가합니다.

다음 문구가 포함된 컬렉션 ID로 컬렉션을 시작하도록 Firebase 에뮬레이터에 표시되는 대화상자

  1. 확장 프로그램이 성공적으로 설치되면 확장 프로그램이 문서에 hash라는 새 필드를 만듭니다.

xv, yv, 해시 필드가 있는 사용자 문서가 포함된 사용자 컬렉션입니다.

충돌을 방지하기 위해 정리하기

  • 테스트를 마치면 확장 프로그램을 제거합니다. 그러면 확장 프로그램 코드를 업데이트하게 되며 이후 현재 확장 프로그램과의 충돌을 방지할 수 있습니다.

확장 프로그램을 사용하면 동일한 확장 프로그램의 여러 버전을 한 번에 설치할 수 있으므로 제거하면 이전에 설치된 확장 프로그램과 충돌이 없는지 확인할 수 있습니다.

firebase ext:uninstall geohash-ext

현재 솔루션은 작동하지만, 프로젝트 시작 부분에서 언급했듯이, 서비스와의 통신을 시뮬레이션하는 하드 코딩된 API 키가 있습니다. 원래 제공된 API 키 대신 최종 사용자의 API 키를 사용하려면 어떻게 해야 하나요? 자세히 알아보려면 계속 읽어보세요.

6. 확장 프로그램을 사용자가 구성할 수 있도록 설정

Codelab의 현재 시점에는 이미 작성한 함수의 독자적인 설정과 함께 사용하도록 구성된 확장 프로그램이 있습니다. 하지만 사용자가 데카르트 평면상의 위치를 나타내는 필드에 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은 사람이 읽을 수 있는 식별자로, 매개변수의 역할을 개발자에게 알려줍니다.
  • description은 값에 대한 자세한 설명을 제공합니다. 이는 마크다운을 지원하므로 추가 문서에 연결하거나 개발자에게 중요한 단어를 강조 표시할 수 있습니다.
  • type은 사용자가 매개변수 값을 설정하는 방식에 대한 입력 메커니즘을 정의합니다. string, select, multiSelect, selectResource, secret 등 여러 유형이 있습니다. 각 옵션에 관한 자세한 내용은 문서를 참고하세요.
  • validationRegex는 개발자 항목을 특정 정규식 값으로 제한합니다 (이 예에서는 여기에 있는 간단한 필드 이름 가이드라인을 기반으로 함). 만약 실패하면...
  • validationErrorMessage가 개발자에게 실패 값을 알립니다.
  • default는 개발자가 텍스트를 입력하지 않은 경우의 값입니다.
  • required는 개발자가 텍스트를 입력할 필요가 없음을 의미합니다.
  • 변경 불가능을 사용하면 개발자가 이 확장 프로그램을 업데이트하고 이 값을 변경할 수 있습니다. 이 경우 요구사항이 변경되면 개발자가 필드 이름을 변경할 수 있어야 합니다.
  • 는 유효한 입력이 어떤 모습일지에 대한 아이디어를 제공합니다.

이해가 되지 않았습니다.

  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!;

일반적으로 환경 변수 값으로 null 검사를 수행하고자 하지만, 이 경우에는 매개변수 값이 올바르게 복사되리라고 신뢰하는 것입니다. 이제 코드가 확장 프로그램 매개변수와 호환되도록 구성되었습니다.

7. 사용자 문서 만들기

에뮬레이터 또는 Firebase Extensions Marketplace에서 코드를 테스트하기 전에 개발자가 확장 프로그램 사용 시 얻을 수 있는 이점을 알 수 있도록 확장 프로그램을 문서화해야 합니다.

  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/extensionextension.yaml 파일이 포함된 디렉터리의 절대 경로로 바꿉니다. 그러면 확장 프로그램의 설치 프로세스가 시작되고 구성을 Firebase 또는 에뮬레이터로 푸시하기 전에 구성이 포함된 .env 파일이 생성됩니다.
firebase ext:install /path/to/extension
  • 프로젝트를 로컬로 배포하므로 Google Cloud Secret Manager가 아닌 로컬 파일을 사용하도록 지정하세요.

da928c65ffa8ce15.png

  1. 로컬 에뮬레이터 도구 모음을 시작합니다.
firebase emulators:start

실제 Firebase 프로젝트로 설치 및 테스트

실제 Firebase 프로젝트에서 확장 프로그램을 설치할 수 있습니다. 테스트에는 테스트 프로젝트를 사용하는 것이 좋습니다. 확장 프로그램의 엔드 투 엔드 흐름을 테스트하려는 경우 또는 Firebase 에뮬레이터 도구 모음에서 확장 프로그램의 트리거를 아직 지원하지 않는 경우 이 테스트 워크플로를 사용하세요 (Extensions 에뮬레이터 옵션 참고). 에뮬레이터는 현재 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/extensionextension.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 Console 또는 에뮬레이터의 WebView 중 해당하는 Firestore 탭으로 이동합니다.
  2. x 필드와 y 필드로 지정된 컬렉션에 문서를 추가합니다. 이 경우 업데이트된 문서는 x 필드가 xv이고 y 필드가 yvu/{uid}에 있습니다.

Firestore 레코드를 추가하는 Firebase 에뮬레이터 화면

  1. 확장 프로그램을 성공적으로 설치한 경우 두 필드를 저장한 후 확장 프로그램에서 문서에 hash라는 새 필드를 만듭니다.

추가된 해시를 보여주는 에뮬레이터의 Firestore 데이터베이스 화면

8. 수고하셨습니다.

첫 번째 Cloud 함수를 Firebase 확장 프로그램으로 변환했습니다.

개발자가 확장 프로그램 배포 방법을 선택할 수 있도록 extension.yaml 파일을 추가하고 구성했습니다. 그런 다음 확장 프로그램 개발자가 확장 프로그램을 설정하기 전에 해야 하는 작업과 확장 프로그램을 성공적으로 설치한 후 취해야 할 단계에 관한 안내를 제공하는 사용자 문서를 만들었습니다.

지금까지 Firebase 함수를 배포 가능한 Firebase Extensions로 변환하는 데 필요한 주요 단계를 알아봤습니다.

다음 단계