תחילת העבודה עם תוסף

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

1. הגדרת הסביבה ואתחול פרויקט

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

  1. מתקינים Node.js 16 או גרסה חדשה יותר. אחת הדרכים להתקין את Node היא באמצעות nvm (או nvm-windows).

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

    npm install -g firebase-tools

עכשיו משתמשים ב-Firebase CLI כדי לאתחל פרויקט תוסף חדש:

  1. יוצרים ספרייה לתוסף ומריצים את הפקודה cd כדי לעבור אליה:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. מריצים את הפקודה ext:dev:init של Firebase CLI:

    firebase ext:dev:init

    כשמוצגת בקשה, בוחרים ב-JavaScript כשפה לפונקציות (אבל אפשר גם להשתמש ב-TypeScript כשמפתחים תוסף משלכם), וכשמוצגת בקשה להתקנת תלות, משיבים 'כן'. (מאשרים את ברירות המחדל לכל האפשרויות האחרות). הפקודה הזו תגדיר בסיס קוד שלד לתוסף חדש, שממנו תוכלו להתחיל לפתח את התוסף.

2. ניסיון להשתמש באמולטור כדי לנסות את התוסף לדוגמה

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

נסו להריץ את התוסף לדוגמה באמולטור:

  1. עוברים לספרייה integration-tests:

    cd functions/integration-tests
  2. מפעילים את האמולטור עם פרויקט הדגמה:

    firebase emulators:start --project=demo-test

    האמולטור טוען את התוסף לפרויקט מוגדר מראש (demo-test). התוסף כולל עד כה פונקציה אחת עם הפעלה באמצעות HTTP‏ (greetTheWorld), שמחזירה את ההודעה "hello world" כשניגשים אליה.

  3. כשהאמולטור עדיין פועל, מנסים את הפונקציה greetTheWorld של התוסף על ידי כניסה לכתובת ה-URL שהוא הדפיס כשהפעלתם אותו.

    בדפדפן מוצגת ההודעה Hello World from greet-the-world.

  4. קוד המקור של הפונקציה הזו נמצא בספרייה functions של התוסף. פותחים את המקור בכלי העריכה או בסביבת הפיתוח המשולבת (IDE) שבחרתם:

    functions/index.js

    const functions = require("firebase-functions/v1");
    
    exports.greetTheWorld = functions.https.onRequest((req, res) => {
      // Here we reference a user-provided parameter
      // (its value is provided by the user during installation)
      const consumerProvidedGreeting = process.env.GREETING;
    
      // And here we reference an auto-populated parameter
      // (its value is provided by Firebase after installation)
      const instanceId = process.env.EXT_INSTANCE_ID;
    
      const greeting = `${consumerProvidedGreeting} World from ${instanceId}`;
    
      res.send(greeting);
    });
    
  5. בזמן שהאמולטור פועל, הוא יטען מחדש באופן אוטומטי את כל השינויים שביצעתם בקוד של הפונקציות. אפשר לנסות לבצע שינוי קטן בפונקציה greetTheWorld:

    functions/index.js

    const greeting = `${consumerProvidedGreeting} everyone, from ${instanceId}`;
    

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

3. הוספת מידע בסיסי לקובץ extension.yaml

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

כצעד ראשון, עורכים את המטא-נתונים המוגדרים מראש של התוסף כדי לשקף את התוסף שרוצים לכתוב במקום greet-the-world. המטא-נתונים האלה מאוחסנים בקובץ extension.yaml.

  1. פותחים את extension.yaml בכלי העריכה ומחליפים את כל התוכן של הקובץ בתוכן הבא:

    name: rtdb-uppercase-messages
    version: 0.0.1
    specVersion: v1beta  # Firebase Extensions specification version; don't change
    
    # Friendly display name for your extension (~3-5 words)
    displayName: Convert messages to upper case
    
    # Brief description of the task your extension performs (~1 sentence)
    description: >-
      Converts messages in RTDB to upper case
    
    author:
      authorName: Your Name
      url: https://your-site.example.com
    
    license: Apache-2.0  # Required license
    
    # Public URL for the source code of your extension
    sourceUrl: https://github.com/your-name/your-repo
    

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

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

    1. ב-functions/integration-tests/firebase.json, משנים את greet-the-world ל-rtdb-uppercase-messages.
    2. שינוי השם של functions/integration-tests/extensions/greet-the-world.env ל-functions/integration-tests/extensions/rtdb-uppercase-messages.env.

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

4. כתיבת פונקציה של Cloud Functions והצהרה עליה כעל משאב של תוסף

עכשיו אפשר להתחיל לכתוב קוד. בשלב הזה תכתבו פונקציית Cloud שתבצע את המשימה העיקרית של התוסף, שהיא מעקב אחרי הודעות ב-Realtime Database והמרתן לאותיות רישיות.

  1. פותחים את המקור של הפונקציות של התוסף (בספרייה functions של התוסף) בכלי העריכה או בסביבת הפיתוח המשולבת (IDE) שבחרתם. מחליפים את התוכן שלו בתוכן הבא:

    functions/index.js

    import { database, logger } from "firebase-functions/v1";
    
    const app = initializeApp();
    
    // Listens for new messages added to /messages/{pushId}/original and creates an
    // uppercase version of the message to /messages/{pushId}/uppercase
    // for all databases in 'us-central1'
    export const makeuppercase = database
      .ref("/messages/{pushId}/uppercase")
      .onCreate(async (snapshot, context) => {
        // Grab the current value of what was written to the Realtime Database.
        const original = snapshot.val();
    
        // Convert it to upper case.
        logger.log("Uppercasing", context.params.pushId, original);
        const uppercase = original.toUpperCase();
    
        // Setting an "uppercase" sibling in the Realtime Database.
        const upperRef = snapshot.ref.parent.child("upper");
        await upperRef.set(uppercase);
    });
    

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

    אגב, בקובץ החדש הזה נעשה שימוש בתחביר של מודול ECMAScript ‏ (import ו-export) במקום CommonJS ‏ (require). כדי להשתמש במודולים של ES ב-Node, צריך לציין "type": "module" ב-functions/package.json:

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. כל פונקציה בתוסף צריכה להיות מוצהרת בקובץ extension.yaml. בדוגמה של התוסף, greetTheWorld הוגדר כפונקציית הענן היחידה של התוסף. עכשיו, אחרי שהחלפתם אותה ב-makeuppercase, אתם צריכים לעדכן גם את ההגדרה שלה.

    פותחים את extension.yaml ומוסיפים שדה resources:

    resources:
      - name: makeuppercase
        type: firebaseextensions.v1beta.function
        properties:
          eventTrigger:
            eventType: providers/google.firebase.database/eventTypes/ref.create
            # DATABASE_INSTANCE (project's default instance) is an auto-populated
            # parameter value. You can also specify an instance.
            resource: projects/_/instances/${DATABASE_INSTANCE}/refs/messages/{pushId}/original
          runtime: "nodejs18"
    
  3. התוסף שלכם משתמש עכשיו ב-Realtime Database כטריגר, ולכן אתם צריכים לעדכן את הגדרת האמולטור כדי להריץ את אמולטור RTDB לצד אמולטור Cloud Functions:

    1. אם האמולטור עדיין פועל, מפסיקים אותו בלחיצה על Ctrl-C.

    2. מהספרייה functions/integration-tests, מריצים את הפקודה הבאה:

      firebase init emulators

      כשמתבקשים, מדלגים על הגדרת פרויקט ברירת מחדל ואז בוחרים באפשרות Functions (פונקציות) ובאפשרות Database emulators (אמולטורים של מסד נתונים). מאשרים את יציאות ברירת המחדל ומאפשרים לכלי ההגדרה להוריד את כל הקבצים הנדרשים.

    3. מפעילים מחדש את האמולטור:

      firebase emulators:start --project=demo-test
  4. כדאי לנסות את התוסף המעודכן:

    1. פותחים את ממשק המשתמש של אמולטור מסד הנתונים באמצעות הקישור שהאמולטור הדפיס כשמפעילים אותו.

    2. עורכים את צומת הבסיס של מסד הנתונים:

      • שדה: messages
      • סוג: json
      • ערך: {"11": {"original": "recipe"}}

      אם הכול מוגדר בצורה נכונה, כששומרים את השינויים במסד הנתונים, הפונקציה makeuppercase של התוסף אמורה להפעיל ולהוסיף רשומת צאצא להודעה 11 עם התוכן "upper": "RECIPE". כדי לוודא שהתוצאות הן כמו שציפיתם, כדאי לעיין ביומנים ובכרטיסיות של מסד הנתונים בממשק המשתמש של האמולטור.

    3. נסו להוסיף עוד כמה צאצאים לצומת messages ({"original":"any text"}). בכל פעם שמוסיפים רשומה חדשה, התוסף אמור להוסיף שדה uppercase שמכיל את התוכן של השדה original באותיות רישיות.

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

5. הצהרה על ממשקי API ותפקידים

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

לא צריך להצהיר על תפקידים כדי להפעיל אירועים של מוצר, אבל כן צריך להצהיר על תפקיד כדי לבצע אינטראקציה עם המוצר. מכיוון שהפונקציה שהוספתם בשלב האחרון כותבת ל-Realtime Database, צריך להוסיף את ההצהרה הבאה ל-extension.yaml:

roles:
  - role: firebasedatabase.admin
    reason: Allows the extension to write to RTDB.

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

6. הגדרת פרמטרים שניתנים להגדרה על ידי המשתמש

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

אפשר להגדיר את הנתיב שבו התוסף מחפש הודעות חדשות על ידי המשתמש:

  1. מוסיפים קטע params לקובץ extension.yaml:

    - param: MESSAGE_PATH
      label: Message path
      description: >-
        What is the path at which the original text of a message can be found?
      type: string
      default: /messages/{pushId}/original
      required: true
      immutable: false
    

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

  2. עדיין בקובץ extension.yaml, חוזרים להצהרה makeuppercase ומשנים את השדה resource לערך הבא:

    resource: projects/_/instances/${DATABASE_INSTANCE}/refs/${param:MESSAGE_PATH}
    

    הטוקן ${param:MESSAGE_PATH} הוא הפניה לפרמטר שהגדרתם זה עתה. כשהתוסף פועל, האסימון הזה מוחלף בכל ערך שהמשתמש הגדיר לפרמטר הזה, וכתוצאה מכך הפונקציה makeuppercase תקשיב לנתיב שהמשתמש ציין. אפשר להשתמש בתחביר הזה כדי להפנות לכל פרמטר שהוגדר על ידי המשתמש בכל מקום ב-extension.yaml (וב-POSTINSTALL.md – נרחיב על כך בהמשך).

  3. אפשר גם לגשת לפרמטרים בהגדרת המשתמש מקוד הפונקציות.

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

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate
    

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

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

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate(
      async (snapshot, context) => {
        logger.log("Found new message at ", snapshot.ref);
    
        // Grab the current value of what was written to the Realtime Database.
        ...
    
  5. בדרך כלל, המשתמשים מתבקשים לספק ערכים לפרמטרים כשהם מתקינים תוסף. כשמשתמשים באמולטור לצורך בדיקה ופיתוח, לא צריך לבצע את תהליך ההתקנה, אלא מספקים ערכים לפרמטרים שהוגדרו על ידי המשתמש באמצעות קובץ env.

    פותחים את functions/integration-tests/extensions/rtdb-uppercase-messages.env ומחליפים את ההגדרה של GREETING בהגדרה הבאה:

    MESSAGE_PATH=/msgs/{pushId}/original
    

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

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

    עורכים את צומת הבסיס של מסד הנתונים באמצעות הנתיב שהגדרתם למעלה:

    • שדה: msgs
    • סוג: json
    • ערך: {"11": {"original": "recipe"}}

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

7. מתן ווים לאירועים ללוגיקה שהוגדרה על ידי המשתמש

ככותבי תוספים, אתם כבר יודעים איך מוצר של Firebase יכול להפעיל את הלוגיקה שסיפקתם בתוסף: יצירה של רשומות חדשות ב-Realtime Database מפעילה את הפונקציה makeuppercase. יכול להיות שיהיה קשר אנלוגי בין התוסף לבין המשתמשים שמתקינים אותו: התוסף יכול להפעיל לוגיקה שהמשתמש מגדיר.

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

במדריך הזה תוסיפו וו אסינכרוני לתוסף, שיאפשר למשתמשים להגדיר שלבי עיבוד משלהם שיפעלו אחרי שהתוסף יכתוב את ההודעה באותיות רישיות ל-Realtime Database. ה-hooks האסינכרוניים משתמשים ב-Eventarc כדי להפעיל פונקציות בהגדרת המשתמש. התוספים מצהירים על סוגי האירועים שהם משדרים, וכשהמשתמשים מתקינים את התוסף, הם בוחרים את סוגי האירועים שמעניינים אותם. אם הם בוחרים לפחות אירוע אחד, מערכת Firebase תקצה ערוץ Eventarc לתוסף כחלק מתהליך ההתקנה. לאחר מכן, המשתמשים יכולים לפרוס פונקציות משלהם בענן שמקשיבות לערוץ הזה ומופעלות כשהתוסף מפרסם אירועים חדשים.

כדי להוסיף וו אסינכרוני:

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

    events:
      - type: test-publisher.rtdb-uppercase-messages.v1.complete
        description: >-
          Occurs when message uppercasing completes. The event subject will contain
          the RTDB URL of the uppercase message.
    

    סוגי האירועים צריכים להיות ייחודיים באופן אוניברסלי. כדי לוודא שהם ייחודיים, תמיד צריך לתת לאירועים שמות בפורמט הבא: <publisher-id>.<extension-id>.<version>.<description>. (עדיין אין לך מזהה של בעל תוכן דיגיטלי, אז בינתיים פשוט צריך להשתמש ב-test-publisher).

  2. בסוף הפונקציה makeuppercase, מוסיפים קוד שמפרסם אירוע מהסוג שהצהרתם עליו:

    functions/index.js

    // Import the Eventarc library:
    import { initializeApp } from "firebase-admin/app";
    import { getEventarc } from "firebase-admin/eventarc";
    
    const app = initializeApp();
    
    // In makeuppercase, after upperRef.set(uppercase), add:
    
    // Set eventChannel to a newly-initialized channel, or `undefined` if events
    // aren't enabled.
    const eventChannel =
      process.env.EVENTARC_CHANNEL &&
      getEventarc().channel(process.env.EVENTARC_CHANNEL, {
        allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
      });
    
    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel &&
      eventChannel.publish({
        type: "test-publisher.rtdb-uppercase-messages.v1.complete",
        subject: upperRef.toString(),
        data: {
          "original": original,
          "uppercase": uppercase,
        },
      });
    

    בדוגמת הקוד הזו נעשה שימוש בעובדה שמשתנה הסביבה EVENTARC_CHANNEL מוגדר רק אם המשתמש הפעיל לפחות סוג אחד של אירוע. אם EVENTARC_CHANNEL לא מוגדר, הקוד לא מנסה לפרסם אירועים.

    אפשר לצרף מידע נוסף לאירוע Eventarc. בדוגמה שלמעלה, לאירוע יש שדה subject שמכיל הפניה לערך החדש שנוצר, ומטען ייעודי (payload) של data שמכיל את ההודעות המקוריות ואת ההודעות באותיות רישיות. פונקציות בהגדרת המשתמש שמופעלות על ידי האירוע יכולות להשתמש במידע הזה.

  3. בדרך כלל, משתני הסביבה EVENTARC_CHANNEL ו-EXT_SELECTED_EVENTS מוגדרים על סמך האפשרויות שהמשתמש בחר במהלך ההתקנה. כדי לבדוק באמצעות האמולטור, צריך להגדיר את המשתנים האלה באופן ידני בקובץ rtdb-uppercase-messages.env:

    EVENTARC_CHANNEL=locations/us-central1/channels/firebase
    EXT_SELECTED_EVENTS=test-publisher.rtdb-uppercase-messages.v1.complete
    

בשלב הזה, השלמתם את השלבים שנדרשים כדי להוסיף לתוסף שלכם וו (hook) של אירוע אסינכרוני.

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

  1. מהספרייה functions/integration-tests, מפעילים פרויקט חדש ב-Firebase:

    firebase init functions

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

  2. עורכים את integration-tests/functions/index.js ומדביקים את הקוד הבא:

    import { logger } from "firebase-functions/v1";
    import { onCustomEventPublished } from "firebase-functions/v2/eventarc";
    
    import { initializeApp } from "firebase-admin/app";
    import { getDatabase } from "firebase-admin/database";
    
    const app = initializeApp();
    
    export const extraemphasis = onCustomEventPublished(
      "test-publisher.rtdb-uppercase-messages.v1.complete",
      async (event) => {
        logger.info("Received makeuppercase completed event", event);
    
        const refUrl = event.subject;
        const ref = getDatabase().refFromURL(refUrl);
        const upper = (await ref.get()).val();
        return ref.set(`${upper}!!!`);
      }
    );
    

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

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

  4. נכנסים לממשק המשתמש של אמולטור מסד הנתונים ועורכים את צומת הבסיס של מסד הנתונים באמצעות הנתיב שהגדרתם למעלה:

    • שדה:msgs
    • סוג: json
    • ערך: {"11": {"original": "recipe"}}

    כששומרים את השינויים במסד הנתונים, הפונקציה makeuppercase של התוסף והפונקציה extraemphasis של המשתמש אמורות להפעיל ברצף, וכתוצאה מכך הערך RECIPE!!! יתווסף לשדה upper.

8. הוספת גורמים שמטפלים באירועים במחזור החיים

התוסף שכתבתם עד עכשיו מעבד את ההודעות כשהן נוצרות. אבל מה קורה אם למשתמשים כבר יש מסד נתונים של הודעות כשהם מתקינים את התוסף? ב-Firebase Extensions יש תכונה שנקראת lifecycle event hooks. אפשר להשתמש בה כדי להפעיל פעולות כשהתוסף מותקן, מתעדכן או מוגדר מחדש. בקטע הזה תשתמשו בווים של אירועים במחזור החיים כדי למלא מחדש את מסד הנתונים הקיים של ההודעות בפרויקט בהודעות באותיות רישיות, כשמשתמש מתקין את התוסף.

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

כדי להוסיף handler של אירוע במחזור החיים שמבצע מילוי חוסרים בהודעות קיימות, פועלים לפי השלבים הבאים:

  1. מגדירים פונקציה חדשה של Cloud Functions שמופעלת על ידי אירועים של תור משימות:

    functions/index.js

    import { tasks } from "firebase-functions/v1";
    
    import { getDatabase } from "firebase-admin/database";
    import { getExtensions } from "firebase-admin/extensions";
    import { getFunctions } from "firebase-admin/functions";
    
    export const backfilldata = tasks.taskQueue().onDispatch(async () => {
      const batch = await getDatabase()
        .ref(process.env.MESSAGE_PATH)
        .parent.parent.orderByChild("upper")
        .limitToFirst(20)
        .get();
    
      const promises = [];
      for (const key in batch.val()) {
        const msg = batch.child(key);
        if (msg.hasChild("original") && !msg.hasChild("upper")) {
          const upper = msg.child("original").val().toUpperCase();
          promises.push(msg.child("upper").ref.set(upper));
        }
      }
      await Promise.all(promises);
    
      if (promises.length > 0) {
        const queue = getFunctions().taskQueue(
          "backfilldata",
          process.env.EXT_INSTANCE_ID
        );
        return queue.enqueue({});
      } else {
        return getExtensions()
          .runtime()
          .setProcessingState("PROCESSING_COMPLETE", "Backfill complete.");
      }
    });
    

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

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

    resources:
      - name: makeuppercase
        ...
      - name: backfilldata
        type: firebaseextensions.v1beta.function
        description: >-
          Backfill existing messages with uppercase versions
        properties:
          runtime: "nodejs18"
          taskQueueTrigger: {}
    

    לאחר מכן מגדירים את הפונקציה כמטפל באירוע של מחזור החיים onInstall:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. אמנם מילוי חוסרים בהודעות קיימות הוא תוספת נחמדה, אבל התוסף יכול לפעול גם בלעדיו. במקרים כאלה, כדאי להגדיר את הפעלת ה-handlers של אירועים במחזור החיים כאופציונלית.

    כדי לעשות זאת, מוסיפים פרמטר חדש ל-extension.yaml:

    - param: DO_BACKFILL
      label: Backfill existing messages
      description: >-
        Generate uppercase versions of existing messages?
      type: select
      required: true
      options:
        - label: Yes
          value: true
        - label: No
          value: false
    

    לאחר מכן, בתחילת הפונקציה backfill, בודקים את הערך של הפרמטר DO_BACKFILL ויוצאים מוקדם אם הוא לא מוגדר:

    functions/index.js

    if (!process.env.DO_BACKFILL) {
      return getExtensions()
        .runtime()
        .setProcessingState("PROCESSING_COMPLETE", "Backfill skipped.");
    }
    

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

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

9. פריסה לפרויקט Firebase אמיתי

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

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

  1. במסוף Firebase, מוסיפים פרויקט חדש.
  2. משדרגים את הפרויקט לתוכנית Blaze עם תשלום לפי שימוש. כדי להתקין תוסף, צריך גם חשבון לחיוב, כי Cloud Functions for Firebase מחייב שיהיה לפרויקט חשבון לחיוב.
  3. בפרויקט החדש, מפעילים את Realtime Database.
  4. מכיוון שאתם רוצים לבדוק את היכולת של התוסף למלא נתונים חסרים קיימים בהתקנה, מייבאים נתוני דוגמה למופע של מסד הנתונים בזמן אמת:
    1. הורדה של נתוני seed של RTDB.
    2. בדף Realtime Database במסוף Firebase, לוחצים על (עוד) > Import JSON (ייבוא JSON) ובוחרים את הקובץ שהורדתם.
  5. כדי להפעיל את פונקציית המילוי החוזר באמצעות השיטה orderByChild, צריך להגדיר את מסד הנתונים לאינדוקס של הודעות לפי הערך של upper:

    {
      "rules": {
        ".read": false,
        ".write": false,
        "messages": {
          ".indexOn": "upper"
        }
      }
    }
    

עכשיו מתקינים את התוסף ממקור מקומי בפרויקט החדש:

  1. יוצרים ספרייה חדשה לפרויקט Firebase:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. מאתחלים פרויקט Firebase בספריית העבודה:

    firebase init database

    כשמתבקשים, בוחרים את הפרויקט שיצרתם.

  3. מתקינים את התוסף בפרויקט Firebase המקומי:

    firebase ext:install /path/to/rtdb-uppercase-messages

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

    אחרי שבוחרים את אפשרויות ההגדרה, ה-CLI של Firebase שומר את ההגדרה בספרייה extensions ומתעד את מיקום המקור של התוסף בקובץ firebase.json. יחד, שתי הרשומות האלה נקראות קובץ המניפסט של התוסף. המשתמשים יכולים להשתמש בקובץ המניפסט כדי לשמור את הגדרות התוספים שלהם ולפרוס אותן בפרויקטים שונים.

  4. פורסים את הגדרת התוסף בפרויקט הפעיל:

    firebase deploy --only extensions

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

10. כתיבת תיעוד

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

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

extension.yaml

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

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

PREINSTALL.md

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

הטקסט בקובץ הזה מוצג למשתמש במרכז התוספים ובפקודה firebase ext:info.

דוגמה לקובץ PREINSTALL:

Use this extension to automatically convert strings to upper case when added to
a specified Realtime Database path.

This extension expects a database layout like the following example:

    "messages": {
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
    }

When you create new string records, this extension creates a new sibling record
with upper-cased text:

    MESSAGE_ID: {
      "original": MESSAGE_TEXT,
      "upper": UPPERCASE_MESSAGE_TEXT,
    }

#### Additional setup

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

#### 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:
  - Realtime Database
  - Cloud Functions (Node.js 10+ runtime)
    [See FAQs](https://firebase.google.com/support/faq#extensions-pricing)
- If you enable events,
  [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

POSTINSTALL.md

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

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

דוגמה לקובץ אחרי ההתקנה של התוסף שמופיע במדריך:

### See it in action

You can test out this extension right away!

1.  Go to your
    [Realtime Database dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/database/${param:PROJECT_ID}/data) in the Firebase console.

1.  Add a message string to a path that matches the pattern `${param:MESSAGE_PATH}`.

1.  In a few seconds, you'll see a sibling node named `upper` that contains the
    message in upper case.

### Using the extension

We recommend adding data by pushing -- for example,
`firebase.database().ref().push()` -- because pushing assigns an automatically
generated ID to the node in the database. During retrieval, these nodes are
guaranteed to be ordered by the time they were added. Learn more about reading
and writing data for your platform (iOS, Android, or Web) in the
[Realtime Database documentation](https://firebase.google.com/docs/database/).

### Monitoring

As a best practice, you can
[monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor)
of your installed extension, including checks on its health, usage, and logs.

CHANGELOG.md

כדאי גם לתעד את השינויים שאתם מבצעים בין הגרסאות של התוסף בקובץ CHANGELOG.md.

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

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

רוב התוספים מספקים גם קובץ readme לטובת משתמשים שנכנסים למאגר התוספים. אפשר לכתוב את הקובץ הזה באופן ידני או ליצור קובץ readme באמצעות הפקודה.

לצורך המדריך הזה, לא נכתוב קובץ readme.

מסמכים נוספים

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

לצורך המדריך הזה, לא נכתוב תיעוד נרחב יותר.

11. פרסום ב-Extensions Hub (מרכז התוספים)

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

כדי לפרסם את העבודה שלכם במרכז התוספים, פועלים לפי השלבים הבאים:

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

    נכון לעכשיו, המשמעות היא שהתוסף שלכם זמין במאגר ציבורי ב-GitHub.

  3. מעלים את התוסף ל-Extensions Hub באמצעות הפקודה firebase ext:dev:upload.

  4. עוברים ללוח הבקרה של בעל האפליקציה במסוף Firebase, מאתרים את התוסף שהעליתם ולוחצים על Publish to Extensions Hub (פרסום במרכז התוספים). הבקשה הזו מועברת לצוות הבדיקה שלנו, והטיפול בה עשוי להימשך כמה ימים. אם הבקשה תאושר, התוסף יפורסם במרכז התוספים. אם הבקשה תידחה, תקבלו הודעה עם הסבר על הסיבה לדחייה. לאחר מכן תוכלו לטפל בבעיות שדווחו ולשלוח מחדש את הבקשה לבדיקה.