Начните создавать расширение

На этой странице описаны шаги, необходимые для создания простого расширения Firebase, которое вы можете установить в свои проекты или поделиться с другими. Этот простой пример расширения Firebase будет отслеживать сообщения в вашей базе данных реального времени и преобразовывать их в верхний регистр.

1. Настройте среду и инициализируйте проект.

Прежде чем вы сможете приступить к созданию расширения, вам необходимо настроить среду сборки с необходимыми инструментами.

  1. Установите Node.js 16 или новее. Один из способов установки Node — использование nvm (или nvm-windows ).

  2. Установите или обновите Firebase CLI до последней версии. Чтобы установить или обновить с помощью npm , выполните следующую команду:

    npm install -g firebase-tools

Теперь используйте Firebase CLI для инициализации нового проекта расширения:

  1. Создайте каталог для вашего расширения и cd в него:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Запустите команду ext:dev:init Firebase CLI:

    firebase ext:dev:init

    При появлении запроса выберите JavaScript в качестве языка для функций (но учтите, что вы также можете использовать TypeScript при разработке собственного расширения) и на вопрос об установке зависимостей ответьте «да». (Для любых других параметров примите значения по умолчанию.) Эта команда создаст скелетную кодовую базу для нового расширения, на основе которой вы сможете начать разработку своего расширения.

2. Попробуйте пример расширения с помощью эмулятора.

Когда интерфейс командной строки Firebase инициализировал новый каталог расширений, он создал простой пример функции и каталог integration-tests , содержащий файлы, необходимые для запуска расширения с помощью набора эмуляторов Firebase.

Попробуйте запустить пример расширения в эмуляторе:

  1. Перейдите в каталог integration-tests :

    cd functions/integration-tests
  2. Запустите эмулятор с демо-проектом:

    firebase emulators:start --project=demo-test

    Эмулятор загружает расширение в предопределённый «пустой» проект ( demo-test ). На данный момент расширение состоит из единственной функции, которая активируется по протоколу HTTP, greetTheWorld , которая при доступе возвращает сообщение «Hello World».

  3. Пока эмулятор все еще работает, попробуйте функцию greetTheWorld расширения, посетив URL-адрес, который он напечатал при запуске.

    Ваш браузер отображает сообщение «Hello World от Welcome-the-World».

  4. Исходный код этой функции находится в каталоге functions расширения. Откройте исходный код в редакторе или IDE по вашему выбору:

    функции/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. Добавьте основную информацию в файл 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. Напишите облачную функцию и объявите ее как ресурс расширения.

Теперь вы можете приступить к написанию кода. На этом этапе вы напишете облачную функцию, которая будет выполнять основную задачу вашего расширения — отслеживать сообщения в базе данных реального времени и преобразовывать их в верхний регистр.

  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. Новая функция запускается событиями базы данных в реальном времени: она отслеживает новые элементы по определенному пути и, когда они обнаруживаются, записывает версию значения в верхнем регистре обратно в базу данных.

    Кстати, в этом новом файле используется синтаксис модуля 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. Поскольку ваше расширение теперь использует базу данных реального времени в качестве триггера, вам необходимо обновить конфигурацию эмулятора, чтобы запускать эмулятор 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 расширения и добавить в сообщение 11 дочернюю запись с содержимым "upper": "RECIPE" . Просмотрите журналы и вкладки базы данных пользовательского интерфейса эмулятора, чтобы подтвердить ожидаемые результаты.

    3. Попробуйте добавить еще несколько дочерних элементов в узел messages ( {"original":"any text"} ). Всякий раз, когда вы добавляете новую запись, расширение должно добавлять поле uppercase , содержащее содержимое original поля в верхнем регистре.

Теперь у вас есть законченное, хотя и простое расширение, работающее с экземпляром RTDB. В следующих разделах вы улучшите это расширение, добавив некоторые дополнительные функции. Затем вы получите расширение, готовое к распространению среди других, и, наконец, узнаете, как опубликовать свое расширение в Extensions Hub.

5. Объявите API и роли

Firebase предоставляет каждому экземпляру установленного расширения ограниченный доступ к проекту и его данным, используя учетную запись службы для каждого экземпляра. Каждая учетная запись имеет минимальный набор разрешений, необходимых для работы. По этой причине вы должны явно объявить все роли IAM, которые требуются вашему расширению; когда пользователи устанавливают ваше расширение, Firebase создает учетную запись службы с предоставленными этими ролями и использует ее для запуска расширения.

Вам не нужно объявлять роли, чтобы инициировать события продукта, но вам нужно объявить роль, чтобы иным образом взаимодействовать с ним. Поскольку функция, которую вы добавили на последнем шаге, записывает данные в базу данных реального времени, вам необходимо добавить следующее объявление в 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. Вы также можете получить доступ к пользовательским параметрам из кода вашей функции.

    В функции, которую вы написали в последнем разделе, вы жестко запрограммировали путь для отслеживания изменений. Измените определение триггера, чтобы вместо этого оно ссылалось на определяемое пользователем значение:

    функции/index.js

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

    Обратите внимание, что в расширениях Firebase это изменение сделано исключительно ради документации: когда облачная функция развертывается как часть расширения, она использует определение триггера из файла extension.yaml и игнорирует значение, указанное в определении функции. Тем не менее, рекомендуется задокументировать в коде, откуда берется это значение.

  4. Возможно, вас разочарует внесение изменений в код, не оказывающих влияния на время выполнения, но важный урок, который следует усвоить, заключается в том, что вы можете получить доступ к любому определяемому пользователем параметру в коде функции и использовать его как обычное значение в логике функции. В качестве намека на эту возможность добавьте следующий оператор журнала, чтобы продемонстрировать, что вы действительно получаете доступ к значению, определенному пользователем:

    функции/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 может активировать вашу логику, предоставляемую расширением: создание новых записей в базе данных реального времени запускает вашу функцию 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 добавьте код, который публикует событие только что объявленного вами типа:

    функции/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 в качестве языка облачных функций и установите необходимые зависимости. Этот проект представляет собой проект пользователя , в котором установлено ваше расширение.

  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 есть функция, называемая перехватчиками событий жизненного цикла , которую вы можете использовать для запуска действий при установке, обновлении или перенастройке вашего расширения. В этом разделе вы будете использовать перехватчики событий жизненного цикла для заполнения существующей базы данных сообщений проекта сообщениями в верхнем регистре, когда пользователь устанавливает ваше расширение.

Расширения Firebase используют облачные задачи для запуска обработчиков событий жизненного цикла. Вы определяете обработчики событий с помощью облачных функций; всякий раз, когда экземпляр вашего расширения достигает одного из поддерживаемых событий жизненного цикла, если вы определили обработчик, он добавляет обработчик в очередь облачных задач. Затем Cloud Tasks асинхронно выполнит обработчик. Пока работает обработчик событий жизненного цикла, консоль 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 объявите функцию обратного заполнения как ресурс расширения, имеющий свойство taskQueueTrigger :

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

    Затем объявите функцию как обработчик события жизненного цикла onInstall :

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Хотя заполнение существующих сообщений полезно, расширение все равно может работать и без него. В подобных ситуациях вам следует сделать запуск обработчиков событий жизненного цикла необязательным.

    Для этого добавьте новый параметр в extension.yaml :

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

    Затем в начале функции обратного заполнения проверьте значение параметра DO_BACKFILL и выйдите раньше, если он не установлен:

    функции/index.js

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

Благодаря вышеуказанным изменениям расширение теперь будет преобразовывать существующие сообщения в верхний регистр при установке.

До этого момента вы использовали эмулятор расширения для разработки расширения и тестирования текущих изменений. Однако эмулятор расширения пропускает процесс установки, поэтому для проверки обработчика событий onInstall вам необходимо установить расширение в реальном проекте. И это даже хорошо, поскольку с добавлением этой функции автоматического заполнения расширение учебного пособия теперь полностью готово к написанию кода!

9. Развертывание в реальном проекте Firebase

Хотя эмулятор расширений — отличный инструмент для быстрого изменения расширения во время разработки, в какой-то момент вам захочется опробовать его в реальном проекте.

Для этого сначала настройте новый проект с включенными некоторыми службами:

  1. В консоли Firebase добавьте новый проект.
  2. Обновите свой проект до плана Blaze с оплатой по мере использования. Cloud Functions for Firebase требует, чтобы в вашем проекте была учетная запись для выставления счетов, поэтому для установки расширения вам также понадобится учетная запись для выставления счетов.
  3. В вашем новом проекте включите базу данных реального времени .
  4. Поскольку вы хотите проверить способность вашего расширения заполнять существующие данные при установке, импортируйте некоторые образцы данных в экземпляр базы данных реального времени:
    1. Загрузите некоторые начальные данные RTDB .
    2. На странице базы данных реального времени консоли Firebase нажмите (подробнее) > Импортировать JSON и выберите файл, который вы только что скачали.
  5. Чтобы функция обратного заполнения могла использовать метод orderByChild , настройте базу данных для индексации сообщений по значению upper :

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

Теперь установите расширение из локального источника в новый проект:

  1. Создайте новый каталог для вашего проекта Firebase:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Инициализируйте проект Firebase в рабочем каталоге:

    firebase init database

    При появлении запроса выберите только что созданный проект.

  3. Установите расширение в свой локальный проект Firebase:

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

    Здесь вы можете увидеть, как работает пользователь при установке расширения с помощью инструмента Firebase CLI. Обязательно выберите «да», когда инструмент настройки спросит, хотите ли вы заполнить существующую базу данных.

    После того, как вы выберете параметры конфигурации, интерфейс командной строки Firebase сохранит вашу конфигурацию в каталоге extensions и запишет расположение источника расширения в файле firebase.json . В совокупности эти две записи называются манифестом расширений . Пользователи могут использовать манифест, чтобы сохранить конфигурацию своих расширений и развернуть ее в разных проектах.

  4. Разверните конфигурацию расширения в работающем проекте:

    firebase deploy --only extensions

Если все пойдет хорошо, Firebase CLI должен загрузить ваше расширение в ваш проект и установить его. После завершения установки запустится задача обратной засыпки, и через несколько минут ваша база данных будет обновлена ​​сообщениями в верхнем регистре. Добавьте несколько новых узлов в базу данных сообщений и убедитесь, что расширение также работает для новых сообщений.

10. Напишите документацию

Прежде чем поделиться своим расширением с пользователями, убедитесь, что вы предоставляете достаточно документации, чтобы они могли добиться успеха.

Когда вы инициализировали проект расширения, Firebase CLI создал незавершенные версии минимально необходимой документации. Обновите эти файлы, чтобы они точно отражали созданное вами расширение.

расширение.yaml

Вы уже обновляли этот файл при разработке этого расширения, поэтому вам не нужно делать какие-либо обновления прямо сейчас.

Однако не забывайте о важности документации, содержащейся в этом файле. В дополнение к важной идентификационной информации расширения — имени, описанию, автору, официальному расположению репозитория — файл extension.yaml содержит пользовательскую документацию для каждого ресурса и настраиваемого пользователем параметра. Эта информация отображается пользователям в консоли Firebase, Extensions Hub и Firebase CLI.

PREINSTALL.md

В этом файле предоставьте информацию, необходимую пользователю перед установкой вашего расширения: кратко опишите, что делает расширение, объясните все предварительные требования и предоставьте пользователю информацию о последствиях установки расширения для выставления счетов. Если у вас есть веб-сайт с дополнительной информацией, это также хорошее место для ссылки на него.

Текст этого файла отображается пользователю в Центре расширений и с помощью команды firebase ext:info .

Вот пример файла PREINSTALL:

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

This extension expects a database layout like the following example:

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

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

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

#### Additional setup

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

#### Billing

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

- This extension uses other Firebase and Google Cloud Platform services, which
  have associated charges if you exceed the service's no-cost tier:
  - Realtime Database
  - Cloud Functions (Node.js 10+ runtime)
    [See FAQs](https://firebase.google.com/support/faq#extensions-pricing)
- If you enable events,
  [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

POSTINSTALL.md

Этот файл содержит информацию, полезную для пользователей после успешной установки вашего расширения: например, последующие шаги по настройке, пример расширения в действии и т. д.

Содержимое POSTINSTALL.md отображается в консоли Firebase после настройки и установки расширения. Вы можете ссылаться на пользовательские параметры в этом файле, и они будут заменены настроенными значениями.

Вот пример файла после установки учебного расширения:

### See it in action

You can test out this extension right away!

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

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

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

### Using the extension

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

### Monitoring

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

CHANGELOG.md

Вам также следует документировать изменения, вносимые вами между выпусками расширения, в файле CHANGELOG.md .

Поскольку пример расширения никогда ранее не публиковался, в журнале изменений имеется только одна запись:

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

Большинство расширений также предоставляют файл readme для пользователей, посещающих репозиторий расширения. вы можете написать этот файл вручную или создать файл read me с помощью команды.

Для целей данного руководства пропустите запись файла readme.

Дополнительная документация

Документация, описанная выше, представляет собой минимальный набор документации, которую вы должны предоставить пользователям. Многие расширения требуют более подробной документации, чтобы пользователи могли их успешно использовать. В этом случае вам следует написать дополнительную документацию и разместить ее там, где вы сможете указать пользователям.

Для целей данного руководства не нужно писать более обширную документацию.

11. Публикация в Центре расширений

Теперь, когда код вашего расширения готов и документирован, вы готовы поделиться им со всем миром в Extensions Hub. Но поскольку это всего лишь руководство, не делайте этого. Идите и начните писать свое собственное расширение, используя то, что вы узнали здесь и в остальной документации издателя Firebase Extensions, а также изучив источник официальных расширений, написанных Firebase.

Когда вы будете готовы опубликовать свою работу в Extensions Hub, сделайте это следующим образом:

  1. Если вы публикуете свое первое расширение, зарегистрируйтесь в качестве издателя расширения . Когда вы регистрируетесь в качестве издателя расширений, вы создаете идентификатор издателя, который позволяет пользователям быстро идентифицировать вас как автора ваших расширений.
  2. Разместите исходный код вашего расширения в общедоступном месте. Если ваш код доступен из проверенного источника, Firebase может опубликовать ваше расширение непосредственно из этого места. Это поможет гарантировать, что вы публикуете текущую выпущенную версию вашего расширения, а также поможет пользователям, позволяя им изучить код, который они устанавливают в свои проекты.

    В настоящее время это означает, что ваше расширение будет доступно в общедоступном репозитории GitHub.

  3. Загрузите свое расширение в Extensions Hub с помощью команды firebase ext:dev:upload .

  4. Перейдите на панель издателя в консоли Firebase, найдите только что загруженное расширение и нажмите «Опубликовать в Extension Hub». Для этого нашим специалистам по проверке требуется проверка, которая может занять несколько дней. В случае одобрения расширение будет опубликовано в Extensions Hub. В случае отклонения вы получите сообщение с объяснением причины; после этого вы сможете устранить обнаруженные проблемы и повторно отправить их на рассмотрение.

,

На этой странице описаны шаги, необходимые для создания простого расширения Firebase, которое вы можете установить в свои проекты или поделиться с другими. Этот простой пример расширения Firebase будет отслеживать сообщения в вашей базе данных реального времени и преобразовывать их в верхний регистр.

1. Настройте среду и инициализируйте проект.

Прежде чем вы сможете приступить к созданию расширения, вам необходимо настроить среду сборки с необходимыми инструментами.

  1. Установите Node.js 16 или новее. Один из способов установки Node — использование nvm (или nvm-windows ).

  2. Установите или обновите Firebase CLI до последней версии. Чтобы установить или обновить с помощью npm , выполните следующую команду:

    npm install -g firebase-tools

Теперь используйте Firebase CLI для инициализации нового проекта расширения:

  1. Создайте каталог для вашего расширения и cd в него:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Запустите команду ext:dev:init Firebase CLI:

    firebase ext:dev:init

    При появлении запроса выберите JavaScript в качестве языка для функций (но учтите, что вы также можете использовать TypeScript при разработке собственного расширения) и на вопрос об установке зависимостей ответьте «да». (Примите значения по умолчанию для любых других параметров.) Эта команда создаст скелетную кодовую базу для нового расширения, на основе которой вы сможете начать разработку своего расширения.

2. Попробуйте пример расширения, используя эмулятор

Когда CLI Firebase инициализировал новый каталог расширений, он создал простую функцию примера и каталог integration-tests , который содержит файлы, необходимые для запуска расширения, используя набор эмуляторов Firebase.

Попробуйте запустить пример расширения в эмуляторе:

  1. Изменение в каталоге integration-tests :

    cd functions/integration-tests
  2. Начните эмулятор с демонстрационного проекта:

    firebase emulators:start --project=demo-test

    Эмулятор загружает расширение в предопределенный проект «Dummy» ( demo-test ). Расширение до сих пор состоит из одной функции, вызванной HTTP, greetTheWorld , которая возвращает сообщение «Hello World» при обращении.

  3. При все еще работающем эмуляторе, попробуйте функцию Extension greetTheWorld , посетив URL, который он напечатал, когда вы его запустили.

    Ваш браузер отображает сообщение «Hello World от Greet-The World».

  4. Исходный код для этой функции находится в каталоге functions расширения. Откройте источник в редакторе или ide по вашему выбору:

    функции/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. Добавьте базовую информацию в Extension.yaml

Теперь, когда у вас есть среда разработки, и вы запускаете эмулятор расширений, вы можете начать писать собственное расширение.

В качестве скромного первого шага отредактируйте предопределенные метаданные расширения, чтобы отразить расширение, которое вы хотите написать вместо greet-the-world . Эти метаданные хранятся в файле extension.yaml .

  1. Open 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, на котором работает расширение, с последующим описанием того, что делает расширение. Вы должны использовать ту же конвенцию в своих собственных расширениях.

  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. Новая функция запускается событиями базы данных в реальном времени: она наблюдает за новыми элементами на определенном пути, и, когда кто-то обнаружен, она записывает версию значения в верхней передаче.

    Кстати, в этом новом файле используется синтаксис модуля ECMASCRIPT ( import и export ) вместо CommonJS ( require ). Чтобы использовать модули ES в узле, укажите "type": "module" в functions/package.json :

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      
    }
    
  2. Каждая функция в вашем расширении должна быть объявлена ​​в файле extension.yaml . Пример расширения объявил greetTheWorld единственной облачной функцией расширения; Теперь, когда вы заменили его на makeuppercase , вам также необходимо обновить его объявление.

    Open 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. Поскольку ваше расширение теперь использует базу данных в реальном времени в качестве триггера, вам необходимо обновить конфигурацию эмулятора для запуска эмулятора RTDB вместе с эмулятором Cloud Functions:

    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 расширения должна запустить и добавить запись дочернего сообщения в сообщение 11 с содержимым "upper": "RECIPE" . Посмотрите на журналы и вкладки базы данных пользовательского интерфейса эмулятора, чтобы подтвердить ожидаемые результаты.

    3. Попробуйте добавить еще немного детей в Node messages ( {"original":"any text"} ). Всякий раз, когда вы добавляете новую запись, расширение должно добавлять поле uppercase , содержащее содержимое прописного заглавного поля original поля.

Теперь у вас есть полное, хотя и простое расширение, которое работает на экземпляре RTDB. В последующих разделах вы усовершенствуете это расширение с помощью некоторых дополнительных функций. Затем вы получите продление, готовое распространять других и, наконец, научитесь опубликовать свое расширение в центре расширений.

5. объявить API и роли

Firebase предоставляет каждый экземпляр установленного расширения, ограниченный доступ к проекту, и его данные с использованием учетной записи службы в течение всего возраста. Каждая учетная запись имеет минимальный набор разрешений, необходимых для работы. По этой причине вы должны явно заявить о любых ролях IAM, которые требуются вашими расширением; Когда пользователи устанавливают ваше расширение, Firebase создает сервисную учетную запись с этими ролями и использует его для запуска расширения.

Вам не нужно объявлять роли, чтобы вызвать события продукта, но вам нужно объявить роль, чтобы иначе взаимодействовать с ним. Поскольку функция, которую вы добавили на последнем шаге, записывается в базу данных в реальном времени, вам необходимо добавить следующее объявление в 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. Вы также можете получить доступ к пользовательским параметрам из кода ваших функций.

    В функции, которую вы написали в последнем разделе, вы сильно кодировали путь, чтобы следить за изменениями. Вместо этого измените определение триггера, чтобы ссылаться на пользовательское значение:

    функции/index.js

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

    Обратите внимание, что в расширениях Firebase это изменение предназначено исключительно ради документации: когда облачная функция развернута как часть расширения, оно использует определение триггера из файла extension.yaml и игнорирует значение, указанное в определении функции. Тем не менее, это хорошая идея документировать в вашем коде, откуда поступает это значение.

  4. Возможно, вам разочаровывает изменение кода, которое не имеет эффекта времени выполнения, но важный урок, который следует убрать, заключается в том, что вы можете получить доступ к любому пользовательскому параметру в коде функции и использовать его в качестве обычного значения в логике функции. В качестве наклона к этой возможности добавьте следующую операцию журнала, чтобы продемонстрировать, что вы действительно получаете доступ к значению, которое определил пользователь:

    функции/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 может запустить вашу предоставленную расширению логику: создание новых записей в базе данных в реальном времени вызывает функцию 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 добавьте немного кода, который публикует событие только что заявленного вами типа:

    функции/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 в качестве языка облачных функций и установите необходимые зависимости. Этот проект представляет собой проект пользователя , который установил ваше расширение.

  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. Добавить обработчики событий жизненного цикла

Расширение, которое вы написали до сих пор, обрабатывает сообщения по мере их создания. Но что, если у ваших пользователей уже есть база данных сообщений, когда они устанавливают расширение? Extensions Firebase имеет функцию, называемую крючками для событий жизненного цикла , которую вы можете использовать для запуска действий, когда ваше расширение будет установлено, обновляется или перенастроен. В этом разделе вы будете использовать крючки для событий жизненного цикла, чтобы заполнить существующую базу данных сообщений проекта с помощью верхних сообщений, когда пользователь устанавливает ваше расширение.

Firebase Extensions использует облачные задачи для запуска обработчиков событий жизненного цикла. Вы определяете обработчики событий, используя облачные функции; Всякий раз, когда экземпляр вашего расширения достигает одного из поддерживаемых событий жизненного цикла, если вы определили обработчик, он добавит обработчика в очередь задач. Облачные задачи будут асинхронно выполнять обработчик. В то время как работает обработчик событий жизненного цикла, консоль 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 объявите свою функцию обратной засыпки как ресурс расширения, который имеет свойство taskQueueTrigger :

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

    Затем объявите функцию обработчиком для события жизненного цикла onInstall :

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. Несмотря на то, что засыпание существующих сообщений приятно иметь, расширение все еще может функционировать без него. В подобных ситуациях вы должны сделать возможность выполнять обработки обработчиков событий жизненного цикла.

    Для этого добавьте новый параметр в extension.yaml :

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

    Затем, в начале функции засыпки, проверьте значение параметра DO_BACKFILL и выйдите рано, если она не установлена:

    функции/index.js

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

С приведенными выше изменениями расширение теперь будет преобразовать существующие сообщения в верхний регистр при его установке.

До этого момента вы использовали эмулятор расширения для разработки вашего расширения и проверки текущих изменений. Тем не менее, эмулятор расширения пропускает процесс установки, поэтому, чтобы проверить ваш обработчик событий onInstall , вам нужно установить расширение в реальном проекте. Это так же хорошо, однако, поскольку с добавлением этой функции автоматической засыпки засыпания, расширение учебника теперь завершено кодом!

9. Развернуть в настоящий проект Firebase

Хотя эмулятор расширений является отличным инструментом для быстрого итерации при расширении во время разработки, в какой -то момент вы захотите попробовать его в реальном проекте.

Для этого сначала настроите новый проект с некоторыми услугами, включенными:

  1. В консоли Firebase добавьте новый проект.
  2. Обновите свой проект до плана платить за платить. Облачные функции для Firebase требуют, чтобы ваш проект имел счет для выставления счетов, поэтому вам также нужна биллинговая учетная запись для установки расширения.
  3. В вашем новом проекте включите базу данных в реальном времени .
  4. Поскольку вы хотите проверить способность вашего расширения засыпать существующие данные при установке, импортируйте некоторые образцы данных в экземпляр базы данных в реальном времени:
    1. Загрузите несколько данных SEED RTDB .
    2. На странице базы данных в реальном времени консоли Firebase нажмите (больше)> Импорт JSON и выберите только что загруженный файл.
  5. Чтобы позволить функции обратной засыпки использовать метод orderByChild , настройте базу данных для индексации сообщений на значение upper :

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

Теперь установите расширение из локального источника в новый проект:

  1. Создайте новый каталог для вашего проекта Firebase:

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. Инициализировать проект Firebase в рабочем каталоге:

    firebase init database

    При запросе выберите только что созданный проект.

  3. Установите расширение в свой местный проект Firebase:

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

    Здесь вы можете увидеть, на что похож пользовательский опыт при установке расширения, используя инструмент Firebase CLI. Обязательно выберите «Да», когда инструмент конфигурации спрашивает, хотите ли вы заполнить свою существующую базу данных.

    После выбора параметров конфигурации CLI Firebase сохранит вашу конфигурацию в каталоге extensions и записит местоположение источника расширения в файле firebase.json . В совокупности эти две записи называются « Расшиваниями» . Пользователи могут использовать манифест, чтобы сохранить конфигурацию своих расширений и развернуть ее в разных проектах.

  4. Разверните конфигурацию расширения в свой живой проект:

    firebase deploy --only extensions

Если все идет хорошо, CLI Firebase должен загрузить ваше расширение в ваш проект и установить его. После завершения установки задача обратной засыпки будет выполнена, и через несколько минут ваша база данных будет обновлена ​​с помощью прописных сообщений. Добавьте несколько новых узлов в базу данных сообщений и убедитесь, что расширение также работает для новых сообщений.

10. Напишите документацию

Прежде чем поделиться своим расширением с пользователями, убедитесь, что вы предоставляете достаточное количество документации, чтобы они были успешными.

Когда вы инициализировали проект расширения, CLI Firebase создал версии Stub минимальной необходимой документации. Обновите эти файлы, чтобы точно отразить расширение, которое вы создали.

Расширение.yaml

Вы уже обновляли этот файл по мере того, как вы разработали это расширение, поэтому вам не нужно делать больше обновлений прямо сейчас.

Однако не упускайте из виду важность документации, содержащейся в этом файле. В дополнение к важнейшей идентификационной информации расширения-названием, описание, автор, официальное местоположение репозитория-файл extension.yaml содержит документацию, связанную с пользователем для каждого ресурса и пользовательский параметр. Эта информация появляется для пользователей в консоли Firebase, центра Ascensions и Firebase CLI.

Preinstall.md

В этом файле предоставьте информацию, необходимую пользователю, прежде чем они установили ваше расширение: кратко опишите, что делает расширение, объясните любые предпосылки и предоставьте пользователю информацию об выставлении счетов по установке расширения. Если у вас есть веб -сайт с дополнительной информацией, это также хорошее место для его связи.

Текст этого файла отображается пользователю в центре расширений и по команде firebase ext:info .

Вот пример предварительного файла:

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. Публикуйте в центре расширений

Теперь, когда ваше расширение завершено и задокументировано, вы готовы поделиться им с миром в центре расширений. Но так как это просто учебник, на самом деле не делайте этого. Идите и начните писать свое собственное расширение, используя то, что вы узнали здесь, и в остальной части документации Firebase Extensions Publisher и изучив источник официального, написанного Firebase, расширений.

Когда вы будете готовы опубликовать свою работу на центре расширений, вот как вы это сделаете:

  1. Если вы публикуете свое первое расширение, зарегистрируйтесь в качестве издателя расширения . Когда вы регистрируетесь в качестве издателя расширений, вы создаете идентификатор издателя, который позволяет пользователям быстро идентифицировать вас как автора ваших расширений.
  2. Разместите исходный код расширения в общедоступном месте. Когда ваш код доступен из проверенного источника, Firebase может опубликовать ваше расширение непосредственно из этого места. Это помогает убедиться, что вы публикуете в настоящее время выпущенную версию вашего расширения, и помогает пользователям, позволяя им изучить код, который они устанавливают в свои проекты.

    В настоящее время это означает предоставление вашего расширения в репозитории публичного GitHub.

  3. Загрузите расширение в центр расширений, используя firebase ext:dev:upload команду.

  4. Перейдите на панель панели издателя в консоли Firebase, найдите только что загруженное расширение, и нажмите «Опубликовать в центр расширения». Это запрашивает обзор нашего персонала, который может занять несколько дней. В случае утверждения расширение будет опубликовано в центр расширения. Если вы отклоните, вы получите сообщение, объясняющее причину; Затем вы можете решить зарегистрированные проблемы и повторно рассмотреть.

,

Эта страница проводит вас через шаги, необходимые для создания простого расширения Firebase, которое вы можете установить в своих проектах или поделиться с другими. Этот простой пример расширения Firebase будет наблюдать за вашей базой данных в реальном времени для сообщений и конвертирует их в верхний регистр.

1. Настройте вашу среду и инициализируйте проект

Прежде чем вы сможете начать создавать расширение, вам нужно будет настроить среду сборки с помощью необходимых инструментов.

  1. Установите node.js 16 или новее. Одним из способов установки узла является использование NVM (или NVM-Windows ).

  2. Установите или обновите в последнюю версию CLI Firebase . Чтобы установить или обновить, используя npm , запустите эту команду:

    npm install -g firebase-tools

Теперь используйте CLI Firebase для инициализации нового проекта расширения:

  1. Создайте каталог для вашего расширения и cd в него:

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
  2. Запустите Firebase Cli's ext:dev:init :

    firebase ext:dev:init

    При запросе выберите JavaScript в качестве языка для функций (но обратите внимание, что вы также можете использовать TypeScript при разработке собственного расширения), и, когда его просят установить зависимости, ответьте «Да». (Примите по умолчанию любые другие параметры.) Эта команда настроит кодовую базу скелета для нового расширения, с которого вы можете начать разработку расширения.

2. Попробуйте пример расширения, используя эмулятор

Когда CLI Firebase инициализировал новый каталог расширений, он создал простую функцию примера и каталог integration-tests , который содержит файлы, необходимые для запуска расширения, используя набор эмуляторов Firebase.

Попробуйте запустить пример расширения в эмуляторе:

  1. Изменение в каталоге integration-tests :

    cd functions/integration-tests
  2. Начните эмулятор с демонстрационного проекта:

    firebase emulators:start --project=demo-test

    Эмулятор загружает расширение в предопределенный проект «Dummy» ( demo-test ). Расширение до сих пор состоит из одной функции, вызванной HTTP, greetTheWorld , которая возвращает сообщение «Hello World» при обращении.

  3. При все еще работающем эмуляторе, попробуйте функцию Extension greetTheWorld , посетив URL, который он напечатал, когда вы его запустили.

    Ваш браузер отображает сообщение «Hello World от Greet-The World».

  4. Исходный код для этой функции находится в каталоге functions расширения. Откройте источник в редакторе или ide по вашему выбору:

    функции/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}`;
    

    Сохраните свои изменения. 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. Replace its contents with the following:

    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. Replace its contents with the following:

    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.