https://d226lax1qjow5r.cloudfront.net/blog/blogposts/manage-a-pool-of-phone-numbers-with-node-js/Dev_Numbers_Node-js_1200x600.png

Gestionar un grupo de Numbers con Node.js

Publicado el May 5, 2021

Tiempo de lectura: 7 minutos

Puede que no siempre estés cerca del teléfono de tu oficina, y cuando este es el caso, los clientes pueden tener dificultades para ponerse en contacto contigo. En este tutorial, crearemos una aplicación que utiliza la API de administración de Numbers de Vonage para administrar varios números de teléfono enmascarados. Cada número redirigirá las llamadas a otro número, como un móvil privado que se puede usar desde casa.

También nos aseguraremos de que los usuarios de nuestra aplicación sólo puedan ver los números comprados y administrados por ella, en lugar de todos los números de tu cuenta API de Vonage. Por último, trabajaremos para asegurarnos de que solo los usuarios que conoces tengan acceso y que no sea accesible desde la web pública sin una contraseña.

The final application screenshot, showing a pool of phone numbers and management options including update and delete

¿Puedo utilizar este proyecto ahora?

El código completo de este proyecto está en Glitch. Puede visitar el proyectoy hacer clic en el botón Remezclar para editar en la parte superior derecha y añadir tus propias credenciales al archivo 🔑.env archivo. A continuación, puede utilizar el proyecto de inmediato haciendo clic en el botón Mostrar en la parte superior de la página.

También puede encontrar el código completo en GitHub.

Requisitos previos

Nota: Nexmo cambió recientemente su marca a Vonage tras ser adquirida en 2016. Notarás que en este tutorial hacemos llamadas a una URL de Nexmo, así que no te alarmes.

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Creación de un proyecto base

Existe un Glitch para que puedas ponerte en marcha rápidamente. Esta aplicación tiene:

  • Instalamos e incluimos nuestras dependencias, lo que puedes hacer en un nuevo proyecto Express abriendo el terminal de Glitch y escribiendo pnpm install express body-parser cors nedb-promises axios qs express-basic-auth.

  • Creado un nuevo nedb en la carpeta .data en Glitch. Esta carpeta es específica de tu versión de la aplicación y no puede ser vista por otros ni copiada.

  • Inicializamos una aplicación Express básica y servimos el archivo views/index.html cuando la gente navega a la URL de nuestro proyecto

  • Incluimos las librerías Vue.js y Axios en el archivo index.html creamos una nueva aplicación Vue.js y añadimos algunos estilos básicos en el archivo public/style.css archivo.

Accede a tu Account de Glitch, y luego haz clic en este enlace para remezclar (copiar) nuestra plantilla en tu Account.

Ya sea que comiences desde cero o utilices nuestra plantilla, deberás ir a tu panel de la API de Vonage, obtener tu clave y secreto de la API y colocarlos en el archivo 🔑.env de tu proyecto. Estos valores no son visibles públicamente pero se puede acceder a ellos en tu aplicación usando process.env.PROPERTY.

Cree un punto final para comprar Numbers

Este endpoint requerirá que se proporcione un country ya que es lo que requiere la API de gestión de números.

Sobre la línea final de tu aplicación, incluye el siguiente código:

app.post('/numbers', async (req, res) => {
    try {
        const { NEXMO_API_KEY, NEXMO_API_SECRET } = process.env;
        const availableNumbers = await axios.get(`https://rest.nexmo.com/number/search?api_key=${NEXMO_API_KEY}&api_secret=${NEXMO_API_SECRET}&country=${req.body.country}&features=SMS,VOICE`);
        const msisdn = availableNumbers.data.numbers[0].msisdn;
        res.send(msisdn);
    } catch (err) {
        res.send(err);
    }
});

Cuando envíe una solicitud POST a /numbersla aplicación realizará una solicitud GET a la API de gestión de números para encontrar un MSISDN (número de teléfono) disponible y devolverá el primero.

Abre tu terminal y ejecuta el siguiente comando para probar el nuevo endpoint de la API: curl -H "Content-Type: application/json" -X POST -d '{"country": "GB"}' https://YOUR_GLITCH_PROJECT_NAME.glitch.me/numbersAsegúrate de sustituir el nombre de tu proyecto Glitch. Si tiene éxito, debería devolver un número de teléfono disponible.

Sustituya res.send(msisdn) por lo siguiente:

await axios({
    method: 'POST',
    url: `https://rest.nexmo.com/number/buy?api_key=${NEXMO_API_KEY}&api_secret=${NEXMO_API_SECRET}`,
    data: qs.stringify({ country: req.body.country, msisdn }),
    headers: { 'content-type': 'application/x-www-form-urlencoded' }
});
await db.insert({ msisdn });
res.send('Number successfully bought');

Toma el primer MSISDN de los resultados, lo compra del crédito de cuenta disponible y almacena un nuevo registro de base de datos para el MSISDN. El paquete qs formatea los datos como una cadena x-www-form-encoded que es lo que requiere la API de gestión de números.

¡Punto de control! Repite la llamada de API a tu aplicación desde la terminal. Deberías recibir un mensaje de éxito y un nuevo número debería estar accesible en tu cuenta de API de Vonage.

Nota: existen múltiples razones por las que la llamada a la API de Vonage puede fallar en tu aplicación y que no tienen nada que ver con tu código. Verifica si puedes usar la API de administración de números para obtener un número en tu país. Si aún así no funciona, es posible que puede necesitar una dirección y significa que debes obtener el número a través del panel de la API de Vonage

Crear una interfaz para comprar Numbers

Puede que tu endpoint de petición POST funcione bien, pero es hora de crear un frontend más amigable para usarlo. Abra views/index.html y añade lo siguiente a tu HTML:

<div id="app">
    <h1>Number Manager</h1>
    <section>
        <h2>Buy New Number</h2>
        <input type="text" v-model="country" placeholder="Country Code" />
        <button @click="buyNumber">Buy new number</button>
    </section>
</div>

Actualice el contenido de su <script> a lo siguiente:

const app = new Vue({
    el: '#app',
    data: {
        country: ''
    },
    methods: {
        async buyNumber() {
            try {
                if(this.country && confirm('Are you sure you would like to buy a number?')) {
                    await axios.post('/numbers', {
                        country: this.form.country
                    })
                    alert('Successfully bought new number');
                }
            } catch(err) {
                alert('Error buying new number', err);
            }
        }
    }
})

Abra la aplicación haciendo clic en Mostrar en la parte superior de tu ventana de Glitch. Escriba "GB" en la casilla y haga clic en "Comprar nuevo número". La función confirm() avisa al usuario con un cuadro emergente y es una buena práctica para evitar compras accidentales. Aunque esta aplicación utiliza Vue.js, puedes crear cualquier aplicación que pueda realizar peticiones HTTP.

Construir un Endpoint para listar Numbers

Cree un nuevo endpoint en su aplicación Express antes de la última línea de código:

app.get("/numbers", async (req, res) => {
    try {
        res.send('ok');
    } catch (err) {
        res.send(err);
    }
});

En la parte superior del try bloque, recupera todas las entradas de la base de datos local y todos los números de la API de administración de números de Vonage para API de Vonage.

const { NEXMO_API_KEY, NEXMO_API_SECRET } = process.env;
const dbNumbers = await db.find();
const vonageNumbers = await axios.get(`https://rest.nexmo.com/account/numbers?api_key=${NEXMO_API_KEY}&api_secret=${NEXMO_API_SECRET}`);

A continuación, cree una nueva matriz que filtre vonageNumbers a sólo aquellos que también aparecen en la base de datos local. Al hacer esto, te aseguras de devolver solo los números de esta cuenta de API de Vonage que son administrados por esta aplicación.

const numbersInBothResponses = vonageNumbers.data.numbers.filter(vonageNumber => {
    return dbNumbers.map(dbNumber => dbNumber.msisdn).includes(vonageNumber.msisdn)
});

A continuación, cree un objeto que agrupe ambas fuentes de datos para cada número:

const combinedResponses = numbersInBothResponses.map(vonageNumber => {
    return {
        ...vonageNumber,
        ...dbNumbers.find(dbNumber => dbNumber.msisdn == vonageNumber.msisdn)
    }
})

combinedResponses contiene ahora datos que es bueno enviar al usuario, así que sustituye res.send('ok'); por res.send(combinedResponses);.

Construir un frontend para listar Numbers

En su archivo index.html cree un nuevo método para obtener los números de nuestro punto final Express:

async getNumbers() {
    const { data } = await axios.get('/numbers')
    this.numbers = data;
}

Actualice el data objeto a lo siguiente:

data: {
    numbers: [],
    country: ''
}

Cargue estos datos añadiendo una función created() justo debajo del objeto data objeto:

created() {
    this.getNumbers();
}

Añade lo siguiente a tu HTML para mostrar los números:

<section>
    <h2>Current Numbers</h2>
    <div class="number" v-for="number in numbers" :key="number.msisdn">
        <h3>{{number.msisdn}}</h3>
        <label for="name">Friendly Name</label>
        <input type="text" v-model="number.name" placeholder="New name">
        <label for="forward">Forwarding Number</label>
        <input type="text" v-model="number.voiceCallbackValue" placeholder="Update forwarding number">
    </div>
</section>

Punto de control Haga clic en Mostrar en la parte superior de tu editor de Glitch y abre tu aplicación frontend. Cuando se cargue, deberías ver tus números de teléfono gestionados.

Por último, para esta sección, actualice el método buyNumber() para incluir this.getNumbers(); después del éxito alert(). Una vez que compre un nuevo número, la lista se actualizará sin necesidad de actualizar la página.

Creación de un endpoint y un frontend para actualizar Numbers

Esta aplicación admite dos tipos de actualizaciones de números de teléfono. Cuando actualice el nombre descriptivo de un número, estará editando entradas en la base de datos local, y cuando actualice el número de reenvío, estará actualizando el número a través de la API de gestión de números. Nuestro endpoint debe soportar ambos y utilizará los datos pasados para decidir cuál actualizar. En server.js añada lo siguiente:

app.patch("/numbers/:msisdn", async (req, res) => {
    try {
        const { NEXMO_API_KEY, NEXMO_API_SECRET } = process.env;
        if(req.body.name) {
            await db.update({ msisdn: req.params.msisdn }, { $set: { name: req.body.name } })
        }
        if(req.body.forward) {
            await axios({
                method: "POST",
                url: `https://rest.nexmo.com/number/update?api_key=${NEXMO_API_KEY}&api_secret=${NEXMO_API_SECRET}`,
                data: qs.stringify({ 
                    country: req.body.country, 
                    msisdn: req.params.msisdn,
                    voiceCallbackType: 'tel',
                    voiceCallbackValue: req.body.forward
                }),
                headers: { "content-type": "application/x-www-form-urlencoded" }
            })
        }
        res.send('Successfully updated')
    } catch(err) {
        res.send(err)
    }
})

Este endpoint PATCH incluye el número de teléfono que está actualizando. Si el cuerpo contiene una propiedad name se actualizará la base de datos local, y si contiene una propiedad forwardla configuración del número se actualizará a través de la API de gestión de números.

En index.htmlcrea el siguiente método:

async updateNumber(number) {
    try {
        const { msisdn, country, name, voiceCallbackValue } = number
        const payload = { country }
        if(name) payload.name = name
        if(voiceCallbackValue) payload.forward = voiceCallbackValue
        await axios.patch(`/numbers/${msisdn}`, payload)
        alert('Successfully updated number');
        this.getNumbers(); 
    } catch(err) {
        alert('Error updating number', err);
    }
}

También debe llamar a este método desde la plantilla - lo que ocurrirá cuando un usuario pulse enter mientras está centrado en una de las entradas de texto. Actualiza las entradas a lo siguiente:

<label for="name">Friendly Name</label>
<input type="text" v-model="number.name" @keyup.enter="updateNumber(number)" placeholder="New name">
<label for="forward">Forwarding Number</label>
<input type="text" v-model="number.voiceCallbackValue" @keyup.enter="updateNumber(number)" placeholder="Update forwarding number">

Punto de control Actualiza el nombre amigable de un número. A continuación, intente actualizar el número de reenvío (recuerde que debe estar en un formato válido)

Creación de un endpoint y un frontend para cancelar Numbers

Cuando un número ya no es necesario, puede optar por cancelarlo, lo que lo libera inmediatamente de su cuenta. Esta es la última parte clave de la gestión de su grupo de números de teléfono virtuales. En server.js añada lo siguiente encima de la última línea de código:

app.delete("/numbers/:msisdn", async (req, res) => {
    try {
        const { NEXMO_API_KEY, NEXMO_API_SECRET } = process.env;
        await axios({
            method: "POST",
            url: `https://rest.nexmo.com/number/cancel?api_key=${NEXMO_API_KEY}&api_secret=${NEXMO_API_SECRET}`,
            data: qs.stringify({ 
                country: req.body.country, 
                msisdn: req.params.msisdn
            }),
            headers: { "content-type": "application/x-www-form-urlencoded" }
        })
        res.send('Successfully cancelled')
    } catch(err) {
        res.send(err)
    }
})

En index.html añada un deleteNumber() método:

async deleteNumber(number) {
    try {
        if(confirm('Are you sure you would like to delete this number?')) {
            const { msisdn, country } = number
            await axios.delete(`/numbers/${msisdn}`, { data: { country } })
            alert('Successfully deleted number')
            this.getNumbers()
        }
    } catch(err) {
        alert('Error deleting number', err);
    }
}

Por último, añada un botón en la plantilla justo debajo de la entrada del número de reenvío:

<button @click="deleteNumber(number)">Delete number</button>

¡Punto de control! Borra un número.

Es posible que hayas notado que no estás eliminando el número de la base de datos local. Puedes elegir implementar esto, pero como el punto final GET Numbers solo devuelve números que existen tanto en tu cuenta de API de Vonage como en la base de datos local, no se devolverán los números eliminados.

Limpieza

Esta aplicación está casi terminada, pero quedan un par de cosas por hacer.

Permitir sólo llamadas a la API desde nuestro frontend

Actualmente, cualquiera puede abrir su terminal y gestionar tus Numbers sin permiso. Cerca de la parte superior de server.jsjusto debajo de las declaraciones app.use() añada lo siguiente:

app.use(cors({ origin: `https://${process.env.PROJECT_NAME}.glitch.me` }));

process.env.PROJECT_NAME es una variable de entorno proporcionada por Glitch y es igual al nombre de este proyecto. Esta configuración sólo permite peticiones desde nuestra URL de Glitch.

Añadir autenticación básica

Incluso si la gente no puede acceder a su API desde sus propias aplicaciones, todavía pueden tropezar con su sitio en vivo. Afortunadamente, la configuración de la autenticación HTTP básica solo consta de dos pasos.

En primer lugar, añada una frase de contraseña en su archivo 🔑.env archivo. A continuación, añada la siguiente línea al final de las app.use() declaraciones:

app.use(basicAuth({ users: { admin: process.env.ADMIN_PASSWORD }, challenge: true }));

Ahora, cuando cargues tu aplicación, tendrás que dar admin como nombre de usuario y la contraseña proporcionada.

¿Y ahora qué?

Esta sencilla aplicación satisfará las necesidades de la mayoría de los equipos, pero seguro que puede introducir algunas mejoras:

  • Permitir la compra de números sólo a determinados usuarios

  • Confirmar el coste de cada número antes de la compra

  • Añadir más datos a cada número de nuestra base de datos local

  • Mejor gestión de errores

Recuerda que el código completo de este proyecto también está en GitHub.

Puedes obtener más información sobre la API de gestión de números para las API de Vonage en nuestra documentacióny si necesitas asistencia adicional, no dudes en comunicarte con nuestro equipo a través de nuestro cuenta de Twitter para desarrolladores de Vonage o la cuenta de Slack de la comunidad de Vonage.

Compartir:

https://a.storyblok.com/f/270183/400x400/c822f15b89/kevinlewis.png
Kevin LewisAntiguos alumnos de Vonage

Antiguo defensor de los desarrolladores de Vonage, donde su función era apoyar a la comunidad tecnológica local de Londres. Es un experimentado organizador de eventos, jugador de mesa y padre de un precioso perrito llamado Moo. También es el principal organizador de You Got This, una red de eventos sobre las habilidades básicas necesarias para una vida laboral feliz y saludable.