משנים את הייעוד של קוד Cloud Functions כתוסף ל-Firebase

1. לפני שמתחילים

תוסף ל-Firebase מבצע משימה ספציפית או קבוצה של משימות בתגובה לבקשות HTTP או לאירועים שמופעלים ממוצרים אחרים של Firebase ו-Google, כמו Firebase Cloud Messaging,‏ Cloud Firestore או Pub/Sub.

מה תפַתחו

ב-Codelab הזה, בונים תוסף של Firebase ל-geohashing. לאחר הפריסה, התוסף ממיר קואורדינטות X ו-Y ל-geohash בתגובה לאירועים ב-Firestore או באמצעות הפעלות של פונקציות שניתן לקרוא להן. אפשר להשתמש באפשרות הזו כחלופה להטמעת ספריית GeoFire בכל פלטפורמות היעד שלכם לאחסון נתונים, וכך לחסוך זמן.

התוסף של geohash שמוצג במסוף Firebase

מה תלמדו

  • איך לוקחים קוד קיים של Cloud Functions והופכים אותו לתוסף Firebase שניתן להפצה
  • איך מגדירים קובץ extension.yaml
  • איך מאחסנים מחרוזות רגישות (מפתחות API) בתוסף
  • איך לאפשר למפתחים של התוסף להגדיר אותו כך שיתאים לצרכים שלהם
  • איך בודקים ומפרסים את התוסף

מה צריך להכין

  • Firebase CLI (התקנה והתחברות)
  • חשבון Google, למשל חשבון Gmail.
  • Node.js ו-npm
  • סביבת הפיתוח המועדפת עליכם

2. ליצירת קמפיין

קבלת הקוד

כל מה שצריך בשביל התוסף הזה נמצא במאגר ב-GitHub. כדי להתחיל, צריך להוריד את הקוד ולפתוח אותו בסביבת הפיתוח המועדפת עליכם.

  1. פותחים את קובץ ה-ZIP שהורדתם.
  2. כדי להתקין את יחסי התלות הנדרשים, פותחים את הטרמינל בספרייה functions ומריצים את הפקודה npm install.

מגדירים את Firebase

מומלץ מאוד להשתמש במהכלי ההדמיה של Firebase במסגרת הקודלאב הזה. אם אתם רוצים להתנסות בפיתוח תוספים בפרויקט Firebase אמיתי, קראו את המאמר יצירת פרויקט Firebase. ה-Codelab הזה משתמש ב-Cloud Functions, לכן אם אתם משתמשים בפרויקט Firebase אמיתי במקום באמולטורים, צריך לשדרג לתוכנית התמחור של Blaze.

רוצה לדלג קדימה?

אפשר להוריד גרסה מלאה של ה-Codelab. אם נתקעים בדרך או רוצים לראות איך נראה תוסף שהושלם, תוכלו לבדוק את ההסתעפות codelab-end במאגר GitHub או להוריד את קובץ ה-ZIP שהושלם.

3. בדיקת הקוד

  • פותחים את הקובץ index.ts מקובץ ה-zip. שימו לב שהוא מכיל שתי הצהרות של Cloud Functions.

למה הפונקציות האלה עושות?

פונקציות ההדגמה האלה משמשות לביצוע גיבוב (hashing) גיאוגרפי. הם לוקחים צמד קואורדינטות והופכים אותם לפורמט שמותאם לשאילתות גיאוגרפיות ב-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 trigger. כשאירוע כתיבת נתונים מתרחש במסד הנתונים, הפונקציה מגיבה לאירוע הזה על ידי חיפוש של שדה xv ושדה yv. אם שני השדות האלה קיימים, היא מחשבת את ה-geohash ומזינה את הפלט במיקום הפלט שצוין למסמך. מסמך הקלט מוגדר על ידי הקבוע users/{uid}, כלומר הפונקציה קוראת כל מסמך שנכתב באוסף users/ ולאחר מכן מעבדת גיבוב גיאוגרפי עבור המסמכים האלה. לאחר מכן, הוא יפיק את הגיבוב לשדה גיבוב (hash) באותו מסמך.

פונקציות שניתן להפעיל

הפונקציה הבאה בקובץ 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. הוא מציין שהפונקציה הזו היא פונקציה שניתן להפעיל, שאפשר להפעיל אותה מתוך הקוד של אפליקציית הלקוח. הפונקציה הניתנת לקריאה מקבלת את הפרמטרים x ו-y ומחזירה geohash. הפונקציה הזו לא תיקרא ישירות ב-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)

השם של התוסף משמש כבסיס למזהה המופע של התוסף (משתמשים יכולים להתקין כמה מופעים של תוסף, לכל אחד מהם מזהה משלו). לאחר מכן, מערכת Firebase יוצרת את השם של חשבונות השירות של התוסף ואת המשאבים הספציפיים לתוסף באמצעות מזהה המכונה הזה. מספר הגרסה מציין את גרסת התוסף. התוסף צריך להתאים לניהול גרסאות סמנטי, וצריך לעדכן אותו בכל פעם שמבצעים שינויים בפונקציונליות של התוסף. גרסת מפרט התוסף משמשת כדי לקבוע אחרי איזה מפרט תוספים ל-Firebase לפעול, במקרה הזה, ייעשה שימוש ב-v1beta.

  1. מוסיפים לקובץ YAML פרטים ידידותיים למשתמש:
...

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

השם המוצג מייצג בצורה ידידותית את שם התוסף בזמן האינטראקציה של מפתחים עם התוסף. התיאור מספק סקירה כללית קצרה של הפעולות שהתוסף מבצע. כשהתוסף נפרס ב-extensions.dev, הוא נראה בערך כך:

התוסף Geohash Converter כפי שהוא מופיע ב-extensions.dev

  1. מציינים את הרישיון לקוד בתוסף.
...

license: Apache-2.0  # The license you want for the extension
  1. מציינים מי כתב את התוסף ואם נדרש חיוב כדי להתקין אותו:
...

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

billingRequired: true

הקטע author משמש כדי להודיע למשתמשים למי לפנות במקרה של בעיות עם התוסף או אם הם רוצים לקבל מידע נוסף עליו. billingRequired הוא פרמטר נדרש וצריך להגדיר אותו כ-true, כי כל התוספים מסתמכים על Cloud Functions, שדורשת את תוכנית Blaze.

הערך הזה מציין את מספר השדות המינימלי שנדרש בקובץ extension.yaml כדי לזהות את התוסף הזה. פרטים נוספים על פרטי זיהוי אחרים שאפשר לציין בתוסף זמינים במסמכי התיעוד.

5. המרת הקוד של Cloud Functions למשאב Extensions

משאב של תוסף הוא פריט שנוצר בפרויקט על ידי Firebase במהלך התקנת התוסף. לאחר מכן, המשאבים האלה יהיו בבעלות התוסף, ויהיו לו חשבון שירות ספציפי שפועל בהם. בפרויקט הזה, המשאבים האלה הם Cloud Functions, וצריך להגדיר אותם בקובץ extension.yaml כי התוסף לא ייצור משאבים באופן אוטומטי מקוד בתיקיית הפונקציות. אם לא תצהירו במפורש על פונקציות Cloud כמשאבים, לא תוכלו לפרוס אותן כשתפרסו את התוסף.

מיקום פריסה שהוגדר על ידי המשתמש

  1. המשתמשים יכולים לציין את המיקום שבו הם רוצים לפרוס את התוסף ולהחליט אם עדיף לארח את התוסף קרוב יותר למשתמשי הקצה או קרוב יותר למסד הנתונים שלו. בקובץ extension.yaml, כוללים את האפשרות לבחור מיקום.

extension.yaml

עכשיו אפשר לכתוב את ההגדרות של משאב הפונקציה.

  1. בקובץ extension.yaml, יוצרים אובייקט משאב לפונקציה locationUpdate. יש להוסיף את הקטע הבא לקובץ extension.yaml:
resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}

מגדירים את name בתור שם הפונקציה שמוגדר בקובץ index.ts של הפרויקט. מציינים את ה-type של הפונקציה שנפרסת, שצריכה להיות תמיד firebaseextensions.v1beta.function. לאחר מכן מגדירים את properties של הפונקציה. הנכס הראשון שמגדירים הוא ה-eventTrigger שמשויך לפונקציה הזו. כדי לשקף את התמיכה הנוכחית של התוסף, משתמשים ב-eventType של providers/cloud.firestore/eventTypes/document.write, שמופיע במסמך כתיבת פונקציות Cloud עבור התוסף. אתם מגדירים את resource כמיקום של המסמכים. מכיוון שהיעד הנוכחי שלכם הוא לשקף את מה שקיים בקוד, נתיב המסמך מאזין ל-users/{uid}, ולפניו מופיע מיקום ברירת המחדל של מסד הנתונים.

  1. לתוסף נדרשות הרשאות קריאה וכתיבה למסד הנתונים של Firestore. בסוף הקובץ extension.yaml, מציינים את תפקידי ה-IAM שלגביהם לתוסף צריכה להיות גישה כדי לעבוד עם מסד הנתונים בפרויקט Firebase של המפתח.
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

התפקיד datastore.user מגיע מרשימת תפקידי IAM נתמכים לתוספים. מכיוון שהתוסף מיועד לקריאה וכתיבה, התפקיד datastore.user מתאים כאן.

  1. צריך להוסיף גם את הפונקציה שניתנת לקריאה. בקובץ extension.yaml, יוצרים משאב חדש במאפיין המשאבים. המאפיינים הבאים ספציפיים לפונקציה שניתן להתקשר אליה:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

על אף שהמשאב הקודם השתמש ב-eventTrigger, כאן משתמשים ב-httpsTrigger, שמכסה גם פונקציות שניתן לקרוא וגם פונקציות HTTPS.

בדיקת הקוד

נדרשו הרבה הגדרות כדי לגרום ל-extension.yaml להתאים לכל הפעולות בקוד שבקובץ index.ts. קובץ extension.yaml המלא אמור להיראות כך בשלב הזה:

extension.yaml

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

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

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

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

billingRequired: true

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

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

בדיקת סטטוס

בשלב הזה, החלק הפונקציונלי הראשוני של התוסף מוגדר, כך שאפשר לנסות אותו באמצעות אמוללטורים של Firebase.

  1. אם עדיין לא עשיתם זאת, קוראים לפונקציה npm run build בתיקיית הפונקציות בפרויקט התוספים שהורדתם.
  2. יוצרים ספרייה חדשה במערכת המארחת ומחברים אותה לפרויקט Firebase באמצעות 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. מאותה ספרייה, מריצים את firebase ext:install. מחליפים את /path/to/extension בנתיב המוחלט לספרייה שמכילה את קובץ extension.yaml.
firebase ext:install /path/to/extension
    This command does two things:
  • תוצג בקשה לציין את ההגדרות למכונה של התוסף, וייווצר קובץ *.env שמכיל את פרטי ההגדרה של המכונה.
  • הוא מוסיף את מופע התוסף לקטע extensions ב-firebase.json. הפעולה הזו משמשת כמפה של מזהה המכונה לגרסת התוסף.
  • מכיוון שאתם פורסים את הפרויקט באופן מקומי, אפשר לציין שאתם רוצים להשתמש בקובץ מקומי ולא ב-Google Cloud Secret Manager.

צילום מסך של תהליך התקנת התוסף, שמראה שהקובץ המקומי משמש לשמירת סודות בהתקנת התוסף הזה

  1. מפעילים את האמולטורים של Firebase עם ההגדרה החדשה:
firebase emulators:start
  1. אחרי שמריצים את emulators:start, עוברים לכרטיסייה Firestore ב-WebView של האמולטורים.
  2. מוסיפים מסמך לאוסף users עם שדה מספר xv ושדה מספר yv.

תיבת דו-שיח שמוצגת באמולטורים של Firebase כדי להתחיל אוסף עם מזהה הקולקציה שמכיל את הביטוי

  1. אם ההתקנה של התוסף הושלמה, התוסף יוצר במסמך שדה חדש בשם hash.

האוסף users עם מסמך משתמש שיש בו שדות xv,‏ yv ו-hash.

ניקוי כדי למנוע התנגשויות

  • אחרי שנסיים את הבדיקה, נצטרך להסיר את התוסף. אנחנו עומדים לעדכן את קוד התוסף, ולא רוצים שיהיה לו קונפליקט עם התוסף הנוכחי בשלב מאוחר יותר.

תוספים מאפשרים להתקין מספר גרסאות של אותו תוסף בו-זמנית, כך שהסרת ההתקנה תבטיח שאין התנגשויות עם תוסף שהותקן קודם לכן.

firebase ext:uninstall geohash-ext

הפתרון הנוכחי עובד, אבל כפי שציינו בתחילת הפרויקט, יש מפתח API בתוך הקוד שמטרתו לדמות תקשורת עם שירות. איך אפשר להשתמש במפתח ה-API של משתמש הקצה במקום במפתח שסופק במקור? כדאי לקרוא בהמשך כדי לגלות.

6. הגדרת המשתמש בתוסף

בשלב הזה ב-Codelab יש תוסף שמוגדר לשימוש עם ההגדרות המקובעות של הפונקציות שכבר כתבת. אבל מה אם המשתמש רוצה להשתמש בקווי אורך ורוחב במקום ב-y וב-x בשדות שמציינים את המיקום במישור קרטיאני? בנוסף, איך אפשר לגרום למשתמש הקצה לספק מפתח API משלו, במקום לאפשר לו לצרוך את מפתח ה-API שסופק? אתם עלולים לחרוג במהירות מהמכסה של ה-API הזה. במקרה כזה, מגדירים פרמטרים ומשתמשים בהם.

מגדירים פרמטרים בסיסיים בקובץ extension.yaml

בתור התחלה, צריך להמיר את הפריטים שלמפתחים יש אפשרות להגדיר עבורם הגדרות אישיות. הפרמטרים הראשונים הם XFIELD ו-YFIELD.

  1. בקובץ extension.yaml, מוסיפים את הקוד הבא, שמשתמש בפרמטר השדה XFIELD ובפרמטר השדה YFIELD. הפרמטרים האלה נמצאים בנכס 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 נותן לפרמטר שם באופן שגלוי לכם, הבעלים של התוסף. יש להשתמש בערך הזה בהמשך, כאשר מציינים את ערכי הפרמטרים.
  • label הוא מזהה קריא לאנשים בשביל המפתח, כדי שהוא יוכל לדעת מה הפרמטר עושה.
  • description – תיאור מפורט של הערך. הפעולה הזו תומכת בתגי עיצוב, ולכן היא יכולה לקשר למסמכי תיעוד נוספים או להדגיש מילים שחשובות למפתח.
  • type מגדיר את מנגנון הקלט של האופן שבו משתמש מגדיר את ערך הפרמטר. יש הרבה סוגים, כולל string,‏ select,‏ multiSelect,‏ selectResource ו-secret. למידע נוסף על כל אחת מהאפשרויות האלה, עיינו במאמרי העזרה.
  • validationRegex מגביל את הערך שהמפתח מזין לערך ביטוי רגולרי מסוים (בדוגמה, הוא מבוסס על ההנחיות לבחירת שמות שדות פשוטים שזמינות כאן). אם הבדיקה נכשלת…
  • validationErrorMessage מתריע למפתח על ערך הכשל.
  • default הוא הערך שיהיה אם המפתח לא מזין טקסט.
  • המשמעות של חובה היא שהמפתח לא נדרש להזין טקסט.
  • הערך לא ניתן לשינוי מאפשר למפתח לעדכן את התוסף הזה ולשנות את הערך. במקרה כזה, למפתח צריכה להיות אפשרות לשנות שמות של שדות כשהדרישות שלהם משתנות.
  • דוגמה מספקת מושג לגבי האופן שבו קלט חוקי עשוי להיראות.

זה היה הרבה מידע להבין!

  1. יש עוד שלושה פרמטרים שצריך להוסיף לקובץ extension.yaml לפני שמוסיפים פרמטר מיוחד.
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has received a value, it notifies the extension to
      calculate a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash

הגדרת פרמטרים רגישים

עכשיו צריך לנהל את מפתח ה-API שהמשתמש מציין. זוהי מחרוזת רגישה שאין לשמור אותה בטקסט פשוט בפונקציה. במקום זאת, כדאי לאחסן את הערך הזה ב-Cloud secret manager. זהו מיקום מיוחד בענן שבו מאוחסנים סודות מוצפנים, כדי למנוע דליפת מידע בטעות. כדי לעשות זאת, המפתח צריך לשלם על השימוש בשירות הזה, אבל הוא מוסיף שכבת אבטחה נוספת למפתחות ה-API שלו ויכול להגביל פעילות שמקורה בתרמית. במסמכי התיעוד למשתמשים מוצג התראה למפתחים על כך שמדובר בשירות בתשלום, כדי שלא יהיו הפתעות בחיוב. באופן כללי, השימוש דומה למשאבי המחרוזות האחרים שצוינו למעלה. ההבדל היחיד הוא הסוג שנקרא secret.

  • לקובץ extension.yaml, מוסיפים את הקוד הבא:

extension.yaml

  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

עדכון המאפיינים resource כדי להשתמש בפרמטרים

כפי שצוין קודם, המשאב (ולא הפונקציה) מגדיר את אופן המעקב אחרי המשאב, לכן צריך לעדכן את המשאב locationUpdate כדי להשתמש בפרמטר החדש.

  • לקובץ extension.yaml, מוסיפים את הקוד הבא:

extension.yaml

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

## To this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}

בודקים את הקובץ extension.yaml

  • בודקים את הקובץ extension.yaml. הוא אמור להיראות כך:

extension.yaml

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

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

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

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

billingRequired: true

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If you don't provide a value for this field, the extension will use 'xv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      Value. If you don't provide a value for this field, the extension will use 'yv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has been modified, it notifies the extension to
      compute a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash
  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

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

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

גישה לפרמטרים בקוד

עכשיו, אחרי שכל הפרמטרים מוגדרים בקובץ extension.yaml, מוסיפים אותם לקובץ index.ts.

  • בקובץ index.ts, מחליפים את ערכי ברירת המחדל בטקסט process.env.PARAMETER_NAME, שמאחזר את ערכי הפרמטרים המתאימים ומאכלס אותם בקוד הפונקציה שנפרס בפרויקט Firebase של המפתח.

index.ts

// Replace this:
const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

// with this:
const documentPath = process.env.INPUTPATH!; // this value is ignored since its read from the resource
const xField = process.env.XFIELD!;
const yField = process.env.YFIELD!;
const apiKey = process.env.APIKEY!;
const outputField = process.env.OUTPUTFIELD!;

בדרך כלל, כדאי לבצע בדיקות של ערכים null עם ערכי משתני הסביבה, אבל במקרה הזה, אתם יכולים לסמוך על כך שערכי הפרמטרים מועתקים בצורה נכונה. עכשיו הקוד מוגדר לפעול עם הפרמטרים של התוסף.

7. יצירת מסמכי תיעוד למשתמשים

לפני שבודקים את הקוד במהדמנים או בזירת המסחר של התוספים של Firebase, צריך לתעד את התוסף כדי שהמפתחים ידעו מה הם מקבלים כשהם משתמשים בו.

  1. מתחילים ביצירת הקובץ PREINSTALL.md, שמשמשים לתיאור הפונקציונליות, הדרישות המוקדמות להתקנה וההשלכות האפשריות על החיוב.

PREINSTALL.md

Use this extension to automatically convert documents with a latitude and
longitude to a geohash in your database. Additionally, this extension includes a callable function that allows users to make one-time calls
to convert an x,y coordinate into a geohash.

Geohashing is supported for latitudes between 90 and -90 and longitudes
between 180 and -180.

#### Third Party API Key

This extension uses a fictitious third-party API for calculating the
geohash. You need to supply your own API keys. (Since it's fictitious,
you can use 1234567890 as an API key).

#### Additional setup

Before installing this extension, make sure that you've [set up a Cloud
Firestore database](https://firebase.google.com/docs/firestore/quickstart) in your Firebase project.

After installing this extension, you'll need to:

- Update your client code to point to the callable geohash function if you
want to perform arbitrary geohashes.

Detailed information for these post-installation tasks are provided after
you install this extension.

#### Billing
To install an extension, your project must be on the [Blaze (pay as you
go) plan](https://firebase.google.com/pricing)

- This extension uses other Firebase and Google Cloud Platform services,
which have associated charges if you exceed the service's no-cost tier:
 - Cloud Firestore
 - Cloud Functions (Node.js 16+ runtime. [See
FAQs](https://firebase.google.com/support/faq#extensions-pricing))
 - [Cloud Secret Manager](https://cloud.google.com/secret-manager/pricing)
  1. כדי לחסוך זמן בכתיבת README.md בפרויקט הזה, אפשר להשתמש בשיטה הנוחות:
firebase ext:info . --markdown > README.md

השילוב הזה כולל את התוכן של הקובץ PREINSTALL.md ופרטים נוספים על התוסף מהקובץ extension.yaml.

לבסוף, עליכם להודיע למפתח התוסף על פרטים נוספים לגבי התוסף שהותקן. יכול להיות שהמפתח יקבל הוראות ומידע נוספים אחרי השלמת ההתקנה, וייתכן שהוא יקבל משימות מפורטות לאחר ההתקנה, כמו הגדרת קוד לקוח.

  1. יוצרים קובץ POSTINSTALL.md ומוסיפים את הפרטים הבאים אחרי ההתקנה:

POSTINSTALL.md

Congratulations on installing the geohash extension!

#### Function information

* **Firestore Trigger** - ${function:locationUpdate.name} was installed
and is invoked when both an x field (${param:XFIELD}) and y field
(${param:YFIELD}) contain a value.

* **Callable Trigger** - ${function:callableHash.name} was installed and
can be invoked by writing the following client code:
 ```javascript
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions();
const geoHash = httpsCallable(functions, '${function:callableHash.name}');
geoHash({ ${param:XFIELD}: -122.0840, ${param:YFIELD}: 37.4221 })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const error = data.error;
    if (error != null) {
        console.error(`callable error : ${error}`);
    }
    const result = data.result;
    console.log(result);
  });

מעקב

מומלץ לעקוב אחרי הפעילות של התוסף שהותקן, כולל בדיקות של התקינות, השימוש והיומנים שלו.

The output rendering looks something like this when it's deployed:

<img src="img/82b54a5c6ca34b3c.png" alt="A preview of the latitude and longitude geohash converter extension in the firebase console"  width="957.00" />


## Test the extension with the full configuration
Duration: 03:00


It's time to make sure that the user-configurable extension is working the way it is intended.

* Change into the functions folder and ensure that the latest compiled version of the extensions exists. In the extensions project functions directory, call:

```console
npm run build

הפעולה הזו יוצרת מחדש את הפונקציות כך שקוד המקור העדכני ביותר מוכן לפריסה לצד התוסף כשהוא נפרס באמולטור או ישירות ב-Firebase.

לאחר מכן יוצרים ספרייה חדשה שממנה רוצים לבדוק את התוסף. מכיוון שהתוסף פותח מפונקציות קיימות, אל תבדקו בתיקייה שבה הוגדר התוסף, כי היא מנסה לפרוס את הפונקציות ואת כללי Firebase לצדה.

התקנה ובדיקה באמצעות אמולטורים של Firebase

  1. יוצרים ספרייה חדשה במערכת המארחת ומקשרים אותה לפרויקט Firebase באמצעות firebase init.
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. בספרייה הזו, מריצים את firebase ext:install כדי להתקין את התוסף. מחליפים את /path/to/extension בנתיב המוחלט לספרייה שמכילה את הקובץ extension.yaml. הפקודה הזו מפעילה את תהליך ההתקנה של התוסף ויוצרת קובץ .env שמכיל את ההגדרות שלכם, לפני שההגדרות מועברות אל Firebase או אל המהדמנים.
firebase ext:install /path/to/extension
  • מכיוון שפורסים את הפרויקט באופן מקומי, מציינים שרוצים להשתמש בקובץ מקומי במקום ב-Google Cloud Secret Manager.

da928c65ffa8ce15.png

  1. מפעילים את חבילת הכלים לאמולטור המקומי:
firebase emulators:start

התקנה ובדיקה באמצעות פרויקט Firebase אמיתי

אפשר להתקין את התוסף בפרויקט Firebase בפועל. מומלץ להשתמש בפרויקט בדיקה כדי לבדוק את הניסוי. מומלץ להשתמש בתהליך העבודה הזה לבדיקה אם אתם רוצים לבדוק את התהליך מקצה לקצה של התוסף, או אם הטריגר של התוסף עדיין לא נתמך בחבילת הסימולטורים של Firebase (ראו האפשרות 'סימולטור של תוספים'). האמולטורים תומכים כרגע בפונקציות שמופעלות על ידי בקשת HTTP ובפונקציות שמופעלות ברקע על ידי אירוע ברקע עבור Cloud Firestore, מסד נתונים בזמן אמת ו-Pub/Sub.

  1. יוצרים ספרייה חדשה במערכת המארחת ומחברים אותה לפרויקט Firebase באמצעות firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. לאחר מכן, מריצים את firebase ext:install מהספרייה הזו כדי להתקין את התוסף. מחליפים את /path/to/extension בנתיב המוחלט לספרייה שמכילה את הקובץ extension.yaml. הפעולה הזו תתחיל את תהליך ההתקנה של התוסף ותיצור קובץ .env שמכיל את ההגדרות האישיות שלכם, לפני שמעבירים את התצורה ל-Firebase או לאמולטורים.
firebase ext:install /path/to/extension
  • מכיוון שאתם רוצים לפרוס את האפליקציה ישירות ב-Firebase ולהשתמש ב-Google Cloud Secret Manager, עליכם להפעיל את Secret Manager API לפני שמתקינים את התוסף.
  1. פורסים את האפליקציה בפרויקט Firebase.
firebase deploy

בדיקת התוסף

  1. אחרי שמריצים את הקוד firebase deploy או firebase emulators:start, עוברים לכרטיסייה Firestore במסוף Firebase או ב-WebView של האמולטורים, לפי הצורך.
  2. הוספת מסמך לאוסף שצוין בשדה x ובשדה y. במקרה כזה, המסמכים המעודכנים נמצאים ב-u/{uid} עם שדה x של xv ושדה y של yv.

המסך של אמולטור Firebase להוספת רשומה ב-Firestore

  1. אם ההתקנה של התוסף הושלמה, אחרי שמשמרים את שני השדות, התוסף יוצר שדה חדש בשם hash במסמך.

מסך של מסד הנתונים ב-Firestore מאמולטור שבו מוצג גיבוב (hash) שנוסף

8. כל הכבוד!

המרת בהצלחה את הפונקציה הראשונה של Cloud Functions לתוסף של Firebase!

הוספתם קובץ extension.yaml והגדרתם אותו כך שמפתחים יוכלו לבחור איך לפרוס את התוסף. לאחר מכן יצרתם מסמכי עזרה למשתמש עם הנחיות למפתחים של התוסף לגבי הפעולות שהם צריכים לבצע לפני הגדרת התוסף, וגם לגבי השלבים שהם עשויים להידרש לבצע אחרי התקנת התוסף.

עכשיו אתם יודעים מהם השלבים העיקריים שנדרשים כדי להמיר פונקציית Firebase לתוסף Firebase שניתן להפצה.

מה השלב הבא?