Conectar llamadas telefónicas a videoconferencias

La Video API de Vonage te permite crear prácticamente cualquier experiencia de vídeo que desees. A menudo, puede ocurrir que un participante se encuentre en una zona donde la cobertura de Internet no sea muy buena, ya sea por problemas con la red móvil o con el proveedor de acceso a Internet, pero aún así necesite unirse a una reunión. Te mostraremos cómo puedes hacer que los participantes que no dispongan de vídeo se conecten a la reunión por teléfono y participen en ella.

En este tutorial

La Video API de Vonage te permite permitir que los usuarios se conecten a una reunión por video o que llamen a un usuario para que se una directamente. Repasaremos cómo poner en marcha una demostración con nuestras demostraciones existentes para que no tengas que escribir ningún código, pero también explicaremos qué hace el código en segundo plano.

  1. Ver la demostración - Echa un vistazo a la demo sin escribir código
  2. Cómo funciona la demostración - El lado del cliente - Qué hace el código del lado del cliente
  3. Cómo funciona la demostración - El lado del servidor - Qué hace el código del lado del servidor

Requisitos previos

Para completar el tutorial, necesitas:

Ver la demostración

Si quieres echar un vistazo a la demo antes de escribir ningún código, tenemos un servidor web de muestra y código JavaScript para probar cómo es una videollamada básica. Todo el código es de código abierto y está disponible públicamente, así que puedes probar la demo y luego usar el código para hacer tus propias modificaciones.

Iniciar el servidor Node.js

El vídeo de demostración requiere un servidor backend para manejar cosas como la creación de tokens de cliente para la autorización y la gestión general de la sesión. Mientras que usted puede construir esto en cualquier idioma que desee, tenemos un servidor pre-construido que puede utilizar para empezar en el Servidor de aprendizaje por vídeo de Vonage (Node.js) en Code Hub. De la Documentación para desarrolladores, haz clic en «Code Hub» en la barra de navegación superior y, a continuación, desplázate hacia abajo hasta encontrar la ficha «Vonage Video Learning Server (Node.js)». Haz clic en ella para abrirla.

Obtendrás una descripción de lo que hace este proyecto. Por ahora, hagamos clic en "Obtener código" para que podamos cargarlo en el editor en línea Code Hub. Haz click en "Crear un nuevo entorno de desarrollo". Nombra el espacio de trabajo "Vonage Video Demo" ya que podemos usar este backend para múltiples demos. Esta demostración requiere que se le asigne un número, ya que el servidor de aprendizaje admite llamadas telefónicas a través de SIP. Aunque no lo utilizaremos en esta demostración, haz clic en "Asignar un número" para asignar un número existente de Vonage o compra uno nuevo para utilizarlo en demostraciones posteriores.

Creating a new workspace

Code Hub creará automáticamente una aplicación para ti, incluida la configuración de las claves públicas y privadas que utilizará nuestra aplicación. Una vez creado el espacio de trabajo, se te redirigirá al editor de código, que es una versión en línea de Visual Studio Code. No dudes en seguir las siguientes partes de esta demostración para ver el código, y podrás editarlo según sea necesario para tus propios proyectos.

Code editor

Para ejecutar la aplicación, haz clic en «Ver» en la parte superior del editor y, a continuación, en «Terminal». Se abrirá una línea de comandos en la que podremos ejecutar comandos. Solo tenemos que escribir vcr deploy y el código se implementará. Esto tardará unos instantes, ya que el sistema empaqueta el código y lo ejecuta en los servidores de Vonage Code Hub. Debemos tomar nota de la «dirección del host de la instancia» que aparece hacia el final.

Deploying the project

Si todo funciona correctamente, debería poder visitar la "dirección del host de instancia" y aparecerá la siguiente página:

Learning Server homepage

Probar el Front End

El servidor backend funciona directamente con todas nuestras demos preconstruidas, incluida esta demo individual. Vaya a https://github.com/Vonage-Community/Video API web samples/tree/main/SIP, que es el código fuente de la parte de interfaz de usuario de esta demostración. Este ejemplo permite que varios usuarios que dispongan de la URL se unan a un chat de voz a través de vídeo o de un número de teléfono, y que el anfitrión pueda llamar a un número.

La forma más sencilla de ejecutar esta demo es hacer clic en el botón "Abrir en Stackblitz" del README.

SIP README

Esto abrirá el proyecto en Stackblitz. Al igual que con el servidor backend, aquí puedes consultar el código y modificarlo si lo deseas. Para esta demostración, lo único que tendremos que hacer es abrir el js/config.js e introduzca la URL de la instancia de Code Hub en el archivo SAMPLE_SERVER_BASE_URL variable:

Stackblitz Demo Config

Una vez guardado el archivo, puede actualizar la vista de demostración en la parte derecha de Stackblitz y su navegador debería pedirle que permita su micrófono y cámara. Una vez que lo permitas, tu imagen debería aparecer en la esquina inferior de la barra lateral. Si copias la URL de Stackblitz encima del panel de demostración y lo visitas en tu dispositivo móvil, otro ordenador, o se lo das a un amigo, ¡cualquiera que se una debería estar conectado a tu demostración!

Cómo funciona la demostración

Configurar una aplicación de Vonage

Para que nuestra aplicación de video funcione, necesitamos que nuestro cliente y servidor se comuniquen con los servidores de Vonage. Code Hub configura esto por nosotros, pero si estás ejecutando el código localmente o quieres saber qué implica una aplicación de video, se configura como cualquier otra API. Necesitamos configurar una aplicación de Vonage para alojar toda la configuración de nuestra aplicación, además de ayudarnos a generar los elementos necesarios para realizar la autenticación.

Diríjase a su Panel de control del cliente de Vonage e inicia sesión. Una vez que hayas iniciado sesión:

  1. Haz clic en «Applications», dentro de «Compilar».
  2. Haga clic en "Crear una nueva aplicación".
  3. Ponle un nombre a la aplicación, por ejemplo, «Demostración básica de vídeo».
  4. Haz clic en «Generar clave pública y privada», lo que te llevará a descargar un archivo llamado private.key. Guarda este archivo para más adelante.
  5. Desplácese hacia abajo y active "Vídeo". Dejaremos estos valores vacíos por ahora.
  6. Haga clic en "Generar nueva aplicación" para crear la aplicación.

Una vez creada la aplicación, anote el ID de la aplicación. Si está ejecutando el código localmente, lo necesitaremos para configurar el backend. Si está utilizando Code Hub, el código del servidor ya tiene acceso al ID de la Aplicación y a la Clave Privada.

El lado del cliente

La parte del lado del cliente de la demostración consta de varios componentes: algunos elementos HTML para insertar las transmisiones de vídeo, código JavaScript para recuperar la información de inicio de sesión y comunicarse con los servidores de Vonage Video, y código JavaScript para llamar al servidor de fondo con el fin de realizar llamadas salientes.

Dado que se trata de una demostración para navegador, utilizamos el SDK de JavaScript que se encuentra en https://unpkg.com/@vonage/client-sdk-video@latest/dist/js/opentok.js, e incluirlo en una etiqueta `script` de nuestro código HTML en index.html.

Para añadir gente a una sala, sólo necesitamos dos elementos: algún sitio donde poner al usuario actual, por ejemplo, Tú, al que llamaremos "publicador". Luego necesitamos algún sitio donde poner a cualquier otra persona que se una, a la que "suscribirás". Los pondremos en un elemento "suscriptores".

Crearemos dos div elementos, y asignar a uno de ellos un ID de publisher y el otro un ID de subscriber. Haremos referencia a estos elementos en el código JavaScript cuando se visite la página y cuando detectemos que otro usuario se ha unido a la videollamada.

// index.html
<div>
    <h2 class="font-black text-2xl">Your Camera</h2>
    <div class="h-80 w-80" id="publisher"></div>
</div>

<div>
    <h2 class="font-black text-2xl">Guests</h2>
    <div class="h-80 w-80" id="subscriber"></div>
</div>

A continuación, disponemos de dos conjuntos de controles. El primero permite que nuestra videoconferencia ofrezca la posibilidad de conectarse por teléfono. Crearemos dos botones para activar esta función.

<div><h2 class="font-black text-2xl">Dial Options</h2></div>
<div>
    <h3 class="font-black text-xl">Phone Conference</h3>
    <p>You can start a phone conference to let people dial in directly. They can call the following number to join once you have started the conference:</p>

    <p id="conference-number" class="text-center pb-4"></p>
</div>
<div>
    <button id="btn-dial-conference" class="bg-blue-500 bold text-white p-4 rounded">Create Phone Conference</button>
    <button id="btn-disconnect-conference" class="bg-red-500 bold text-white p-4 rounded">Disconnect Phone Conference</button>
</div>

Tendremos entonces un conjunto de controles que nos permitirán marcar a un usuario. Puede introducir un número de teléfono y hacer que nuestro sistema marque al usuario, y cuando acepte la llamada se le conectará a la conferencia.

<div>
    <h3 class="font-black text-xl">Direct Dial</h3>
    <p>Directly dial a phone number and add them to the conference. They will appear as an additional guest and be automatically added to the conference call if you have already started one.</p>
</div>
<div>
    <label for="phone">Number to call:</label>
    <input name="phone" id="phone" type="text" placeholder="15554441234" class="border border-black p-4 w-full">
    <button id="btn-dial-number" class="bg-blue-500 bold text-white p-4 rounded">Call Number</button>
</div>

Manejo de vídeos

En cuanto a JavaScript, lo primero que haremos será obtener cierta información sobre la propia videollamada. Para conectarnos a la videollamada, necesitamos un ID de aplicación, un ID de sesión y un token.

  • El ID de la solicitud es un identificador que utiliza el Client SDK para hacer referencia a diferentes configuraciones de nuestra aplicación de vídeo en el lado de Vonage.
  • El ID de sesión es una sesión de vídeo concreta a la que queremos conectarnos, ya que una misma aplicación puede tener varias sesiones de vídeo simultáneas a la vez.
  • El Token es un token de autenticación JWT que le permite unirse a una sesión específica con derechos específicos.

Aunque es posible generar el ID de sesión y el token con antelación, en la práctica se suelen generar sobre la marcha. Nuestro código muestra cómo se haría eso. En breve veremos cómo se genera esa información, pero la obtendremos del servidor backend que hemos implementado.

// src/app.js

// ...
} else if (SAMPLE_SERVER_BASE_URL) {
  // Make a GET request to get the Vonage Video Application ID, session ID, and token from the server
  fetch(SAMPLE_SERVER_BASE_URL + '/session')
  .then((response) => response.json())
  .then((json) => {
    applicationId = json.applicationId;
    sessionId = json.sessionId;
    token = json.token;
    // Initialize a Vonage Video Session object
    initializeSession();
  }).catch((error) => {
    handleError(error);
    alert('Failed to get Vonage Video sessionId and token. Make sure you have updated the config.js file.');
  });
}

Una vez que tengamos toda la información de conexión, podemos seguir adelante y llamar al SDK JavaScript de Vonage Video, que se encarga de todo el trabajo para conectarnos a la Video API de Vonage en el front end. Primero, tomamos un objeto de sesión con OT.initSession(). A continuación, empezamos a escuchar en el streamCreated evento con session.on(). Esto nos permite establecer una llamada de retorno para que se ejecute cuando se cree un flujo desde otro editor. En este caso, utilizamos session.subscribe() para conectarse al evento entrante, y empujarlo en el subscriber div que configuramos en el HTML. También escuchamos el sessionDisconnected evento para saber cuándo se desconecta el otro usuario, pero lo único que hacemos en esta demostración es registrar que nos hemos dado cuenta de que se ha ido.

A continuación, creamos el publisher objeto con OT.initPublisher(). Le decimos a qué div debe adjuntarse (publisher), y algunas opciones básicas de formato. Esto conecta su cámara y micrófono a la Video API.

A continuación, llamamos session.connect() para conectarnos a la sesión, utilizando el token JWT de conexión que hemos obtenido del servidor. ¡Eso es todo lo que hace falta para que dos personas se unan a una sala!

// src/app.js

function initializeSession() {
  const session = OT.initSession(applicationId, sessionId);

  // Subscribe to a newly created stream
  session.on('streamCreated', (event) => {
    const subscriberOptions = {
      insertMode: 'append',
      width: '100%',
      height: '100%'
    };
    session.subscribe(event.stream, 'subscriber', subscriberOptions, handleError);
  });

  session.on('sessionDisconnected', (event) => {
    console.log('You were disconnected from the session.', event.reason);
  });

  // initialize the publisher
  const publisherOptions = {
    insertMode: 'append',
    width: '100%',
    height: '100%',
    resolution: '1280x720'
  };
  const publisher = OT.initPublisher('publisher', publisherOptions, handleError);

  // Connect to the session
  session.connect(token, (error) => {
    if (error) {
      handleError(error);
    } else {
      // If the connection is successful, publish the publisher to the session
      session.publish(publisher, handleError);
    }
  });
}

Gestión de llamadas telefónicas

Todo el trabajo pesado relacionado con el soporte de las llamadas lo gestionarán la propia Video API y nuestro servidor backend. El código del lado del cliente solo accederá a algunas rutas del servidor backend para habilitar la llamada SIP, así como para desconectarse de la conferencia telefónica cuando hayamos terminado. Para habilitar la telefonía, hay que pulsar el /sip/session/dial en nuestro servidor backend, que detallaremos más adelante.

// js/index.js
document.getElementById('btn-dial-conference').addEventListener('click', async () => {
    const resp = await fetch(`${SAMPLE_SERVER_BASE_URL}/sip/session/dial`, {
        method: "POST"
    })
    .then(res => res.json())

    console.log(resp);
})

Esta misma ruta puede utilizarse para llamar a un usuario concreto. Basta con pasar el número de teléfono introducido en la interfaz del cliente:

// js/index.js
document.getElementById('btn-dial-number').addEventListener('click', async () => {
    const msisdn = document.getElementById('phone').value;
    const resp = await fetch(`${SAMPLE_SERVER_BASE_URL}/sip/session/dial`, {
        method: "POST",
        body: JSON.stringify({
            msisdn
        }),
        headers: {
            "Content-Type": "application/json"
        }
    })
    .then(res => res.json())

    console.log(resp);
})

Cuando un usuario entre en la multiconferencia, o nos conectemos con él marcando directamente, se añadirá un nuevo abonado a la lista de participantes. La Video API reenviará automáticamente el audio de la conexión SIP a todas las personas conectadas a la sesión de vídeo.

Por último, podemos finalizar cualquiera de los dos tipos de llamada accediendo a la función /sip/session/hangup en nuestro servidor:

// js/index.js
 document.getElementById('btn-disconnect-conference').addEventListener('click', async () => {
    const resp = await fetch(`${SAMPLE_SERVER_BASE_URL}/sip/session/hangup`, {
        method: "POST"
    })
        .then(res => res.json())

    console.log(resp);
})

El lado del servidor

La parte del lado del servidor de cualquier aplicación de Vonage Video se utiliza para gestionar la creación de sesiones, la generación de tokens de autenticación y tareas administrativas como iniciar y detener los archivos. Para esta demostración, lo único que nos interesa es crear sesiones y tokens para que los usuarios puedan unirse a la sala. Aunque la API en sí es una API REST y se puede invocar como se desee, te recomendamos que utilices la SDK de Vonage Node que se encarga de toda la autenticación y las llamadas HTTP por ti. Puedes instalarlo en tu propia aplicación con:

npm install -s @vonage/server-sdk

El código de demostración ya lo tiene preinstalado. Si estás ejecutando el código de forma local, tendrás que ejecutar:

npm install

para descargar todas las dependencias y, a continuación, copiar .envcopy a un nuevo archivo llamado .env. Deberá rellenar la información solicitada en .env como el ID de la aplicación, la ubicación de la clave privada en el disco y tu clave y secreto de la API de Vonage.

Creación de la sesión e incorporación a la misma

Lo primero que hacemos es buscar si ya tenemos una sesión para la sala que estamos generando. Mantenemos un diccionario en memoria en roomToSessionIdDictionaryy si la sala ya tiene una sesión, simplemente extraemos la sesión del diccionario. Luego usamos el Vonage Video Node SDK para crear un token de cliente llamando a vonage.video.generateClientToken(), pasándole el ID de sesión y un objeto con algunos parámetros de configuración. Por el momento, lo único que hacemos es establecer el usuario como un moderator para esta sencilla demostración. A continuación, devolvemos el ID de aplicación, el ID de sesión y el token configurados al front-end.

Si la sesión no existe, creamos una nueva con vonage.video.createSession(). Esto contacta a la API de Vonage y crea una sesión a la que los usuarios pueden conectarse. No tenemos ninguna configuración específica para esta sesión, pero aquí sería donde configuraríamos cosas como reglas de archivo y cómo se debe manejar la sesión, como enrutamiento o peer-to-peer. Entonces como antes creamos un token, y enviamos toda esa información de vuelta al navegador.

// routes/index.js

async function createSession(response, roomName, sessionProperties = {}, role = 'moderator') {
  let sessionId;
  let token;
  console.log(`Creating ${role} creds for ${roomName}`);

  if (roomToSessionIdDictionary[roomName]) {
    sessionId = roomToSessionIdDictionary[roomName];
    // generate token for user
    token = vonage.video.generateClientToken(sessionId, { role })
    response.setHeader('Content-Type', 'application/json');
    response.send({
      applicationId: appId,
      sessionId: sessionId,
      token: token
    });
  } else {
    try {
      // Create the session
      const session = await vonage.video.createSession(sessionProperties);
      roomToSessionIdDictionary[roomName] = session.sessionId;

      // generate token for user
      token = vonage.video.generateClientToken(session.sessionId, { role });
      response.setHeader('Content-Type', 'application/json');
      response.send({
        applicationId: appId,
        sessionId: session.sessionId,
        token: token
      });
    } catch(error) {
      console.error("Error creating session: ", error);
      response.status(500).send({ error: 'createSession error:' + error });
    }
  }
}

Conexión al puente SIP

Vonage pone a disposición un puente SIP para que lo utilice la Video API. Lo único que necesitas es un número de teléfono al que te hayas suscrito a través de tu panel de control de cliente de Vonage. A continuación, podemos utilizar ese número de teléfono como interfaz SIP para las llamadas entrantes. También utilizaremos el Funcionalidad de conversación de Voice API de Vonage para unir a varios usuarios en una conferencia de audio.

Lo primero que tenemos que hacer es integrar nuestra sesión de vídeo en la propia conferencia telefónica. Crearemos un token de cliente para la conexión SIP con el fin de unírnos a la sesión de vídeo y, a continuación, realizaremos una llamada a vonage.video.initiateSIPCall() con nuestra configuración SIP para unirlo todo.

// routes/index.js
const { msisdn } = req.body;
const sessionId = findSessionIdForRoom(req.params.room);
const conversation = findConversationFromSessionId(sessionId);
const token = vonage.video.generateClientToken(sessionId, {
  data: JSON.stringify({
    sip: true,
    role: 'client',
    name: conversation.conversationName,
  })
})

const options = {
  token, 
  sip: {
    auth: {
      username: process.env.VCR_API_ACCOUNT_ID, // Your Vonage API Key
      password: process.env.VCR_API_ACCOUNT_SECRET, // Your Vonage API Secret
    },
    uri: `sip:${process.env.CONFERENCE_NUMBER}@sip.nexmo.com;transport=tls`,
    secure: false,
  }
}

// ...

await vonage.video.intiateSIPCall(sessionId, options)
  .then(data => {
    // Update the conversation with connection data
    conversation.connectionId = data.connectionId;
    conversation.streamId = data.streamId;
    sipConversationToSessionIdDictionary[sessionId] = conversation;

    res.send(data)
  })

¿Dónde se establece la conversación? Cuando iniciamos la llamada SIP, esto hace que nuestro puente SIP marque a un número de conferencia a través del Voice API. Nuestro número de conferencia se configura a través del Panel de Control del Cliente para golpear el /sip/vapi/answer en nuestro servidor backend. Si está utilizando Cloud Runtime esto se configura automáticamente, pero si está configurando esto a mano tendrá que ir a la configuración de la Aplicación para esta aplicación, y luego establecer la "URL de respuesta" a https://your-domain.com/sip/vapi/answerdonde your-domain es el nombre de dominio en el que se ha implementado la demo.

La ruta devolverá un Acción de conversación de la NCCO que crea una conversación mediante la Voice API, uniendo a todo el mundo.

// routes/index.js
router.get('/sip/vapi/answer', async function (req, res) {
  const ncco = new NCCOBuilder();
  const conversation = findConversationFromSessionId(findSessionIdForRoom('session'));

  // If the call is not from the SIP connector, then announce we are connecting
  // to the conference call
  if (!req.query['SipHeader_X-OpenTok-SessionId']) {
    ncco.addAction(new Talk('Please wait while we connect you'));
  }

  // Call an individual user
  if (req.query['SipHeader_X-learningserver-msisdn']) {
    ncco.addAction(new Connect({type: 'phone', number: req.query['SipHeader_X-learningserver-msisdn']}, process.env.CONFERENCE_NUMBER));
  } else {
    ncco.addAction(new Conversation(conversation.conversationName, null, true, true, false, null, null, false));
  }

  res.send(ncco.build());
});

En este punto, un usuario puede marcar el número de la conferencia y la Voice API lo unirá todo. Si un usuario marca nuestro número de conferencia, se enrutará a la API de voz. /sip/vapi/answer punto final. Añadimos una acción adicional que indica a la persona que llama que se está conectando a la conferencia y, a continuación, se le conecta.

Marcar a un usuario

En general, el proceso es el mismo para llamar a un usuario que para establecer una multiconferencia. La única diferencia es que pasamos un número para marcar al usuario. /sip/:room/dial y añadimos ese número como opción a las cabeceras SIP.

// routes/index.js
router.post("/sip/:room/dial", async function (req, res) {
  // Set up client token and SIP options as before

  // Add a header that will get passed to the Voice API
  if (msisdn) {
    options.sip.headers = {
      "X-learningserver-msisdn": msisdn
    }
  }

  //Initiate the call as before
  await vonage.video.intiateSIPCall(sessionId, options)
    .then(data => {
      // Update the conversation with connection data
      conversation.connectionId = data.connectionId;
      conversation.streamId = data.streamId;
      sipConversationToSessionIdDictionary[sessionId] = conversation;

      res.send(data)
    })
});

Cuando se realiza la llamada SIP, ese adicional X-learningserver-msisdn El encabezado se envía como parte de la llamada a la Voice API que acepta nuestro servidor backend. Esto hace que nuestro código añada un paso NCCO adicional para marcar primero el número de teléfono solicitado a través de un Conectar la acción de la OCNC, y cuando respondan, haz que se incorporen a la conversación.

// routes/index.js

router.get('/sip/vapi/answer', async function (req, res) {
  // Find the conversation info as before

  // If this header exists, call the user to bridge them in
  if (req.query['SipHeader_X-learningserver-msisdn']) {
    ncco.addAction(new Connect({type: 'phone', number: req.query['SipHeader_X-learningserver-msisdn']}, process.env.CONFERENCE_NUMBER));
  } else {
    ncco.addAction(new Conversation(conversation.conversationName, null, true, true, false, null, null, false));
  }

  res.send(ncco.build());
});

Colgados

Cuando hayamos terminado, la interfaz de usuario nos ofrece la opción de desconectarnos directamente de la llamada SIP. Esto se gestiona mediante el /sip/:room/hangup ruta y simplemente desconecta la conexión de la llamada SIP de la sesión.

// routes/index.js

router.post("/sip/:room/hangup", async function (req, res) {
  // Get the session ID
  // Look up the connection from calls ID
  const sessionId = findSessionIdForRoom(req.params.room)
  const conversation = findConversationFromSessionId(sessionId);
  await vonage.video.disconnectClient(sessionId, conversation.connectionId)
    .then(data => 
      res.send(data)
    )
    .catch(error => res.status(500).send(error));
});

Si no colgamos manualmente, esperaremos a que se produzcan eventos de la Voice API que nos indiquen cuándo ha finalizado una conversación. Una conversación finalizará automáticamente una vez que todos los participantes se hayan desconectado. Esperamos hasta recibir un completed y cuando lo hagamos nos aseguramos de realizar la misma desconexión de la sesión que en el caso anterior.

// routes/index.js

// This must be all because VAPI sometimes sends events as POST no matter what
// your event URL config is set to. This is a known bug.
router.all('/sip/vapi/events', async function (req, res) {
  if (req.query.status === "completed") {
    const conversation = findConversationFromSessionId(findSessionIdForRoom('session'));
    await vonage.video.disconnectClient(findSessionIdForRoom('session'), conversation.connectionId)
      .then(data => res.send(data))
      .catch(error => res.status(500).send(error));
  } else {
    res.send();
  }
})

Conclusión

En este tutorial, viste lo que se incluye en el servidor backend para transferir llamadas de telefonía a través de SIP, cómo crear un cliente web para que los usuarios se unan a una sesión y se vean y escuchen entre sí, y un vistazo a lo fácil que es usar Vonage Code Hub y Stack Blitz para probar muestras rápidamente.

Lecturas complementarias