https://a.storyblok.com/f/270183/139704/be2438ae70/e_sms_nuxt-js_1200x600.png

Como enviar e receber mensagens SMS com o Nuxt.js e a SMS API da Nexmo

Publicado em February 19, 2020

Tempo de leitura: 12 minutos

Vue.js é uma das novas estruturas progressivas de JavaScript que estão ganhando destaque no mundo do front-end. É uma das estruturas mais acessíveis, versáteis e de alto desempenho que existem, e aqui na Nexmo, recentemente migramos nosso Portal do Desenvolvedor da Nexmo para usar o Vue.js.

Em 2018 nós impulsionamos o EMF Camp usando aplicativos Vue.js em conjunto com os SDKs do Nexmo Client.

Eu queria explorar um pouco mais o ecossistema do Vue.js e acabei descobrindo Nuxt.js. É um framework modular do Vue.js que facilita a configuração para uma aplicação em produção. Com o Vue.js, geralmente é necessário um componente de back-end para lidar com o modo de histórico nas URLs, e o Nuxt.js já cuida disso de fábrica. Ele também adiciona uma camada de abstração às suas URLs, fornecendo middleware prontos para uso. Middlewares são métodos que são executados antes do código de gerenciamento de rotas e funcionam tanto no código do Vue.js quanto no servidor.

Então, achei que isso seria uma excelente alternativa para lidar com chamadas de API no servidor, em vez de ter que configurar um segundo aplicativo de back-end. Nesta postagem do blog, vou usar o middleware do servidor do Nuxt.js para enviar e receber mensagens SMS.

Para SMS, vou usar a API da Nexmo SMS API, que permite enviar e receber um grande volume de SMS em qualquer lugar do mundo. Depois de obter seu número de telefone virtual, você pode usar a API para gerenciar mensagens enviadas (“envio”) e mensagens recebidas (“recebimento”).

Veja a seguir o que estamos desenvolvendo:

nexmo send sms vue

O código deste tutorial pode ser encontrado no GitHub.

Pré-requisitos

Antes de começar, certifique-se de ter:

  • A Account da Nexmo

  • Node.js instalado no seu computador

  • ngrok para tornar o código em nossa máquina local acessível ao mundo exterior

  • A versão beta do Nexmo CLI: npm install -g nexmo-cli@beta

Gerar uma nova aplicação Nuxt.js

Para facilitar o início, a equipe do Nuxt.js criou uma ferramenta CLI chamada create-nuxt-app, que cria uma estrutura inicial para um novo projeto e permite que você escolha entre todos os módulos que podem ser incluídos em um aplicativo Nuxt.js. Usei essa ferramenta para gerar um novo projeto, chamado nexmo-nuxt-sms.

$ npx create-nuxt-app nexmo-nuxt-sms

Escolhi npm como meu gerenciador de pacotes. Encontrei um ótimo componente do Tailwind CSS que que queria usar, então escolhi o Tailwind como minha estrutura de interface do usuário. Quanto a uma estrutura de servidor personalizada, optei por não usar nenhuma, seguindo a recomendação do Nuxt.js. Para módulos, escolhi axios para solicitações HTTP e dotenv para poder usar um .env arquivo para minhas variáveis de compilação. Sou fã do ESlint, então escolhi essa como minha ferramenta de linting. Não vou escrever nenhum teste para este post, então optei por não adicionar uma estrutura de testes. Escolhi Universal como meu modo de renderização porque isso me proporcionou renderização do lado do servidor (SSR) pronta para uso. Como meu editor preferido para Vue.js é o VS Code, escolhi jsconfig.json como ferramenta de desenvolvimento adicional para a última etapa do processo de criação do esqueleto.

Create a Nuxt.js App

Depois que a estrutura inicial ficou pronta, mudei para o diretório do meu novo projeto e executei o projeto usando npm run dev. Isso inicia os processos do cliente e do servidor e os torna disponíveis em http://localhost:3000. Além disso, ele fará a recarga automática sempre que eu fizer uma alteração, para que eu possa ver as mudanças em tempo real sem precisar reiniciar os processos.

$ cd nexmo-nuxt-sms
$ npm run dev

O comando gerou toda uma estrutura de diretórios, que é a base do Nuxt.js. Na pasta raiz, há nuxt.config.js, que é o arquivo de configuração do Nuxt.js. Vamos atualizá-lo para adicionar serverMiddleware. O middleware do servidor funciona especificando rotas e os arquivos JavaScript associados a serem executados quando essas rotas forem acessadas. Vamos criar duas rotas, /api/send e /api/receive, para lidar com o envio e o recebimento de mensagens SMS por meio delas. No final do arquivo, adicione uma propriedade para serverMiddleware:

export default {
  ...
  },
  serverMiddleware: [
    { path: '/api/send', handler: '~/api/send-sms.js' },
    { path: '/api/receive', handler: '~/api/receive-sms.js' }
  ]
}

Envio de uma mensagem SMS

Configuramos o Nuxt.js para usar o ~/api/send-sms.js quando for feita uma solicitação para /api/send é feita, mas ainda não criamos o arquivo. Vamos criar uma api pasta e um send-sms.js arquivo dentro dela.

$ mkdir api
$ touch send-sms.js

Para enviar mensagens SMS com a SMS API da Nexmo, vamos usar o nexmo SDK do Node.js. Precisamos instalá-lo primeiro:

$ npm install nexmo

Vamos usá-lo dentro do arquivo, e precisamos importá-lo e, em seguida, instanciá-lo com sua chave e seu segredo da API do Nexmo. Você pode encontrá-los no seu Painel do Nexmo. Atualize send-sms.js para que fique assim:

require('dotenv').config()
const Nexmo = require('nexmo')

const nexmo = new Nexmo({
  apiKey: process.env.NEXMO_API_KEY,
  apiSecret: process.env.NEXMO_API_SECRET
})

export default function (req, res) {
  console.log(req.method, req.url)
}

Estamos usando dotenv aqui para obter a chave e o segredo da API a partir do .env arquivo, em vez de inseri-los diretamente no código. Portanto, precisaremos atualizar o .env arquivo na raiz do seu projeto gerado com os valores para NEXMO_API_KEY e NEXMO_API_SECRET.

NEXMO_API_KEY=aabbcc0
NEXMO_API_SECRET=s3cRet$tuff

O arquivo exporta uma função padrão que contém os objetos padrão de solicitação e resposta do Node.js. Como eles já estão lá, e eu não queria adicionar a dependência extra do express, vamos usá-los para criar um servidor HTTP clássico do Node.js. Vamos atualizar o export no send-sms.js arquivo para que fique assim:

export default function (req, res, next) {
  console.log(req.method, req.url)
  if (req.method === 'GET') {
    const url = new URL(req.url, `http://${req.headers.host}`)

    nexmo.message.sendSms(
      process.env.FROM_NUMBER,
      url.searchParams.get('number'),
      url.searchParams.get('text'),
      (err, responseData) => {
        let message

        if (err) {
          message = JSON.stringify(err)
        } else if (responseData.messages[0].status === '0') {
          message = 'Message sent successfully.'
        } else {
          message = `Message failed with error: ${responseData.messages[0]['error-text']}`
        }

        res
          .writeHead(200, {
            'Content-Length': Buffer.byteLength(message),
            'Content-Type': 'text/plain'
          })
          .end(message)
      }
    )
  } else {
    res.statusCode = 200
    res.end()
  }
}

Estou verificando se a solicitação é uma GET solicitação aqui e, em seguida, uso a função trecho de código “Enviar um SMS” para enviar um SMS. O nexmo.message.sendSms método recebe um from, to e text para determinar o destinatário, o remetente e o conteúdo da mensagem SMS. Ele também recebe um callback método que será executado assim que a chamada à API for concluída. Estou usando o from parâmetro do .env arquivo, e esse será um número de telefone da Nexmo. O to e text vêm dos parâmetros de consulta da solicitação HTTP recebida.

Minha callback função é uma função anônima, e primeiro verifico se houve algum erro na solicitação. Se houve um erro, converto o objeto de erro em String e o passo para a mensagem de resposta. Se não houve erro, verifico o status da mensagem nos dados da resposta. Um status de 0 significa que o SMS foi enviado com sucesso, então atualizo a mensagem de resposta. Se o status não for 0, isso significa que houve um erro ao enviar a mensagem SMS da API da Nexmo, passando pelas redes das operadoras de telecomunicações, até um celular. Vou atualizar a mensagem com o texto de erro apropriado.

Como este é um servidor Node.js, preciso definir explicitamente o cabeçalho da solicitação com um 200 status, o Content-Length e Content-Type da mensagem, antes de poder enviar a mensagem na solicitação.

Há também uma alternativa para que todas as solicitações que não sejam GET retornem uma resposta vazia 200 OK resposta vazia.

Compre um número da Nexmo

Você provavelmente percebeu que usei process.env.FROM_NUMBER como meu ID de remetente, e isso significa que o Nuxt.js vai procurá-lo no .env arquivo. Antes de podermos adicioná-lo lá, precisaremos adquirir um número de telefone habilitado para SMS no Painel do Nexmo.

Também poderíamos comprar um número por meio da CLI do Nexmo, e é exatamente isso que vou fazer. Caso você ainda não tenha usado a CLI do Nexmo, é preciso configurá-la com sua chave e seu segredo da API do Nexmo antes de podermos usá-la.

$ nexmo setup NEXMO_API_KEY NEXMO_API_SECRET

Vamos usar o number:search para verificar se um número está disponível antes de comprá-lo. O comando aceita um código de país de duas letras como entrada (usei GB para números do Reino Unido), e podemos especificar alguns parâmetros para filtrar a lista de números de telefone disponíveis retornada. Estou usando --sms para marcar os números com SMS habilitado, --size=5 para limitar o tamanho da lista retornada e --verbose para retornar uma tabela bem formatada com informações adicionais sobre os números de telefone disponíveis.

$ nexmo number:search GB --sms --size=5 --verbose

A resposta que recebi foi mais ou menos assim:

Item 1-5 of 7633

msisdn       | country | cost | type       | features
------------------------------------------------------
447451272708 | GB      | 1.25 | mobile-lvn | VOICE,SMS
447451272710 | GB      | 1.25 | mobile-lvn | VOICE,SMS
447451272713 | GB      | 1.25 | mobile-lvn | VOICE,SMS
447451272714 | GB      | 1.25 | mobile-lvn | VOICE,SMS
447451272719 | GB      | 1.25 | mobile-lvn | VOICE,SMS

Escolhi o primeiro número da resposta, então vamos em frente e comprar esse número na plataforma Nexmo.

$ nexmo number:buy 447451272708 --confirm

Agora que você já tem esse número de telefone, vamos adicioná-lo ao .env arquivo.

NEXMO_API_KEY=aabbcc0
NEXMO_API_SECRET=s3cRet$tuff
FROM_NUMBER=447451272708

Podemos testar o endpoint que criamos para ter certeza de que funciona. Como se trata de uma GET solicitação, não precisamos de uma ferramenta adicional como o Postman; podemos usar a URL diretamente no navegador. Se você carregar uma URL com uma consulta como http://localhost:3000/api/send?text=hello&number=YOUR_PHONE_NUMBER, substituindo YOUR_PHONE_NUMBER pelo seu número de celular, você deve receber um SMS com o texto hello no seu celular.

Send SMS

Recebimento de uma mensagem SMS

Quando um número de telefone da Nexmo recebe uma mensagem SMS, a Nexmo encaminha essa mensagem para um Webhook que você especificou no Painel da Nexmo. Precisaremos criar o /api/receive endpoint, torná-lo publicamente acessível à Nexmo via ngrok e, em seguida, vinculá-lo no Painel da Nexmo.

Já registramos o /api/receive endpoint no middleware do servidor Nuxt.js; vamos criar o arquivo para lidar com ele. Dentro do api diretório, crie um receive-sms.js arquivo.

$ cd api
$ touch receive-sms.js

O arquivo funciona de maneira semelhante ao send-sms.js arquivo que criamos anteriormente; ele tem a mesma export default function sintaxe, recebendo um objeto de solicitação e resposta do Node.js. Vamos prosseguir e preencher o receive-sms.js arquivo com um manipulador de solicitação POST, que constrói o corpo da solicitação a partir de blocos e, em seguida, o registra no console.

export default function (req, res) {
  console.log(req.method, req.url)
  if (req.method === 'POST') {
    const body = []
    req.on('data', (chunk) => {
      body.push(chunk)
    })
    req.on('end', () => {
      const sms = JSON.parse(body)
      console.log(sms)
    })
  }

  res.statusCode = 200
  res.end()
}

Estou verificando se a solicitação recebida é uma POST solicitação e, em seguida, escuto os blocos de dados da solicitação, adicionando-os a uma body array. Quando a solicitação termina, faço a análise do body em JSON e registro isso no console. Esses serão, na prática, os dados de SMS provenientes da Nexmo. A Nexmo espera um 200 OK status na solicitação, então estou respondendo com ele.

Nosso endpoint de Webhook já está ativo no localhost, mas precisamos expô-lo à internet para que o Nexmo possa acessá-lo. Vamos usar o ngrok para fazer isso.

Executar o ngrok

Se você ainda não usou o ngrok, há um postagem no blog que explica como usá-lo. Se você já conhece o ngrok, execute-o com http na porta 3000.

$ ngrok http 3000

Depois que o ngrok for executado, ele fornecerá uma URL que parece aleatória, que usaremos mais tarde como base para o nosso Webhook. A minha é assim: http://3dea3250.ngrok.io.

Vincule o Webhook ao Nexmo

Para configurar a URL do webhook, clique no ícone de engrenagem ao lado de seus números de telefone no Painel do Nexmo e preencha o campo “URL do webhook de entrada” com YOUR_NGROK_URL/api/receive.

Ou podemos usar a CLI da Nexmo para vincular o número de telefone da Nexmo que você comprou anteriormente à URL do Webhook do ngrok:

nexmo link:sms 447451272708 http://YOUR_NGROK_URL.ngrok.io/api/receive

Você pode enviar agora uma mensagem SMS do seu celular para o seu número de telefone da Nexmo e deverá vê-la registrada no terminal onde sua aplicação Nuxt.js está em execução.

Received SMS message with Nexmo

Criação de uma interface de usuário com Vue.js

Já criamos a funcionalidade do servidor para enviar e receber mensagens SMS; agora é hora de criar uma interface de usuário para interagir com essa funcionalidade a partir do navegador.

Primeiro, vamos organizar a interface de usuário (UI) que o Nuxt.js criou para nós. Substitua o conteúdo do /layouts/default.vue arquivo por:

<template>
  <div>
    <nuxt />
  </div>
</template>

<style>
html {
  background-color: #4299e1;
}
</style>

Estou usando um modelo do Terminal do Mac do site tailwindcomponents.com, então vamos substituir o conteúdo da <template> tag no /pages/index.vue arquivo pela nova interface do usuário:

<template>
  <div class="w-1/2 mx-auto py-20">
    <div class="w-full shadow-2xl subpixel-antialiased rounded h-64 bg-black border-black mx-auto">
      <div
        id="headerTerminal"
        class="flex items-center h-6 rounded-t bg-gray-100 border-b border-gray-500 text-center text-black"
      >
        <div
          id="closebtn"
          class="flex ml-2 items-center text-center border-red-900 bg-red-500 shadow-inner rounded-full w-3 h-3"
        />
        <div
          id="minbtn"
          class="ml-2 border-yellow-900 bg-yellow-500 shadow-inner rounded-full w-3 h-3"
        />
        <div
          id="maxbtn"
          class="ml-2 border-green-900 bg-green-500 shadow-inner rounded-full w-3 h-3"
        />
        <div id="terminaltitle" class="mx-auto pr-16">
          <p class="text-center text-sm">
            <logo />Terminal
            <logo />
          </p>
        </div>
      </div>
      <div id="console" class="pl-1 pt-1 h-auto text-green-500 font-mono text-xs bg-black">
        <p class="pb-1">
          Last login: {{ new Date().toUTCString() }} on ttys002
        </p>
        <p v-for="counter in counters" :key="counter.id" class="pb-1">
          <span class="text-red-600">@lakatos88</span>
          <span class="text-yellow-600 mx-1">></span>
          <span class="text-blue-600">~/nexmo/nexmo-nuxt-sms</span>
          <span class="text-red-600 mx-1">$</span>
          <span v-if="!counter.message" class="blink" contenteditable="true" @click.once="stopBlinking" @keydown.enter.once="runCommand">_</span>
          <span v-if="counter.message">{{ counter.message }}</span>
        </p>
      </div>
    </div>
  </div>
</template>

Modifiquei um pouco o modelo para ajustar as cores à configuração do meu terminal e atualizei as informações do usuário para que também correspondam ao meu terminal.

As edições que fiz ocorreram na console div; vamos dar uma olhada nisso. Estou usando {{ new Date().toUTCString() }} para obter a data atual e exibi-la na tela.

Em seguida, estou usando a diretiva do Vue.js v-for para percorrer um counters array e exibir um sublinhado piscando ou uma mensagem na janela do terminal, para cada entrada do array counters. O sublinhado piscando possui o atributo `contenteditable`, o que significa que você pode editar seu conteúdo no navegador. Estou usando a @click para executar uma função JavaScript stopBlinking função na primeira vez que um usuário clica nela e faço com que ela pare de piscar. A mesma tag HTML possui uma @keydown.enter diretiva também, para executar uma runCommand função na primeira vez que o usuário pressionar a tecla Enter, enviando efetivamente o comando para o terminal.

Precisaremos criar o counters na estrutura de dados do Vue.js e definir os métodos para stopBlinking e runCommand. Vamos substituir a <script> no mesmo arquivo por:

<script>
import Logo from '~/components/Logo.vue'

export default {
  components: {
    Logo
  },
  data () {
    return {
      counters: [{ id: 0 }]
    }
  },
  mounted () {
  },
  methods: {
    stopBlinking (event) {
      event.target.classList.remove('blink')
      event.target.textContent = '\u00A0'
    },
    async runCommand (event) {
      const splitCommand = event.target.textContent.trim().split(' ')
      event.target.contentEditable = false
      if (splitCommand.length > 3 && splitCommand[0] === 'nexmo' && splitCommand[1] === 'sms') {
        const sms = await this.$axios.$get(`/api/send?text=${splitCommand.slice(3).join(' ')}&number=${splitCommand[2]}`)
        this.counters.push({ id: this.counters.length, message: sms })
      } else {
        this.counters.push({ id: this.counters.length, message: `Unrecognized command "${splitCommand[0]}".` })
      }
      this.counters.push({ id: this.counters.length })
    }
  }
}
</script>

O runCommand método é assíncrono e impede que o elemento HTML seja contentEditable. Ele também divide o comando do terminal em quatro partes: o nome do comando, o argumento, o número de telefone e a mensagem de texto. O método verifica se há mais de três partes no comando e se a primeira é nexmo e a segunda seja sms. Se for esse o caso, ele faz uma GET usando axios para o /api/send ponto de extremidade que criamos anteriormente, passando o texto e o número do comando. Em seguida, ele usa a mensagem recebida para exibi-la na interface do usuário.

Se o comando não for nexmo sms number text, ele exibe uma mensagem de erro genérica na interface do usuário. Feito isso, ele adiciona uma nova linha com um sublinhado piscando à interface do usuário, aguardando o próximo comando.

Também substituí o conteúdo da <style> tag para posicionar os logotipos do Nuxt.js na parte superior da janela do terminal e criar a animação de piscar para o sublinhado.

<style>
.NuxtLogo {
  width: 10px;
  height: 10px;
  position: relative;
  margin: 0 10px;
  bottom: 2px;
  display: inline-block;
}

.blink {
  animation-duration: 1s;
  animation-name: blink;
  animation-iteration-count: infinite;
}

@keyframes blink {
  from {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}
</style>

Isso permitirá que você envie mensagens SMS a partir da interface do usuário do Vue.js, mas ainda não permite receber mensagens SMS. Como o Webhook de recebimento de SMS é acionado pelo Nexmo, não podemos saber, a partir do código da interface do usuário, quando há uma nova mensagem SMS para solicitá-la. Precisaremos adicionar algum tipo de mecanismo de polling a ele.

Adicionar WebSockets

Não sou muito fã do long polling, então, em vez disso, decidi criar um par cliente/servidor WebSocket para isso. Para o servidor, estou usando o ws pacote npm, então precisaremos instalá-lo:

$ npm install ws

Para criar o servidor WebSocket, vamos editar o /api/receive-sms.js arquivo, para criar um servidor WebSocket no início do mesmo. Também estou substituindo a parte que registra o SMS no console, para enviá-lo pelo WebSocket.

const WebSocket = require('ws')
let websocket = {}
const wss = new WebSocket.Server({ port: 3001 })
wss.on('connection', (ws) => {
  websocket = ws
})

export default function (req, res, next) {
  console.log(req.method, req.url)
  if (req.method === 'POST') {
    const body = []
    req.on('data', (chunk) => {
      body.push(chunk)
    })
    req.on('end', () => {
      const sms = JSON.parse(body)
      websocket.send(`Message from ${sms.msisdn}: ${sms.text}`)
    })
  }

  res.statusCode = 200
  res.end()
}

O servidor está iniciando na porta 3001e enviará a mensagem SMS assim que terminar de montá-la a partir da solicitação. Precisaremos adicionar um cliente WebSocket à interface do usuário também, para receber a mensagem e exibi-la na interface. Vamos atualizar o /pages/index.vue arquivo, especificamente o mounted() método, para criar um cliente WebSocket assim que o componente Vue.js terminar de ser montado.

mounted () {
  console.log(process.env.WS_URL)
  const ws = new WebSocket(process.env.WS_URL)

  ws.onmessage = (event) => {
    this.counters[this.counters.length - 1].message = event.data
    this.counters.push({ id: this.counters.length })
  }
},

O cliente WebSocket se conecta ao process.env.WS_URLe define um ouvinte para mensagens. Quando há uma nova mensagem no WebSocket, ele atualiza o último comando na tela com os dados do evento recebidos do servidor, ou seja, a mensagem SMS. Ele também adiciona uma nova linha na interface do usuário, com um sublinhado piscando.

Você deve ter percebido que estamos usando o process.env.WS_URL, então precisamos adicioná-lo ao nosso .env arquivo.

WS_URL=ws://localhost:3001

Como a interface do usuário do Vue.js precisa saber da existência do arquivo de ambiente, precisamos adicionar uma entrada sobre ele ao arquivo de configuração do Nuxt.js, nuxt.config.js.

env: {
    wsUrl: process.env.WS_URL || 'ws://localhost:3001'
},

Experimente

Você pode carregar http://localhost:3000/ no seu navegador, clicar no sublinhado piscando e digitar nexmo sms YOUR_PHONE_NUMBER hello. Depois de pressionar Enter no teclado, a mensagem SMS deve ser enviada para o seu celular. Se você responder a essa mensagem, poderá vê-la aparecer também no seu navegador.

Send and Received SMS message with Nuxt.js and Nexmo

Espero que tenha dado certo e que você tenha acabado de aprender a enviar e receber mensagens SMS usando as APIs da Nexmo e o Nuxt.js.

Compartilhar:

https://a.storyblok.com/f/270183/384x384/dabe7c5397/laka.png
Alex LakatosEx-funcionários da Vonage

Alex Lakatos é um Developer Advocate de JavaScript na Nexmo. Em seu tempo livre, ele atua como voluntário na Mozilla como palestrante técnico e mentor do programa Reps. Como desenvolvedor de JavaScript que trabalha na web aberta, ele vem ampliando seus limites a cada dia. Quando não está programando em Londres, ele gosta de viajar pelo mundo; por isso, é provável que você o encontre em um lounge de aeroporto.