Ponowne wykorzystanie kodu Cloud Functions jako rozszerzenia Firebase

1. Zanim zaczniesz

Rozszerzenie Firebase wykonuje określone zadanie lub zestaw zadań w odpowiedzi na żądania HTTP lub zdarzenia wywołujące z innych usług Firebase i Google, takich jak Komunikacja w chmurze Firebase, Cloud Firestore czy Pub/Sub.

Co utworzysz

W tym ćwiczeniu utworzysz rozszerzenie Firebase do geohashingu. Po wdrożeniu rozszerzenie przekształca współrzędne X i Y w geohashe w odpowiedzi na zdarzenia Firestore lub wywołania funkcji, które można wywołać. Możesz użyć tej metody zamiast wdrażać bibliotekę geofire na wszystkich platformach docelowych do przechowywania danych, co pozwoli Ci zaoszczędzić czas.

Rozszerzenie geohash wyświetlane w konsoli Firebase

Czego się nauczysz

  • Jak przekształcić istniejący kod Cloud Functions w rozszerzenie Firebase do rozpowszechniania
  • Jak skonfigurować plik extension.yaml
  • Jak przechowywać w rozszerzeniu ciągi znaków zawierające informacje poufne (klucze interfejsu API)
  • Jak umożliwić deweloperom rozszerzenia skonfigurowanie go zgodnie z ich potrzebami
  • Jak przetestować i wdrożyć rozszerzenie

Czego potrzebujesz

  • Wiersz poleceń Firebase (instalacja i logowanie)
  • konto Google, np. konto Gmail;
  • Node.js i npm
  • Twoje ulubione środowisko programistyczne

2. Pierwsze kroki

Pobierz kod

Wszystko, czego potrzebujesz do tego rozszerzenia, znajdziesz w repozytorium GitHub. Aby rozpocząć, pobierz kod i otwórz go w ulubionym środowisku programistycznym.

  1. Rozpakuj pobrany plik ZIP.
  2. Aby zainstalować wymagane zależności, otwórz terminal w katalogu functions i uruchom polecenie npm install.

Konfigurowanie Firebase

W tym samouczku zdecydowanie zalecamy używanie emulatorów Firebase. Jeśli chcesz wypróbować tworzenie rozszerzeń w prawdziwym projekcie Firebase, zapoznaj się z artykułem tworzenie projektu Firebase. W tym laboratorium używamy Cloud Functions, więc jeśli korzystasz z prawdziwego projektu Firebase zamiast emulatorów, musisz przejść na abonament Blaze.

Chcesz pominąć ten krok?

Możesz pobrać ukończoną wersję codelabu. Jeśli napotkasz trudności lub chcesz zobaczyć, jak wygląda gotowe rozszerzenie, sprawdź gałąź codelab-endrepozytorium GitHub lub pobierz gotowy plik ZIP.

3. Sprawdź kod

  • Otwórz plik index.ts z pliku ZIP. Zwróć uwagę, że zawiera on 2 deklaracje funkcji w Cloud Functions.

Do czego służą te funkcje?

Te funkcje demonstracyjne są używane do geohashingu. Pobierają parę współrzędnych i przekształcają ją w format zoptymalizowany pod kątem zapytań geograficznych w Firestore. Funkcje te symulują użycie wywołania interfejsu API, dzięki czemu możesz dowiedzieć się więcej o obsłudze typów danych wrażliwych w rozszerzeniach. Więcej informacji znajdziesz w dokumentacji na temat uruchamiania zapytań geograficznych dotyczących danych w Firestore.

Stałe funkcji

Stałe są deklarowane na początku pliku index.ts, u góry. Niektóre z tych stałych są używane w określonych w rozszerzeniu regułach.

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

Aktywator Firestore

Pierwsza funkcja w pliku index.ts wygląda tak:

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,
        }
      );
  });

Ta funkcja to wyzwalacz Firestore. Gdy w bazie danych wystąpi zdarzenie zapisu, funkcja reaguje na nie, wyszukując pola xvyv. Jeśli oba te pola istnieją, oblicza geohash i zapisuje wynik w określonej lokalizacji dokumentu wyjściowego. Dokument wejściowy jest zdefiniowany przez stałą users/{uid}, co oznacza, że funkcja odczytuje każdy dokument zapisany w kolekcji users/, a następnie przetwarza geohash dla tych dokumentów. Następnie zapisuje identyfikator w polu identyfikatora w tym samym dokumencie.

Funkcje wywoływane

Kolejna funkcja w pliku index.ts wygląda tak:

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};
});

Zwróć uwagę na funkcję onCall. Oznacza to, że funkcja jest funkcją wywoływalną, którą można wywołać z kodu aplikacji klienckiej. Ta funkcja wywoływana przyjmuje parametry xy oraz zwraca geohash. Chociaż ta funkcja nie będzie wywoływana bezpośrednio w tym laboratorium, jest tu uwzględniona jako przykład elementu do skonfigurowania w rozszerzeniu Firebase.

4. Konfigurowanie pliku extension.yaml

Teraz, gdy wiesz już, co robi kod Cloud Functions w Twoim rozszerzeniu, możesz go spakować i rozpowszechnić. Każde rozszerzenie Firebase ma plik extension.yaml, który opisuje, co robi rozszerzenie i jak działa.

Plik extension.yaml wymaga początkowych metadanych rozszerzenia. Każdy z tych kroków pomoże Ci zrozumieć, co oznaczają poszczególne pola i dlaczego są potrzebne.

  1. Utwórz plik extension.yaml w katalogu głównym pobranego wcześniej projektu. Zacznij od dodania tych elementów:
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

Nazwa rozszerzenia jest używana jako podstawa identyfikatora instancji rozszerzenia (użytkownicy mogą instalować wiele instancji rozszerzenia, z których każda ma własny identyfikator). Firebase wygeneruje wtedy nazwy kont usługi rozszerzenia i zasobów specyficznych dla rozszerzenia, używając tego identyfikatora instancji. Numer wersji wskazuje wersję rozszerzenia. Musi ona być zgodna z semantycznym numerowaniem wersji i musisz ją aktualizować za każdym razem, gdy wprowadzasz zmiany w funkcjonalności rozszerzenia. Wersja specyfikacji rozszerzenia służy do określania, której specyfikacji rozszerzeń Firebase należy użyć. W tym przypadku jest to v1beta.

  1. Dodaj do pliku YAML kilka informacji, które będą przydatne dla użytkowników:
...

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

Wyświetlana nazwa to przyjazna reprezentacja nazwy rozszerzenia, która jest widoczna, gdy deweloperzy wchodzą z nim w interakcję. Opis zawiera krótki przegląd działania rozszerzenia. Gdy rozszerzenie zostanie wdrożone na stronie extensions.dev, będzie wyglądać mniej więcej tak:

Rozszerzenie Geohash Converter widoczne na stronie extensions.dev

  1. Określ licencję na kod w rozszerzeniu.
...

license: Apache-2.0  # The license you want for the extension
  1. Określ, kto napisał rozszerzenie, i czy do jego zainstalowania wymagane jest rozliczenie:
...

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

billingRequired: true

Sekcja author służy do informowania użytkowników, z kim mogą się skontaktować, jeśli mają problemy z rozszerzeniem lub chcą uzyskać więcej informacji na jego temat. billingRequired to wymagany parametr, który musi mieć wartość true, ponieważ wszystkie rozszerzenia korzystają z Cloud Functions, co wymaga abonamentu Blaze.

Obejmuje to minimalną liczbę pól wymaganych w pliku extension.yaml do identyfikacji tego rozszerzenia. Więcej informacji o innych informacjach identyfikacyjnych, które możesz określić w rozszerzeniu, znajdziesz w dokumentacji.

5. Konwertowanie kodu Cloud Functions na zasób rozszerzenia

Zasób rozszerzenia to element, który Firebase tworzy w projekcie podczas instalacji rozszerzenia. Rozszerzenie staje się właścicielem tych zasobów i ma określone konto usługi, które na nich działa. W tym projekcie są to funkcje Cloud Functions, które muszą być zdefiniowane w pliku extension.yaml, ponieważ rozszerzenie nie utworzy automatycznie zasobów z kodu w folderze funkcji. Jeśli funkcje Cloud Functions nie są wyraźnie zadeklarowane jako zasób, nie można ich wdrożyć podczas wdrażania rozszerzenia.

Lokalizacja wdrożenia zdefiniowana przez użytkownika

  1. Umożliwia użytkownikowi określenie lokalizacji, w której chce wdrożyć to rozszerzenie, i podjęcie decyzji, czy lepiej będzie hostować rozszerzenie bliżej użytkowników, czy bliżej bazy danych. W pliku extension.yaml uwzględnij opcję wyboru lokalizacji.

extension.yaml

Możesz teraz napisać konfigurację zasobu funkcji.

  1. W pliku extension.yaml utwórz obiekt zasobu dla funkcji locationUpdate. Dodaj do pliku extension.yaml te wiersze:
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 to nazwa funkcji zdefiniowana w pliku index.ts projektu. Określasz type wdrażanej funkcji, która na razie powinna zawsze mieć wartość firebaseextensions.v1beta.function. Następnie zdefiniuj properties tej funkcji. Pierwszą właściwością, którą zdefiniujesz, jest eventTrigger powiązany z tą funkcją. Aby odzwierciedlić to, co obecnie obsługuje rozszerzenie, użyj eventType funkcji providers/cloud.firestore/eventTypes/document.write, którą znajdziesz w dokumentacji Pisanie funkcji Cloud Functions dla rozszerzenia. resource określasz jako lokalizację dokumentów. Ponieważ Twoim obecnym celem jest odzwierciedlenie tego, co znajduje się w kodzie, ścieżka dokumentu nasłuchuje users/{uid}, a przed nią znajduje się domyślna lokalizacja bazy danych.

  1. Rozszerzenie potrzebuje uprawnień do odczytu i zapisu w bazie danych Firestore. Na samym końcu pliku extension.yaml określ role IAM, do których rozszerzenie powinno mieć dostęp, aby pracować z bazą danych w projekcie Firebase dewelopera.
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

Rola datastore.user pochodzi z listy obsługiwanych ról uprawnień w przypadku rozszerzeń. Rozszerzenie będzie odczytywać i zapisywać dane, więc rola datastore.user będzie w tym przypadku odpowiednia.

  1. Należy też dodać funkcję wywoływaną. W pliku extension.yaml utwórz nowy zasób we właściwości resources. Te właściwości są specyficzne dla funkcji wywoływanej:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

W poprzednim zasobie użyto znaku eventTrigger, a tutaj używasz znaku httpsTrigger, który obejmuje zarówno funkcje wywoływane, jak i funkcje HTTPS.

Sprawdzanie kodu

Wymagało to sporo konfiguracji, aby extension.yaml pasowało do wszystkiego, co robi kod w pliku index.ts. W tym momencie ukończony plik extension.yaml powinien wyglądać tak:

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.

Sprawdzanie stanu

Na tym etapie masz już skonfigurowane początkowe funkcjonalne elementy rozszerzenia, więc możesz je wypróbować za pomocą emulatorów Firebase.

  1. Jeśli jeszcze tego nie zrobiono, wywołaj funkcję npm run build w folderze funkcji pobranego projektu rozszerzeń.
  2. Utwórz nowy katalog w systemie hosta i połącz go z projektem Firebase za pomocą firebase init.
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. W tym samym katalogu uruchom firebase ext:install. Zastąp /path/to/extension ścieżką bezwzględną do katalogu, który zawiera plik extension.yaml.
firebase ext:install /path/to/extension
    This command does two things:
  • Poprosi Cię o określenie konfiguracji instancji rozszerzenia i utworzy plik *.env zawierający informacje o konfiguracji instancji.
  • Spowoduje to dodanie instancji rozszerzenia do sekcji extensions na stronie firebase.json. Działa to jak mapa identyfikatorów instancji do wersji rozszerzenia.
  • Ponieważ wdrażasz projekt lokalnie, możesz określić, że chcesz używać pliku lokalnego zamiast usługi Google Cloud Secret Manager.

Zrzut ekranu przedstawiający proces instalacji rozszerzenia, z którego wynika, że podczas instalacji tego rozszerzenia do przechowywania danych tajnych używany jest plik lokalny.

  1. Uruchom emulatory Firebase z nową konfiguracją:
firebase emulators:start
  1. Po uruchomieniu emulators:start otwórz kartę Firestore w widoku internetowym emulatorów.
  2. Dodaj dokument do kolekcji users z polem liczbowym xv i polem liczbowym yv.

Okno dialogowe wyświetlane w emulatorach Firebase, które umożliwia rozpoczęcie zbierania danych z użyciem identyfikatora kolekcji zawierającego frazę

  1. Jeśli instalacja rozszerzenia się powiedzie, w dokumencie pojawi się nowe pole o nazwie hash.

Kolekcja użytkowników z dokumentem użytkownika zawierającym pola xv, yv i hash.

Czyszczenie, aby uniknąć konfliktów

  • Po zakończeniu testowania odinstaluj rozszerzenie, ponieważ będziesz aktualizować jego kod i nie chcesz, aby w przyszłości doszło do konfliktu z obecnym rozszerzeniem.

Rozszerzenia umożliwiają zainstalowanie kilku wersji tego samego rozszerzenia jednocześnie, więc odinstalowanie zapewnia brak konfliktów z wcześniej zainstalowanym rozszerzeniem.

firebase ext:uninstall geohash-ext

Obecne rozwiązanie działa, ale jak wspomniano na początku projektu, jest w nim zakodowany klucz interfejsu API, który symuluje komunikację z usługą. Jak używać klucza interfejsu API użytkownika zamiast klucza dostarczonego pierwotnie? Czytaj dalej, aby się dowiedzieć.

6. Umożliwianie użytkownikom konfigurowania rozszerzenia

Na tym etapie samouczka masz rozszerzenie skonfigurowane do używania z opartą na opiniach konfiguracją funkcji, które zostały już napisane. Co jednak, jeśli użytkownik chce używać szerokości i długości geograficznej zamiast yx w polach wskazujących lokalizację na płaszczyźnie kartezjańskiej? Jak możesz też sprawić, aby użytkownik podawał własny klucz interfejsu API, zamiast korzystać z dostarczonego klucza? Możesz szybko przekroczyć limit tego interfejsu API. W tym przypadku konfigurujesz i używasz parametrów.

Określ podstawowe parametry w extension.yaml pliku.

Zacznij od przekonwertowania elementów, dla których deweloperzy mogą mieć niestandardową konfigurację. Pierwsze to parametry XFIELDYFIELD.

  1. W pliku extension.yaml dodaj ten kod, który korzysta z parametrów pól XFIELDYFIELD. Te parametry znajdują się w zdefiniowanej wcześniej właściwości YAML params:

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 nadaje parametrowi nazwę widoczną dla Ciebie, czyli producenta rozszerzenia. Użyj tej wartości później, gdy będziesz określać wartości parametrów.
  • label to identyfikator czytelny dla dewelopera, który informuje go o tym, do czego służy parametr.
  • Tag description zawiera szczegółowy opis wartości. Obsługuje on format Markdown, więc może zawierać linki do dodatkowej dokumentacji lub wyróżniać słowa, które mogą być ważne dla dewelopera.
  • Atrybut type określa mechanizm wprowadzania, za pomocą którego użytkownik może ustawić wartość parametru. Istnieje wiele typów, m.in. string, select, multiSelect, selectResourcesecret. Więcej informacji o każdej z tych opcji znajdziesz w dokumentacji.
  • validationRegex ogranicza wpis dewelopera do określonej wartości wyrażenia regularnego (w przykładzie jest to oparte na prostych wytycznych dotyczących nazw pól znajdujących się tutaj); a jeśli to się nie uda...
  • validationErrorMessage informuje dewelopera o wartości błędu.
  • default to wartość, która byłaby używana, gdyby deweloper nie wpisał żadnego tekstu.
  • Wymagane oznacza, że deweloper nie musi wpisywać żadnego tekstu.
  • immutable umożliwia deweloperowi aktualizowanie tego rozszerzenia i zmianę tej wartości. W takim przypadku deweloper powinien mieć możliwość zmiany nazw pól w miarę zmieniających się wymagań.
  • Przykład pokazuje, jak mogą wyglądać prawidłowe dane wejściowe.

To było trudne do zrozumienia.

  1. Zanim dodasz parametr specjalny, musisz dodać do pliku extension.yaml jeszcze 3 parametry.
  - 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

Określanie parametrów o charakterze kontrowersyjnym

Teraz musisz zarządzać kluczem interfejsu API określonym przez użytkownika. Jest to ciąg znaków zawierający informacje poufne, które nie powinny być przechowywane w funkcji w postaci zwykłego tekstu. Zamiast tego zapisz tę wartość w usłudze Cloud Secret Manager. Jest to specjalna lokalizacja w chmurze, w której przechowywane są zaszyfrowane obiekty tajne, co zapobiega ich przypadkowemu wyciekowi. Wymaga to od dewelopera opłaty za korzystanie z tej usługi, ale zapewnia dodatkową warstwę zabezpieczeń kluczy interfejsu API i potencjalnie ogranicza nieuczciwe działania. Dokumentacja użytkownika informuje dewelopera, że jest to usługa płatna, dzięki czemu nie będzie żadnych niespodzianek w rozliczeniach. Ogólnie rzecz biorąc, użycie jest podobne do innych zasobów ciągów znaków wymienionych powyżej. Jedyną różnicą jest typ o nazwie secret.

  • W pliku extension.yaml dodaj ten kod:

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

Zaktualizuj resource atrybuty, aby używać parametrów

Jak wspomnieliśmy wcześniej, sposób obserwacji zasobu określa sam zasób (a nie funkcja), więc aby używać nowego parametru, należy zaktualizować locationUpdatezasób.

  • W pliku extension.yaml dodaj ten kod:

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}

Sprawdź plik extension.yaml

  • Sprawdź plik extension.yaml. Powinna wyglądać mniej więcej tak:

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.

Dostęp do parametrów w kodzie

Teraz, gdy wszystkie parametry są skonfigurowane w pliku extension.yaml, dodaj je do pliku index.ts.

  • W pliku index.ts zastąp wartości domyślne wartością process.env.PARAMETER_NAME, która pobiera odpowiednie wartości parametrów i wypełnia nimi kod funkcji wdrożony w projekcie Firebase dewelopera.

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

Zwykle warto sprawdzać, czy wartości zmiennych środowiskowych nie są wartościami null, ale w tym przypadku możesz założyć, że wartości parametrów są prawidłowo kopiowane. Kod jest teraz skonfigurowany do współpracy z parametrami rozszerzenia.

7. Tworzenie dokumentacji użytkownika

Zanim przetestujesz kod na emulatorach lub w usłudze Firebase Extensions Marketplace, musisz udokumentować rozszerzenie, aby deweloperzy wiedzieli, co otrzymają, gdy go użyją.

  1. Zacznij od utworzenia pliku PREINSTALL.md, który służy do opisywania funkcji, wszelkich wymagań wstępnych dotyczących instalacji i potencjalnych konsekwencji związanych z płatnościami.

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. Aby zaoszczędzić czas na pisaniu README.md dla tego projektu, użyj wygodnej metody:
firebase ext:info . --markdown > README.md

Łączy on zawartość pliku PREINSTALL.md z dodatkowymi szczegółami rozszerzenia z pliku extension.yaml.

Na koniec poinformuj dewelopera rozszerzenia o dodatkowych szczegółach dotyczących właśnie zainstalowanego rozszerzenia. Po zakończeniu instalacji deweloper może otrzymać dodatkowe instrukcje i informacje, a także szczegółowe zadania do wykonania po instalacji, takie jak skonfigurowanie kodu klienta.

  1. Utwórz plik POSTINSTALL.md i dodaj do niego te informacje po instalacji:

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);
  });

Monitorowanie

Zalecamy monitorowanie aktywności zainstalowanego rozszerzenia, w tym sprawdzanie jego stanu, użycia i logów.

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

Spowoduje to ponowną kompilację funkcji, dzięki czemu najnowszy kod źródłowy będzie gotowy do wdrożenia wraz z rozszerzeniem na emulatorze lub bezpośrednio w Firebase.

Następnie utwórz nowy katalog, w którym będziesz testować rozszerzenie. Rozszerzenie zostało opracowane na podstawie istniejących funkcji, więc nie testuj go w folderze, w którym zostało skonfigurowane, ponieważ spowoduje to również próbę wdrożenia funkcji i reguł Firebase.

Instalowanie i testowanie za pomocą emulatorów Firebase

  1. Utwórz nowy katalog w systemie hosta i połącz go z projektem Firebase za pomocą firebase init.
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. W tym katalogu uruchom firebase ext:install, aby zainstalować rozszerzenie. Zastąp /path/to/extension ścieżką bezwzględną do katalogu, który zawiera plik extension.yaml. Spowoduje to rozpoczęcie procesu instalacji rozszerzenia i utworzenie pliku .env zawierającego konfiguracje przed przesłaniem konfiguracji do Firebase lub emulatorów.
firebase ext:install /path/to/extension
  • Ponieważ wdrażasz projekt lokalnie, wskaż, że chcesz użyć pliku lokalnego zamiast usługi Google Cloud Secret Manager.

da928c65ffa8ce15.png

  1. Uruchom lokalny pakiet emulatorów:
firebase emulators:start

Instalowanie i testowanie w rzeczywistym projekcie Firebase

Rozszerzenie możesz zainstalować w rzeczywistym projekcie Firebase. Do testowania zalecamy używanie projektu testowego. Skorzystaj z tej instrukcji testowania, jeśli chcesz przetestować cały proces rozszerzenia lub jeśli wyzwalacz rozszerzenia nie jest jeszcze obsługiwany przez pakiet emulatorów Firebase (patrz Opcja emulatora rozszerzeń). Obecnie emulatory obsługują funkcje aktywowane przez żądania HTTP oraz funkcje aktywowane przez zdarzenia w tle w przypadku Cloud Firestore, Bazy danych czasu rzeczywistego i Pub/Sub.

  1. Utwórz nowy katalog w systemie hosta i połącz go z projektem Firebase za pomocą firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Następnie w tym katalogu uruchom polecenie firebase ext:install, aby zainstalować rozszerzenie. Zastąp /path/to/extension ścieżką bezwzględną do katalogu, który zawiera plik extension.yaml. Spowoduje to rozpoczęcie procesu instalacji rozszerzenia i utworzenie pliku .env zawierającego konfiguracje przed przesłaniem konfiguracji do Firebase lub emulatorów.
firebase ext:install /path/to/extension
  1. Wdróż w projekcie Firebase.
firebase deploy

Testowanie rozszerzenia

  1. Po uruchomieniu polecenia firebase deploy lub firebase emulators:start otwórz kartę Firestore w konsoli Firebase lub w widoku internetowym emulatorów.
  2. Dodaje dokument do kolekcji określonej przez pole x i pole y. W tym przypadku zaktualizowane dokumenty znajdują się w u/{uid}, a pole x ma wartość xv, a pole y ma wartość yv.

Ekran emulatorów Firebase umożliwiający dodanie rekordu Firestore

  1. Jeśli instalacja rozszerzenia się powiedzie, po zapisaniu 2 pól w dokumencie pojawi się nowe pole o nazwie hash.

Ekran bazy danych Firestore z emulatora pokazujący dodany skrót

8. Gratulacje!

Udało Ci się przekształcić pierwszą funkcję Cloud Functions w rozszerzenie Firebase.

Dodano plik extension.yaml i skonfigurowano go tak, aby deweloperzy mogli wybrać sposób wdrażania rozszerzenia. Następnie utworzono dokumentację użytkownika, która zawiera wskazówki dla deweloperów rozszerzenia dotyczące tego, co powinni zrobić przed skonfigurowaniem rozszerzenia, oraz jakie kroki mogą być konieczne po jego pomyślnej instalacji.

Znasz już najważniejsze kroki wymagane do przekształcenia Funkcji Firebase w rozpowszechniane rozszerzenie Firebase.

Co dalej?