Триггеры Realtime Database (первое поколение)

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

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

  1. Ожидает изменений в определенном местоположении Realtime Database.
  2. Срабатывает при возникновении события и выполняет свои задачи (см. раздел Что можно делать с помощью Cloud Functions?). Примеры использования приведены в разделе
  3. Получает объект данных, содержащий снимок данных, хранящихся в указанном документе.

Как настроить триггер функции Realtime Database

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

Настройте обработчик событий

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

  • onWrite(), который срабатывает, когда данные создаются, обновляются или удаляются в Realtime Database.
  • onCreate(), который срабатывает при создании новых данных в Realtime Database.
  • onUpdate(), который срабатывает при обновлении данных в Realtime Database .
  • onDelete(), который срабатывает при удалении данных из Realtime Database .

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

Чтобы указать, когда и где должна запускаться функция, вызовите ref(path) и укажите путь. При необходимости вы можете указать экземпляр Realtime Database с помощью instance('INSTANCE_NAME'). Если вы не укажете экземпляр, функция будет развернута в экземпляре Realtime Database по умолчанию для проекта Firebase. Пример:

  • Экземпляр Realtime Database по умолчанию: functions.database.ref('/foo/bar')
  • Экземпляр с названием my-app-db-2: functions.database.instance('my-app-db-2').ref('/foo/bar')

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

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

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

Вы можете указать компонент пути в качестве подстановочного знака, заключив его в фигурные скобки. ref('foo/{bar}') соответствует любому дочернему элементу /foo. Значения этих компонентов пути подстановочных знаков доступны в объекте EventContext.params вашей функции. В нашем примере значение доступно как context.params.bar.

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

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

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

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

При обработке события Realtime Database возвращается объект данных DataSnapshot. Для событий onWrite или onUpdate первый параметр – это объект Change, который содержит два снимка, представляющие состояние данных до и после события, вызвавшего срабатывание. Для событий onCreate и onDelete возвращаемый объект данных представляет собой снимок созданных или удаленных данных.

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

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.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 snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Доступ к информации об аутентификации пользователей

Из EventContext.auth и EventContext.authType можно получить доступ к информации о пользователе, который активировал функцию, включая разрешения. Это может быть полезно для обеспечения соблюдения правил безопасности, позволяя вашей функции выполнять различные операции в зависимости от уровня разрешений пользователя:

const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

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

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

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

У объекта Change есть свойство before, которое позволяет проверить, что было сохранено в Realtime Database до события. Свойство before возвращает DataSnapshot, в котором все методы (например, val() и exists()) ссылаются на предыдущее значение. Вы можете снова прочитать новое значение, используя исходный DataSnapshot или прочитав свойство after. Это свойство любого объекта Change представляет собой другой объект DataSnapshot, который отражает состояние данных после события.

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

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.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 change.after.ref.parent.child('uppercase').set(uppercase);
    });