
Compartir:
Phil is Head of Developer Relations at Hookdeck, an asynchronous messaging platform, and a proud Vonage alumni.
Llamadas telefónicas desde un navegador web con Vue.js y Vonage
Tiempo de lectura: 19 minutos
En esta entrada de blog te explicaremos cómo puedes hacer una llamada telefónica desde un navegador web a un teléfono con Vonage In-App Voice usando el Vonage Client SDK para JavaScript y Vue.JS. In-App Voice y In-App Messaging se encuentra en la versión preliminar para desarrolladores, por lo que nos encantaría recibir tus comentarios tanto sobre la experiencia de desarrollo como sobre la funcionalidad provista. Puedes ponerte en contacto a través de Slack de la comunidad de desarrolladores de Vonage.
Para realizar una llamada telefónica desde un navegador web vamos a necesitar una serie de componentes en nuestra aplicación. Una aplicación Vue.JS que se ejecuta en el navegador y que usa el Vonage Client SDK para JavaScript, un servidor de aplicaciones que se usa para autenticar al usuario de la aplicación con Vonage generando un JWT de usuario y un teléfono para recibir la llamada telefónica.
El siguiente diagrama de secuencia muestra cómo funcionarán las cosas una vez que hayamos creado nuestra aplicación. En esta entrada de blog, primero crearemos la aplicación Vue.JS con una interfaz de usuario que permita introducir un número de teléfono. A continuación, crearemos un servidor de aplicaciones que pueda generar el JWT de usuario necesario. Una vez que el servidor de aplicaciones esté en funcionamiento, actualizaremos la aplicación Vue.JS para recuperar el JWT y usarlo con el Vonage Client SDK para JavaScript para iniciar sesión en la plataforma de Vonage e iniciar la llamada telefónica. Luego, debemos actualizar el servidor de la aplicación para manejar una solicitud GET que Vonage realizará para recuperar las instrucciones sobre cómo proceder con la llamada telefónica. Esas instrucciones le dirán a Vonage que conecte la llamada desde la aplicación Vue.JS en el navegador web a un teléfono.
/* https://bramp.github.io/js-sequence-diagrams/ Participante Vue.JS App as V Participant App Server as A Participant Nexmo as N Participant Phone as P V->A: Get User JWT Note right of V: In a production app<br/>this request should<br/>be authenticaterd A-->V: User JWT V -> V: Create Nexmo<br/>Conversation Client V -> N: Login N --> V: Logged In V -> N: Call Phone N -> A: GET answer_url A --> N: NCCO connect N -> P: Call */
Call from Web Browser Sequence Diagram
Así que hay que dar unos cuantos pasos, pero el resultado merece la pena.
Si prefieres sumergirte directamente en el código, puedes encontrar la sección Llamada desde el navegador en GitHub.
Antes de empezar
Hilo para la gestión de paquetes
La página Vue CLI para andamiar nuestra aplicación y ejecutar un servidor de desarrollo
A Account Nexmo para poder utilizar el SDK y realizar llamadas telefónicas
El sitio Nexmo CLI para crear y configurar rápidamente una aplicación Nexmo desde la línea de comandos. Por favor, utilice la versión beta de la CLI e.g.
npm install -g nexmo@betaUna solución de túnel local como Ngrok para que la plataforma Nexmo pueda llegar a un servidor web que se ejecute localmente). Para esta entrada de blog vamos a utilizar Ngrok.
Una vez hecho esto, empecemos.
Crear un nuevo proyecto Vue.JS
Ejecute el siguiente comando vue en el terminal y seleccione por defecto (babel, eslint) cuando se le solicite.
$ vue create call-from-browser
# navigate into the newly created Vue project folder
cd call-from-browserEl resultado será la siguiente estructura de directorios y archivos:
call-from-browser
├── README.md
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
└── yarn.lockVamos a crear un componente CallFromBrowser componente así que vamos a renombrar el HelloWorld que fue creado.
Con esto estamos listos para empezar a construir la interfaz de usuario.
Crear una interfaz sencilla de introducción y marcación de números de teléfono
Vue.JS tiene un ecosistema fuerte y en crecimiento por lo que tiene sentido utilizar los componentes existentes si los hay. Por suerte, hay unas cuantas opciones y vamos a utilizar el componente componente vue-tel-input de Steven Dao.
vue-tel-input example animation
Instale el componente:
Ahora que tenemos el componente que nos ayuda a validar los números de teléfono podemos añadirlo al componente CallFromBrowser componente. Abra src/components/CallFromBrowser.vue en un editor de código.
Actualice el <template> como sigue:
<template>
<main class="call-from-browser">
<vue-tel-input @oninput="onInput">
</vue-tel-input>
<button class="call-control" v-bind:class="{'call-in-progress': callInProgress}" v-on:click="controlCallClick"></button>
<p>{{infoMessage}}</p>
</main>
</template>La plantilla utiliza el componente <vue-tel-input> y establece un @onInput manejador. Tenemos un <button> que tendrá una clase call-in-progress basada en el valor de una propiedad callInProgress y un manejador de clic que llamará a un método controlCallClick método. La plantilla también tiene un <p>{{infoMessage}}</p> que nos permite proporcionar alguna información al usuario a través de una propiedad data.infoMessage que estamos a punto de definir.
A continuación, actualicemos el contenido de la etiqueta <script> dentro del mismo archivo.
<script>
import 'vue-tel-input/dist/vue-tel-input.css'
import VueTelInput from 'vue-tel-input'
export default {
name: 'CallFromBrowser',
components: {
'vue-tel-input': VueTelInput
},
...
</code></pre>
<p>This imports the CSS and the component definition for the telephone input component sets the name of the component to <code>CallFromBrowser</code> and registers the <code>vue-tel-input</code> component dependency so it can be used within the template.</p>
<p>Next let's set up some properties for data binding such as the <code>callInProgress</code> property relied upon by the <code>template</code> and add any methods that are expected to be in place, as show, in the template:</p>
<pre><code class="language-javascript">export default {
name: 'CallFromBrowser',
components: {
'vue-tel-input': VueTelInput
},
data() {
return {
phone: {
number: '',
isValid: false,
country: {}
},
infoMessage: "",
callInProgress: false
}
},
methods: {
onInput({ number, isValid, country }) {
this.phone.number = number;
this.phone.isValid = isValid;
this.phone.country = country;
if(!isValid) {
this.infoMessage = "Please enter a valid phone number"
}
else {
this.infoMessage = `Thanks for entering a valid ${this.phone.country.name} phone number`
}
},
controlCallClick() {
}
}
}
</script>La función data devuelve un objeto phone que rellenamos en el onInput manejador. En ese manejador, establecemos el número de teléfono que el usuario ha introducido en el componente vue-tel-input y las propiedades que representan la validez del número de teléfono y el país al que corresponde el número.
También proporcionamos al usuario información sobre la validez del número de teléfono mediante la propiedad infoMessage . Vue data-binding significa que el valor que establecemos aquí se refleja en la interfaz de usuario.
También se añade un método controlCallClick para manejar el método <button> sea pulsado.
El último paso para configurar el componente CallFromBrowser es añadir algo de estilo. Sustituya el elemento <style> y su contenido por lo siguiente:
<style scoped="">
.vue-tel-input {
width: 200px;
margin: auto;
}
.call-control {
font-size: 11em;
}
.call-control:before {
content: '☎️';
}
.call-control.call-in-progress:before {
content: '?'
}
</style>El estilo establece el valor predeterminado content del <button> sea el emoji de teléfono rojo (☎️). Si la clase call-in-progress está presente, se establece dinámicamente si la clase callInProgress devuelve true entonces el content será en cambio un emoji de teléfono antiguo (?).
El último paso para poner en marcha la interfaz de usuario básica es actualizar App.vue sustituyendo las etiquetas template y script etiquetas. Deje la etiqueta style tal cual.
<template>
<div id="app">
<callfrombrowser>
</callfrombrowser></div>
</template>
<script>
import CallFromBrowser from './components/CallFromBrowser.vue'
export default {
name: 'app',
components: {
CallFromBrowser
}
}
</script>Sustituya <template> contenido, importar la CallFromBrowser.vue definición del componente y registrar el componente importado.
Ya podemos ejecutar la aplicación:
Con esto en marcha, navegar en un navegador a http://localhost:8080 y probando a introducir números de teléfono en el componente vue-input-tel componente. Veremos el número de teléfono validado en la parte inferior de la interfaz de usuario de la aplicación.
Call from Browser simple user interface
Cómo crear un JWT de usuario para iniciar sesión en la plataforma de Vonage
El SDK JavaScript de Nexmo Stitch se conecta a la plataforma de Vonage para habilitar la funcionalidad In-App Voice dentro del navegador web. Para conectarse a la plataforma Nexmo, necesitamos login con un JWT (JSON Web Token) de autenticación de usuario válido para el usuario de la aplicación que define los permisos de ese usuario. Para crear un JWT de usuario vamos a necesitar crear algunas cosas:
un servidor simple que genera el JWT de usuario que puede ser recuperado por el componente
CallFromBrowsercomponente Vue.JSuna Applications dentro de la plataforma de Vonage - podemos hacer esto usando el Nexmo CLI
un Usuario dentro de la Aplicación para los usuarios actuales de la aplicación web
Empecemos por crear un servidor sencillo. Crea un directorio server instala algunas dependencias y crea un directorio index.js y .env para la funcionalidad que necesitamos.
Para el servidor, vamos a utilizar Express.js con el middleware CORS y body-parser. dotenv para cargar el fichero .env que contendrá configuración que no queremos en el control de código fuente. También hemos instalado la librería librería Nexmo Node.JS para ayudar con la generación de JWT de usuario.
Antes de ver el código del servidor vamos a crear también la Application y el User para esa aplicación. Podemos hacer esto usando el Nexmo CLI:
Ejecutando este comando se obtendrá un ID de aplicación. También añadirá los detalles de la Aplicación a un archivo .nexmo-app archivo. Tome el ID de la aplicación y añádalo al archivo .env junto con una variable para la private.key ubicación:
NEXMO_PRIVATE_KEY=private.key
NEXMO_APP_ID=YOUR_APPLICATION_IDLa última pieza de la configuración de Aplicaciones es crear un usuario dentro de la aplicación. Es posible hacer esto utilizando las bibliotecas Nexmo pero en este caso, vamos a configurar un usuario utilizando el Nexmo CLI:
Este comando creará el usuario para el ID de aplicación identificado en el archivo .nexmo-app archivo. Añada una variable de entorno para el nombre de usuario al archivo .env archivo.
NEXMO_PRIVATE_KEY=private.key
NEXMO_APP_ID=YOUR_APPLICATION_ID
NEXMO_APP_USER_NAME=demoAhora abre index.js para añadir el código básico del servidor:
// Load .env config
require('dotenv').config({
path: __dirname + '/.env'
});
const Nexmo = require('nexmo')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const app = express()
app.use(bodyParser.json())
app.use(cors())
// endpoint that doesn't authenticate the user
// it will simply return a JWT with every request
app.get('/no-auth', (req, res) => {
res.json({userJwt: null})
})
app.listen(3000, () => console.log('Example app listening on port 3000!'))En el código anterior, cargamos Express y lo configuramos para que analice las solicitudes entrantes como JSON (lo utilizaremos más adelante). También configuramos Express para que soporte Cross-Origin Resource Sharing (CORS). Esto es necesario porque la aplicación Vue.JS se ejecuta en localhost:8080 y el código JavaScript que se ejecuta en el navegador necesita hacer una llamada a este servidor que se ejecuta en localhost:3000un puerto diferente.
Ahora puede ejecutar node index.js y luego acceder a http://localhost:3000/no-auth para asegurarte de que el endpoint devuelve el JSON esperado.
User JWT with null value
Ahora, agreguemos el código para generar el JWT de usuario que se utilizará con Vonage Client SDK para JavaScript.
const userAcl = {
"paths": {
"/v1/users/**": {},
"/v1/conversations/**": {},
"/v1/sessions/**": {},
"/v1/knocking/**": {}
}
}
// endpoint that doesn't authenticate the user
// it will simply return a JWT with every request
app.get('/no-auth', (req, res) => {
const jwt = Nexmo.generateJwt(process.env.NEXMO_PRIVATE_KEY, {
application_id: process.env.NEXMO_APP_ID,
sub: process.env.NEXMO_APP_USER_NAME,
exp: new Date().getTime() + 86400,
acl: userAcl
})
res.json({userJwt: jwt})
})Las variables userAcl proporcionan un conjunto de reclamaciones o reglas de acceso que se utilizan al crear el JWT junto con el ID de la aplicación, una variable sub para el nombre del usuario y un exp como tiempo de expiración del JWT. Consulte la Visión general de JWT y ACL en Nexmo Developer para más información.
Reiniciar el nodo index.js procesando y accediendo a http://localhost:3000/no-auth mostrará que se ha generado un JWT real.
User JWT with real JWT value
Nota: A veces puede ser útil echar un vistazo a Depurador JWT para comprobar el contenido de tu JWT.
Obtención del JWT de usuario desde el navegador web
Con la generación de JWT en su lugar, podemos volver al cliente para recuperar el JWT de usuario que creamos en el servidor.
Como sería una mala práctica codificar al 100% la URL del servidor, haremos que se pueda establecer mediante una propiedad de Vue.JS con un valor predeterminado que sea útil para nuestra configuración de desarrollo.
...
callInProgress: false
}
},
props: {
jwtUrl: {
type: String,
default: process.env.VUE_APP_JWT_URL || "http://localhost:3000/no-auth"
}
},El valor jwtUrl puede sobrescribirse estableciendo una propiedad jwt-url en el elemento <CallFromBrowser> y el valor default puede cambiarse cuando se construye el componente estableciendo un valor VUE_APP_JWT_URL en un archivo .env en nuestro directorio de nivel superior. Para más información ver Vue.JS props y Variables de entorno y modos de Vue CLI 3.
Con la URL del servidor establecida, ahora podemos fetch el JWT de usuario. Vue.JS tiene varios ganchos de ciclo de vida. Recuperaremos el JWT dentro del hook created hook. Para ello definiremos una función created dentro de la definición CallFromBrowser definición.
props: {
jwtUrl: {
type: String,
default: process.env.VUE_APP_JWT_URL || "http://localhost:3000/no-auth"
}
},
created() {
fetch(this.$props.jwtUrl)
.then(response => {
return response.json();
})
.then(json => {
console.log(json)
})
.catch(error => {
console.error(error)
})
},Asegúrese de que su servidor de desarrollo de Vue.JS sigue funcionando (ejecútelo yarn serve desde el directorio call-from-browser si no es así) navega a http://localhost:8080 y abre tus herramientas de desarrollo para comprobar la consola y asegurarte de que el JWT de usuario está registrado.
JWT now present in browser and output via console.log
Cómo agregar Vonage Client SDK para JavaScript
Con el JWT de usuario en el cliente, podemos pasar a incluir el Vonage Client SDK para JavaScript:
A continuación, incluya el SDK en el componente CallFromBrowser.vue e importe la definición de objeto ConversationClient definición del objeto:
<script>
import 'vue-tel-input/dist/vue-tel-input.css'
import VueTelInput from 'vue-tel-input'
import ConversationClient from 'nexmo-stitch'Una vez que tengamos incluida la definición necesaria, tenemos que crear una nueva instancia del archivo ConversationClient y login. Haremos esto después de recuperar el JWT de usuario:
created() {
fetch(this.$props.jwtUrl)
.then(response => {
return response.json();
})
.then(json => {
this.conversationClient = new ConversationClient({debug: true})
return this.conversationClient.login(json.userJwt)
})
.then(app => {
this.app = app
// When the active member (the user) makes a call
// keep a reference to the Call object so we can
// hang up later
this.app.on("member:call", (member, call) => {
this.call = call
});
// Keep track of call status so we know how to
// interact with the call e.g. hangup
this.app.on("call:status:changed", (call) => {
this.callInProgress =
[
"machine",
"timeout",
"unanswered",
"rejected",
"busy",
"failed",
"completed"
].indexOf(call.status) === -1;
})
})
.catch(error => {
console.error(error)
})
},Una vez que se resuelve la Promesa de inicio de sesión, recibimos una referencia a una representación de la Aplicación a través de la variable app variable. Mantenemos una referencia de esa aplicación para uso futuro (this.app) y también nos enlazamos a dos eventos de la aplicación.
Nos enlazamos a member:call que se activa cuando el usuario activo realiza una llamada. En el manejador de eventos almacenamos una referencia a la llamada actual con this.call.
También enlazamos con call:status:changed para realizar un seguimiento del estado de la llamada. Dentro del manejador de eventos actualizamos la propiedad callInProgress en función del estado de la llamada. Si la llamada está en cualquiera de los estados finales, entonces la llamada no está en curso. En caso contrario, el estado de la llamada es en curso. Estos estados se reflejarán en la propiedad <button> en la plantilla.
Hacer una llamada telefónica desde el navegador web
La última cosa que tenemos que hacer en el cliente - antes de hacer una última actualización al servidor y completar la aplicación - es manejar el usuario haga clic en el botón <button>.
...
},
controlCallClick() {
if(this.callInProgress) {
this.call.hangUp()
}
else if(this.phone.isValid) {
this.app.callPhone(this.phone.number)
}
}Arriba hemos actualizado controlCallClick con la lógica para comprobar si no hay ninguna llamada en curso y el usuario ha introducido un número de teléfono válido, esto debería desencadenar la llamada. Si hay una llamada activa esto debería colgar la llamada. En ambos casos llamamos a la función apropiada en la referencia this.call que se estableció en el member:call controlador de eventos.
Con toda la funcionalidad del lado del cliente instalada, puedes introducir un número de teléfono válido, hacer clic en el botón de llamada y, a continuación, verás un error en la consola que proviene de la plataforma de Vonage
conversación:error:no encontrado
conversation-not-found message in browser console
Cuando la plataforma Nexmo inicia o recibe una llamada, realiza una solicitud HTTP a una answer_url para la aplicación Nexmo asociada correspondiente. El servidor que recibe esa solicitud HTTP debe devolver un Objeto de Control de Conversación Nexmo (NCCO); un conjunto de instrucciones que informan a Nexmo de cómo proceder con la llamada.
Conectar el navegador a un teléfono
Volver server/index.js añadir un /answer punto final para gestionar la GET solicitud de la plataforma de Vonage:
app.get('/answer', (req, res) => {
const ncco = [{
"action": "connect",
"from": process.env.NEXMO_FROM_NUMBER,
"endpoint": [{
"type": "phone",
"number": req.query.to
}]
}]
res.json(ncco)
})Nexmo espera que se le devuelva una estructura JSON, la NCCO, que le indique cómo proceder con la llamada. La estructura ncco estructura JSON que devolvemos informa a Nexmo de connect la llamada a un phone endpoint con el número identificado por el valor en req.query.to - el to parámetro de consulta en la solicitud GET entrante. Este número es el número que pasamos a this.app.callPhone en nuestra aplicación Vue.JS.
_Notas:
Recuerde que no tenemos autenticación a nivel de aplicación en nuestra aplicación web, por lo que tendrá que añadirla usted mismo, por ejemplo, en el punto final de la URL de respuesta, puede comprobar el campo
req.query.toyreq.query.frompara asegurarte de que el usuario (identificado porfrom) puede realizar la llamada solicitada.Si tiene un número de teléfono virtual Nexmo, debe añadir una entrada
NEXMO_FROM_NUMBERentrada al archivo.envpara que los destinatarios de las llamadas telefónicas vean un número en su llamada entrante. De lo contrario, puede aparecer como "Número privado" o "Desconocido".
Reinicia el proceso Node del servidor para que se ejecute con el código actualizado.
Por último, tenemos que hacer posible que la plataforma Nexmo llegue a la URL de respuesta. Para ello, utilice Ngrok para crear un túnel local a localhost:3000.
$ ngrok http 3000
Ngrok output in a terminalY actualiza el answer_url de tu Aplicación Nexmo para utilizar las URLs del túnel Ngrok usando el Nexmo CLI.
$ nexmo app:update NEXMO_APP_ID "call-from-browser" https://4ca73ac6.ngrok.io/answer https://4ca73ac6.ngrok.io/event
Nota
NEXMO_APP_IDenserver/.envoserver/.nexmo-app
Vuelve a la aplicación Vue.JS en el navegador, introduce un número de teléfono y haz clic en el botón para realizar una llamada saliente desde el navegador web.
Full Vue.JS Call from Browser application working alongside screenshot of phone ringing
Conclusión
El objetivo de esta entrada de blog era mostrar cómo crear una aplicación que permita a un usuario llamar a cualquier teléfono del planeta directamente desde un navegador web utilizando Vue.JS e In-App Voice con el Vonage Client SDK para JavaScript. Proporciona lo básico y espero que haya servido de inspiración para los casos de uso que esto puede permitir. Ah, y también puedes actualizar la aplicación para que admitir llamadas telefónicas entrantes.
Como se mencionó al principio de este artículo, In-App Voice se encuentra en la versión preliminar para desarrolladores, por lo tanto, si tienes alguna opinión sobre la experiencia que has tenido al crear esta aplicación o si tienes algún otro comentario, comunícate con la Slack de la comunidad de Vonage.
Próximo destino
Si esta entrada le ha parecido interesante, también merece la pena consultar los siguientes recursos: