https://a.storyblok.com/f/270183/59557/6edfde49ac/tw_live-chat_1200x675.png

Como criar um chat ao vivo na página

Publicado em May 13, 2021

Tempo de leitura: 19 minutos

A API de Conversação da Vonage Conversation API permite que desenvolvedores criem recursos de conversação em que a comunicação possa ocorrer por meio de diversos canais. Um aspecto fundamental disso é que o contexto das conversas pode ser mantido entre os diferentes canais, o que abre uma infinidade de possibilidades.

Este tutorial explicará os conceitos básicos de funcionamento da Conversation API, utilizando-a para criar um chat ao vivo básico na página como exemplo de caso de uso. Essa janela de chat permitirá que os clientes enviem mensagens a um agente de suporte em tempo real, e o agente de suporte poderá responder ao cliente.

Além disso, também abordaremos a parte relacionada ao estilo e ao layout, incluindo como criar uma janela de bate-papo que desliza para dentro e como organizar as mensagens de bate-papo em ambos os lados da interface. O código completo e a demonstração estão disponível no Glitch, então fique à vontade para adaptá-lo como quiser.

Pré-requisitos

Você precisará cumprir os seguintes pré-requisitos antes de iniciar este tutorial:

  • Tem o Node.js instalado no seu computador

  • Instale a versão beta do Vonage CLI

    npm install @vonage/cli -g
  • Configure a CLI para usar sua chave e seu segredo da API da Vonage, que estão disponíveis na página de configurações do Painel da Vonage

    vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRET

Para usar o console no Glitch da mesma forma que você faria em um computador local, clique em Ferramentase, em seguida, Logs e, por fim, Console.

Glitch consoleGlitch console

Sobre Glitch, não use a -g sinalizador ao instalar o CLI da Vonage

npm install @vonage/cli

O CLI da Vonage possui plug-ins que, quando instalados, oferecem funcionalidades adicionais. Neste tutorial, trabalharemos com o Conversations; portanto, eis o comando para instalar o plug-in correspondente:

vonage plugins:install @vonage/cli-plugin-conversations

Cenário

O que vamos criar é semelhante às interfaces dos sites que oferecem a opção de chat ao vivo, geralmente ativada ao clicar em um botão que abre uma janela de chat.

A janela de bate-papo conectará você a um agente de suporte do outro lado do portal de atendimento ao cliente, e vocês dois poderão conversar em tempo real.

Configuração inicial

Uma maneira fácil de começar o projeto é usar o Glitch, pois ele já oferece, de fábrica, uma aplicação Node.js construída com o Express. Você tem total liberdade para usar qualquer outra estrutura do Node.js que preferir, ou até mesmo criar a sua própria, mas, para este tutorial, usaremos o Express.

Primeiro, crie um novo aplicativo Vonage com o vonage apps:create comando.

vonage apps:create "Support Agent" --rtc_event_url=https://YOUR_GLITCH_PROJECT.glitch.me/webhooks/event

Vamos ver o que as opções e os parâmetros adicionais fazem. vonage apps:create "NAME_OF_APPLICATION" cria um aplicativo Vonage com o nome que você quiser dar ao seu aplicativo e é obrigatório para que o comando funcione.

O --rtc_event_url especifica a URL do evento, que é o webhook para o qual a Vonage envia todos os eventos que ocorrem no aplicativo.

Ao executar o comando, você deverá obter um resultado semelhante a este (os caminhos ficarão assim se você usar o Glitch):

Application created: aaaaaaaa-bbbb-cccc-dddd-0123456789ab ... Private Key File: .../support_agent.key

A longa sequência gerada é o ID da aplicação, que você deve anotar. Vamos nos referir a ele como YOUR_APP_ID ao longo do tutorial. A chave privada é usada para gerar JWTs, que servem para autenticar suas interações com a Vonage.

Se você executar ls -al, você deverá conseguir ver o arquivo arquivo support_agent.key na sua pasta pessoal. Mova o arquivo para um .data/ , já que o Glitch usa essa pasta para dados confidenciais.

mv support_agent.key .data/support_agent.key

O próximo comando criará um usuário. Esse usuário é o agente de suporte que sempre será adicionado ao chat. Em um cenário real, sempre haverá alguns agentes de suporte conversando com vários usuários que precisam de assistência.

vonage apps:users:create agent --display_name="Support Agent"

Isso criará um usuário e exibirá algo assim no console:

USR-00000xxx-000x-0x00-0x00-0x00xx0x0000

Anote esse ID de usuário, pois você precisará dele quando quiser adicionar o agente de suporte à conversa.

Você também pode armazenar suas credenciais no arquivo arquivo .env na raiz do projeto.

NEXMO_API_KEY="x0xx0x0x" NEXMO_API_SECRET="00x0x0x0xxx0x0000x0xx00x" NEXMO_APPLICATION_ID="aaaaaaaa-bbbb-cccc-dddd-0123456789ab" NEXMO_APPLICATION_PRIVATE_KEY_PATH=".data/private.key" SUPPORT_AGENT="USR-00000xxx-000x-0x00-0x00-0x00xx0x0000"

Configurando seu aplicativo Node

Se você tivesse começado com o modelo básico hello-express modelo Glitch, seu arquivo server.js estaria bastante simples, com apenas o Express instalado.

const express = require("express");
const app = express();

app.use(express.static("public"));

app.get("/", function(request, response) {
  response.sendFile(__dirname + "/views/index.html");
});

const listener = app.listen(process.env.PORT, function() {
  console.log("Your app is listening on port " + listener.address().port);
});

Mas este é um bom ponto de partida. Primeiro, instale a versão beta da biblioteca nexmo-node, do body-parser e de um gerador aleatório de nomes de usuário:

npm install nexmo@beta body-parser username-generator

Adicione essas dependências ao arquivo server.js :

const bodyParser = require('body-parser');
const rug = require('username-generator');

const Nexmo = require('nexmo');

Em seguida, você deve instanciar uma instância do Nexmo da seguinte maneira:

const nexmo = new Nexmo({
  apiKey: process.env.NEXMO_API_KEY,
  apiSecret: process.env.NEXMO_API_SECRET,
  applicationId: process.env.NEXMO_APPLICATION_ID,
  privateKey: process.env.NEXMO_APPLICATION_PRIVATE_KEY_PATH
});

Vamos resolver as partes do arquivo server.js , ou seja, as rotas para nossas respectivas páginas da web e a configuração do body-parser middleware para analisar os corpos das solicitações recebidas.

app.use(bodyParser.json());
app.use(
  bodyParser.urlencoded({
    extended: true
  })
);
app.use(express.static('public'));

app.get('/', function(request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

app.get('/agent', function(request, response) {
  response.sendFile(__dirname + '/views/agent.html');
});

Como você pode ver nas rotas, há duas páginas da web distintas. Em teoria, seriam duas Applications separadas, mas, para os fins deste tutorial, nós as combinamos em um único projeto.

Vamos definir uma Lista de Controle de Acesso (ACL) no arquivo arquivo server.js . Trata-se de uma lista de caminhos que correspondem à API da Vonage e são usados para gerar o JWT com as permissões adequadas.

const ACL = {
  paths: {
    '/*/users/**': {},
    '/*/conversations/**': {},
    '/*/sessions/**': {},
    '/*/devices/**': {},
    '/*/image/**': {},
    '/*/media/**': {},
    '/*/push/**': {},
    '/*/knocking/**': {},
    '/*/legs/**': {}
  }
};

Precisamos definir algumas variáveis para implementar alguma forma de persistência em memória para este aplicativo do tutorial. A visão geral do processo é a seguinte: se a conversa já existir, retorne os detalhes da conversa para a interface do usuário. Caso contrário, serão executadas as seguintes etapas:

  1. Crie um novo usuário aleatório (que atuará como um cliente solicitando suporte)

  2. Se a criação do usuário for bem-sucedida, crie uma nova conversa

  3. Se a criação da conversa for bem-sucedida, adicione o usuário recém-criado à conversa

  4. Se o usuário for adicionado com sucesso, adicione o agente de suporte à conversa

  5. Se o agente de suporte for adicionado com sucesso, retorne os detalhes da conversa ativa para a interface do usuário

let activeConversationDetails;
let agentMember;

app.route('/api/new').get((req, res) => {
  if (activeConversationDetails) {
    res.json(activeConversationDetails);
  } else {
    nexmo.users.create( /* Creates a new random user */
      {
        name: rug.generateUsername('-')
      },
      (error, user) => {
        if (error) console.log(error);

        if (user) { /* If user creation successful, create a new conversation */
          nexmo.conversations.create(
            {
              display_name: rug.generateUsername()
            },
            (error, conversation) => {
              if (error) console.log(error);

              if (conversation) { /* If conversation creation successful, add the newly created user to the conversation */
                nexmo.conversations.members.add(
                  conversation.id,
                  {
                    action: 'join',
                    user_id: user.id,
                    channel: {
                      type: 'app'
                    }
                  },
                  (error, member) => {
                    if (error) console.log(error);

                    if (member) { /* If user was successfully added, then add the support agent */
                      nexmo.conversations.members.add(
                        conversation.id,
                        {
                          action: 'join',
                          user_id: process.env.SUPPORT_AGENT,
                          channel: {
                            type: 'app'
                          }
                        },
                        (error, agent) => {
                          if (error) console.log(error);
                          const jwt = Nexmo.generateJwt( /* Generate JWT for random user, needed for logging into the client SDK */
                            process.env.NEXMO_APPLICATION_PRIVATE_KEY_PATH,
                            {
                              application_id: process.env.NEXMO_APPLICATION_ID,
                              sub: member.name,
                              exp: new Date().getTime() + 86400,
                              acl: ACL
                            }
                          );
                          if (agent) { /* If agent was successfully added, then return active conversation details */
                            agentMember = agent.id;
                            activeConversationDetails = {
                              user,
                              conversation,
                              member,
                              agent,
                              jwt
                            };
                            res.json(activeConversationDetails);
                          }
                        }
                      );
                    }
                  }
                );
              }
            }
          );
        }
      }
    );
  }
});

Para o agente de suporte, o caminho é bem mais curto.

app.route('/api/jwt/:user').get((req, res) => {
  const jwt = Nexmo.generateJwt( /* For programatically generating JWT */
    process.env.NEXMO_APPLICATION_PRIVATE_KEY_PATH,
    {
      application_id: process.env.NEXMO_APPLICATION_ID,
      sub: req.params.user,
      exp: new Date().getTime() + 86400,
      acl: ACL
    }
  );
  res.json({
    jwt: jwt,
    conversation: activeConversationDetails.conversation
  });
});

Por fim, precisamos configurar a URL do webhook, que capta todos os eventos que ocorrem no aplicativo e pode ser usada para depuração ou para o desenvolvimento de funcionalidades adicionais.

app.route('/webhooks/event').post((req, res) => {
  console.log(req.body);
});

Utilizando o Vonage Client SDK para JavaScript

O Glitch começa com um único arquivo index.html no diretório pasta views/ . Adicione outro arquivo HTML à pasta pasta views/ chamado agent.html.

Add another html fileAdd another html file

Também teremos arquivos CSS e JavaScript separados para cada página. Para evitar complexidade adicional decorrente dos carregadores de módulos, este tutorial colocou todas as funções compartilhadas em um arquivo common.js .

Sua pasta pública ficaria mais ou menos assim:

public/ |-- agent.css |-- agent.js |-- client.css |-- client.js `-- common.js

A maior parte do trabalho é feita com o Vonage Client SDK para JavaScript. Você pode instalar a biblioteca do cliente via NPM ou usar uma versão hospedada em CDN. Inclua o script tanto no arquivo index.html e agent.html

<script src="https://cdn.jsdelivr.net/npm/nexmo-client@6.0.1/dist/nexmoClient.js"></script>

As funcionalidades, tanto do lado do cliente quanto do agente, são bastante semelhantes, e algumas delas podem ser agrupadas em um arquivo JavaScript comum. Precisamos de uma função para obter os detalhes da conversa do servidor, para que possam ser utilizados na interface do usuário.

let activeConversation;

function setupConversation(apiPath) {
  fetch(apiPath) /* To generate the JWT for the agent */
    .then(function(response) {
      return response.json();
    })
    .then(function(response) {
      new NexmoClient({
        debug: false
      })
        .login(response.jwt) /* Used to log into Nexmo */
        .then(app => {
          console.log('*** Logged into app', app);
          return app.getConversation(response.conversation.id); /* Grabs conversation from Nexmo's server */
        })
        .then(conversation => {
          console.log('*** Retrieved conversations', conversation);
          activeConversation = conversation;
          setupListeners();
        })
        .catch(console.error);
    });
}

Também queremos ter alguns ouvintes que exibam o texto inserido por qualquer uma das partes em suas respectivas janelas de bate-papo. O texto é obtido a partir da carga útil retornada pela solicitação de busca do servidor Nexmo.

function setupListeners() {
  const form = document.getElementById('textentry');
  const textbox = document.getElementById('textbox');

  activeConversation.on('text', (sender, message) => {
    console.log(sender, message);
    appendMessage(
      message.body.text,
      `${sender.user.name === 'agent' ? 'agent' : 'input'}`
    );
  });

  form.addEventListener('submit', event => {
    event.preventDefault();
    event.stopPropagation();
    const inputText = textbox.value;
    activeConversation.sendText(inputText);
    textbox.value = '';
  }, false);
}

let messageId = 0;

function appendMessage(message, sender, appendAfter) {
  const messageDiv = document.createElement('div');
  messageDiv.classList = `message ${sender}`;
  messageDiv.innerHTML = '<p>' + message + '</p>';
  messageDiv.dataset.messageId = messageId++;

  const messageArea = document.getElementById('message-area');
  if (appendAfter == null) {
    messageArea.appendChild(messageDiv);
  } else {
    const inputMsg = document.querySelector(
      `.message[data-message-id='${appendAfter}']`
    );
    inputMsg.parentNode.insertBefore(messageDiv, inputMsg.nextElementSibling);
  }

  messageArea.scroll({ /* Scroll the message area to the bottom. */
    top: messageArea.scrollHeight,
    behavior: 'smooth'
  });

  return messageDiv.dataset.messageId; /* Return this message id so that a reply can be posted to it later */
}

Do lado do cliente

A interface do lado do cliente incluiria uma janela de chat que seria ativada ao clicar no botão de chat. Essa janela deslizaria da direita para a esquerda e permitiria que o cliente conversasse com o agente de suporte.

A estrutura dessa janela de bate-papo não é muito complicada. Ela possui um cabeçalho, uma área principal para mensagens e um campo para digitação de texto na parte inferior da janela.

<aside id="chatWindow">
  <div class="header">
    <h1>Live support</h1>
    <button class="btn-close" id="closeChat"><svg viewBox="0 0 47.971 47.971"><path fill="white" d="M28.228 23.986L47.092 5.122a2.998 2.998 0 000-4.242 2.998 2.998 0 00-4.242 0L23.986 19.744 5.121.88a2.998 2.998 0 00-4.242 0 2.998 2.998 0 000 4.242l18.865 18.864L.879 42.85a2.998 2.998 0 104.242 4.241l18.865-18.864L42.85 47.091c.586.586 1.354.879 2.121.879s1.535-.293 2.121-.879a2.998 2.998 0 000-4.242L28.228 23.986z"/></svg></button>
  </div>

  <div id="message-area" class="messages">
  </div>

  <div class="controls">
    <form id="textentry">
      <input id="textbox" type="text" />
      <input id="submit" type="submit" value="Send" />
    </form>
  </div>
</aside>

Não vamos abordar cada linha de CSS para isso, mas quero destacar como fazer com que a janela de bate-papo deslize lateralmente de uma maneira relativamente mais eficiente. Em geral, as propriedades que podem ser animadas com segurança são `transforms` e `opacity`.

O ideal é que a janela de bate-papo permaneça fixa enquanto o usuário rola a página principal; para isso, você usaria um position: fixed na janela de bate-papo. Além disso, você deve fazer com que a janela de bate-papo saia do quadro e a faça deslizar para dentro quando o gatilho for clicado.

aside {
  position: fixed;
  top: 0;
  right: 0;
  transform: translateX(100%);
  display: flex;
  flex-direction: column;
  min-width: 20em;
  width: 25%;
  height: 100%;
  background: var(--background);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.24);
  transition: transform 0.5s ease;
}

aside.active {
  transform: translateX(0);
}

O uso display: flex para a janela de bate-papo nos permite garantir que o cabeçalho e o rodapé fiquem sempre na parte superior e inferior da janela de bate-papo, respectivamente, enquanto a área de mensagens se expande para preencher todo o espaço disponível.

.messages {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow-y: scroll;
  padding: 1em 1.5em;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.24);
  max-height: calc(100vh - 6em);
}

Além disso, em um bate-papo típico, a conversa é apresentada em balões de mensagem que se alternam à esquerda e à direita, de acordo com os respectivos participantes do bate-papo. Esse layout também fica mais simples com o flexbox.

Mais uma vez, aplique um display: flex na área de mensagens. Isso permite que você use as propriedades de alinhamento da caixa nas mensagens individuais. Em seguida, basta aplicar um align-self: flex-end nas mensagens que você deseja que apareçam à direita da área de mensagens.

.message.input {
  position: relative;
  align-self: flex-end;
}

O JavaScript do lado do cliente para a janela de bate-papo é necessário para ativar ou desativar a classe CSS apropriada, a fim de ocultar e exibir a janela de bate-papo.

function triggerChat() {
  const button = document.getElementById('showChat');
  appendMessage('Hello! My name is James, how can I help you today?', 'agent');
  button.addEventListener('click', event => {
    const chatWindow = document.getElementById('chatWindow');
    chatWindow.classList.toggle('active');     
  }, false);
}

function closeChat() {
  const button = document.getElementById('closeChat');
  console.log(button);
  button.addEventListener('click', event => {
    const chatWindow = document.getElementById('chatWindow');
    chatWindow.classList.remove('active');
  }, false);
}

window.addEventListener('load', function() {
  triggerChat();
  closeChat();
  setupConversation('/api/new');
});

Chat exampleChat example

Do lado do agente

No lado do agente, as mensagens podem ocupar toda a janela de visualização e ser exibidas por padrão. Portanto, o arquivo de JavaScript do lado do cliente para a página do agente envolve passar a rota correta para a solicitação de busca ao servidor.

window.addEventListener('load', function() {
  setupConversation('/api/jwt/agent');
});

Quanto ao estilo das mensagens, seria bastante semelhante ao da janela de chat com o cliente, exceto que os estilos seriam invertidos, já que o padrão de design típico prevê que suas próprias mensagens apareçam à direita, enquanto as mensagens recebidas aparecem à esquerda.

Chat portal finalChat portal final

E agora, para onde vamos?

Este tutorial não utilizou nenhum framework de front-end nem carregadores de módulos, pois teve como objetivo simplificar o aplicativo para dar ênfase à Conversation API, o que ela faz e como funciona. Se você quiser explorar mais a Conversation API, aqui estão alguns links que podem ser úteis:

Compartilhar:

https://a.storyblok.com/f/270183/384x384/46621147f0/huijing.png
Hui Jing ChenEx-funcionários da Vonage

Hui Jing é Developer Advocate na Nexmo. Ela tem um amor imenso por CSS e tipografia e, de modo geral, é apaixonada por tudo que diz respeito à web.