Transmit
Transmit é um módulo nativo Server-Sent-Event (SSE) opinativo criado para AdonisJS. É uma maneira simples e eficiente de enviar atualizações em tempo real para o cliente, como notificações, mensagens de chat ao vivo ou qualquer outro tipo de dados em tempo real.
NOTA
A transmissão de dados ocorre apenas do servidor para o cliente, não o contrário. Você precisa usar um formulário ou uma solicitação de busca para obter a comunicação do cliente para o servidor.
Instalação
Instale e configure o pacote usando o seguinte comando :
node ace add @adonisjs/transmit
Veja as etapas executadas pelo comando add
Instala o pacote
@adonisjs/transmit
usando o gerenciador de pacotes detectado.Registra o provedor de serviços
@adonisjs/transmit/transmit_provider
dentro do arquivoadonisrc.ts
.Cria um novo arquivo
transmit.ts
dentro do diretórioconfig
.
Você também terá que instalar o pacote do cliente Transmit para escutar eventos no lado do cliente.
npm install @adonisjs/transmit-client
Configuração
A configuração do pacote de transmissão é armazenada no arquivo config/transmit.ts
.
Veja também: Config stub
import { defineConfig } from '@adonisjs/transmit'
export default defineConfig({
pingInterval: false,
transport: null,
})
pingInterval
O intervalo usado para enviar mensagens de ping para o cliente. O valor está em milissegundos ou usando um formato de string Duration
(por exemplo: 10s
). Defina como false
para desabilitar mensagens de ping.
transport
O Transmit suporta sincronização de eventos em vários servidores ou instâncias. Você pode habilitar o recurso referenciando a camada de transporte desejada (somente redis
é suportado por enquanto). Defina como null
para desabilitar a sincronização.
import env from '#start/env'
import { defineConfig } from '@adonisjs/transmit'
import { redis } from '@adonisjs/transmit/transports'
export default defineConfig({
transport: {
driver: redis({
host: env.get('REDIS_HOST'),
port: env.get('REDIS_PORT'),
password: env.get('REDIS_PASSWORD'),
keyPrefix: 'transmit',
})
}
})
NOTA
Certifique-se de ter ioredis
instalado ao usar o transporte redis
.
Registrar rotas
Você precisa registrar as rotas de transmissão para permitir que o cliente se conecte ao servidor. As rotas são registradas manualmente.
// start/routes.ts
import transmit from '@adonisjs/transmit/services/main'
transmit.registerRoutes()
Você também pode registrar cada rota manualmente vinculando o controlador manualmente.
// start/routes.ts
const EventStreamController = () => import('@adonisjs/transmit/controllers/event_stream_controller')
const SubscribeController = () => import('@adonisjs/transmit/controllers/subscribe_controller')
const UnsubscribeController = () => import('@adonisjs/transmit/controllers/unsubscribe_controller')
router.get('/__transmit/events', [EventStreamController])
router.post('/__transmit/subscribe', [SubscribeController])
router.post('/__transmit/unsubscribe', [UnsubscribeController])
Se você quiser modificar a definição da rota, por exemplo, para usar o Rate Limiter
e o middleware auth para evitar abuso de algumas rotas de transmissão, você pode alterar a definição da rota ou passar um retorno de chamada para o método transmit.registerRoutes
.
// start/routes.ts
import transmit from '@adonisjs/transmit/services/main'
transmit.registerRoutes((route) => {
// Certifique-se de que você está autenticado para registrar seu cliente
if (route.getPattern() === '__transmit/events') {
route.middleware(middleware.auth())
return
}
// Adicione um middleware de aceleração a outras rotas de transmissão
route.use(throttle)
})
Canais
Os canais são usados para agrupar eventos. Por exemplo, você pode ter um canal para notificações, outro para mensagens de bate-papo e assim por diante. Eles são criados na hora quando o cliente os assina.
Nomes de canais
Os nomes de canais são usados para identificar o canal. Eles diferenciam maiúsculas de minúsculas e devem ser uma string. Você não pode usar caracteres especiais ou espaços no nome do canal, exceto /
. A seguir estão alguns exemplos de nomes de canais válidos:
import transmit from '@adonisjs/transmit/services/main'
transmit.broadcast('global', { message: 'Hello' })
transmit.broadcast('chats/1/messages', { message: 'Hello' })
transmit.broadcast('users/1', { message: 'Hello' })
DICA
Os nomes de canais usam a mesma sintaxe que route no AdonisJS, mas não estão relacionados a eles. Você pode definir livremente uma rota http e um canal com a mesma chave.
Autorização de canal
Você pode autorizar ou rejeitar uma conexão a um canal usando o método authorize
. O método recebe o nome do canal e o HttpContext
. Ele deve retornar um valor booleano.
// start/transmit.ts
import transmit from '@adonisjs/transmit/services/main'
import Chat from '#models/chat'
import type { HttpContext } from '@adonisjs/core/http'
transmit.authorize<{ id: string }>('users/:id', (ctx: HttpContext, { id }) => {
return ctx.auth.user?.id === +id
})
transmit.authorize<{ id: string }>('chats/:id/messages', async (ctx: HttpContext, { id }) => {
const chat = await Chat.findOrFail(+id)
return ctx.bouncer.allows('accessChat', chat)
})
Transmitindo eventos
Você pode transmitir eventos para um canal usando o método broadcast
. O método recebe o nome do canal e os dados para enviar.
import transmit from '@adonisjs/transmit/services/main'
transmit.broadcast('global', { message: 'Hello' })
Você também pode transmitir eventos para qualquer canal, exceto um, usando o método broadcastExcept
. O método recebe o nome do canal, os dados para enviar e o UID que você deseja ignorar.
transmit.broadcastExcept('global', { message: 'Hello' }, 'uid-of-sender')
Sincronizando entre vários servidores ou instâncias
Por padrão, a transmissão de eventos funciona apenas no contexto de uma solicitação HTTP. No entanto, você pode transmitir eventos em segundo plano usando o serviço transmit
se registrar um transport
na sua configuração.
A camada de transporte é responsável por sincronizar eventos entre vários servidores ou instâncias. Ele funciona transmitindo quaisquer eventos (como eventos transmitidos, assinaturas e cancelamentos de assinaturas) para todos os servidores ou instâncias conectados usando um Message Bus
.
O servidor ou instância responsável pela conexão do seu cliente receberá o evento e o transmitirá ao cliente.
Transmit Client
Você pode ouvir eventos no lado do cliente usando o pacote @adonisjs/transmit-client
. O pacote fornece uma classe Transmit
. O cliente usa a API EventSource
por padrão para se conectar ao servidor.
import { Transmit } from '@adonisjs/transmit-client'
export const transmit = new Transmit({
baseUrl: window.location.origin
})
DICA
Você deve criar apenas uma instância da classe Transmit
e reutilizá-la em todo o seu aplicativo.
Configurando a Instância Transmit
A classe Transmit
aceita um objeto com as seguintes propriedades:
baseUrl
A URL base do servidor. A URL deve incluir o protocolo (http ou https) e o nome do domínio.
uidGenerator
Uma função que gera um identificador exclusivo para o cliente. A função deve retornar uma string. O padrão é crypto.randomUUID
.
eventSourceFactory
Uma função que cria uma nova instância EventSource
. O padrão é a WebAPI EventSource
. Você precisa fornecer uma implementação personalizada se quiser usar o cliente em Node.js
, React Native
ou qualquer outro ambiente que não suporte a API EventSource
.
eventTargetFactory
Uma função que cria uma nova instância EventTarget
. O padrão é o WebAPI EventTarget
. Você precisa fornecer uma implementação personalizada se quiser usar o cliente em Node.js
, React Native
ou qualquer outro ambiente que não suporte a API EventTarget
. Retorne null
para desabilitar a API EventTarget
.
httpClientFactory
Uma função que cria uma nova instância HttpClient
. É usada principalmente para fins de teste.
beforeSubscribe
Uma função que é chamada antes de assinar um canal. Ela recebe o nome do canal e o objeto Request
enviado ao servidor. Use esta função para adicionar cabeçalhos personalizados ou modificar o objeto de solicitação.
beforeUnsubscribe
Uma função que é chamada antes de cancelar a assinatura de um canal. Ela recebe o nome do canal e o objeto Request
enviado ao servidor. Use esta função para adicionar cabeçalhos personalizados ou modificar o objeto de solicitação.
maxReconnectAttempts
O número máximo de tentativas de reconexão. O padrão é 5
.
onReconnectAttempt
Uma função que é chamada antes de cada tentativa de reconexão e recebe o número de tentativas feitas até o momento. Use esta função para adicionar lógica personalizada.
onReconnectFailed
Uma função que é chamada quando as tentativas de reconexão falham. Use esta função para adicionar lógica personalizada.
onSubscribeFailed
Uma função que é chamada quando a assinatura falha. Ela recebe o objeto Response
. Use esta função para adicionar lógica personalizada.
onSubscription
Uma função que é chamada quando a assinatura é bem-sucedida. Ela recebe o nome do canal. Use esta função para adicionar lógica personalizada.
onUnsubscription
Uma função que é chamada quando o cancelamento da assinatura é bem-sucedido. Ela recebe o nome do canal. Use esta função para adicionar lógica personalizada.
Criando uma assinatura
Você pode criar uma assinatura para um canal usando o método subscription
. O método recebe o nome do canal.
const subscription = transmit.subscription('chats/1/messages')
await subscription.create()
O método create
registra a assinatura no servidor. Ele retorna uma promessa de que você pode await
ou void
.
NOTA
Se você não chamar o método create
, a assinatura não será registrada no servidor e você não receberá nenhum evento.
Ouvindo eventos
Você pode ouvir eventos na assinatura usando o método onMessage
que recebe uma função de retorno de chamada. Você pode chamar o método onMessage
várias vezes para adicionar diferentes retornos de chamada.
subscription.onMessage((data) => {
console.log(data)
})
Você também pode ouvir um canal apenas uma vez usando o método onMessageOnce
que recebe uma função de retorno de chamada.
subscription.onMessageOnce(() => {
console.log('I will be called only once')
})
Parar de ouvir eventos
Os métodos onMessage
e onMessageOnce
retornam uma função que você pode chamar para parar de ouvir um retorno de chamada específico.
const stopListening = subscription.onMessage((data) => {
console.log(data)
})
// Pare de ouvir
stopListening()
Excluindo uma assinatura
Você pode excluir uma assinatura usando o método delete
. O método retorna uma promessa que você pode await
ou void
. Este método cancelará o registro da assinatura no servidor.
await subscription.delete()
Evitando a interferência do GZip
Ao implantar aplicativos que usam @adonisjs/transmit
, é importante garantir que a compactação do GZip não interfira no tipo de conteúdo text/event-stream
usado por Eventos Enviados pelo Servidor (SSE). A compactação aplicada ao text/event-stream
pode causar problemas de conexão, levando a desconexões frequentes ou falhas do SSE.
Se sua implantação usar um proxy reverso (como Traefik ou Nginx) ou outro middleware que aplique GZip, certifique-se de que a compactação esteja desabilitada para o tipo de conteúdo text/event-stream
.
Exemplo de configuração para o Traefik
traefik.http.middlewares.gzip.compress=true
traefik.http.middlewares.gzip.compress.excludedcontenttypes=text/event-stream
traefik.http.routers.my-router.middlewares=gzip