Tutorial: Constrói uma aplicação de chat simples com um hub de chat

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

Tu:

  • Configure um servidor backend para emitir URLs de acesso ao cliente
  • Ligue clientes a um centro de chat Web PubSub
  • Criar uma sala
  • Enviar e receber mensagens
  • Gerir a pertença a salas

No final, terá uma experiência de chat funcional suportada pelo Azure Web PubSub.

Pré-requisitos

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

Crie um recurso Web PubSub com um centro de chat

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

Instalar dependências

Dependências de servidores

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 ao cliente

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 }
);

Porque é que este passo existe

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

O seu servidor gera uma URL de acesso ao cliente com limite de tempo que codifica a identidade do utilizador e as permissões. Esta abordagem:

  • Mantém as credenciais seguras
  • Permite que a sua aplicação controle a autenticação e a autorização
  • Alinha-se com as expectativas de segurança empresarial

Passo 2: Adicionar um endpoint de negociação

O ponto final de negociação devolve um URL de acesso para o cliente que os clientes de chat usam para se ligarem.

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,
  });
});

Porque é que este passo existe

Os clientes de chat têm de se ligar como um utilizador específico.

O endpoint de negociação é onde a sua aplicação:

  • Mapeia a identidade ao nível da aplicação para um utilizador de chat
  • Gera um URL de acesso temporário com âmbito limitado
  • Decide quem pode ligar-se

Em produção, este endpoint 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}`);
});

O seu backend está agora pronto para aceitar ligações de clientes.

Passo 4: Liga os clientes ao chat hub

No cliente, busca URLs de acesso do servidor e inicia sessão 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}`);

Porque é que este passo existe

O hub de chat Web PubSub baseia-se no modelo de ligação Web PubSub. Este passo de autenticação:

  • Estabelece uma ligação em tempo real
  • Associa a ligação a uma identidade de utilizador
  • Permite funcionalidades específicas do chat, como salas e histórico de mensagens

Passo 5: Ouça os eventos de chat

Registe os 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, pode ouvir room-left, member-joined, member-left, started e stopped. Use off com os mesmos argumentos para remover um escutador.

Porque é que este passo existe

O chat é inerentemente orientado por eventos. Estes ouvintes permitem que a sua candidatura:

  • Reagir às mensagens recebidas
  • Atualize a interface quando os utilizadores entram nas salas
  • Mantenha-se sincronizado em vários dispositivos ou separadores 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.

Porque é que este passo existe

As salas oferecem estrutura para conversas:

  • Eles definem quem recebe as mensagens.
  • Mantêm o histórico das mensagens.
  • Permitem que o chat escale para além das mensagens individuais.

Passo 7: Obter o histórico de mensagens

Obter mensagens anteriores de uma sala. listRoomMessages devolve um iterador assíncrono paginado, para que possas iterar diretamente sobre cada mensagem:

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 e depois mais 50 ao deslocar para cima"):

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

Porque é que este passo existe

Clientes recém-ligados muitas vezes precisam de contexto.

O histórico de mensagens permite que a sua aplicação:

  • Apresentar mensagens existentes
  • Retomar a conversa após reconectar
  • Suporte para utilização em vários dispositivos

Passo 8: Gerir os membros da sala

Adicionar um utilizador a uma sala:

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

Remover um utilizador de uma sala:

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

As alterações à adesão entram em vigor imediatamente.

Passo 9: Limpar

Quando o cliente já não precisa de receber mensagens:

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

O que construíste

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

  • URLs de acesso seguro ao cliente emitidos a partir de um servidor
  • Clientes ligados a um centro de chat
  • Criei e juntei-me a salas de chat
  • Mensagens enviadas e recebidas em tempo real
  • Histórico de mensagens carregadas
  • Adesão a uma sala gerida

Tudo isto sem gerir servidores WebSocket, lógica de fan-out ou persistência de mensagens.