Con Cloud Functions, puedes controlar eventos en
Firebase Realtime Database sin necesidad de actualizar el código del cliente.
Cloud Functions te permite ejecutar operaciones de Realtime Database con privilegios administrativos completos y garantiza que cada cambio en Realtime Database se procese de forma individual. Puedes realizar cambios en Firebase Realtime Database a través de
DataSnapshot
o el SDK de Admin.
En un ciclo de vida típico, una función Firebase Realtime Database hace lo siguiente:
- Espera a que ocurran cambios en una ubicación Realtime Database en particular.
- Se activa cuando ocurre un evento y realiza sus tareas (consulta ¿Qué puedo hacer con Cloud Functions? para ver ejemplos de casos de uso).
- Recibe un objeto de datos que contiene una instantánea de los datos almacenados en el documento especificado.
Activa una función de Realtime Database
Crea funciones nuevas para los eventos 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 Realtime Database en la que se detectarán los eventos.
Configura el controlador de eventos
Las funciones te permiten controlar los eventos de Realtime Database 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 admite 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 DatabaseonUpdate()
, que se activa cuando se actualizan datos en Realtime Database.onDelete()
, que se activa cuando se borran datos de Realtime Database
Especifica la instancia y la ruta
Para controlar el momento y la ubicación en los que se debe activar la función, llama a ref(path)
a fin de especificar una ruta de acceso y, opcionalmente, utiliza Realtime Database para especificar una instancia de instance('INSTANCE_NAME')
. Si no especificas una instancia, la función se implementa en la instancia de Realtime Database predeterminada del proyecto de Firebase. Por ejemplo:
- Instancia de Realtime Database predetermina:
functions.database.ref('/foo/bar')
- Instancia denominada “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 Realtime Database. Las coincidencias de las especificaciones de la ruta de acceso se establecen con todas las escrituras que afectan una ruta,
incluidas las que ocurren por debajo de esa ruta. Si configuras la ruta
para la función como /foo/bar
, se establecerán coincidencias con los eventos que ocurren 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 existe la posibilidad de que los eventos generen un gran volumen de datos, tal vez sea conveniente que uses 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 ponerlo entre llaves. Por ejemplo, se determinará la existencia de coincidencia entre ref('foo/{bar}')
y cualquier elemento secundario de /foo
. Los valores de estos componentes de ruta de acceso comodín están disponibles en el objeto EventContext.params
de la función. En este ejemplo, el valor está disponible como context.params.bar
.
Pueden establecerse coincidencias entre las rutas de acceso con comodines y varios eventos de una misma escritura. En el caso de la inserción de
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
existen dos coincidencias con la ruta de acceso "/foo/{bar}"
: una con "hello": "world"
y otra con "firebase": "functions"
.
Administra datos de eventos
Cuando se controla un evento Realtime Database, el objeto de datos que se muestra es un DataSnapshot
.
En el caso de los eventos onWrite
o onUpdate
, el primer parámetro es un objeto Change
que contiene dos instantáneas, las cuales 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 de la ruta de acceso especificada, convierte a mayúsculas la cadena que se encuentra en esa ubicación y escribe esa cadena 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(); 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); });
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 tu función complete diferentes operaciones según el nivel de permisos del usuario:
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);
}
});
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 inspeccionar lo que se guardó en Realtime Database 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 lees la propiedad after
. Esta propiedad, presente en todo Change
, es otra DataSnapshot
que representa el estado de los datos después del evento.
Por ejemplo, se puede usar la propiedad before
para garantizar que la función solo
convierta el texto en 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);
});