شروع به ساخت یک افزونه کنید

این صفحه شما را از طریق مراحل مورد نیاز برای ساختن یک افزونه Firebase ساده راهنمایی می کند که می توانید آن را در پروژه های خود نصب کنید یا با دیگران به اشتراک بگذارید. این مثال ساده از یک افزونه Firebase، پایگاه داده Realtime شما را برای پیام‌ها مشاهده می‌کند و آنها را به حروف بزرگ تبدیل می‌کند.

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

    وقتی از شما خواسته شد، جاوا اسکریپت را به‌عنوان زبان توابع انتخاب کنید (اما توجه داشته باشید که می‌توانید هنگام توسعه برنامه افزودنی خود نیز از 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 تشکیل شده است که در صورت دسترسی، پیام «سلام جهان» را برمی‌گرداند.

  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 Function بنویسید و آن را به عنوان یک منبع افزونه اعلام کنید

حالا می توانید شروع به نوشتن چند کد کنید. در این مرحله، یک Cloud Function می نویسید که وظیفه اصلی برنامه افزودنی شما را انجام می دهد، یعنی مشاهده پایگاه داده Realtime شما برای پیام ها و تبدیل آنها به حروف بزرگ.

  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 بود که با دسترسی به نقطه پایانی 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 به عنوان یک راه‌انداز استفاده می‌کند، باید پیکربندی شبیه‌ساز خود را به‌روزرسانی کنید تا شبیه‌ساز RTDB را در کنار شبیه‌ساز توابع ابری اجرا کنید:

    1. اگر شبیه ساز همچنان در حال اجرا است، با فشار دادن Ctrl-C آن را متوقف کنید.

    2. از دایرکتوری functions/integration-tests دستور زیر را اجرا کنید:

      firebase init emulators

      هنگامی که از شما خواسته شد، از تنظیم یک پروژه پیش فرض صرفنظر کنید، سپس شبیه سازهای توابع و پایگاه داده را انتخاب کنید. پورت های پیش فرض را بپذیرید و به ابزار راه اندازی اجازه دهید تا فایل های مورد نیاز را دانلود کند.

    3. شبیه ساز را دوباره راه اندازی کنید:

      firebase emulators:start --project=demo-test
  4. برنامه افزودنی به روز شده خود را امتحان کنید:

    1. رابط کاربری شبیه‌ساز پایگاه داده را با استفاده از پیوندی که شبیه‌ساز هنگام راه‌اندازی چاپ کرد، باز کنید.

    2. گره ریشه پایگاه داده را ویرایش کنید:

      • زمینه: messages
      • نوع: json
      • مقدار: {"11": {"original": "recipe"}}

      اگر همه چیز به درستی تنظیم شده باشد، وقتی تغییرات پایگاه داده خود را ذخیره می‌کنید، عملکرد makeuppercase برنامه افزودنی باید یک رکورد فرزند را با محتوای "upper": "RECIPE" . برای تأیید نتایج مورد انتظار، به گزارش‌ها و برگه‌های پایگاه داده رابط کاربری شبیه‌ساز نگاهی بیندازید.

    3. سعی کنید چند فرزند دیگر را به گره messages اضافه کنید ( {"original":"any text"} ). هر زمان که یک رکورد جدید اضافه می کنید، برنامه افزودنی باید یک فیلد uppercase حاوی محتویات حروف بزرگ فیلد original اضافه کند.

اکنون یک برنامه افزودنی کامل، هرچند ساده، دارید که بر روی یک نمونه RTDB کار می کند. در بخش‌های بعدی، این افزونه را با برخی ویژگی‌های اضافی اصلاح خواهید کرد. سپس، برنامه افزودنی را برای توزیع به دیگران آماده خواهید کرد و در نهایت، نحوه انتشار برنامه افزودنی خود را در Extensions Hub یاد خواهید گرفت.

5. API ها و نقش ها را اعلام کنید

Firebase به هر نمونه از یک برنامه افزودنی نصب شده دسترسی محدود به پروژه و داده های آن را با استفاده از یک حساب سرویس هر نمونه اعطا می کند. هر حساب دارای حداقل مجموعه مجوزهای مورد نیاز برای فعالیت است. به همین دلیل، شما باید به صراحت هر نقش IAM مورد نیاز برنامه افزودنی خود را اعلام کنید. وقتی کاربران برنامه افزودنی شما را نصب می‌کنند، Firebase یک حساب سرویس با این نقش‌ها ایجاد می‌کند و از آن برای اجرای برنامه افزودنی استفاده می‌کند.

برای فعال کردن رویدادهای یک محصول، نیازی به اعلام نقش ندارید، اما باید نقشی را برای تعامل با آن اعلام کنید. از آنجا که تابعی که در مرحله آخر اضافه کردید در پایگاه داده Realtime می نویسد، باید اعلان زیر را به extension.yaml اضافه کنید:

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

به طور مشابه، API های Google را که یک برنامه افزودنی در فیلد apis استفاده می کند، اعلام می کنید. هنگامی که کاربران برنامه افزودنی شما را نصب می کنند، از آنها پرسیده می شود که آیا می خواهند به طور خودکار این API ها را برای پروژه خود فعال کنند. این معمولاً فقط برای APIهای Google غیر 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 Extensions، این تغییر صرفاً برای مستندسازی است: وقتی یک Cloud Function به عنوان بخشی از یک برنامه افزودنی مستقر می‌شود، از تعریف ماشه از فایل extension.yaml استفاده می‌کند و مقدار مشخص‌شده در تعریف تابع را نادیده می‌گیرد. با این وجود، ایده خوبی است که در کد خود مستند کنید که این مقدار از کجا آمده است.

  4. ممکن است تغییر کدی که اثر زمان اجرا ندارد، ناامیدکننده باشد، اما درس مهمی که باید از آن حذف کرد این است که می‌توانید به هر پارامتر تعریف‌شده توسط کاربر در کد تابع خود دسترسی داشته باشید و از آن به عنوان یک مقدار معمولی در منطق تابع استفاده کنید. به عنوان اشاره ای به این قابلیت، عبارت log زیر را اضافه کنید تا نشان دهید که واقعاً به مقداری که کاربر تعریف کرده است دسترسی دارید:

    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 عملکرد makeuppercase شما را فعال می‌کند. برنامه افزودنی شما می‌تواند با کاربرانی که برنامه افزودنی شما را نصب می‌کنند رابطه مشابهی داشته باشد: برنامه افزودنی شما می‌تواند منطقی را که کاربر تعریف می‌کند راه‌اندازی کند.

یک برنامه افزودنی می‌تواند قلاب‌های همزمان ، قلاب‌های ناهمزمان یا هر دو را فراهم کند. قلاب های همزمان راهی را به کاربران می دهد تا کارهایی را انجام دهند که تکمیل یکی از عملکردهای برنامه افزودنی را مسدود می کند. این می تواند مفید باشد، برای مثال، به کاربران راهی برای انجام پیش پردازش سفارشی قبل از اینکه یک برنامه افزودنی کار خود را انجام دهد، مفید باشد.

در این راهنما، شما یک قلاب ناهمزمان به برنامه افزودنی خود اضافه می‌کنید، که به کاربران امکان می‌دهد مراحل پردازش خود را برای اجرا پس از نوشتن پیام بزرگ در پایگاه داده بلادرنگ تعریف کنند. قلاب های ناهمزمان از 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 است که حاوی ارجاع به مقدار تازه ایجاد شده و یک بار 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

    هنگامی که از شما خواسته شد، از راه اندازی یک پروژه پیش فرض خودداری کنید، جاوا اسکریپت را به عنوان زبان 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 کاربر باید به ترتیب فعال شوند و در نتیجه فیلد upper مقدار RECIPE!!! .

8. کنترل کننده رویداد چرخه حیات را اضافه کنید

افزونه‌ای که تاکنون نوشته‌اید، پیام‌ها را هنگام ایجاد پردازش می‌کند. اما اگر کاربران شما از قبل یک پایگاه داده از پیام ها را هنگام نصب برنامه افزودنی داشته باشند چه؟ Firebase Extensions دارای یک ویژگی به نام قلاب رویداد چرخه حیات است که می‌توانید هنگام نصب، به‌روزرسانی یا پیکربندی مجدد برنامه افزودنی، برای فعال کردن اقدامات استفاده کنید. در این بخش، از قلاب‌های رویداد چرخه حیات برای پر کردن پایگاه داده پیام موجود پروژه با پیام‌های بزرگ، زمانی که کاربر برنامه افزودنی شما را نصب می‌کند، استفاده می‌کنید.

Firebase Extensions از Cloud Tasks برای اجرای مدیریت رویدادهای چرخه حیات شما استفاده می کند. شما کنترل کننده رویداد را با استفاده از توابع ابری تعریف می کنید. هر زمان که یک نمونه از برنامه افزودنی شما به یکی از رویدادهای چرخه حیات پشتیبانی شده برسد، اگر یک handler تعریف کرده باشید، کنترل کننده را به صف وظایف Cloud اضافه می کند. سپس Cloud Tasks به صورت ناهمزمان کنترل کننده را اجرا می کند. هنگامی که یک کنترل کننده رویداد چرخه حیات در حال اجرا است، کنسول Firebase به کاربر گزارش می دهد که نمونه برنامه افزودنی دارای یک کار پردازشی در حال انجام است. این به عملکرد کنترل کننده شما بستگی دارد که وضعیت در حال انجام و تکمیل کار را به کاربر گزارش دهد.

برای اضافه کردن یک کنترل کننده رویداد چرخه حیات که پیام های موجود را پر می کند، موارد زیر را انجام دهید:

  1. یک تابع Cloud جدید را تعریف کنید که توسط رویدادهای صف کار ایجاد می شود:

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

    توجه داشته باشید که تابع فقط چند رکورد را قبل از اینکه دوباره به صف کار اضافه شود پردازش می کند. این یک استراتژی متداول برای مقابله با وظایف پردازشی است که نمی‌توانند در پنجره زمان‌بندی یک عملکرد ابری کامل شوند. از آنجایی که نمی‌توانید پیش‌بینی کنید که کاربر در هنگام نصب برنامه افزودنی شما چند پیام ممکن است در پایگاه داده خود داشته باشد، این استراتژی مناسب است.

  2. در فایل extension.yaml ، تابع backfill خود را به عنوان یک منبع پسوندی که دارای ویژگی 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
    

    سپس در ابتدای تابع 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 برای Firebase نیاز دارد که پروژه شما یک حساب صورت‌حساب داشته باشد، بنابراین برای نصب برنامه افزودنی به یک حساب صورت‌حساب نیز نیاز دارید.
  3. در پروژه جدید خود، پایگاه داده بلادرنگ را فعال کنید .
  4. از آنجایی که می‌خواهید توانایی برنامه افزودنی خود را برای تکمیل داده‌های موجود در هنگام نصب آزمایش کنید، برخی از داده‌های نمونه را به نمونه پایگاه داده بی‌درنگ خود وارد کنید:
    1. برخی از داده‌های RTDB را دانلود کنید.
    2. در صفحه Real-time Database کنسول Firebase، روی (بیشتر) > Import JSON کلیک کنید و فایلی را که به تازگی دانلود کرده اید انتخاب کنید.
  5. برای فعال کردن تابع backfill برای استفاده از متد 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 چگونه است. هنگامی که ابزار پیکربندی از شما می پرسد که آیا می خواهید پایگاه داده موجود خود را پر کنید، حتما "بله" را انتخاب کنید.

    پس از انتخاب گزینه های پیکربندی، Firebase CLI پیکربندی شما را در فهرست extensions ذخیره می کند و محل منبع برنامه افزودنی را در فایل firebase.json ثبت می کند. در مجموع، این دو رکورد مانیفست پسوند نامیده می شوند. کاربران می توانند از مانیفست برای ذخیره پیکربندی افزونه های خود و استقرار آن در پروژه های مختلف استفاده کنند.

  4. پیکربندی برنامه افزودنی خود را در پروژه زنده خود مستقر کنید:

    firebase deploy --only extensions

اگر همه چیز خوب پیش رفت، Firebase CLI باید افزونه شما را در پروژه شما آپلود کرده و آن را نصب کند. پس از اتمام نصب، کار Backfill اجرا می شود و ظرف چند دقیقه، پایگاه داده شما با پیام های بزرگ به روز می شود. چند گره جدید به پایگاه داده پیام‌ها اضافه کنید و مطمئن شوید که برنامه افزودنی برای پیام‌های جدید نیز کار می‌کند.

10. مستندات را بنویسید

قبل از اینکه برنامه افزودنی خود را با کاربران به اشتراک بگذارید، مطمئن شوید که اسناد کافی برای موفقیت آنها ارائه می کنید.

هنگامی که پروژه برنامه افزودنی را مقداردهی اولیه کردید، Firebase CLI نسخه‌های خرد حداقل اسناد مورد نیاز را ایجاد کرد. این فایل‌ها را به‌روزرسانی کنید تا پسوندی که ساخته‌اید به‌طور دقیق منعکس شود.

extension.yaml

شما در حال به روز رسانی این فایل با توسعه این پسوند بوده اید، بنابراین در حال حاضر نیازی به به روز رسانی بیشتری ندارید.

با این حال، اهمیت مستندات موجود در این فایل را نادیده نگیرید. علاوه بر اطلاعات شناسایی حیاتی یک برنامه افزودنی - نام، توضیحات، نویسنده، مکان مخزن رسمی - فایل extension.yaml حاوی اسناد رو به رو کاربر برای هر منبع و پارامتر قابل تنظیم توسط کاربر است. این اطلاعات در اختیار کاربران کنسول Firebase، Extensions Hub و Firebase CLI قرار می‌گیرد.

PREINSTALL.md

در این فایل، اطلاعات مورد نیاز کاربر را قبل از نصب برنامه افزودنی خود ارائه دهید: به طور مختصر توضیح دهید که برنامه افزودنی چه کاری انجام می دهد، پیش نیازها را توضیح دهید و اطلاعات مربوط به پیامدهای صورتحساب نصب برنامه افزودنی را به کاربر ارائه دهید. اگر وب سایتی با اطلاعات اضافی دارید، این نیز مکان خوبی برای پیوند دادن آن است.

متن این فایل در Extensions 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 را برای کاربرانی که از مخزن برنامه افزودنی بازدید می‌کنند، ارائه می‌کنند. می توانید این فایل را با دست بنویسید یا با استفاده از دستور یک read me ایجاد کنید.

برای هدف این راهنما، از نوشتن یک فایل readme صرفنظر کنید.

اسناد اضافی

مستنداتی که در بالا مورد بحث قرار گرفت حداقل مجموعه اسنادی است که باید به کاربران ارائه دهید. بسیاری از برنامه های افزودنی به مستندات دقیق تری نیاز دارند تا کاربران بتوانند با موفقیت از آنها استفاده کنند. در این صورت، باید اسناد اضافی بنویسید و در جایی میزبانی کنید که بتوانید کاربران را به آن راهنمایی کنید.

برای هدف این راهنما، از نوشتن مستندات گسترده تر صرف نظر کنید.

11. در Extensions Hub منتشر کنید

اکنون که کد برنامه افزودنی شما کامل و مستند شده است، آماده به اشتراک گذاری آن با جهان در Extensions Hub هستید. اما از آنجایی که این فقط یک آموزش است، در واقع این کار را انجام ندهید. بروید و با استفاده از آنچه در اینجا و سایر اسناد ناشر Firebase Extensions آموخته‌اید، و با بررسی منبع برنامه‌های افزودنی رسمی، نوشته شده توسط Firebase، شروع به نوشتن برنامه افزودنی خود کنید.

هنگامی که آماده انتشار کار خود در Extensions Hub هستید، در اینجا نحوه انجام این کار را می‌بینید:

  1. اگر اولین برنامه افزودنی خود را منتشر می کنید، به عنوان ناشر برنامه افزودنی ثبت نام کنید . هنگامی که به عنوان ناشر برنامه های افزودنی ثبت نام می کنید، یک شناسه ناشر ایجاد می کنید که به کاربران امکان می دهد به سرعت شما را به عنوان نویسنده برنامه های افزودنی خود شناسایی کنند.
  2. کد منبع برنامه افزودنی خود را در مکانی قابل تأیید عمومی میزبانی کنید. وقتی کد شما از یک منبع قابل تأیید در دسترس است، Firebase می‌تواند برنامه افزودنی شما را مستقیماً از این مکان منتشر کند. انجام این کار به شما کمک می کند تا مطمئن شوید که نسخه منتشر شده فعلی برنامه افزودنی خود را منتشر می کنید و به کاربران کمک می کند تا کدی را که در حال نصب در پروژه های خود نصب می کنند بررسی کنند.

    در حال حاضر، این به معنای در دسترس قرار دادن برنامه افزودنی خود در یک مخزن عمومی GitHub است.

  3. برنامه افزودنی خود را با استفاده از فرمان firebase ext:dev:upload در Extensions Hub آپلود کنید.

  4. به داشبورد ناشر خود در کنسول Firebase بروید، افزونه‌ای را که اخیراً آپلود کرده‌اید پیدا کنید و روی «انتشار در مرکز برنامه‌های افزودنی» کلیک کنید. این درخواست از کارکنان بازبینی ما دارد که ممکن است چند روز طول بکشد. در صورت تایید، برنامه افزودنی در Extensions Hub منتشر خواهد شد. اگر رد شد، پیامی دریافت خواهید کرد که دلیل آن را توضیح می دهد. سپس می توانید به مشکلات گزارش شده رسیدگی کنید و مجددا برای بررسی ارسال کنید.

،

این صفحه شما را از طریق مراحل مورد نیاز برای ساختن یک افزونه Firebase ساده راهنمایی می کند که می توانید آن را در پروژه های خود نصب کنید یا با دیگران به اشتراک بگذارید. این مثال ساده از یک افزونه Firebase، پایگاه داده Realtime شما را برای پیام‌ها مشاهده می‌کند و آنها را به حروف بزرگ تبدیل می‌کند.

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

    وقتی از شما خواسته شد، جاوا اسکریپت را به‌عنوان زبان توابع انتخاب کنید (اما توجه داشته باشید که می‌توانید هنگام توسعه برنامه افزودنی خود نیز از TypeScript استفاده کنید)، و وقتی از شما خواسته شد که وابستگی‌ها را نصب کنید، به «بله» پاسخ دهید. (پیش‌فرض‌ها را برای هر گزینه دیگری بپذیرید.) این دستور یک پایگاه کد اسکلت برای یک برنامه افزودنی جدید راه‌اندازی می‌کند، که از آن می‌توانید توسعه برنامه افزودنی خود را شروع کنید.

2. پسوند مثال را با استفاده از شبیه ساز امتحان کنید

هنگامی که Firebase CLI دایرکتوری جدید پسوندها را آغاز کرد ، یک عملکرد نمونه ساده و یک فهرست integration-tests ایجاد کرد که حاوی پرونده های لازم برای اجرای یک پسوند با استفاده از مجموعه Emulator Firebase است.

سعی کنید پسوند مثال را در شبیه ساز اجرا کنید:

  1. تغییر در فهرست integration-tests :

    cd functions/integration-tests
  2. شبیه ساز را با یک پروژه نمایشی شروع کنید:

    firebase emulators:start --project=demo-test

    شبیه ساز پسوند را به یک پروژه "ساختگی" از پیش تعریف شده ( demo-test ) بارگیری می کند. پسوند تاکنون شامل یک عملکرد منفرد HTTP ، greetTheWorld است که هنگام دسترسی پیام "سلام جهان" را برمی گرداند.

  3. با استفاده از شبیه ساز که هنوز هم در حال اجرا است ، با مراجعه به URL که هنگام شروع آن چاپ شده است ، عملکرد greetTheWorld را امتحان کنید.

    مرورگر شما پیام "سلام جهان را از Well-the-World" نشان می دهد.

  4. کد منبع برای این عملکرد در فهرست 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 ایجاد کنید:

    توابع/index.js

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

    تغییرات خود را ذخیره کنید. شبیه ساز کد شما را بارگیری مجدد می کند ، و اکنون ، هنگامی که از URL عملکرد بازدید می کنید ، تبریک به روز شده را مشاهده خواهید کرد.

3. اطلاعات اساسی را به پسوند اضافه کنید.

اکنون که یک محیط توسعه را تنظیم کرده اید و شبیه ساز Extensions را اجرا می کنید ، می توانید نوشتن برنامه افزودنی خود را شروع کنید.

به عنوان یک مرحله اول متوسط ​​، ابرداده پسوند از پیش تعریف شده را ویرایش کنید تا پسوند مورد نظر خود را به جای 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. یک عملکرد ابر بنویسید و آن را به عنوان یک منبع پسوند اعلام کنید

اکنون می توانید نوشتن کد را شروع کنید. در این مرحله ، شما یک تابع ابری می نویسید که وظیفه اصلی پسوند شما را انجام می دهد ، یعنی تماشای پایگاه داده زمان واقعی خود برای پیام ها و تبدیل آنها به پرونده بزرگ.

  1. منبع را برای توابع برنامه افزودنی (در فهرست functions برنامه افزودنی) در ویرایشگر یا IDE مورد نظر خود باز کنید. محتوای آن را با موارد زیر جایگزین کنید:

    توابع/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 بود که هنگام دسترسی به یک نقطه پایانی HTTP اجرا شد. عملکرد جدید توسط رویدادهای پایگاه داده در زمان واقعی ایجاد می شود: در یک مسیر خاص موارد جدید را تماشا می کند و وقتی یکی از آنها شناسایی می شود ، نسخه بزرگ این مقدار را به پایگاه داده می نویسد.

    به هر حال ، این پرونده جدید به جای مشترک ( require ) از نحو ماژول ECMAScript ( import و export ) استفاده می کند. برای استفاده از ماژول های ES در گره ، "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 به عنوان یک ماشه استفاده می کند ، برای اجرای شبیه ساز RTDB در کنار شبیه ساز توابع Cloud ، باید پیکربندی شبیه ساز خود را به روز کنید:

    1. اگر شبیه ساز هنوز در حال اجرا است ، با فشار دادن CTRL-C ، آن را متوقف کنید.

    2. از دایرکتوری functions/integration-tests ، دستور زیر را اجرا کنید:

      firebase init emulators

      وقتی از آنها سؤال شد ، از راه اندازی یک پروژه پیش فرض پرش کنید ، سپس توابع و شبیه سازهای پایگاه داده را انتخاب کنید. درگاه های پیش فرض را بپذیرید و به ابزار Setup اجازه دهید هر پرونده مورد نیاز را بارگیری کند.

    3. شبیه ساز را مجدداً راه اندازی کنید:

      firebase emulators:start --project=demo-test
  4. پسوند به روز شده خود را امتحان کنید:

    1. با استفاده از پیوندی که شبیه ساز چاپ شده هنگام شروع آن ، Emulator UI را باز کنید.

    2. گره ریشه پایگاه داده را ویرایش کنید:

      • زمینه: messages
      • نوع: json
      • مقدار: {"11": {"original": "recipe"}}

      اگر همه چیز به درستی تنظیم شده است ، هنگامی که تغییرات پایگاه داده خود را ذخیره می کنید ، عملکرد makeuppercase پسوند باید باعث شود و یک رکورد کودک را با محتویات "upper": "RECIPE" به پیام 11 اضافه کند. برای تأیید نتایج مورد انتظار ، به گزارش ها و برگه های پایگاه داده از UI شبیه ساز نگاهی بیندازید.

    3. سعی کنید چند کودک دیگر را به گره messages اضافه کنید ( {"original":"any text"} ). هر زمان که یک رکورد جدید اضافه کنید ، پسوند باید یک میدان uppercase را که حاوی محتویات بزرگ قسمت original است ، اضافه کند.

شما اکنون یک برنامه افزودنی کامل و هرچند ساده دارید که در نمونه RTDB کار می کند. در بخش هایی که در زیر آمده است ، این برنامه را با برخی از ویژگی های اضافی اصلاح خواهید کرد. سپس ، شما برنامه افزودنی را برای توزیع به دیگران آماده می کنید و در آخر ، یاد می گیرید که چگونه پسوند خود را در Hub Extensions منتشر کنید.

5. API و نقش ها را اعلام کنید

Firebase به هر نمونه از یک برنامه افزودنی نصب شده محدود به پروژه و داده های آن با استفاده از یک حساب سرویس در هر حالت کمک می کند. هر حساب دارای حداقل مجموعه مجوزهای لازم برای کار است. به همین دلیل ، شما باید صریحاً هرگونه نقش IAM را که پسوند شما نیاز دارد اعلام کنید. هنگامی که کاربران پسوند شما را نصب می کنند ، Firebase با این نقش های اعطا شده یک حساب کاربری ایجاد می کند و از آن برای اجرای برنامه افزودنی استفاده می کند.

برای ایجاد وقایع یک محصول نیازی به اعلام نقش ندارید ، اما لازم است نقشی را برای تعامل در غیر این صورت با آن اعلام کنید. از آنجا که عملکردی که در مرحله آخر اضافه کرده اید به پایگاه داده Realtime می نویسد ، باید اعلامیه زیر را به extension.yaml اضافه کنید:

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

به طور مشابه ، شما API های Google را اعلام می کنید که یک برنامه افزودنی در قسمت apis استفاده می کند. هنگامی که کاربران پسوند شما را نصب می کنند ، از آنها سؤال می شود که آیا می خواهند به طور خودکار این API ها را برای پروژه خود فعال کنند. این به طور معمول فقط برای API های Google غیر آتشین ضروری است و برای این راهنما لازم نیست.

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 به مسیری که کاربر تعیین شده گوش می دهد. شما می توانید POSTINSTALL.md این نحو برای مرجع هر پارامتر تعریف شده توسط کاربر در هر نقطه در extension.yaml استفاده کنید.

  3. همچنین می توانید از کد توابع خود به پارامترهای تعریف شده توسط کاربر دسترسی پیدا کنید.

    در عملکردی که در بخش آخر نوشتید ، مسیر تماشای تغییرات را سخت رمزگذاری کرده اید. به جای آن ، تعریف ماشه را به مرجع مقدار تعریف شده توسط کاربر تغییر دهید:

    توابع/index.js

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

    توجه داشته باشید که در برنامه های افزودنی Firebase ، این تغییر صرفاً به خاطر مستندات است: هنگامی که یک عملکرد ابر به عنوان بخشی از یک پسوند مستقر می شود ، از تعریف ماشه از پرونده extension.yaml استفاده می کند و مقدار مشخص شده در تعریف عملکرد را نادیده می گیرد. با این وجود ، ایده خوبی است که در کد خود مستند کنید که این مقدار از کجا حاصل می شود.

  4. ممکن است شما را ناامید کننده بدانید که یک تغییر کد که هیچ اثر اجرا وجود ندارد ، ناامید کننده است ، اما درس مهم برای از بین بردن این است که می توانید به هر پارامتر تعریف شده توسط کاربر در کد عملکرد خود دسترسی پیدا کرده و از آن به عنوان یک مقدار معمولی در منطق عملکرد استفاده کنید. به عنوان گره ای از این قابلیت ، عبارت log زیر را اضافه کنید تا نشان دهید که شما واقعاً به مقداری که کاربر تعریف کرده است دسترسی دارید:

    توابع/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. به طور معمول ، از کاربران خواسته می شود هنگام نصب پسوند ، مقادیر پارامترهایی را ارائه دهند. با این حال ، هنگامی که از Emulator برای آزمایش و توسعه استفاده می کنید ، فرآیند نصب را رد می کنید ، بنابراین در عوض مقادیر پارامترهای تعریف شده توسط کاربر را با استفاده از یک فایل env ارائه می دهید.

    functions/integration-tests/extensions/rtdb-uppercase-messages.env و تعریف GREETING با موارد زیر جایگزین کنید:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    توجه کنید که مسیر بالا با مسیر پیش فرض و از مسیری که قبلاً تعریف کرده اید متفاوت است. این فقط برای اثبات خود در هنگام امتحان کردن به روزرسانی خود در مورد تعریف شما است.

  6. اکنون ، شبیه ساز را مجدداً راه اندازی کنید و یک بار دیگر از Emulator UI به پایگاه داده مراجعه کنید.

    با استفاده از مسیری که در بالا تعریف کرده اید ، گره ریشه پایگاه داده را ویرایش کنید:

    • زمینه: msgs
    • نوع: json
    • مقدار: {"11": {"original": "recipe"}}

    هنگامی که تغییرات پایگاه داده خود را ذخیره می کنید ، عملکرد makeuppercase پسوند باید مانند گذشته انجام شود ، اما اکنون باید پارامتر تعریف شده توسط کاربر را نیز در ورود به سیستم کنسول چاپ کند.

7. قلاب های رویداد را برای منطق تعریف شده توسط کاربر فراهم کنید

شما قبلاً به عنوان نویسنده پسوند دیده اید ، چگونه یک محصول Firebase می تواند منطق ارائه شده توسط پسوند شما را تحریک کند: ایجاد سوابق جدید در پایگاه داده Realtime باعث عملکرد makeuppercase شما می شود. پسوند شما می تواند با کاربرانی که پسوند شما را نصب می کنند رابطه مشابه داشته باشد: پسوند شما می تواند منطقی را که کاربر تعریف می کند ، ایجاد کند.

یک برنامه افزودنی می تواند قلاب های همزمان ، قلاب های ناهمزمان یا هر دو را فراهم کند. قلاب های همزمان به کاربران راهی برای انجام کارهایی می دهد که مانع از تکمیل یکی از عملکردهای پسوند می شود. این می تواند مفید باشد ، به عنوان مثال ، به کاربران راهی برای انجام پیش پردازش سفارشی قبل از انجام کار خود می دهد.

در این راهنما ، یک قلاب ناهمزمان به پسوند خود اضافه می کنید ، که به کاربران این امکان را می دهد تا مراحل پردازش خود را تعریف کنند که پس از آنکه پسوند شما پیام بزرگ را به پایگاه داده Realtime می نویسد ، اجرا شود. قلاب های ناهمزمان از 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 ، کد هایی را اضافه کنید که رویدادی از نوع شما را منتشر کرده است که اخیراً اعلام کرده اید:

    توابع/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 است که حاوی اشاره ای به مقدار تازه ایجاد شده و یک بار 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 Tunctions انتخاب کنید و وابستگی های لازم را نصب کنید. این پروژه نمایانگر پروژه کاربر است که برنامه افزودنی شما را نصب کرده است.

  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 گوش می کند و در صورت تحریک ، سه نکته تعجب را به پیام تازه Uppercased اضافه می کند.

  3. شبیه ساز را مجدداً راه اندازی کنید. شبیه ساز توابع پسوند و همچنین عملکرد پس از پردازش "کاربر" تعریف شده را بارگیری می کند.

  4. با استفاده از مسیری که در بالا تعریف کرده اید ، از Emulator ui به Emulator UI مراجعه کرده و گره ریشه پایگاه داده را ویرایش کنید:

    • زمینه: msgs
    • نوع: json
    • مقدار: {"11": {"original": "recipe"}}

    هنگامی که تغییرات پایگاه داده خود را ذخیره می کنید ، عملکرد makeuppercase Extension و عملکرد extraemphasis کاربر باید به ترتیب توالی ایجاد شود و در نتیجه قسمت upper RECIPE!!! .

8. به دستگیرندگان رویداد چرخه حیات اضافه کنید

برنامه افزودنی که تاکنون نوشتید ، پیام های ایجاد شده را پردازش می کند. اما اگر کاربران شما هنگام نصب پسوند ، پایگاه داده ای از پیام ها داشته باشند ، چه می شود؟ Extensions Firebase دارای ویژگی ای به نام قلاب های رویداد چرخه عمر است که می توانید هنگام نصب ، به روزرسانی یا تنظیم مجدد خود ، از آنها برای ایجاد اقدامات استفاده کنید. در این بخش از قلاب های رویداد Lifecycle برای بازگرداندن پایگاه داده پیام موجود یک پروژه با پیام های برقی در هنگام نصب کاربر ، از برنامه های DataBase موجود استفاده می کنید.

Extensions Firebase از وظایف ابری برای اجرای گیرنده های رویداد چرخه عمر خود استفاده می کند. شما گیرنده های رویداد را با استفاده از توابع ابر تعریف می کنید. هر زمان که نمونه ای از پسوند شما به یکی از حوادث چرخه عمر پشتیبانی شده برسد ، اگر یک کنترل کننده را تعریف کرده اید ، کنترل کننده را به صف کارهای ابری اضافه می کند. سپس وظایف ابری به طور غیر همزمان کنترل کننده را اجرا می کند. در حالی که یک کنترل کننده رویداد چرخه عمر در حال اجرا است ، کنسول Firebase به کاربر گزارش می دهد که نمونه پسوند دارای یک کار پردازش در حال انجام است. این به عملکرد کنترل کننده شما بستگی دارد که وضعیت در حال انجام و تکمیل کار را به کاربر برگردانید.

برای اضافه کردن یک کنترل کننده رویداد چرخه عمر که پیام های موجود را باز می کند ، موارد زیر را انجام دهید:

  1. یک عملکرد ابر جدید را تعریف کنید که توسط رویدادهای صف کار ایجاد شده است:

    توابع/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.");
      }
    });
    

    توجه کنید که این عملکرد فقط قبل از اضافه کردن خود به صف کار ، چند سوابق را پردازش می کند. این یک استراتژی متداول برای مقابله با وظایف پردازش است که نمی توانند در پنجره زمان بندی یک عملکرد ابر انجام شود. از آنجا که شما نمی توانید پیش بینی کنید که چه تعداد پیام یک کاربر ممکن است هنگام نصب پسوند شما در پایگاه داده خود داشته باشد ، این استراتژی مناسب است.

  2. در پرونده extension.yaml ، عملکرد Backfill خود را به عنوان یک منبع پسوند که دارای ویژگی 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
    

    سپس در ابتدای عملکرد Backfill ، مقدار پارامتر DO_BACKFILL را بررسی کنید و در صورت عدم تنظیم زودتر از آن خارج شوید:

    توابع/index.js

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

با تغییرات فوق ، پسوند اکنون پیام های موجود را هنگام نصب به حروف بزرگ تبدیل می کند.

تا این مرحله ، شما از Extension Emulator برای توسعه برنامه افزودنی و آزمایش های مداوم خود استفاده کردید. با این حال ، Extension Emulator روند نصب را رد می کند ، بنابراین برای آزمایش کنترل کننده رویداد onInstall ، باید پسوند را در یک پروژه واقعی نصب کنید. این هم همینطور است ، زیرا با افزودن این ویژگی اتوماتیک Backfill ، پسوند آموزش اکنون کد است!

9. به یک پروژه Firebase واقعی مستقر شوید

اگرچه Extensions Emulator ابزاری عالی برای تکرار سریع در طول توسعه است ، اما در بعضی مواقع می خواهید آن را در یک پروژه واقعی امتحان کنید.

برای انجام این کار ، ابتدا یک پروژه جدید را با برخی از خدمات فعال کنید:

  1. در کنسول Firebase ، یک پروژه جدید اضافه کنید.
  2. پروژه خود را به برنامه Blaze Pay-as-Go ارتقا دهید . توابع ابر برای Firebase به پروژه شما نیاز دارد تا یک حساب صورتحساب داشته باشد ، بنابراین برای نصب یک پسوند نیز به یک حساب صورتحساب نیز نیاز دارید.
  3. در پروژه جدید خود ، پایگاه داده در زمان واقعی را فعال کنید .
  4. از آنجا که می خواهید توانایی پسوند خود را برای بازگرداندن داده های موجود در نصب ، آزمایش کنید ، برخی از داده های نمونه را به نمونه پایگاه داده در زمان واقعی خود وارد کنید:
    1. برخی از داده های RTDB SEED را بارگیری کنید.
    2. در صفحه پایگاه داده زمان واقعی کنسول Firebase ، روی (بیشتر)> واردات JSON کلیک کنید و فایل مورد نظر خود را انتخاب کنید.
  5. برای فعال کردن عملکرد Backfill برای استفاده از روش 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 ، تجربه کاربر را ببینید. هنگامی که ابزار پیکربندی می پرسد آیا می خواهید پایگاه داده موجود خود را دوباره انتخاب کنید ، "بله" را انتخاب کنید.

    پس از انتخاب گزینه های پیکربندی ، Firebase CLI پیکربندی شما را در فهرست extensions ذخیره می کند و مکان منبع پسوند را در پرونده firebase.json ضبط می کند. در مجموع ، این دو رکورد به عنوان پسوند آشکار می شوند. کاربران می توانند از مانیفست برای ذخیره پیکربندی پسوندهای خود استفاده کرده و آن را در پروژه های مختلف مستقر کنند.

  4. پیکربندی برنامه افزودنی خود را در پروژه زنده خود مستقر کنید:

    firebase deploy --only extensions

اگر همه چیز خوب پیش برود ، Firebase CLI باید پسوند شما را در پروژه خود بارگذاری کرده و آن را نصب کند. پس از اتمام نصب ، کار Backfill اجرا می شود و در عرض چند دقیقه پایگاه داده شما با پیام های بزرگ به روز می شود. برخی از گره های جدید را به پایگاه داده پیام ها اضافه کنید و اطمینان حاصل کنید که پسوند نیز برای پیام های جدید کار می کند.

10. مستندات بنویسید

قبل از اینکه برنامه افزودنی خود را با کاربران به اشتراک بگذارید ، اطمینان حاصل کنید که اسناد کافی را برای موفقیت در آنها ارائه می دهید.

هنگامی که پروژه پسوند را آغاز کردید ، Firebase CLI نسخه های خردی حداقل مستندات مورد نیاز را ایجاد کرد. این پرونده ها را به روز کنید تا به طور دقیق پسوند ساخته شده را منعکس کنید.

extension.yaml

شما قبلاً این فایل را به عنوان این برنامه افزودنی به روز کرده اید ، بنابراین نیازی به به روزرسانی بیشتر در حال حاضر نیست.

با این حال ، از اهمیت مستندات موجود در این پرونده غافل نشوید. علاوه بر اطلاعات مهم شناسایی یک برنامه ، نام ، توضیحات ، نویسنده ، مکان رسمی مخزن-پرونده extension.yaml حاوی مستندات کاربر کاربر برای هر منبع و پارامتر قابل تنظیم کاربر است. این اطلاعات برای کاربران در کنسول Firebase ، Hub Extensions و Firebase CLI ظاهر می شود.

preinstall.md

در این پرونده ، اطلاعاتی را که کاربر به آن نیاز دارد قبل از نصب پسوند شما ارائه دهید: به طور خلاصه توضیح دهید که پسوند چه کاری انجام می دهد ، هرگونه پیش نیاز را توضیح دهید و اطلاعات کاربر را در مورد پیامدهای صورتحساب نصب پسوند ارائه دهید. اگر یک وب سایت با اطلاعات بیشتر دارید ، این مکان نیز برای پیوند دادن آن نیز هست.

متن این فایل در Hub Extensions و توسط firebase ext:info Command به کاربر نمایش داده می شود.

در اینجا نمونه ای از یک پرونده از پیش نصب شده است:

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 پرش کنید.

اسناد اضافی

مستندات مورد بحث در بالا حداقل مجموعه مستنداتی است که باید به کاربران ارائه دهید. بسیاری از پسوندها برای استفاده موفقیت آمیز از آنها به مستندات دقیق تری نیاز دارند. در این صورت ، شما باید مستندات اضافی بنویسید و در جایی که می توانید به کاربران اشاره کنید ، آن را میزبانی کنید.

به منظور این راهنما ، نوشتن مستندات گسترده تر را پرش کنید.

11. انتشار در Hub Extensions

اکنون که پسوند شما کد کامل و مستند است ، شما آماده هستید تا آن را با World on Extensions Hub به اشتراک بگذارید. اما از آنجا که این فقط یک آموزش است ، در واقع این کار را نکنید. بروید و با استفاده از آنچه در اینجا آموخته اید و در بقیه مستندات ناشر Extensions Firebase ، و با بررسی منبع رسمی ، Firebase نوشته شده ، پسوند ، نوشتن پسوند خود را شروع کنید.

هنگامی که شما آماده انتشار کار خود در Hub Extensions هستید ، چگونه این کار را انجام می دهید:

  1. اگر اولین برنامه افزودنی خود را منتشر می کنید ، به عنوان ناشر پسوند ثبت نام کنید . هنگامی که به عنوان ناشر پسوند ثبت نام می کنید ، یک شناسه ناشر ایجاد می کنید که به کاربران امکان می دهد شما را به سرعت شما را به عنوان نویسنده برنامه های افزودنی خود معرفی کنند.
  2. کد منبع پسوند خود را در یک مکان قابل تأیید عمومی میزبانی کنید. هنگامی که کد شما از یک منبع قابل تأیید در دسترس است ، Firebase می تواند پسوند شما را مستقیماً از این مکان منتشر کند. انجام این کار به شما اطمینان می دهد که نسخه منتشر شده در حال حاضر از پسوند خود را منتشر می کنید و به کاربران کمک می کند تا به آنها اجازه دهید کدی را که در پروژه های خود نصب می کنند بررسی کنند.

    در حال حاضر ، این به معنای تهیه پسوند شما در یک مخزن عمومی GitHub است.

  3. با استفاده از firebase ext:dev:upload پسوند خود را در Hub Extensions بارگذاری کنید.

  4. به داشبورد ناشر خود در کنسول Firebase بروید ، برنامه افزودنی را که تازه بارگذاری شده اید پیدا کنید و روی "Publish to Extensions Hub" کلیک کنید. این درخواست بررسی از کارکنان بررسی ما می کند ، که می تواند چند روز طول بکشد. در صورت تصویب ، پسوند به Hub Extensions منتشر می شود. در صورت رد ، پیامی را توضیح می دهید که دلیل آن را توضیح می دهد. سپس می توانید به موضوعات گزارش شده بپردازید و برای بررسی دوباره ارسال کنید.

،

این صفحه شما را از طریق مراحل مورد نیاز برای ساخت یک پسوند ساده Firebase ، که می توانید در پروژه های خود نصب کنید یا با دیگران به اشتراک بگذارید ، طی می کند. این مثال ساده از پسوند Firebase پایگاه داده Realtime شما را برای پیام ها تماشا می کند و آنها را به پرونده بالا تبدیل می کند.

1. محیط خود را تنظیم کنید و یک پروژه را آغاز کنید

قبل از اینکه بتوانید یک برنامه افزودنی را شروع کنید ، باید یک محیط ساخت را با ابزارهای مورد نیاز تنظیم کنید.

  1. node.js 16 یا جدیدتر را نصب کنید. یکی از راه های نصب گره استفاده از 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. اجرای Firebase CLI's ext:dev:init COMMENT:

    firebase ext:dev:init

    در صورت درخواست ، JavaScript را به عنوان زبان برای توابع انتخاب کنید (اما توجه داشته باشید که می توانید هنگام توسعه پسوند خود از TypeScript نیز استفاده کنید) ، و در صورت درخواست نصب وابستگی ها ، پاسخ "بله" را پاسخ دهید. (پیش فرض را برای هر گزینه دیگر بپذیرید.) این دستور برای یک پسوند جدید یک پایگاه کد اسکلت تنظیم می کند ، که از این طریق می توانید توسعه خود را شروع کنید.

2. پسوند مثال را با استفاده از شبیه ساز امتحان کنید

هنگامی که Firebase CLI دایرکتوری جدید پسوندها را آغاز کرد ، یک عملکرد نمونه ساده و یک فهرست integration-tests ایجاد کرد که حاوی پرونده های لازم برای اجرای یک پسوند با استفاده از مجموعه Emulator Firebase است.

سعی کنید پسوند مثال را در شبیه ساز اجرا کنید:

  1. تغییر در فهرست integration-tests :

    cd functions/integration-tests
  2. شبیه ساز را با یک پروژه نمایشی شروع کنید:

    firebase emulators:start --project=demo-test

    شبیه ساز پسوند را به یک پروژه "ساختگی" از پیش تعریف شده ( demo-test ) بارگیری می کند. پسوند تاکنون شامل یک عملکرد منفرد HTTP ، greetTheWorld است که هنگام دسترسی پیام "سلام جهان" را برمی گرداند.

  3. با استفاده از شبیه ساز که هنوز هم در حال اجرا است ، با مراجعه به URL که هنگام شروع آن چاپ شده است ، عملکرد greetTheWorld را امتحان کنید.

    مرورگر شما پیام "سلام جهان را از Well-the-World" نشان می دهد.

  4. کد منبع برای این عملکرد در فهرست 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 ایجاد کنید:

    توابع/index.js

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

    تغییرات خود را ذخیره کنید. شبیه ساز کد شما را بارگیری مجدد می کند ، و اکنون ، هنگامی که از URL عملکرد بازدید می کنید ، تبریک به روز شده را مشاهده خواهید کرد.

3. Add basic information to extension.yaml

Now that you have a development environment set up and are running the extensions emulator, you can start writing your own extension.

As a modest first step, edit the predefined extension metadata to reflect the extension you want to write instead of greet-the-world . This metadata is stored in the extension.yaml file.

  1. Open extension.yaml in your editor, and replace the entire contents of the file with the following:

    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
    

    Note the naming convention used in the name field: official Firebase extensions are named with a prefix indicating the primary Firebase product the extension operates on, followed by a description of what the extension does. You should use the same convention in your own extensions.

  2. Since you've changed the name of your extension, you should also update your emulator configuration with the new name:

    1. In functions/integration-tests/firebase.json , change greet-the-world to rtdb-uppercase-messages .
    2. Rename functions/integration-tests/extensions/greet-the-world.env to functions/integration-tests/extensions/rtdb-uppercase-messages.env .

There are still some remnants of the greet-the-world extension remaining in your extension code, but leave them for now. You'll update those in the next few sections.

4. Write a Cloud Function and declare it as an extension resource

Now you can get started writing some code. In this step, you will write a Cloud Function that performs the core task of your extension, which is to watch your Realtime Database for messages and convert them to upper case.

  1. Open the source for the extension's functions (in the extension's functions directory) in the editor or IDE of your choice. محتوای آن را با موارد زیر جایگزین کنید:

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

    The old function, which you replaced, was an HTTP-triggered function, which ran when an HTTP endpoint was accessed. The new function is triggered by real-time database events: it watches for new items at a particular path and, when one is detected, it writes the uppercase version of the value back to the database.

    By the way, this new file uses ECMAScript module syntax ( import and export ) instead of CommonJS ( require ). To use ES modules in Node, specify "type": "module" in functions/package.json :

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. Every function in your extension must be declared in the extension.yaml file. The example extension declared greetTheWorld as the extension's only Cloud Function; now that you've replaced it with makeuppercase , you also need to update its declaration.

    Open extension.yaml and add a resources field:

    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. Since your extension is now using Realtime Database as a trigger, you need to update your emulator configuration to run the RTDB emulator alongside the Cloud Functions emulator:

    1. If the emulator is still running, stop it by pressing Ctrl-C.

    2. From the functions/integration-tests directory, run the following command:

      firebase init emulators

      When asked, skip setting up a default project, then select the Functions and Database emulators. Accept the default ports and allow the setup tool to download any required files.

    3. Restart the emulator:

      firebase emulators:start --project=demo-test
  4. Try out your updated extension:

    1. Open the Database emulator UI using the link the emulator printed when you started it.

    2. Edit the root node of the database:

      • Field: messages
      • Type: json
      • Value: {"11": {"original": "recipe"}}

      If everything is set up correctly, when you save your database changes, the extension's makeuppercase function should trigger and add a child record to message 11 with the contents "upper": "RECIPE" . Take a look at the logs and the database tabs of the emulator UI to confirm the expected results.

    3. Try adding some more children to the messages node ( {"original":"any text"} ). Whenever you add a new record, the extension should add an uppercase field containing the uppercase contents of the original field.

You now have a complete, though simple, extension that operates on an RTDB instance. In the sections that follow, you will refine this extension with some additional features. Then, you'll get the extension ready to distribute to others, and finally, learn how to publish your extension on Extensions Hub.

5. Declare APIs and roles

Firebase grants each instance of an installed extension limited access to the project and its data using a per-instance service account. Each account has the minimum set of permissions needed to operate. For this reason, you must explicitly declare any IAM roles your extension requires; when users install your extension, Firebase creates a service account with these roles granted and uses it to run the extension.

You don't need to declare roles to trigger off a product's events, but you do need to declare a role to otherwise interact with it. Because the function you added in the last step writes to Realtime Database, you need to add the following declaration to extension.yaml :

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

Similarly, you declare the Google APIs that an extension uses in the apis field. When users install your extension, they will be asked if they want to automatically enable these APIs for their project. This is typically only necessary for non-Firebase Google APIs, and isn't needed for this guide.

6. Define user-configurable parameters

The function you created in the last two steps watched a specific RTDB location for incoming messages. Sometimes, watching a specific location really is what you want, such as when your extension operates on a database structure that you use exclusively for your extension. However, most of the time, you will want to make these values configurable by users who install your extension in their projects. This way, users can make use of your extension to work with their existing database setup.

Make the path that the extension watches for new messages user-configurable:

  1. In the extension.yaml file, add a params section:

    - 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
    

    This defines a new string parameter that users will be prompted to set when they install your extension.

  2. Still in the extension.yaml file, go back to your makeuppercase declaration and change the resource field to the following:

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

    The ${param:MESSAGE_PATH} token is a reference to the parameter you just defined. When your extension runs, this token will be replaced by whatever value the user configured for that parameter, with the result that the makeuppercase function will listen to the path the user specified. You can use this syntax to reference any user-defined parameter anywhere in extension.yaml (and in POSTINSTALL.md —more on that later).

  3. You can also access user-defined parameters from your functions code.

    In the function you wrote in the last section, you hard-coded the path to watch for changes. Change the trigger definition to reference the user-defined value instead:

    functions/index.js

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

    Note that in Firebase Extensions, this change is purely for the sake of documentation: when a Cloud Function is deployed as part of an extension, it uses the trigger definition from the extension.yaml file and ignores the value specified in the function definition. Nevertheless, it's a good idea to document in your code where this value comes from.

  4. You might find it disappointing to make a code change that has no runtime effect, but the important lesson to take away is that you can access any user-defined parameter in your function code and use it as an ordinary value in the function's logic. As a nod to this capability, add the following log statement to demonstrate that you are indeed accessing the value that the user defined:

    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. Normally, users are prompted to provide values for parameters when they install an extension. When you use the emulator for testing and development, however, you skip the installation process, so you instead provide values for user-defined parameters using an env file.

    Open functions/integration-tests/extensions/rtdb-uppercase-messages.env and replace the GREETING definition with the following:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    Notice that the path above is different from the default path and from the path you defined previously; this is just to prove to yourself when you try your updated extension that your definition is taking effect.

  6. Now, restart the emulator and once again visit the database emulator UI.

    Edit the root node of the database, using the path you defined above:

    • Field: msgs
    • Type: json
    • Value: {"11": {"original": "recipe"}}

    When you save your database changes, the extension's makeuppercase function should trigger as it did before, but now it should also print the user-defined parameter to the console log.

7. Provide event hooks for user-defined logic

You've already seen, as an extension author, how a Firebase product can trigger your extension-provided logic: the creation of new records in Realtime Database triggers your makeuppercase function. Your extension can have an analogous relationship with the users who install your extension: your extension can trigger logic that the user defines.

An extension can provide synchronous hooks , asynchronous hooks , or both. Synchronous hooks give users a way to perform tasks that block the completion of one of the extension's functions. This can be useful, for example, to give users a way to perform custom preprocessing before an extension does its work.

In this guide, you'll add an asynchronous hook to your extension, which will enable users to define their own processing steps to be run after your extension writes the uppercase message to Realtime Database. Asynchronous hooks use Eventarc to trigger user-defined functions. Extensions declare the types of events they emit, and when users install the extension, they choose which event types they're interested in. If they choose at least one event, Firebase will provision an Eventarc channel for the extension as part of the installation process. Users can then deploy their own cloud functions that listen on that channel and trigger when the extension publishes new events.

Follow these steps to add an asynchronous hook:

  1. In the extension.yaml file, add the following section, which declares the one event type the extension emits:

    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.
    

    Event types must be universally unique; to ensure uniqueness, always name your events using the following format: <publisher-id>.<extension-id>.<version>.<description> . (You don't have a publisher ID yet, so just use test-publisher for now.)

  2. At the end of the makeuppercase function, add some code that publishes an event of the type you just declared:

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

    This example code takes advantage of the fact that the EVENTARC_CHANNEL environment variable is defined only when the user enabled at least one event type. if EVENTARC_CHANNEL is not defined, the code does not attempt to publish any events.

    You can attach extra information to an Eventarc event. In the example above, the event has a subject field that contains a reference to the newly-created value, and a data payload that contains the original and uppercase messages. User-defined functions that trigger off the event can make use of this information.

  3. Normally, the EVENTARC_CHANNEL and EXT_SELECTED_EVENTS environment variables are defined based on the options the user selected during installation. For testing with the emulator, manually define these variables in the rtdb-uppercase-messages.env file:

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

At this point, you have completed the steps needed to add an asynchronous event hook to your extension.

To try out this new feature that you have just implemented, in the next few steps, assume the role of a user who is installing the extension:

  1. From the functions/integration-tests directory, initialize a new Firebase project:

    firebase init functions

    When prompted, decline to set up a default project, select JavaScript as the Cloud Functions language, and install the required dependencies. This project represents a user's project, which has your extension installed.

  2. Edit integration-tests/functions/index.js and paste the following code:

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

    This is an example of a post-processing function a user might write. In this case, the function listens for the extension to publish a complete event, and when triggered, adds three exclamation points to the newly-uppercased message.

  3. Restart the emulator. The emulator will load the extension's functions as well as the post-processing function the "user" defined.

  4. Visit the database emulator UI and edit the root node of the database, using the path you defined above:

    • Field: msgs
    • Type: json
    • Value: {"11": {"original": "recipe"}}

    When you save your database changes, the extension's makeuppercase function and the user's extraemphasis function should trigger in sequence, resulting in the upper field getting the value RECIPE!!! .

8. Add lifecycle event handlers

The extension you've written so far processes messages as they are created. But what if your users already have a database of messages when they install the extension? Firebase Extensions has a feature called lifecycle event hooks that you can use to trigger actions when your extension gets installed, updated, or reconfigured. In this section, you will use lifecycle event hooks to backfill a project's existing message database with uppercased messages when a user installs your extension.

Firebase Extensions uses Cloud Tasks to run your lifecycle event handlers. You define event handlers using Cloud Functions; whenever an instance of your extension reaches one of the supported lifecycle events, if you have defined a handler, it will add the handler to a Cloud Tasks queue. Cloud Tasks will then asynchronously execute the handler. While a lifecycle event handler is running, the Firebase console will report to the user that the extension instance has a processing task in progress. It's up to your handler function to report ongoing status and task completion back to the user.

To add a lifecycle event handler that backfills existing messages, do the following:

  1. Define a new Cloud Function that's triggered by task queue events:

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

    Notice that the function only processes a few records before adding itself back to the task queue. This is a commonly used strategy to deal with processing tasks that cannot complete within the timeout window of a Cloud Function. Since you can't predict how many messages a user might already have in their database when they install your extension, this strategy is a good fit.

  2. In the extension.yaml file, declare your backfill function as an extension resource that has the taskQueueTrigger property:

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

    Then declare the function as the handler for the onInstall lifecycle event:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Although backfilling existing messages is nice to have, the extension could still function without it. In situations like this, you should make running the lifecycle event handlers optional.

    To do so, add a new parameter to 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
    

    Then at the beginning of the backfill function, check the value of the DO_BACKFILL parameter and exit early if it's not set:

    functions/index.js

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

With the above changes, the extension will now convert existing messages to uppercase when it is installed.

Up to this point, you used the extension emulator to develop your extension and test ongoing changes. However, the extension emulator skips the installation process, so to test your onInstall event handler, you'll need to install the extension in a real project. That's just as well though, since with the addition of this automatic backfill feature, the tutorial extension is now code-complete!

9. Deploy into a real Firebase project

Although the extensions emulator is a great tool for rapidly iterating on an extension during development, at some point you'll want to try it in a real project.

To do so, first set up a new project with some services enabled:

  1. In the Firebase console , add a new project.
  2. Upgrade your project to the pay-as-you-go Blaze plan. Cloud Functions for Firebase requires your project to have a billing account, so you also need a billing account to install an extension.
  3. In your new project, enable Real-time Database .
  4. Since you want to test your extension's ability to backfill existing data on installation, import some sample data into your real-time database instance:
    1. Download some seed RTDB data .
    2. On the Real-time Database page of the Firebase console, click (more) > Import JSON and select the file you just downloaded.
  5. To enable the backfill function to use the orderByChild method, configure the database to index messages on the value of upper :

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

Now install your extension from local source into the new project:

  1. Create a new directory for your Firebase project:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Initialize a Firebase project in the working directory:

    firebase init database

    When prompted, select the project you just created.

  3. Install the extension into your local Firebase project:

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

    Here you can see what the user experience is like when installing an extension using the Firebase CLI tool. Be sure to select "yes" when the configuration tool asks if you want to backfill your existing database.

    After you select configuration options, the Firebase CLI will save your configuration in the extensions directory and record the extension source location in the firebase.json file. Collectively, these two records are called the extensions manifest . Users can use the manifest to save their extensions configuration and deploy it to different projects.

  4. Deploy your extension configuration to your live project:

    firebase deploy --only extensions

If all goes well, the Firebase CLI should upload your extension to your project and install it. After installation completes, the backfill task will run and, in a few minutes, your database will be updated with uppercase messages. Add some new nodes to the messages database and make sure the extension is also working for new messages.

10. Write documentation

Before you share your extension with users, make sure you're providing enough documentation for them to be successful.

When you initialized the extension project, the Firebase CLI created stub versions of the minimum required documentation. Update these files to accurately reflect the extension you've built.

extension.yaml

You've already been updating this file as you've developed this extension, so you don't need to make any more updates right now.

However, don't overlook the importance of the documentation contained in this file. In addition to an extension's crucial identifying information—name, description, author, official repository location—the extension.yaml file contains user-facing documentation for every resource and user-configurable parameter. This information is surfaced to users in the Firebase console, Extensions Hub, and Firebase CLI.

PREINSTALL.md

In this file, provide information the user needs before they install your extension: briefly describe what the extension does, explain any prerequisites, and give the user information on the billing implications of installing the extension. If you have a website with additional information, this is also a good place to link it.

The text of this file is displayed to the user in Extensions Hub and by the firebase ext:info command.

Here is an example of a PREINSTALL file:

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

This file contains information useful for users after they have successfully installed your extension: for example, follow-up setup steps, an example of the extension in action, and so on.

The contents of POSTINSTALL.md are displayed in the Firebase console after an extension is configured and installed. You can reference user parameters in this file and they will be replaced by the configured values.

Here is an example post-install file for the tutorial extension:

### 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

You should also document the changes you make between releases of an extension in the CHANGELOG.md file.

Since the example extension has never been published before, the change log has only one entry:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

Most extensions also provide a readme file for the benefit of users visiting the extension's repository. you can write this file by hand or generate a read me using the command.

For the purpose of this guide, skip writing a readme file.

اسناد اضافی

The documentation discussed above is the minimum set of documentation you should provide users. Many extensions require more detailed documentation for users to successfully use them. When this is the case, you should write additional documentation and host it somewhere you can point users to.

For the purpose of this guide, skip writing more extensive documentation.

11. Publish on Extensions Hub

Now that your extension is code complete and documented, you are ready to share it with the world on Extensions Hub. But since this is just a tutorial, don't actually do that. Go and start writing your own extension using what you've learned here and in the rest of the Firebase Extensions publisher documentation, and by examining the source of the official, Firebase-written, extensions.

When you're ready to publish your work on Extensions Hub here's how you will do it:

  1. If you are publishing your first extension, register as an extension publisher . When you register as an extensions publisher, you create a publisher ID that lets users quickly identify you as the author of your extensions.
  2. Host your extension's source code in a publicly verifiable location. When your code is available from a verifiable source, Firebase can publish your extension directly from this location. Doing so helps ensure that you're publishing the currently released version of your extension, and helps users by letting them examine the code they're installing into their projects.

    Currently, this means making your extension available in a public GitHub repository.

  3. Upload your extension to Extensions Hub using the firebase ext:dev:upload command.

  4. Go to your publisher dashboard in the Firebase console, find the extension you just uploaded, and click "Publish to Extensions Hub". This requests a review from our review staff, which can take a few days. If approved, the extension will be published to Extensions Hub. If rejected, you'll get a message explaining the reason; you can then address the reported issues and resubmit for review.

،

This page walks you through the steps required to build a simple Firebase Extension, which you can install in your projects or share with others. This simple example of a Firebase Extension will watch your Realtime Database for messages and convert them to upper case.

1. Set up your environment and initialize a project

Before you can start building an extension, you'll need to set up a build environment with the required tools.

  1. Install Node.js 16 or newer. One way to install Node is by using nvm (or nvm-windows ).

  2. Install or update to the latest version of the Firebase CLI . To install or update using npm , run this command:

    npm install -g firebase-tools

Now use the Firebase CLI to initialize a new extension project:

  1. Create a directory for your extension and cd into it:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Run the Firebase CLI's ext:dev:init command:

    firebase ext:dev:init

    When prompted, choose JavaScript as the language for functions (but note that you can also use TypeScript when you develop your own extension), and, when asked to install dependencies, answer "yes". (Accept the defaults for any other options.) This command will set up a skeleton codebase for a new extension, from which you can start developing your extension.

2. Try the example extension using the emulator

When the Firebase CLI initialized the new extensions directory, it created a simple example function and an integration-tests directory that contains the files necessary to run an extension using the Firebase emulator suite.

Try running the example extension in the emulator:

  1. Change to the integration-tests directory:

    cd functions/integration-tests
  2. Start the emulator with a demo project:

    firebase emulators:start --project=demo-test

    The emulator loads the extension into a predefined "dummy" project ( demo-test ). The extension so far consists of a single HTTP-triggered function, greetTheWorld , which returns a "hello world" message when accessed.

  3. With the emulator still running, try the extension's greetTheWorld function by visiting the URL it printed when you started it.

    Your browser displays the message "Hello World from greet-the-world".

  4. The source code for this function is in the extension's functions directory. Open the source in the editor or IDE of your choice:

    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. While the emulator is running, it will automatically reload any changes you make to your Functions code. Try making a small change to the greetTheWorld function:

    functions/index.js

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

    تغییرات خود را ذخیره کنید. The emulator will reload your code, and now, when you visit the function URL, you'll see the updated greeting.

3. Add basic information to extension.yaml

Now that you have a development environment set up and are running the extensions emulator, you can start writing your own extension.

As a modest first step, edit the predefined extension metadata to reflect the extension you want to write instead of greet-the-world . This metadata is stored in the extension.yaml file.

  1. Open extension.yaml in your editor, and replace the entire contents of the file with the following:

    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
    

    Note the naming convention used in the name field: official Firebase extensions are named with a prefix indicating the primary Firebase product the extension operates on, followed by a description of what the extension does. You should use the same convention in your own extensions.

  2. Since you've changed the name of your extension, you should also update your emulator configuration with the new name:

    1. In functions/integration-tests/firebase.json , change greet-the-world to rtdb-uppercase-messages .
    2. Rename functions/integration-tests/extensions/greet-the-world.env to functions/integration-tests/extensions/rtdb-uppercase-messages.env .

There are still some remnants of the greet-the-world extension remaining in your extension code, but leave them for now. You'll update those in the next few sections.

4. Write a Cloud Function and declare it as an extension resource

Now you can get started writing some code. In this step, you will write a Cloud Function that performs the core task of your extension, which is to watch your Realtime Database for messages and convert them to upper case.

  1. Open the source for the extension's functions (in the extension's functions directory) in the editor or IDE of your choice. محتوای آن را با موارد زیر جایگزین کنید:

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

    The old function, which you replaced, was an HTTP-triggered function, which ran when an HTTP endpoint was accessed. The new function is triggered by real-time database events: it watches for new items at a particular path and, when one is detected, it writes the uppercase version of the value back to the database.

    By the way, this new file uses ECMAScript module syntax ( import and export ) instead of CommonJS ( require ). To use ES modules in Node, specify "type": "module" in functions/package.json :

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. Every function in your extension must be declared in the extension.yaml file. The example extension declared greetTheWorld as the extension's only Cloud Function; now that you've replaced it with makeuppercase , you also need to update its declaration.

    Open extension.yaml and add a resources field:

    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. Since your extension is now using Realtime Database as a trigger, you need to update your emulator configuration to run the RTDB emulator alongside the Cloud Functions emulator:

    1. If the emulator is still running, stop it by pressing Ctrl-C.

    2. From the functions/integration-tests directory, run the following command:

      firebase init emulators

      When asked, skip setting up a default project, then select the Functions and Database emulators. Accept the default ports and allow the setup tool to download any required files.

    3. Restart the emulator:

      firebase emulators:start --project=demo-test
  4. Try out your updated extension:

    1. Open the Database emulator UI using the link the emulator printed when you started it.

    2. Edit the root node of the database:

      • Field: messages
      • Type: json
      • Value: {"11": {"original": "recipe"}}

      If everything is set up correctly, when you save your database changes, the extension's makeuppercase function should trigger and add a child record to message 11 with the contents "upper": "RECIPE" . Take a look at the logs and the database tabs of the emulator UI to confirm the expected results.

    3. Try adding some more children to the messages node ( {"original":"any text"} ). Whenever you add a new record, the extension should add an uppercase field containing the uppercase contents of the original field.

You now have a complete, though simple, extension that operates on an RTDB instance. In the sections that follow, you will refine this extension with some additional features. Then, you'll get the extension ready to distribute to others, and finally, learn how to publish your extension on Extensions Hub.

5. Declare APIs and roles

Firebase grants each instance of an installed extension limited access to the project and its data using a per-instance service account. Each account has the minimum set of permissions needed to operate. For this reason, you must explicitly declare any IAM roles your extension requires; when users install your extension, Firebase creates a service account with these roles granted and uses it to run the extension.

You don't need to declare roles to trigger off a product's events, but you do need to declare a role to otherwise interact with it. Because the function you added in the last step writes to Realtime Database, you need to add the following declaration to extension.yaml :

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

Similarly, you declare the Google APIs that an extension uses in the apis field. When users install your extension, they will be asked if they want to automatically enable these APIs for their project. This is typically only necessary for non-Firebase Google APIs, and isn't needed for this guide.

6. Define user-configurable parameters

The function you created in the last two steps watched a specific RTDB location for incoming messages. Sometimes, watching a specific location really is what you want, such as when your extension operates on a database structure that you use exclusively for your extension. However, most of the time, you will want to make these values configurable by users who install your extension in their projects. This way, users can make use of your extension to work with their existing database setup.

Make the path that the extension watches for new messages user-configurable:

  1. In the extension.yaml file, add a params section:

    - 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
    

    This defines a new string parameter that users will be prompted to set when they install your extension.

  2. Still in the extension.yaml file, go back to your makeuppercase declaration and change the resource field to the following:

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

    The ${param:MESSAGE_PATH} token is a reference to the parameter you just defined. When your extension runs, this token will be replaced by whatever value the user configured for that parameter, with the result that the makeuppercase function will listen to the path the user specified. You can use this syntax to reference any user-defined parameter anywhere in extension.yaml (and in POSTINSTALL.md —more on that later).

  3. You can also access user-defined parameters from your functions code.

    In the function you wrote in the last section, you hard-coded the path to watch for changes. Change the trigger definition to reference the user-defined value instead:

    functions/index.js

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

    Note that in Firebase Extensions, this change is purely for the sake of documentation: when a Cloud Function is deployed as part of an extension, it uses the trigger definition from the extension.yaml file and ignores the value specified in the function definition. Nevertheless, it's a good idea to document in your code where this value comes from.

  4. You might find it disappointing to make a code change that has no runtime effect, but the important lesson to take away is that you can access any user-defined parameter in your function code and use it as an ordinary value in the function's logic. As a nod to this capability, add the following log statement to demonstrate that you are indeed accessing the value that the user defined:

    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. Normally, users are prompted to provide values for parameters when they install an extension. When you use the emulator for testing and development, however, you skip the installation process, so you instead provide values for user-defined parameters using an env file.

    Open functions/integration-tests/extensions/rtdb-uppercase-messages.env and replace the GREETING definition with the following:

    MESSAGE_PATH=/msgs/{pushId}/original
    

    Notice that the path above is different from the default path and from the path you defined previously; this is just to prove to yourself when you try your updated extension that your definition is taking effect.

  6. Now, restart the emulator and once again visit the database emulator UI.

    Edit the root node of the database, using the path you defined above:

    • Field: msgs
    • Type: json
    • Value: {"11": {"original": "recipe"}}

    When you save your database changes, the extension's makeuppercase function should trigger as it did before, but now it should also print the user-defined parameter to the console log.

7. Provide event hooks for user-defined logic

You've already seen, as an extension author, how a Firebase product can trigger your extension-provided logic: the creation of new records in Realtime Database triggers your makeuppercase function. Your extension can have an analogous relationship with the users who install your extension: your extension can trigger logic that the user defines.

An extension can provide synchronous hooks , asynchronous hooks , or both. Synchronous hooks give users a way to perform tasks that block the completion of one of the extension's functions. This can be useful, for example, to give users a way to perform custom preprocessing before an extension does its work.

In this guide, you'll add an asynchronous hook to your extension, which will enable users to define their own processing steps to be run after your extension writes the uppercase message to Realtime Database. Asynchronous hooks use Eventarc to trigger user-defined functions. Extensions declare the types of events they emit, and when users install the extension, they choose which event types they're interested in. If they choose at least one event, Firebase will provision an Eventarc channel for the extension as part of the installation process. Users can then deploy their own cloud functions that listen on that channel and trigger when the extension publishes new events.

Follow these steps to add an asynchronous hook:

  1. In the extension.yaml file, add the following section, which declares the one event type the extension emits:

    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.
    

    Event types must be universally unique; to ensure uniqueness, always name your events using the following format: <publisher-id>.<extension-id>.<version>.<description> . (You don't have a publisher ID yet, so just use test-publisher for now.)

  2. At the end of the makeuppercase function, add some code that publishes an event of the type you just declared:

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

    This example code takes advantage of the fact that the EVENTARC_CHANNEL environment variable is defined only when the user enabled at least one event type. if EVENTARC_CHANNEL is not defined, the code does not attempt to publish any events.

    You can attach extra information to an Eventarc event. In the example above, the event has a subject field that contains a reference to the newly-created value, and a data payload that contains the original and uppercase messages. User-defined functions that trigger off the event can make use of this information.

  3. Normally, the EVENTARC_CHANNEL and EXT_SELECTED_EVENTS environment variables are defined based on the options the user selected during installation. For testing with the emulator, manually define these variables in the rtdb-uppercase-messages.env file:

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

At this point, you have completed the steps needed to add an asynchronous event hook to your extension.

To try out this new feature that you have just implemented, in the next few steps, assume the role of a user who is installing the extension:

  1. From the functions/integration-tests directory, initialize a new Firebase project:

    firebase init functions

    When prompted, decline to set up a default project, select JavaScript as the Cloud Functions language, and install the required dependencies. This project represents a user's project, which has your extension installed.

  2. Edit integration-tests/functions/index.js and paste the following code:

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

    This is an example of a post-processing function a user might write. In this case, the function listens for the extension to publish a complete event, and when triggered, adds three exclamation points to the newly-uppercased message.

  3. Restart the emulator. The emulator will load the extension's functions as well as the post-processing function the "user" defined.

  4. Visit the database emulator UI and edit the root node of the database, using the path you defined above:

    • Field: msgs
    • Type: json
    • Value: {"11": {"original": "recipe"}}

    When you save your database changes, the extension's makeuppercase function and the user's extraemphasis function should trigger in sequence, resulting in the upper field getting the value RECIPE!!! .

8. Add lifecycle event handlers

The extension you've written so far processes messages as they are created. But what if your users already have a database of messages when they install the extension? Firebase Extensions has a feature called lifecycle event hooks that you can use to trigger actions when your extension gets installed, updated, or reconfigured. In this section, you will use lifecycle event hooks to backfill a project's existing message database with uppercased messages when a user installs your extension.

Firebase Extensions uses Cloud Tasks to run your lifecycle event handlers. You define event handlers using Cloud Functions; whenever an instance of your extension reaches one of the supported lifecycle events, if you have defined a handler, it will add the handler to a Cloud Tasks queue. Cloud Tasks will then asynchronously execute the handler. While a lifecycle event handler is running, the Firebase console will report to the user that the extension instance has a processing task in progress. It's up to your handler function to report ongoing status and task completion back to the user.

To add a lifecycle event handler that backfills existing messages, do the following:

  1. Define a new Cloud Function that's triggered by task queue events:

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

    Notice that the function only processes a few records before adding itself back to the task queue. This is a commonly used strategy to deal with processing tasks that cannot complete within the timeout window of a Cloud Function. Since you can't predict how many messages a user might already have in their database when they install your extension, this strategy is a good fit.

  2. In the extension.yaml file, declare your backfill function as an extension resource that has the taskQueueTrigger property:

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

    Then declare the function as the handler for the onInstall lifecycle event:

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Although backfilling existing messages is nice to have, the extension could still function without it. In situations like this, you should make running the lifecycle event handlers optional.

    To do so, add a new parameter to 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
    

    Then at the beginning of the backfill function, check the value of the DO_BACKFILL parameter and exit early if it's not set:

    functions/index.js

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

With the above changes, the extension will now convert existing messages to uppercase when it is installed.

Up to this point, you used the extension emulator to develop your extension and test ongoing changes. However, the extension emulator skips the installation process, so to test your onInstall event handler, you'll need to install the extension in a real project. That's just as well though, since with the addition of this automatic backfill feature, the tutorial extension is now code-complete!

9. Deploy into a real Firebase project

Although the extensions emulator is a great tool for rapidly iterating on an extension during development, at some point you'll want to try it in a real project.

To do so, first set up a new project with some services enabled:

  1. In the Firebase console , add a new project.
  2. Upgrade your project to the pay-as-you-go Blaze plan. Cloud Functions for Firebase requires your project to have a billing account, so you also need a billing account to install an extension.
  3. In your new project, enable Real-time Database .
  4. Since you want to test your extension's ability to backfill existing data on installation, import some sample data into your real-time database instance:
    1. Download some seed RTDB data .
    2. On the Real-time Database page of the Firebase console, click (more) > Import JSON and select the file you just downloaded.
  5. To enable the backfill function to use the orderByChild method, configure the database to index messages on the value of upper :

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

Now install your extension from local source into the new project:

  1. Create a new directory for your Firebase project:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Initialize a Firebase project in the working directory:

    firebase init database

    When prompted, select the project you just created.

  3. Install the extension into your local Firebase project:

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

    Here you can see what the user experience is like when installing an extension using the Firebase CLI tool. Be sure to select "yes" when the configuration tool asks if you want to backfill your existing database.

    After you select configuration options, the Firebase CLI will save your configuration in the extensions directory and record the extension source location in the firebase.json file. Collectively, these two records are called the extensions manifest . Users can use the manifest to save their extensions configuration and deploy it to different projects.

  4. Deploy your extension configuration to your live project:

    firebase deploy --only extensions

If all goes well, the Firebase CLI should upload your extension to your project and install it. After installation completes, the backfill task will run and, in a few minutes, your database will be updated with uppercase messages. Add some new nodes to the messages database and make sure the extension is also working for new messages.

10. Write documentation

Before you share your extension with users, make sure you're providing enough documentation for them to be successful.

When you initialized the extension project, the Firebase CLI created stub versions of the minimum required documentation. Update these files to accurately reflect the extension you've built.

extension.yaml

You've already been updating this file as you've developed this extension, so you don't need to make any more updates right now.

However, don't overlook the importance of the documentation contained in this file. In addition to an extension's crucial identifying information—name, description, author, official repository location—the extension.yaml file contains user-facing documentation for every resource and user-configurable parameter. This information is surfaced to users in the Firebase console, Extensions Hub, and Firebase CLI.

PREINSTALL.md

In this file, provide information the user needs before they install your extension: briefly describe what the extension does, explain any prerequisites, and give the user information on the billing implications of installing the extension. If you have a website with additional information, this is also a good place to link it.

The text of this file is displayed to the user in Extensions Hub and by the firebase ext:info command.

Here is an example of a PREINSTALL file:

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

This file contains information useful for users after they have successfully installed your extension: for example, follow-up setup steps, an example of the extension in action, and so on.

The contents of POSTINSTALL.md are displayed in the Firebase console after an extension is configured and installed. You can reference user parameters in this file and they will be replaced by the configured values.

Here is an example post-install file for the tutorial extension:

### 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

You should also document the changes you make between releases of an extension in the CHANGELOG.md file.

Since the example extension has never been published before, the change log has only one entry:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

Most extensions also provide a readme file for the benefit of users visiting the extension's repository. you can write this file by hand or generate a read me using the command.

For the purpose of this guide, skip writing a readme file.

اسناد اضافی

The documentation discussed above is the minimum set of documentation you should provide users. Many extensions require more detailed documentation for users to successfully use them. When this is the case, you should write additional documentation and host it somewhere you can point users to.

For the purpose of this guide, skip writing more extensive documentation.

11. Publish on Extensions Hub

Now that your extension is code complete and documented, you are ready to share it with the world on Extensions Hub. But since this is just a tutorial, don't actually do that. Go and start writing your own extension using what you've learned here and in the rest of the Firebase Extensions publisher documentation, and by examining the source of the official, Firebase-written, extensions.

When you're ready to publish your work on Extensions Hub here's how you will do it:

  1. If you are publishing your first extension, register as an extension publisher . When you register as an extensions publisher, you create a publisher ID that lets users quickly identify you as the author of your extensions.
  2. Host your extension's source code in a publicly verifiable location. When your code is available from a verifiable source, Firebase can publish your extension directly from this location. Doing so helps ensure that you're publishing the currently released version of your extension, and helps users by letting them examine the code they're installing into their projects.

    Currently, this means making your extension available in a public GitHub repository.

  3. Upload your extension to Extensions Hub using the firebase ext:dev:upload command.

  4. Go to your publisher dashboard in the Firebase console, find the extension you just uploaded, and click "Publish to Extensions Hub". This requests a review from our review staff, which can take a few days. If approved, the extension will be published to Extensions Hub. If rejected, you'll get a message explaining the reason; you can then address the reported issues and resubmit for review.