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

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

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

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

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

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

    npm install -g firebase-tools

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

  1. יוצרים ספרייה לתוסף ואז cd לתוכה:

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

    firebase ext:dev:init

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

2. רוצה לנסות את התוסף לדוגמה באמצעות האמולטור?

כש-Firebase CLI הפעיל את ספריית התוספים החדשה, הוא יצר פונקציית דוגמה פשוטה וספרייה 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. בזמן שהאמולטור פועל, הוא יטען מחדש באופן אוטומטי את כל השינויים שתבצעו בקוד של Functions. כדאי לנסות לבצע שינוי קטן בפונקציה 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 Functions שתבצע את המשימה המרכזית של התוסף, כלומר לבדוק אם יש הודעות ב-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 כפונקציה של Cloud Functions היחידה. עכשיו, אחרי שהחלפתם אותו ב-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 ושל מסדי נתונים. מאשרים את יציאות ברירת המחדל ומאפשרים לכלי ההגדרה להוריד את כל הקבצים הנדרשים.

    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. בקובץ extension.yaml, מוסיפים את הקטע params:

    - 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, השינוי הזה חל רק למטרות התיעוד: כשפונקציה של Cloud Functions פרוסה כחלק מתוסף, היא משתמשת בהגדרת הטריגר מהקובץ 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. עכשיו מפעילים מחדש את האמולטור ונכנסים שוב לממשק המשתמש של אמולטור מסדי הנתונים.

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

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

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

7. הוספת אירועי הוק לצורך לוגיקה מוגדרת על ידי משתמש

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

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

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

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

  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
    

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

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

  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. נכנסים לממשק המשתמש של האמולטור של מסד הנתונים ועורכים את הצומת הבסיסי (root) של מסד הנתונים, באמצעות הנתיב שהגדרתם למעלה:

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

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

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

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

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

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

  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. אמנם נחמד שאפשר למלא את הנתונים החסרים של הודעות קיימות, אבל התוסף עדיין יכול לפעול בלי זה. במצבים כאלה, כדאי להגדיר את ההפעלה של הגורמים שמטפלים באירועים במחזור החיים כאופציונלית.

    כדי לעשות זאת, מוסיפים פרמטר חדש ל-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
    

    לאחר מכן, בתחילת פונקציית המילוי החוזר, בודקים את הערך של הפרמטר 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. בפרויקט החדש, מפעילים את מסד הנתונים Real-time.
  4. כדי לבדוק את היכולת של התוסף למלא נתונים קיימים במהלך ההתקנה, צריך לייבא נתונים לדוגמה למכונה של מסד הנתונים בזמן אמת:
    1. הורד נתוני RTDB של זרועים.
    2. בדף Real-time Database במסוף Firebase, לוחצים על (עוד) > Import 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

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

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

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

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

export.yaml

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

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

PREINSTALL.md

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

הטקסט של הקובץ הזה מוצג למשתמש ב-תוספים Hub ובאמצעות הפקודה 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

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

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

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

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

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

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