Agrega hooks de usuario a una extensión

Puedes proporcionar a los usuarios que instalan tu extensión la capacidad de insertar su propia lógica personalizada en la ejecución de la extensión. Hay dos maneras de lograrlo:

  • Eventos de Eventarc: Para brindarles a los usuarios una forma de reaccionar de forma asíncrona a los eventos, puedes publicar en Eventarc. Los usuarios pueden implementar funciones del controlador de eventos que, por ejemplo, envían notificaciones después de que se completan las tareas de larga duración, o pueden definir sus propias funciones de procesamiento posterior.

  • Hooks síncronos: Para brindarles a los usuarios una forma de agregar lógica de bloqueo a tu extensión, puedes agregar hooks síncronos en puntos predefinidos en el funcionamiento de la extensión. En este punto, ejecutas una función de proveedor de proporcionada por el usuario y continúas solo después de que se complete. Las tareas de procesamiento previo suelen corresponder a esta categoría.

Una extensión puede usar uno o ambos métodos.

Eventos de Eventarc

Para publicar eventos desde una extensión, sigue estos pasos:

  1. Declara los tipos de eventos que publicarás en el archivo extension.yaml:

    events:
      - type: publisher-id.extension-name.version.event-name
        description: event-description
      - type: publisher-id.extension-name.version.another-event-name
        description: another-event-description
    

    El identificador type consta de varios campos delimitados por puntos. Los campos ID de publicador, nombre de la extensión y nombre del evento son obligatorios. Se recomienda completar el campo de versión. Elige un nombre de evento único y descriptivo para cada tipo de evento que publiques.

    Por ejemplo, la extensión storage-resize-images declara un solo tipo de evento:

    events:
      - type: firebase.extensions.storage-resize-images.v1.complete
        description: |
          Occurs when image resizing completes. The event will contain further
          details about specific formats and sizes.
    

    Los usuarios podrán elegir a qué eventos suscribirse cuando instalen la extensión.

  2. En tus funciones de extensión, importa la API de Eventarc desde el SDK de Admin y, luego, inicializa un canal de eventos mediante la configuración de instalación del usuario. Esta configuración se expone mediante las siguientes variables de entorno:

    • EVENTARC_CHANNEL: Es el nombre completo del canal de Eventarc en el que el usuario eligió publicar eventos.
    • EXT_SELECTED_EVENTS: Es una lista separada por comas de los tipos de eventos que el usuario eligió publicar. Cuando inicializas un canal con este valor, el SDK de Admin filtra automáticamente los eventos que el usuario no seleccionó.
    • EVENTARC_CLOUD_EVENT_SOURCE: Es el identificador de origen del evento de Cloud. El SDK de Admin pasa automáticamente este valor en el campo source de los eventos publicados. Por lo general, no necesitas usar esta variable de forma explícita.

    Si no se habilitaron los eventos en la instalación, estas variables no se definirán. Puedes usar este hecho para inicializar un canal de eventos solo cuando están habilitados los eventos:

    import * as admin from "firebase-admin";
    import {getEventarc} from 'firebase-admin/eventarc';
    
    admin.initializeApp();
    
    // 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,
      });
    
  3. Publica eventos en el canal en los puntos de la extensión que deseas exponer a los usuarios. Por ejemplo:

    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel && eventChannel.publish({
        type: 'firebase.extensions.storage-resize-images.v1.complete',
        subject: filename,  // the name of the original file
        data: {
          // ...
        }
    });
    
  4. Documenta los eventos que publicas, en el archivo PREINSTALL o POSTINSTALL.

    Para cada evento, documenta la siguiente información:

    • Su propósito previsto
    • El punto en la lógica de la extensión que ejecuta
    • Los datos de salida que incluye
    • Las condiciones para su ejecución

    Además, advierte a los usuarios que no realicen ninguna acción en sus controladores de eventos que pueda activar la misma extensión, lo que podría generar un bucle infinito.

Cuando publicas eventos desde una extensión, los usuarios pueden implementar controladores de eventos para responder con una lógica personalizada.

En el siguiente ejemplo, se borra la imagen original después de cambiar de tamaño. Ten en cuenta que este controlador de ejemplo usa la propiedad subject del evento, que en este caso es el nombre de archivo original de la imagen.

exports.onimageresized = onCustomEventPublished(
    "firebase.extensions.storage-resize-images.v1.complete",
    (event) => {
      logger.info("Received image resize completed event", event);
      // For example, delete the original.
      return admin.storage()
          .bucket("my-project.appspot.com")
          .file(event.subject)
          .delete();
    });

Consulta Activadores de eventos personalizados para obtener más información.

Ejemplo

La extensión de cambio de tamaño de imágenes oficial proporciona un hook asíncrono mediante la publicación en Eventarc después de cambiar el tamaño de una imagen.

Hooks síncronos

Usa hooks síncronos cuando desees proporcionar a los usuarios un hook que se deba completar correctamente para que funcione una de las funciones de la extensión.

Un hook síncrono llama a una Cloud Function que admite llamadas HTTPS definida por el usuario y espera que se complete (posiblemente con un valor mostrado) antes de continuar. Un error en la función proporcionada por el usuario genera un error en la función de la extensión.

Para exponer un hook síncrono, haz lo siguiente:

  1. Agrega un parámetro a tu extensión que permita a los usuarios configurar la extensión con la URL a su Cloud Function personalizada. Por ejemplo:

    - param: PREPROCESSING_FUNCTION
      label: Pre-processing function URL
      description: >
        An HTTPS callable function that will be called to transform the input data
        before it is processed by this function.
      type: string
      example: https://us-west1-my-project-id.cloudfunctions.net/preprocessData
      required: false
    
  2. En el punto de la extensión en el que deseas exponer el hook, llama a la función con la URL. Por ejemplo:

    const functions = require('firebase-functions');
    const fetch = require('node-fetch');
    
    const preprocessFunctionURL = process.env.PREPROCESSING_FUNCTION;
    
    exports.yourFunctionName = functions.firestore.document("collection/{doc_id}")
        .onWrite((change, context) => {
          // PREPROCESSING_FUNCTION hook begins here.
          // If a preprocessing function is defined, call it before continuing.
          if (preprocessFunctionURL) {
            try {
              await fetch(preprocessFunctionURL); // Could also be a POST request if you want to send data.
            } catch (e) {
              // Preprocessing failure causes the function to fail.
              functions.logger.error("Preprocessor error:", e);
              return;
            }
          }
          // End of PREPROCESSING_FUNCTION hook.
    
          // Main function logic follows.
          // ...
        });
    
  3. Documenta los hooks que tengas disponibles en el archivo PREINSTALL o POSTINSTALL.

    Para cada hook, documenta la siguiente información:

    • Su propósito previsto
    • El punto en la lógica de la extensión que ejecuta
    • Sus entradas y salidas esperadas
    • Las condiciones (o las opciones) para su ejecución

    Además, advierte a los usuarios que no realicen ninguna acción en la función del hook que pueda activar la misma extensión, lo que generará un bucle infinito.

Ejemplo

La extensión de búsqueda de Algolia proporciona un hook síncrono para llamar a una función de transformación proporcionada por el usuario antes de escribir en Algolia.