Триггеры Realtime Database

С помощью Cloud Functions вы можете обрабатывать события в Firebase Realtime Database, не обновляя код клиента. Cloud Functions позволяет выполнять операции Realtime Database с полными правами администратора и гарантирует, что каждое изменение в Realtime Database обрабатывается отдельно. Вы можете внести Firebase Realtime Database изменений с помощью снимка данных или Admin SDK.

В типичном жизненном цикле функция Firebase Realtime Database выполняет следующие действия:

  1. Ожидает изменений в определенном пути Realtime Database.
  2. Срабатывает при возникновении события и выполняет свои задачи.
  3. Получает объект данных, содержащий снимок данных, хранящихся по этому пути.

Вы можете активировать функцию в ответ на запись, создание, обновление или удаление узлов базы данных в Firebase Realtime Database. Чтобы указать, когда должна срабатывать функция, выберите один из обработчиков событий и укажите путь к Realtime Database, по которому будут отслеживаться события.

Укажите, где должна использоваться функция.

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

Обработка событий Realtime Database

Функции позволяют обрабатывать события Realtime Database на двух уровнях детализации: вы можете прослушивать только события записи, создания, обновления или удаления или же любые изменения в ссылке.

Доступны следующие обработчики событий Realtime Database:

Node.js

  • onValueWritten() Срабатывает, когда данные создаются, обновляются или удаляются в Realtime Database.
  • onValueCreated() Срабатывает, только когда данные создаются в Realtime Database.
  • onValueUpdated() Срабатывает только при обновлении данных в Realtime Database.
  • onValueDeleted() Срабатывает, только когда данные удаляются в Realtime Database.

Python

  • on_value_written() Срабатывает, когда данные создаются, обновляются или удаляются в Realtime Database.
  • on_value_created() Срабатывает, только когда данные создаются в Realtime Database.
  • on_value_updated() Срабатывает только при обновлении данных в Realtime Database.
  • on_value_deleted() Срабатывает, только когда данные удаляются в Realtime Database.

Импортируйте необходимые модули

В исходный код функции необходимо импортировать модули SDK, которые вы хотите использовать. В этом примере необходимо импортировать модули HTTP и Realtime Database, а также модуль Firebase Admin SDK для записи в Realtime Database.

Node.js

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/https");
const {onValueCreated} = require("firebase-functions/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();

Python

# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

Укажите экземпляр и путь

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

Пример:

Node.js

// All Realtime Database instances at path "/user/{uid}"
// There must be at least one Realtime Database present.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
  },
  (event) => {
    // …
  }
);

Python

# All Realtime Database instances at path "/user/{uid}"
# There must be at least one Realtime Database present.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

Эти параметры указывают функции, как обрабатывать записи по определенному пути в экземпляре Realtime Database.

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

 /foo/bar
 /foo/bar/baz/really/deep/path

В обоих случаях Firebase интерпретирует событие как произошедшее в /foo/bar, а данные о событии включают старые и новые данные в /foo/bar. Если данных о событии может быть много, рекомендуем использовать несколько функций на более глубоких путях вместо одной функции рядом с корнем базы данных. Чтобы обеспечить оптимальную производительность, запрашивайте данные только на самом глубоком уровне.

Подстановочные знаки и захват

Для записи можно использовать {key}, {key=*}, {key=prefix*}, {key=*suffix}. *, prefix*, *suffix для подстановки в один сегмент. Примечание. ** – это подстановочный знак для нескольких сегментов, который не поддерживается в Realtime Database. Подробнее о шаблонах путей…

Подстановочные знаки в путях. В качестве компонента пути можно указать подстановочный знак:

  • Используйте звездочку, *. Например, foo/* соответствует всем дочерним элементам, которые находятся на один уровень ниже foo/ в иерархии узлов.
  • Используйте сегмент, содержащий только звездочку (*). Например, foo/app*-us соответствует любым дочерним сегментам ниже foo/ с префиксом app и суффиксом -us.

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

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

соответствует пути "/foo/*" дважды: один раз с "hello": "world" и ещё раз с "firebase": "functions".

Запись пути. Вы можете сохранять совпадения путей в именованных переменных, чтобы использовать их в коде функции (например, /user/{uid}, /user/{uid=*-us}).

Значения переменных захвата доступны в объекте database.DatabaseEvent.params вашей функции.

Подстановочные знаки для экземпляров. Вы также можете указать компонент экземпляра с помощью подстановочного знака. Подстановочный знак экземпляра может иметь префикс, суффикс или и то, и другое (например, my-app-*-prod).

Подстановочные знаки и захват

В Cloud Functions (второе поколение) и Realtime Database при указании ref и instance можно использовать шаблон. В каждом интерфейсе триггера будут следующие варианты области действия функции:

Указание ref Указание instance Алгоритм работы
Сингл (/foo/bar) Не указывать Обработчик областей действия для всех экземпляров в регионе функции.
Сингл (/foo/bar) Сингл (‘my-new-db') Обработчик областей действия для конкретного экземпляра в регионе функции.
Сингл (/foo/bar) Узор (‘inst-prefix*') Обработчик областей действия для всех экземпляров, соответствующих шаблону в регионе функции.
Узор (/foo/{bar}) Не указывать Обработчик областей действия для всех экземпляров в регионе функции.
Узор (/foo/{bar}) Сингл (‘my-new-db') Обработчик областей действия для конкретного экземпляра в регионе функции.
Узор (/foo/{bar}) Узор (‘inst-prefix*') Обработчик областей действия для всех экземпляров, соответствующих шаблону в регионе функции.

Обработка данных о событиях

При активации события Realtime Database в функцию обработчика передается объект Event. У этого объекта есть свойство data, которое для событий создания и удаления содержит снимок созданных или удаленных данных.

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

Node.js

// 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'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);

Python

@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

Прочитать предыдущее значение

Для событий write или update свойство data – это объект Change, который содержит два снимка, представляющих состояние данных до и после события-триггера. Объект Change имеет свойство before, которое позволяет проверить, что было сохранено в Realtime Database до события, и свойство after, которое представляет состояние данных после события.

Например, свойство before можно использовать, чтобы функция преобразовывала текст в верхний регистр только при его создании:

Node.js

  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.params.pushId, original);
        const uppercase = original.toUpperCase();
        // You must return a Promise when performing asynchronous tasks inside a Functions such as
        // writing to the Firebase Realtime Database.
        // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
        return event.data.after.ref.parent.child('uppercase').set(uppercase);
      });

Python

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

Контекст аутентификации доступа

Для функций, активируемых событиями Eventarc в RTDB, контекст аутентификации включается в полезную нагрузку события:

  • authtype – тип субъекта, который активировал событие. Возможные значения:
    • app_user – конечный пользователь приложения разработчика.
    • admin – сервисный аккаунт.
    • unauthenticated – неаутентифицированный пользователь.
    • unknown – значение по умолчанию, когда информация для аутентификации недоступна.
  • authid – уникальный идентификатор пользователя.
    • Если для параметра authtype задано значение app_user, это уникальный идентификатор пользователя.
    • Если authtype – admin, это адрес электронной почты сервисного аккаунта или пользователя IAM.

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

Node.js

// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onValueWritten} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();

exports.dbtrigger = onValueWritten("/messages/{pushId}/original", async (event) => {
  // 1. Check whether authtype is admin. If it is, skip this operation.
  if (event.authType === "admin") {
    logger.log("Modification by admin detected. Skipping uppercase conversion.");
    return null;
  }

  // 2. Retrieve the userID of the sender (assumed sibling node 'senderId')
  const snapshot = await event.data.after.ref.parent.child("senderId").get();
  const senderId = snapshot.val();

  // 3. Check if userID of sender of message = event.authid
  if (senderId !== event.authId) {
    logger.error(`Unauthorized write: senderId (${senderId}) does not match authId (${event.authId})`);
    return null;
  }

  // Grab the value that was written to the Realtime Database.
  const original = event.data.after.val();
  logger.log("Uppercasing", event.params.pushId, original);
  const uppercase = original.toUpperCase();

  // Return the promise to set the "uppercase" sibling node.
  return event.data.after.ref.parent.child("uppercase").set(uppercase);
});

Python

from firebase_functions import db_fn
from firebase_admin import initialize_app, db

initialize_app()

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[db_fn.Change]) -> None:
    # 1. Check whether authtype is admin. If it is, skip this operation.
    if event.auth_type == "admin":
        print("Admin user detected. Skipping.")
        return

    # 2. Retrieve the userID of the sender (assumed sibling node: 'senderId')
    parent_ref = db.reference(event.reference).parent
    sender_id = parent_ref.child("senderId").get()

    # 3. Check if userID of sender = event.auth_id
    if sender_id != event.auth_id:
        print(f"Unauthorized: sender_id {sender_id} != auth_id {event.auth_id}")
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value and uppercase it
    original = event.data.after
    if not isinstance(original, str):
        return

    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent_ref.child("uppercase").set(upper)