WebSocket
O feed ao vivo. Uma conexão por batalha, por espectador.
wss://slotbattle.example.com/ws?battle_id=btl_CIkE8lctOrFDe1hJlG0T
Sec-WebSocket-Protocol: slotbattle.jwt, <token de viewer>
O feed roda na própria porta
/ws não roda na porta REST. Ele é servido por um servidor HTTP separado, porque uma conexão
feita para ficar aberta durante uma batalha inteira não sobrevive ao write timeout global da
superfície REST. Por padrão a API REST escuta em 8070 e o feed em 8071.
A consequência para você: use o ws_url que a API retorna em vez de montar um a partir da URL
base REST. Veja Arquitetura.
ws_url na porta REST significa que falta uma configuraçãoSe a instância não declara uma URL base pública de WebSocket, o ws_url é derivado do host da
requisição e volta apontando para a porta REST, onde nada aceita o upgrade. Verificado ao vivo:
com a configuração vazia, o control plane retornou um ws_url na própria porta REST enquanto o
feed escutava em outra.
Se o ws_url não conecta, é a primeira coisa a reportar ao seu host. Você não consegue
corrigir isso do seu lado.
O handshake
Browsers não conseguem definir headers arbitrários num handshake de WebSocket, então o token viaja na lista de subprotocolos, o único header que eles controlam:
const ws = new WebSocket(wsUrl, ['slotbattle.jwt', token])
Duas entradas: o nome fixo slotbattle.jwt, depois o token cru. O servidor negocia de volta
slotbattle.jwt.
Verificado contra uma instância em execução:
| Handshake | Resposta |
|---|---|
| Token válido | 101 Switching Protocols, Sec-WebSocket-Protocol: slotbattle.jwt |
| Sem token | 401 Unauthorized |
| Token inválido ou expirado | 401 Unauthorized |
battle_id ausente | 400 Bad Request |
Um handshake recusado responde texto puro, e não {"error":{...}}, porque é outro servidor.
Não escreva um cliente que tente fazer parse de um upgrade falho como JSON.
Um token emitido para uma batalha diferente da que está no battle_id é recusado: o claim e o
parâmetro de query precisam concordar.
O que chega
Os frames são eventos JSON. Só oito tipos chegam a um browser:
lobby.updated | Um assento mudou |
battle.started | Assentos despachados, a gravação começa |
spin.started | Um giro começou em algum assento |
segment.new | Novo segmento de vídeo disponível |
playlist.update | A playlist HLS mudou |
battle.completed | Todos os assentos reportaram |
battle.failed | A batalha falhou |
battle.cancelled | A batalha foi cancelada |
Todo o resto do vocabulário, incluindo browser.ready, capture.*, click.* e user.*, é
interno e nunca vai chegar. Veja Eventos para o envelope e a lista
completa.
ws.onmessage = (e) => {
const ev = JSON.parse(e.data)
switch (ev.type) {
case 'lobby.updated': return renderSeats(ev.data)
case 'battle.started': return startPlayers()
case 'spin.started': return flashSeat(ev.user_id)
case 'segment.new':
case 'playlist.update': return nudgePlayer(ev.user_id)
case 'battle.completed':
case 'battle.failed':
case 'battle.cancelled': return showTerminal(ev)
}
}
Não há replay
Um espectador que conecta no meio da batalha vê o que acontece dali em diante, não o histórico. Nada é bufferizado e nada é reentregue.
Projete para isso: busque o estado atual da batalha no seu backend quando a página carrega, renderize isso, e deixe o socket aplicar mudanças por cima. Um front end que assume ter visto a sequência desde o começo renderiza um lobby quebrado para quem chegou tarde ou reconectou.
Reconectando
O socket pode fechar por motivos comuns: mudança de rede, notebook dormindo, idle timeout de proxy. Um loop de reconexão que funciona:
- Reconecte com backoff e jitter, com teto de poucos segundos.
- Busque o estado da batalha de novo, porque você perdeu eventos enquanto esteve desconectado.
- Se faltar menos de um minuto para o token expirar, emita um novo pelo seu backend antes.
- Pare quando a batalha chegar a um estado terminal; não há mais nada para receber.
Um 401 na reconexão quase sempre significa token expirado, e não revogado. Emita e tente de
novo uma vez antes de mostrar erro.
Vídeo
O feed não carrega vídeo. segment.new e playlist.update informam que o stream HLS de um
assento avançou; o vídeo em si vem da CDN, no hls_url do assento.
Aponte um player HLS por assento para essa URL e deixe os eventos avisarem para buscar novos segmentos. Para uma batalha ainda em andamento, veja o padrão de playlist pelo socket em Assistir ao vivo.
Próximo
- Eventos: envelope e vocabulário completo
- Tokens de viewer: emissão e expiração
- Assistir a uma batalha ao vivo: uma implementação funcional