Ir a la consola

Activadores de Realtime Database

Con Cloud Functions, puedes administrar eventos en Firebase Realtime Database sin necesidad de actualizar el código de cliente. Cloud Functions te permite ejecutar operaciones de base de datos con privilegios administrativos completos y garantiza que cada cambio en la base de datos se procese de forma individual. Puedes hacer cambios en Firebase Realtime Database mediante DataSnapshot o el SDK de Admin.

En un ciclo de vida típico, una función de Firebase Realtime Database hace lo siguiente:

  1. Espera cambios en una ubicación de base de datos específica.
  2. Se activa cuando ocurre un evento y realiza sus tareas (consulta ¿Qué puedo hacer con Cloud Functions? para ver ejemplos de casos prácticos).
  3. Recibe un objeto de datos que contiene una instantánea de los datos almacenados en el documento especificado.

Activa una función de base de datos

Crea funciones nuevas para los eventos de Realtime Database con functions.database. Para controlar cuándo se debe activar la función, especifica uno de los controladores de eventos y la ruta de acceso de la base de datos en la que se detectarán los eventos.

Configura el controlador de eventos

Las funciones te permiten controlar los eventos de la base de datos en dos niveles de especificidad: puedes detectar específicamente solo eventos de creación, actualización o eliminación, o puedes detectar cambios de cualquier tipo en una ruta de acceso. Cloud Functions es compatible con los siguientes controladores de eventos para Realtime Database:

  • onWrite(), que se activa cuando se crean, actualizan o borran datos en Realtime Database
  • onCreate(), que se activa cuando se crean datos nuevos en Realtime Database
  • onUpdate(), que se activa cuando se actualizan datos en Realtime Database
  • onDelete(), que se activa cuando se borran datos de Realtime Database

Especifica la instancia de base de datos y la ruta de acceso

A fin de controlar cuándo y dónde se debe activar la función, llama a ref(path) para especificar una ruta de acceso y, opcionalmente, una instancia de base de datos con instance('INSTANCE_NAME'). Si no especificas una instancia, la función se implementa en la instancia de base de datos predeterminada del proyecto de Firebase. Por ejemplo:

  • Instancia de base de datos predeterminada: functions.database.ref('/foo/bar')
  • Nombre de la instancia "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

Estos métodos le indican a la función que administre las escrituras en una ruta de acceso específica dentro de la instancia de bases de datos. Las especificaciones de la ruta de acceso coinciden con todas las escrituras que afectan una ruta, incluidas aquellas que ocurren en cualquier lugar dentro de ella. Si configuras la ruta de acceso /foo/bar para la función, se muestran coincidencias de eventos en estas dos ubicaciones:

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

En ambos casos, Firebase interpreta que el evento ocurre en /foo/bar y los datos del evento incluyen los datos antiguos y nuevos en /foo/bar. Si los datos del evento son grandes, evalúa la posibilidad de usar varias funciones en rutas de acceso más profundas en lugar de una sola función cerca de la raíz de la base de datos. Para obtener el mejor rendimiento, solicita datos únicamente en el nivel más profundo posible.

Para especificar un componente de ruta de acceso como comodín, puedes encerrarlo entre llaves; ref('foo/{bar}') coincide con cualquier elemento secundario de /foo. Los valores de estos componentes comodín de la ruta de acceso están disponibles en el objeto EventContext.params de tu función. En este ejemplo, el valor está disponible como event.params.bar.

Las rutas de acceso con comodines pueden coincidir con varios eventos de una misma escritura. La inserción de

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

coincide con la ruta de acceso "/foo/{bar}" dos veces: una vez con "hello": "world" y otra con "firebase": "functions".

Administra datos de eventos

Cuando se controla un evento de Realtime Database, el objeto de datos que se muestra es una DataSnapshot. En el caso de los eventos onWrite y onUpdate, el primer parámetro es un objeto Change que contiene dos instantáneas que representan el estado de los datos antes y después del evento de activación. En el caso de los eventos onCreate y onDelete, el objeto de datos que se muestra es una instantánea de los datos creados o borrados.

En este ejemplo, la función recupera la instantánea para la ruta de acceso especificada como snap, convierte la string que se encuentra en esa ubicación en mayúsculas y escribe esa string modificada en la base de datos:

// 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();
      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 snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Accede a la información de autenticación del usuario

Desde EventContext.auth y EventContext.authType, puedes acceder a la información del usuario que activó una función, incluidos sus permisos. Esto puede ser útil para aplicar reglas de seguridad, lo que permite que la función complete diferentes operaciones según el nivel de permisos del usuario:

const functions = require('firebase-functions');
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);
      }
    });

Además, puedes aprovechar la información de autenticación del usuario para "suplantar" a un usuario y ejecutar operaciones de escritura en su nombre. Asegúrate de borrar la instancia de la app como se muestra a continuación para evitar problemas de simultaneidad:

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

Lee el valor anterior

El objeto Change tiene una propiedad before que te permite examinar lo que se guardó en la base de datos antes del evento. La propiedad before muestra una DataSnapshot en la que todos los métodos (por ejemplo, val() y exists()) hacen referencia al valor anterior. Puedes volver a leer el valor nuevo si usas la DataSnapshot original o si lees la propiedad after. Esta propiedad, presente en todo Change, es otra DataSnapshot que representa el estado de los datos después de que ocurrió el evento.

Por ejemplo, la propiedad before se puede usar para garantizar que la función solo transforme el texto a mayúsculas cuando se crea por primera vez:

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