https://a.storyblok.com/f/270183/127644/1b77b3c798/video-chat-application-with-opentok-and-nexmo-in-app-messaging.png

Crie um aplicativo de chat por vídeo com o OpenTok e o Nexmo In-App Messaging

Publicado em May 11, 2021

Tempo de leitura: 6 minutos

Nesta postagem do blog, vamos criar um aplicativo web que permita aos usuários fazerem videochamadas e trocarem mensagens entre si usando OpenTok e Nexmo In-App Messaging.

Para ver o código completo, acesse o seguinte repositório. Você também pode conferir nosso webinar recente que aborda o aplicativo.

Pré-requisitos

Estrutura do aplicativo

Crie uma pasta e dê a ela o nome que quiser:

mkdir video-messaging-app cd video-messaging-app

Vamos criar alguns arquivos e subpastas dentro do diretório usando os seguintes comandos:

mkdir public public/js views touch public/js/index.js views/index.ejs server.js config.js

A estrutura do nosso projeto agora deve ficar assim:

video-messaging-app
├── package.json
├── package-lock.json
├── views
│   ├── index.ejs
├── public
│   ├── js
│       ├── index.js
├── config.js
├── server.js

Dependências

Vamos criar um projeto NPM e instalar todas as dependências necessárias para o projeto:

npm init -y // we use the -y flag to skip through the questions
npm install opentok @opentok/client nexmo nexmo-stitch express ejs

Agora, vamos criar nosso servidor adicionando o código do servidor ao server.js arquivo.

const OpenTok = require('opentok');
const Nexmo = require('nexmo');
const express = require('express');

const app = express();
app.use(express.static(`${__dirname}/public`));

app.get('/', (req, res) => {
 res.json({
   opentokApiKey: null,
   opentokSessionId: null,
   opentokToken: null,
   nexmoConversationId: null,
   nexmoJWT: null,
 });
});

const PORT  = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Running server on PORT: ${PORT}`));

Observe que criamos um servidor usando ExpressJS e estamos retornando credenciais vazias tanto para o OpenTok quanto para o Nexmo. Não se preocupe, vamos gerar as credenciais nas próximas etapas, mas antes disso vamos criar nosso aplicativo de mensagens do Nexmo usando a CLI do Nexmo:

nexmo app:create video-messaging-app https://example.com/answer https://example.com/event --keyfile=private.key
nexmo conversation:create display_name="Nexmo In-App Messaging"
nexmo user:create name="jamie"
nexmo member:add YOUR_CONVERSATION_ID action=invite channel='{"type":"app"}' user_id=USER_ID // make sure to replace the conversation ID and the user ID

Usando o app:create comando, obteremos o ID do aplicativo para o nosso video-messaging-app , juntamente com uma chave privada que será adicionada ao diretório. Observe que definimos as URLs de resposta e de evento como exemplos, mas podemos alterá-las posteriormente. Usando o conversation:create , também criamos uma conversa chamada Nexmo In-App Messaging. Isso resultará em um ID de conversa que usaremos mais tarde para nos conectarmos à conversa. O user:create comando também permite criar um usuário vinculado ao aplicativo. Anote o nome desse usuário, pois vamos usá-lo como parte do nosso processo de geração de JWT.

Agora vamos criar um projeto da API do OpenTok usando o painel do TokBox para que você possa obter acesso à chave e ao segredo da API.

Agora, vamos abrir nosso config.js arquivo para que possamos armazenar nossas credenciais:

module.exports = {
 opentokApiKey: '',
 opentokApiSecret: '',
 nexmoApiKey: '',
 nexmoApiSecret: '',
 nexmoApplicationId: '',
 nexmoPrivateKey: '',
 nexmoConversationId: '',
};

Certifique-se de adicionar as credenciais adequadas ao config.js arquivo

Importando as variáveis de configuração:

No nosso server.js arquivo, vamos importar as variáveis de configuração para que possamos usá-las para instanciar OpenTok e Nexmo classes.

const {
 opentokApiKey,
 opentokApiSecret,
 nexmoApiKey,
 nexmoApiSecret,
 nexmoApplicationId,
 nexmoPrivateKey,
 nexmoConversationId,
 } = require('./config');


const opentok = new OpenTok(opentokApiKey, opentokApiSecret);
const nexmo = new Nexmo({
 apiKey: nexmoApiKey,
 apiSecret: nexmoApiSecret,
 applicationId: nexmoApplicationId,
 privateKey: nexmoPrivateKey,
});

Agora vamos atualizar o caminho da solicitação GET para que possamos retornar credenciais válidas:

app.get('/', (req, res) => {
 opentok.createSession({
   mediaMode: 'routed'
 }, (error, session) => {
   if (error) {
     res.status(500).send('There was an error generating an OpenTok session');
   } else {
     const opentokSessionId = session.sessionId;
     const opentokToken = opentok.generateToken(opentokSessionId);
     const nexmoJWT = nexmo.generateJwt({
       exp: new Date().getTime() + 86400,
       acl: {
          "paths": {
            "/v1/users/**": {},
            "/v1/conversations/**": {},
            "/v1/sessions/**": {},
            "/v1/devices/**": {},
            "/v1/image/**": {},
            "/v3/media/**": {},
            "/v1/push/**": {},
            "/v1/knocking/**": {}
          }
       },
       sub: 'jamie' // this is the name we set when creating the user with the Nexmo CLI
     });
     res.json({
       opentokApiKey,
       opentokSessionId,
       opentokToken,
       nexmoConversationId,
       nexmoJWT,
     });
   }
 });
});

Usando o código acima, criaremos o seguinte sempre que alguém acessar a / caminho a partir do navegador:

  • ID da sessão do OpenTok

  • Token do OpenTok para o ID da sessão correspondente

  • Token JWT para nosso aplicativo Nexmo com as ACLs adequadas

Agora que criamos um mecanismo para obter as credenciais, vamos prosseguir e trabalhar no lado do cliente do aplicativo.

Abra o index.js arquivo localizado no js diretório.

const OT = require('@opentok/client');
const ConversationClient = require('nexmo-stitch');

const session = OT.initSession(opentokApiKey, opentokSessionId);
const publisher = OT.initPublisher('publisher');

session.on({
 streamCreated: (event) => {
   const subscriberClassName = `subscriber-${event.stream.streamId}`;
   const subscriber = document.createElement('div');
   subscriber.setAttribute('id', subscriberClassName);
   document.getElementById('subscribers').appendChild(subscriber);
   session.subscribe(event.stream, subscriberClassName);
  },
 streamDestroyed: (event) => {
   console.log(`Stream ${event.stream.name} ended because ${event.reason}.`);
  },
  sessionConnected: event => {
    session.publish(publisher);
  },
});

session.connect(opentokToken, (error) => {
 if (error) {
   console.log('error connecting to session');
 }
});

No código acima, inicializamos um OpenTok Session chamando o initSession método no OT objeto. Em seguida, criamos um publisher e definimos os seguintes ouvintes de eventos: streamCreated, streamDestroyede sessionConnected. Esses ouvintes de evento são usados para se inscrever em fluxos quando um fluxo é criado, exibir uma mensagem quando um fluxo é destruído e publicar na sessão quando estivermos conectados. Em seguida, prosseguimos com a conexão à sessão usando o token que geramos no servidor.

Agora que já adicionamos o código para o chat por vídeo, vamos adicionar o recurso de In-App Messaging.

class ChatApp {
  constructor() {
   this.messageTextarea = document.getElementById('messageTextarea');
   this.messageFeed = document.getElementById('messageFeed');
   this.sendButton = document.getElementById('send');
   this.loginForm = document.getElementById('login');
  }
}

A ChatApp classe será usada para adicionar nossos recursos de In-App Messaging. Também obteremos a referência a alguns elementos DOM que criaremos em nosso index.ejs arquivo.

Vamos adicionar alguns métodos auxiliares à ChatApp classe para registrar nossos eventos e erros no console:

errorLogger(error) {
   console.log(`There was an error ${error}`);
 }

 eventLogger(event) {
   console.log(`This event happened: ${event}`);
 }

Continuando, precisamos instanciar um ConversationClient e nos autenticar com o nexmoJWT token gerado pelo nosso servidor:

 joinConversation(userToken) {
   new ConversationClient({
     debug: false
   })
   .login(userToken)
   .then(app => {
     console.log('*** Logged into app', app)
     return app.getConversation(nexmoConversationId)
   })
   .then(this.setupConversationEvents.bind(this))
   .catch(this.errorLogger)
 }

Agora que já temos uma referência à conversa, vamos definir nossos eventos de conversa:

 setupConversationEvents(conversation) {
   console.log('*** Conversation Retrieved', conversation)
   console.log('*** Conversation Member', conversation.me)

   conversation.on('text', (sender, message) => {
     console.log('*** Message received', sender, message)
     const date = new Date(Date.parse(message.timestamp))
     const text = `${sender.user.name} @ ${date}: <b>${message.body.text}</b><br>`
     this.messageFeed.innerHTML = text + this.messageFeed.innerHTML
   });
   this.showConversationHistory(conversation);
 }

Podemos recuperar o histórico de conversas chamando o getEvents método no conversation objeto. Vamos criar um método auxiliar para que possamos exibir o histórico de bate-papo no DOM. Como você pode ver abaixo, estamos usando os diferentes types para distinguir entre os eventos:

  showConversationHistory(conversation) {
   conversation.getEvents().then((events) => {
     var eventsHistory = ""
      events.forEach((value, key) => {
       if (conversation.members.get(value.from)) {
         const date = new Date(Date.parse(value.timestamp))
         switch (value.type) {
           case 'text:seen':
             break;
           case 'text:delivered':
             break;
           case 'text':
             eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date}: <b>${value.body.text}</b><br>` + eventsHistory
             break;
            case 'member:joined':
             eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date}: <b>joined the conversation</b><br>` + eventsHistory
             break;
           case 'member:left':
             eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date}: <b>left the conversation</b><br>` + eventsHistory
             break;
           case 'member:invited':
             eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date}: <b>invited to the conversation</b><br>` + eventsHistory
             break;
            default:
             eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date}: <b>unknown event</b><br>` + eventsHistory
         }
       }
     })
      this.messageFeed.innerHTML = eventsHistory + this.messageFeed.innerHTML
   })
 }

Também devemos definir alguns eventos de usuário para saber quando o usuário final aciona alguma ação na página HTML:

 setupUserEvents() {
   this.sendButton.addEventListener('click', () => {
     this.conversation.sendText(this.messageTextarea.value).then(() => {
         this.eventLogger('text');
         this.messageTextarea.value = '';
     }).catch(this.errorLogger)
 })
 this.loginForm.addEventListener('submit', (event) => {
     event.preventDefault();
     document.getElementById('messages').style.display = 'block';
     document.getElementById('login').style.display = 'none';
     this.joinConversation(nexmoJWT);
  });
 }

Vamos nos certificar de chamar o setupUserEvents() em nosso construtor:

class ChatApp {
  constructor() {
   this.messageTextarea = document.getElementById('messageTextarea');
   this.messageFeed = document.getElementById('messageFeed');
   this.sendButton = document.getElementById('send');
   this.loginForm = document.getElementById('login');
   this.setupUserEvents();
  }
}

Vamos recapitular o que fizemos no código acima. Criamos uma classe chamada ChatApp que cria um ConversationClient que autenticamos usando o nexmoJWT token. Também definimos um ouvinte de eventos, text, no objeto de conversa para monitorar quaisquer mensagens recebidas. Observe que, para recuperar mensagens mais antigas da conversa, usamos o getEvents método. Usamos alguns ouvintes de eventos no DOM para exibir informações quando há alterações.

Agora que criamos a classe ChatApp, vamos instanciar uma instância dessa classe quando o evento onload for disparado, para que possamos usar os elementos do DOM conforme necessário.

window.onload = () => {
 new ChatApp();
}

Depois de concluir nosso index.js, vamos em frente e adicionar algumas informações ao nosso index.ejs arquivo:

<!DOCTYPE html>
<html>
  <head>
    <style>
      #login,
      #messages {
        width: 80% ; height: 300px;
      }

      #messages {
        display: none
      }

      #conversations {
        display: none
      }
    </style>
    <script type="text/javascript">
      const opentokApiKey = '<%= opentokApiKey %>';
      const opentokSessionId = '<%= opentokSessionId %>';
      const opentokToken = '<%= opentokToken %>';
      const nexmoConversationId = '<%= nexmoConversationId %>';
      const nexmoJWT = '<%= nexmoJWT %>';
    </script>
    <script src="/js/bundle.js"></script>
  </head>

  <body>
    <form id="login">
      <h1>Login</h1>
      <input type="text" name="username" value="">
      <input type="submit" value="Login" />
    </form>

    <section id="messages">
      <button id="leave">Leave Conversation</button>
      <h1>Messages</h1>

      <div id="messageFeed"></div>

      <textarea id="messageTextarea"></textarea>
      <br>
      <button id="send">Send</button>
    </section>

    <section id="conversations">
      <h1>Conversations</h1>
    </section>
  </body>
</html>

O código acima é gerado pelo nosso servidor quando alguém acessa o / caminho. Como você pode ver, passamos nossas credenciais, que usamos para a sessão do OpenTok e para o Nexmo Conversation Client.

Por fim, vamos modificar nosso servidor para renderizar a index.ejs visualização com as variáveis corretas:

res.render('index.ejs', {
    opentokApiKey,
    opentokSessionId,
    opentokToken,
    nexmoConversationId,
    nexmoJWT,
  });

Agora que já temos tudo configurado, vamos adicionar um start script ao nosso package.json arquivo para que possamos iniciar o servidor com facilidade:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "browserify public/js/index.js -o public/js/bundle.js && node server.js"
  }

Execute npm start no seu terminal e execute o aplicativo!

Conclusão

Neste blog, abordamos conceitos importantes do OpenTok e do Nexmo In-App Messaging, demonstrando a capacidade de adicionar vídeo ao vivo e mensagens no aplicativo a aplicativos web. Para ver o código completo, consulte o seguinte repositório.

Compartilhar:

https://a.storyblok.com/f/270183/384x384/63f654d765/manik.png
Manik SachdevaEx-funcionários da Vonage

Manik é engenheiro de software sênior. Ele gosta de trabalhar com desenvolvedores e criar APIs. Quando não está desenvolvendo APIs ou SDKs, é possível encontrá-lo palestrando em conferências e encontros.