Tutorial: Construa um aplicativo de chat simples com um hub de chat

Neste tutorial, você constrói um fluxo de chat simples em tempo real usando um hub de chat Web PubSub.

Você:

  • Configure um servidor backend para emitir URLs de acesso ao cliente
  • Conecte clientes a um hub de chat Web PubSub
  • Criar uma sala
  • Enviar e receber mensagens
  • Gerencie a associação de quartos

Ao final, você tem uma experiência de chat de trabalho respaldada pelo Azure Web PubSub.

Pré-requisitos

  • Uma assinatura de Azure
  • Node.js 18 ou posterior

Crie um recurso Web PubSub com um hub de chat

Crie um recurso Azure Web PubSub e configure um hub de chat chamado demo-chat.

Instalar dependências

Dependências de servidor

npm install express @azure/web-pubsub @azure/web-pubsub-express

Dependências do cliente

npm install @azure/web-pubsub-chat-client

Passo 1: Criar o servidor backend

O servidor backend é responsável por:

  • Autenticando usuários
  • Emissão de URLs de acesso dos clientes

Código do servidor

import express from 'express';
import { WebPubSubServiceClient } from '@azure/web-pubsub';
import { WebPubSubEventHandler } from '@azure/web-pubsub-express';

const hubName = 'demo-chat';
const port = process.env.PORT || 3000;

const connectionString = process.env.WEB_PUBSUB_CONNECTION_STRING;
if (!connectionString) {
  throw new Error('WEB_PUBSUB_CONNECTION_STRING is not set');
}

const app = express();

const serviceClient = new WebPubSubServiceClient(
  connectionString,
  hubName,
  { allowInsecureConnection: true }
);

Por que esse passo existe

Embora o serviço Web PubSub suporte conexões anônimas, o padrão de produção mais comum utiliza um modelo de acesso fornecido pelo servidor.

Seu servidor gera uma URL de acesso ao cliente com tempo limitado que codifica a identidade do usuário e as permissões. Essa abordagem:

  • Mantém as credenciais seguras
  • Permite que seu app controle autenticação e autorização
  • Está alinhado com as expectativas de segurança corporativa

Passo 2: Adicionar um endpoint de negociação

O endpoint de negociação retorna uma URL de acesso que os clientes de chat usam para se conectar.

app.get('/negotiate', async (req, res) => {
  console.log(`received negotiate request: ${JSON.stringify(req.query)}`);

  const userId = req.query.userId;
  if (!userId) {
    return res.status(400).send('Missing userId');
  }

  const token = await serviceClient.getClientAccessToken({
    userId,
  });

  res.json({
    url: token.url,
  });
});

Por que esse passo existe

Os clientes de chat devem se conectar como um usuário específico.

O ponto de extremidade de negociação é o ponto em que seu aplicativo:

  • Mapeia identidade em nível de aplicativo para um usuário de chat
  • Gera uma URL temporária de acesso com escopo definido
  • Decide quem pode se conectar

Em produção, este ponto de extremidade tipicamente:

  • Verifica autenticação (cookies, cabeçalhos, tokens)
  • Aplica regras de autorização

Passo 3: Iniciar o servidor

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

Seu backend agora está pronto para aceitar conexões de clientes.

Passo 4: Conecte os clientes ao hub de chat

No cliente, busque URLs de acesso do servidor e faça login no demo-chat hub.

import { ChatClient } from '@azure/web-pubsub-chat-client';

// Fetch a fresh client access URL from the negotiate endpoint.
const getClientAccessUrl = (userId) =>
  fetch(`/negotiate?userId=${userId}`)
    .then(r => r.json())
    .then(d => d.url);

// Option 1: start with a one-time client access URL.
const alice = await ChatClient.start(await getClientAccessUrl('alice'));
console.log(`Started as: ${alice.userId}`);

// Option 2: start with a credential so the client can refresh the URL itself.
const charlie = await ChatClient.start({
  getClientAccessUrl: () => getClientAccessUrl('charlie'),
});
console.log(`Started as: ${charlie.userId}`);

Por que esse passo existe

O hub de chat Web PubSub se baseia no modelo de conexão Web PubSub. Esta etapa de autenticação:

  • Estabelece uma conexão em tempo real
  • Associa a conexão a uma identidade de usuário
  • Permite recursos específicos do chat, como salas e histórico de mensagens

Passo 5: Ouça os eventos de chat

Registre ouvintes para receber atualizações em tempo real.

alice.on('message', (event) => {
  const msg = event.message;
  console.log(`Alice received: ${msg.createdBy}: ${msg.content.text}`);
});

alice.on('room-joined', (event) => {
  console.log(`Alice joined room: ${event.room.title}`);
});

charlie.on('message', (event) => {
  const msg = event.message;
  console.log(`Charlie received: ${msg.createdBy}: ${msg.content.text}`);
});

charlie.on('room-joined', (event) => {
  console.log(`Charlie joined room: ${event.room.title}`);
});

O cliente de chat é um emissor de eventos. Além de message e room-joined, você pode detectar room-left, member-joined, member-left, started e stopped. Use off com os mesmos argumentos para remover um ouvinte.

Por que esse passo existe

O chat é inerentemente movido por eventos. Esses detectores permitem que seu aplicativo faça:

  • Reagir às mensagens recebidas
  • Atualiza a interface do usuário quando os usuários entram nas salas
  • Mantenha a sincronização entre vários dispositivos ou abas do navegador

Passo 6: Crie uma sala e envie mensagens

Crie uma sala e adicione membros iniciais:

const room = await alice.createRoom('My Room', ['charlie']);

Envie uma mensagem para a sala:

await alice.sendToRoom(room.roomId, 'Hello!');

As mensagens são entregues em tempo real a todos os membros da sala.

Por que esse passo existe

Salas oferecem estrutura para conversas:

  • Eles definem quem recebe as mensagens.
  • Eles mantêm o histórico de mensagens.
  • Eles permitem que o chat escale além da mensagem um para um.

Passo 7: Obter histórico de mensagens

Recupere mensagens anteriores de uma sala. listRoomMessages retorna um iterador assíncrono paginado, para que você possa iterar sobre cada mensagem diretamente:

for await (const msg of alice.listRoomMessages(room.roomId)) {
  console.log(`${msg.createdBy}: ${msg.content.text}`);
}

Ou carregar o histórico uma página de cada vez (por exemplo, "carregar 50, depois mais 50 na rolagem para cima"):

const pages = alice.listRoomMessages(room.roomId).byPage({ maxPageSize: 50 });
const firstPage = await pages.next();
const messages = firstPage.value ?? [];

Por que esse passo existe

Clientes recém-conectados frequentemente precisam de contexto.

O histórico de mensagens permite que seu app:

  • Exibir mensagens existentes
  • Retomar o chat após a reconexão
  • Suporte ao uso multidispositivo

Passo 8: Gerencie os membros da sala

Adicione um usuário a uma sala:

await alice.addUserToRoom(room.roomId, 'bob');

Remover um usuário de uma sala:

await alice.removeUserFromRoom(room.roomId, 'bob');

As mudanças de membros entram em vigor imediatamente.

Passo 9: Limpar

Quando o cliente não precisa mais receber mensagens:

await alice.stop();
await charlie.stop();

O que você criou

Neste guia de início rápido, você:

  • URLs de acesso seguro ao cliente emitidas a partir de um servidor
  • Conectei clientes a um hub de chat
  • Criei e entrou em salas de bate-papo
  • Mensagens enviadas e recebidas em tempo real
  • Histórico de mensagens carregadas
  • Associação a salas gerenciadas

Tudo isso sem gerenciar servidores WebSocket, lógica de espalhamento ou persistência de mensagens.