Ponowne wykorzystanie kodu Cloud Functions jako rozszerzenia Firebase

1. Zanim zaczniesz

Rozszerzenie Firebase wykonuje określone zadanie lub zbiór zadań w odpowiedzi na żądania HTTP albo aktywuje zdarzenia z innych usług Firebase i Google, takich jak Firebase Cloud Messaging, Cloud Firestore czy Pub/Sub.

Co utworzysz

W ramach tego ćwiczenia w Codelabs utworzysz rozszerzenie Firebase do szyfrowania geograficznego. Po wdrożeniu rozszerzenie przekształca współrzędne X i Y na geohasze w odpowiedzi na zdarzenia Firestore lub za pomocą wywołań funkcji. Możesz użyć tej funkcji jako alternatywy dla implementowania biblioteki geofire na wszystkich platformach docelowych do przechowywania danych, co pozwala zaoszczędzić czas.

Rozszerzenie z geografią wyświetlane w konsoli Firebase

Czego się nauczysz

  • Jak wykorzystać istniejący kod Cloud Functions i przekształcić go w dostępne do dystrybucji rozszerzenie Firebase
  • Jak skonfigurować plik extension.yaml
  • Jak przechowywać poufne ciągi znaków (klucze interfejsu API) w rozszerzeniu
  • jak umożliwić programistom jego skonfigurowanie zgodnie z własnymi potrzebami.
  • Testowanie i wdrażanie rozszerzenia

Czego potrzebujesz

2. Konfiguracja

Pobierz kod

Wszystko, czego potrzebujesz do działania tego rozszerzenia, znajduje się w repozytorium GitHub. Zacznij od pobrania kodu i otwarcia 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.

Skonfiguruj Firebase

W ramach tego ćwiczenia w Codelabs zalecamy korzystanie z emulatorów Firebase. Jeśli chcesz wypróbować programowanie rozszerzeń w prawdziwym projekcie Firebase, przeczytaj artykuł o tworzeniu projektu Firebase. To ćwiczenie w Codelabs wykorzystuje Cloud Functions, więc jeśli zamiast emulatorów używasz prawdziwego projektu Firebase, musisz przejść na abonament Blaze.

Chcesz przejść dalej?

Możesz pobrać ukończoną wersję ćwiczenia z programowania. Jeśli napotkasz jakieś problemy lub chcesz zobaczyć, jak wygląda gotowe rozszerzenie, zajrzyj do gałęzi codelab-end w repozytorium GitHub lub pobierz gotowy plik ZIP.

3. Sprawdź kod

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

Do czego służą te funkcje?

Te funkcje demonstracyjne są używane do geohasła. Pobierają one parę współrzędnych i przekształcają je w format zoptymalizowany pod kątem zapytań geograficznych w Firestore. Te funkcje symulują użycie wywołań 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 dotyczącej uruchamiania zapytań geograficznych dotyczących danych w Firestore.

Stałe funkcji

Stałe są deklarowane wcześnie na początku pliku index.ts. Do niektórych z tych stałych odwołują się zdefiniowane reguły rozszerzenia.

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 aktywator Firestore. Gdy w bazie danych wystąpi zdarzenie zapisu, funkcja reaguje na to zdarzenie, wyszukując pola xv i yv, a następnie, jeśli istnieją oba te pola, oblicza geoszyfrowanie i zapisuje dane wyjściowe w określonej lokalizacji dokumentu. Dokument wejściowy jest definiowany przez stałą users/{uid}, co oznacza, że funkcja odczytuje każdy dokument zapisany w kolekcji users/, a następnie przetwarza geoszyfrowanie tych dokumentów. Następnie haszuje w polu skrótu w tym samym dokumencie.

Funkcje wywoływane

Następna 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 ona, że jest to funkcja wywoływana, którą można wywołać z kodu aplikacji klienckiej. Ta funkcja wywoływana przyjmuje parametry x i y oraz zwraca geohash. Chociaż ta funkcja nie będzie wywoływana bezpośrednio w tym ćwiczeniach z programowania, została ona uwzględniona tutaj jako przykład do skonfigurowania w rozszerzeniu Firebase.

4. Konfigurowanie pliku extensions.yaml

Wiesz już, jak działa kod Cloud Functions w rozszerzeniu, więc możesz przygotować go do dystrybucji. Każde rozszerzenie Firebase ma plik extension.yaml, który opisuje działanie rozszerzenia i sposób jego działania.

Plik extension.yaml wymaga wstępnych metadanych dotyczących rozszerzenia. Każdy z tych kroków pomoże Ci zrozumieć, co oznaczają wszystkie pola i dlaczego ich potrzebujesz.

  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 stanowi podstawę identyfikatora instancji rozszerzenia (użytkownicy mogą instalować wiele wystąpień rozszerzenia, każde z własnym identyfikatorem). Następnie Firebase generuje nazwy kont usługi rozszerzenia i zasoby związane z rozszerzeniem, korzystając z tego identyfikatora instancji. Numer wersji wskazuje wersję rozszerzenia. Wymagana jest semantyczna obsługa wersji, którą musisz aktualizować za każdym razem, gdy wprowadzasz zmiany w funkcjach rozszerzenia. Wersja specyfikacji rozszerzenia służy do określenia, której specyfikacji rozszerzenia Firebase należy zastosować. W tym przypadku używana jest specyfikacja rozszerzenia v1beta.

  1. Dodaj proste w użyciu szczegóły do pliku YAML:
...

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

Wyświetlana nazwa w przyjazny sposób odzwierciedla nazwę rozszerzenia, gdy deweloperzy z niego korzystają. Opis zawiera krótki opis funkcji rozszerzenia. Po wdrożeniu rozszerzenia na stronie extensions.dev wygląda ono mniej więcej tak:

Rozszerzenie Geohash Converter widoczne w witrynie extensions.dev

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

license: Apache-2.0  # The license you want for the extension
  1. Wskaż, kto napisał rozszerzenie i czy do jego zainstalowania wymagane są opłaty:
...

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

billingRequired: true

W sekcji author użytkownicy będą wiedzieć, z kim mają się skontaktować w razie problemów z rozszerzeniem lub chcą uzyskać więcej informacji. Parametr billingRequired jest wymagany, a jego wartość należy ustawić na true, ponieważ wszystkie rozszerzenia korzystają z Cloud Functions, który wymaga abonamentu Blaze.

Obejmuje to minimalną liczbę pól wymaganych w pliku extension.yaml do identyfikacji tego rozszerzenia. Więcej informacji na temat innych informacji umożliwiających identyfikację, które można podać w rozszerzeniu, znajdziesz w dokumentacji.

5. Konwertowanie kodu Cloud Functions na zasób rozszerzeń

Zasób rozszerzenia to element, który Firebase tworzy w projekcie podczas instalacji rozszerzenia. Właścicielem tych zasobów jest rozszerzenie, które ma skonfigurowane konto usługi, które na nich działa. W tym projekcie te zasoby to funkcje Cloud Functions, które muszą być zdefiniowane w pliku extension.yaml, ponieważ rozszerzenie nie będzie automatycznie tworzyć zasobów z kodu w folderze funkcji. Jeśli funkcje w Cloud Functions nie są jawnie zadeklarowane jako zasoby, nie można ich wdrożyć po wdrożeniu rozszerzenia.

Zdefiniowana przez użytkownika lokalizacja wdrożenia

  1. Zezwól użytkownikowi na określenie lokalizacji, w której chce wdrożyć to rozszerzenie, i zdecyduj, czy lepiej byłoby hostować je bliżej użytkowników, czy bliżej swojej bazy danych. W pliku extension.yaml umieść opcję wyboru lokalizacji.

extension.yaml (w języku angielskim)

Teraz możesz zapisać konfigurację zasobu funkcji.

  1. Utwórz w pliku extension.yaml obiekt zasobu dla funkcji locationUpdate. Dołącz do pliku extension.yaml ten kod:
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}

Definiujesz name jako nazwę funkcji zdefiniowaną w pliku index.ts projektu. Określasz właściwość type wdrażanej funkcji. Na razie powinna to być zawsze firebaseextensions.v1beta.function. Następnie określ properties tej funkcji. pierwszą zdefiniowaną przez Ciebie właściwość to eventTrigger powiązana z tą funkcją. Jeśli chcesz zobaczyć, jakie funkcje są obecnie obsługiwane przez rozszerzenie, użyj funkcji eventType z providers/cloud.firestore/eventTypes/document.write, którą znajdziesz w dokumentacji Zapisywanie funkcji w Cloud Functions na potrzeby rozszerzenia. resource określasz jako lokalizację dokumentów. Bieżący cel to odzwierciedlenie zawartości kodu, więc ścieżka dokumentu nasłuchuje źródła users/{uid}, poprzedzając domyślną lokalizację bazy danych.

  1. Rozszerzenie wymaga uprawnień do odczytu i zapisu w bazie danych Firestore. Na końcu pliku extension.yaml określ role uprawnień, do których rozszerzenie powinno mieć dostęp, aby móc korzystać z bazy 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ń na potrzeby rozszerzeń. Ponieważ rozszerzenie będzie odczytywać i pisać, rola datastore.user dobrze sprawdzi się w tym przypadku.

  1. Trzeba też dodać funkcję wywoływaną przez funkcję. W pliku extension.yaml utwórz nowy zasób dla właściwości zasobów. Te właściwości są specyficzne dla funkcji, którą można wywołać:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

Chociaż poprzedni zasób używał eventTrigger, tutaj używasz httpsTrigger, który obejmuje zarówno funkcje wywoływane i HTTPS.

Sprawdzanie kodu

Wymagało to obszernej konfiguracji, aby element extension.yaml pasował do wszystkich działań wykonanych przez kod w pliku index.ts. Gotowy plik extension.yaml powinien teraz wyglądać tak:

extension.yaml (w języku angielskim)

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 elementy rozszerzenia, które możesz wypróbować za pomocą emulatorów Firebase.

  1. Wywołaj polecenie npm run build w folderze funkcji w projekcie pobranych rozszerzeń.
  2. Utwórz w systemie hosta nowy katalog i połącz go z projektem Firebase za pomocą usługi 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 polecenie firebase ext:install. Zastąp /path/to/extension ścieżką bezwzględną do katalogu zawierającego plik extension.yaml.
firebase ext:install /path/to/extension
    This command does two things:
  • Zobaczysz prośbę o określenie konfiguracji instancji rozszerzenia i zostanie utworzony plik *.env zawierający informacje o konfiguracji instancji.
  • Spowoduje to dodanie wystąpienia rozszerzenia do sekcji extensions Twojego konta firebase.json. Służy do mapowania identyfikatora instancji na wersję rozszerzenia.
  • Wdrażasz projekt lokalnie, więc możesz wskazać, że zamiast Google Cloud Secret Manager używać pliku lokalnego.

Zrzut ekranu pokazujący proces instalacji rozszerzenia pokazujący, że podczas instalowania tego rozszerzenia plik lokalny jest używany do obiektów tajnych

  1. Uruchom emulatory Firebase z nową konfiguracją:
firebase emulators:start
  1. Po uruchomieniu funkcji emulators:start przejdź do karty Firestore w widoku internetowym emulatorów.
  2. Dodaj do kolekcji users dokument z polem liczbowym xv i polem z numerem yv.

Okno wyświetlane w emulatorach Firebase, które umożliwia tworzenie kolekcji z identyfikatorem kolekcji zawierającym wyrażenie.

  1. Jeśli udało się zainstalować rozszerzenie, utworzy ono w dokumencie nowe pole o nazwie hash.

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

Czyszczenie danych w celu uniknięcia konfliktów

  • Po zakończeniu testów odinstaluj rozszerzenie. Jeśli chcesz zaktualizować jego kod, nie musisz w przyszłości kolidować z bieżącym rozszerzeniem.

Rozszerzenia umożliwiają jednoczesne zainstalowanie wielu wersji tego samego rozszerzenia, więc odinstalowanie pozwala uniknąć konfliktów z wcześniej zainstalowanym rozszerzeniem.

firebase ext:uninstall geohash-ext

Obecne rozwiązanie działa, ale jak wspomnieliśmy na początku projektu, dostępny jest zakodowany na stałe klucz interfejsu API do symulowania komunikacji z usługą. Jak można użyć klucza interfejsu API użytkownika zamiast dostarczonego pierwotnie? Przekonaj się.

6. Umożliwianie konfigurowania rozszerzenia przez użytkownika

Na tym etapie ćwiczeń z programowania masz rozszerzenie, które jest skonfigurowane do użytku z zalecaną konfiguracją napisanych przez Ciebie funkcji. Co jednak, jeśli użytkownik chce użyć szerokości i długości geograficznej zamiast y i x w polach wskazujących lokalizację na płaszczyźnie kartezjańskiej? Poza tym co zrobić, aby użytkownik końcowy dostarczał własny klucz interfejsu API, zamiast korzystać z podanego przez niego klucza? Można szybko przekroczyć limit dla tego interfejsu API. W tym przypadku konfigurujesz parametry i korzystasz z nich.

Zdefiniuj podstawowe parametry w pliku extension.yaml

Zacznij od przekonwertowania elementów, dla których deweloperzy mogą mieć niestandardową konfigurację. Pierwszym z nich są parametry XFIELD i YFIELD.

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

extension.yaml (w języku angielskim)

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 nazwę parametrowi w sposób widoczny dla Ciebie, producenta rozszerzenia. Użyj tej wartości później podczas określania wartości parametrów.
  • Parametr label jest zrozumiały dla programisty i informuje go, do czego służy dany parametr.
  • Parametr description zawiera szczegółowy opis wartości. Ta funkcja obsługuje Markdown, dlatego może zawierać link do dodatkowej dokumentacji lub wyróżniać słowa, które mogą być ważne dla programisty.
  • Parametr type określa mechanizm, na podstawie którego użytkownik może ustawić wartość parametru. Istnieje wiele typów, np. string, select, multiSelect, selectResource i secret. Więcej informacji o każdej z tych opcji znajdziesz w dokumentacji.
  • Parametr validationRegex ogranicza wpis dewelopera do określonej wartości wyrażenia regularnego (w tym przykładzie jest ona oparta na prostych wytycznych dotyczących nazw pól dostępnych tutaj). a jeśli to nie pomoże...
  • validationErrorMessage informuje dewelopera o wartości błędu.
  • default (domyślna) to wartość, jaką należałoby ustawić, gdyby deweloper nie dodał żadnego tekstu.
  • required oznacza, że programista nie musi wpisywać żadnego tekstu.
  • niezmienna 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ę zmiany wymagań.
  • Przykład pokazuje, jak mogą wyglądać prawidłowe dane wejściowe.

Trzeba było to zrozumieć.

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

Definiowanie parametrów poufnych

Teraz musisz zarządzać kluczem interfejsu API określonym przez użytkownika. Jest to ciąg poufny, którego nie należy przechowywać w formie zwykłego tekstu. Zamiast tego zapisz tę wartość w menedżerze obiektów tajnych Cloud. Jest to specjalna lokalizacja w chmurze, w której przechowywane są zaszyfrowane obiekty tajne, aby zapobiegać ich przypadkowemu wyciekom. Wymaga to od dewelopera płacenia za korzystanie z tej usługi, ale stanowi to dodatkową warstwę zabezpieczeń dla kluczy interfejsu API i potencjalnie ogranicza nieuczciwe działania. Dokumentacja użytkownika informuje dewelopera, że jest to usługa płatna, aby uniknąć niespodzianek w płatnościach. Ogólnie użycie jest podobne do innych zasobów z ciągami znaków wspomnianymi powyżej. Jedyną różnicą jest typ o nazwie secret.

  • W pliku extension.yaml dodaj ten kod:

extension.yaml (w języku angielskim)

  - 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, zasób (nie funkcja) określa sposób obserwacji zasobu, więc zasób locationUpdate musi zostać zaktualizowany, aby można było użyć nowego parametru.

  • W pliku extension.yaml dodaj ten kod:

extension.yaml (w języku angielskim)

## 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 (w języku angielskim)

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.

Parametry dostępu w kodzie

W pliku extension.yaml są już skonfigurowane wszystkie parametry, więc dodaj je do pliku index.ts.

  • W pliku index.ts zastąp wartości domyślne ciągiem process.env.PARAMETER_NAME, który pobiera odpowiednie wartości parametrów i wypełnia je w kodzie funkcji wdrożonego 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!;

Normalnie chcesz sprawdzić wartości null dla wartości zmiennych środowiskowych, ale w tym przypadku masz pewność, że wartości parametrów zostały prawidłowo skopiowane. Kod jest teraz skonfigurowany do działania z parametrami rozszerzenia.

7. Utwórz dokumentację użytkownika

Przed przetestowaniem kodu w emulatorach lub na platformie handlowej rozszerzeń Firebase konieczne jest udokumentowanie tego rozszerzenia, aby deweloperzy wiedzieli, co uzyskują, gdy go używają.

  1. Zacznij od utworzenia pliku PREINSTALL.md. Opisuje on funkcje, wymagania wstępne dotyczące instalacji i potencjalne konsekwencje związane z rozliczeniami.

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 podczas zapisywania właściwości README.md w tym projekcie, użyj metody wygodnej:
firebase ext:info . --markdown > README.md

Łączy zawartość pliku PREINSTALL.md z dodatkowymi informacjami o rozszerzeniu z pliku extension.yaml.

Na koniec przekaż programiście rozszerzenia o kilka dodatkowych informacji na temat właśnie zainstalowanego rozszerzenia. Po ukończeniu instalacji deweloper może uzyskać dodatkowe instrukcje i informacje, a po instalacji szczegółowe zadania, np. konfigurację kodu klienta.

  1. Utwórz plik POSTINSTALL.md, a po instalacji dołącz następujące informacje:

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

Sprawdzoną metodą jest monitorowanie aktywności zainstalowanego rozszerzenia, w tym kontrola jego stanu, użycia i dziennikó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 ponowne skompilowanie funkcji w taki sposób, że najnowszy kod źródłowy będzie gotowy do wdrożenia razem z rozszerzeniem, gdy zostanie ono wdrożone w emulatorze lub bezpośrednio w Firebase.

Następnie utwórz nowy katalog, z którego chcesz przetestować rozszerzenie. Rozszerzenie powstało na podstawie istniejących funkcji, dlatego nie testuj go w folderze, w którym je skonfigurowano, ponieważ powoduje to też próbę wdrożenia funkcji i reguł Firebase obok niego.

Instalowanie i testowanie za pomocą emulatorów Firebase

  1. Utwórz w systemie hosta nowy katalog i połącz go z projektem Firebase za pomocą usługi 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 ciąg /path/to/extension ścieżką bezwzględną do katalogu zawierającego Twój plik extension.yaml. Spowoduje to rozpoczęcie procesu instalacji rozszerzenia i utworzenie pliku .env zawierającego Twoje konfiguracje, zanim przekażesz konfigurację do Firebase lub do emulatorów.
firebase ext:install /path/to/extension
  • Wdrażasz projekt lokalnie, dlatego wskaż, że zamiast usługi Google Cloud Secret Manager używać pliku lokalnego.

da928c65ffa8ce15.png

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

Instalowanie i testowanie z użyciem prawdziwego projektu Firebase

Możesz zainstalować swoje rozszerzenie w rzeczywistym projekcie Firebase. Do testowania zalecamy użycie projektu testowego. Skorzystaj z tego procesu testowania, jeśli chcesz przetestować cały proces rozszerzenia lub jeśli wyzwalacz rozszerzenia nie jest jeszcze obsługiwany przez pakiet emulatora Firebase (patrz opcja emulatora rozszerzeń). Emulatory obecnie obsługują funkcje wyzwalane przez żądania HTTP oraz funkcje aktywowane w tle w Cloud Firestore, Bazie danych czasu rzeczywistego i Pub/Sub.

  1. Utwórz w systemie hosta nowy katalog i połącz go z projektem Firebase za pomocą usługi firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Następnie w tym katalogu uruchom firebase ext:install, aby zainstalować rozszerzenie. Zastąp ciąg /path/to/extension ścieżką bezwzględną do katalogu zawierającego Twój plik extension.yaml. Spowoduje to rozpoczęcie procesu instalacji rozszerzenia i utworzenie pliku .env zawierającego Twoje konfiguracje, zanim przekażesz konfigurację do Firebase lub do emulatorów.
firebase ext:install /path/to/extension
  • Chcesz przeprowadzić bezpośrednie wdrożenie w Firebase i skorzystać z usługi Google Cloud Secret Manager, dlatego przed zainstalowaniem rozszerzenia musisz aktywować interfejs Secret Manager API.
  1. Wdróż w projekcie Firebase.
firebase deploy

Testowanie rozszerzenia

  1. Po uruchomieniu firebase deploy lub firebase emulators:start otwórz kartę Firestore konsoli Firebase lub widok internetowy emulatorów (w zależności od potrzeb).
  2. Dodaj dokument do kolekcji określonej w polach x i y. W tym przypadku zaktualizowane dokumenty znajdują się w folderze u/{uid}, gdzie pole x ma wartość xv, a y ma wartość yv.

Ekran emulatorów Firebase pozwalający dodać rekord Firestore

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

Ekran bazy danych Firestore z emulatora z dodanym haszem

8. Gratulacje!

Udało Ci się przekonwertować swoją pierwszą funkcję w Cloud Functions na rozszerzenie Firebase.

Dodałeś i skonfigurowałeś plik extension.yaml, aby deweloperzy mogli wybrać sposób wdrażania rozszerzenia. Następnie utworzyliśmy dokumentację użytkownika z instrukcjami, co programiści powinni zrobić, zanim je skonfigurują, i co mogą oni zrobić po ich zainstalowaniu.

Znasz już najważniejsze kroki wymagane do przekształcenia funkcji Firebase w możliwe do dystrybucji rozszerzenie Firebase.

Co dalej?