feat: tipos WSHub, WSClient, WSMessage, SSEEvent (issue 0011 fase 1)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package infra
|
||||
|
||||
// SSEEvent es un evento Server-Sent Events segun la spec W3C.
|
||||
// Campos opcionales: si Event esta vacio se envia solo data,
|
||||
// si ID esta vacio no se incluye campo id, Retry en ms (0 = omitir).
|
||||
type SSEEvent struct {
|
||||
Event string `json:"event"`
|
||||
Data string `json:"data"`
|
||||
ID string `json:"id"`
|
||||
Retry int `json:"retry"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
// WSClient representa una conexion WebSocket individual.
|
||||
// Cada cliente tiene su propio canal de envio buffereado y una
|
||||
// referencia al hub al que pertenece.
|
||||
type WSClient struct {
|
||||
Hub *WSHub
|
||||
Conn *websocket.Conn
|
||||
Send chan []byte
|
||||
ID string
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package infra
|
||||
|
||||
// WSHub gestiona el ciclo de vida de conexiones WebSocket.
|
||||
// Mantiene un mapa de clientes activos y canales para registro,
|
||||
// desregistro y broadcast. Se ejecuta como goroutine via su Run().
|
||||
type WSHub struct {
|
||||
Clients map[*WSClient]bool
|
||||
Broadcast chan []byte
|
||||
Register chan *WSClient
|
||||
Unregister chan *WSClient
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewWSHub crea un WSHub vacio con canales inicializados.
|
||||
// Ejecutar Run() en una goroutine para activar el loop de eventos.
|
||||
func NewWSHub() *WSHub {
|
||||
return &WSHub{
|
||||
Clients: make(map[*WSClient]bool),
|
||||
Broadcast: make(chan []byte, 256),
|
||||
Register: make(chan *WSClient),
|
||||
Unregister: make(chan *WSClient),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Run ejecuta el loop principal del hub. Atiende registros, desregistros
|
||||
// y broadcasts en un select. Bloqueante: lanzar como goroutine.
|
||||
// Para parar, llamar a Stop().
|
||||
func (h *WSHub) Run() {
|
||||
for {
|
||||
select {
|
||||
case <-h.done:
|
||||
// Cerrar todos los Send y vaciar el mapa
|
||||
for client := range h.Clients {
|
||||
delete(h.Clients, client)
|
||||
close(client.Send)
|
||||
}
|
||||
return
|
||||
case client := <-h.Register:
|
||||
h.Clients[client] = true
|
||||
case client := <-h.Unregister:
|
||||
if _, ok := h.Clients[client]; ok {
|
||||
delete(h.Clients, client)
|
||||
close(client.Send)
|
||||
}
|
||||
case msg := <-h.Broadcast:
|
||||
for client := range h.Clients {
|
||||
select {
|
||||
case client.Send <- msg:
|
||||
default:
|
||||
// Cliente lento: desconectar
|
||||
delete(h.Clients, client)
|
||||
close(client.Send)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop cierra el loop de Run() y limpia todos los clientes.
|
||||
// Idempotente — llamar mas de una vez no panica.
|
||||
func (h *WSHub) Stop() {
|
||||
select {
|
||||
case <-h.done:
|
||||
// Ya cerrado
|
||||
default:
|
||||
close(h.done)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package infra
|
||||
|
||||
// WSMessage es un mensaje tipado que viaja por WebSocket.
|
||||
// El campo Type permite al receptor decidir como procesar el payload.
|
||||
type WSMessage struct {
|
||||
Type string `json:"type"`
|
||||
Payload []byte `json:"payload"`
|
||||
SenderID string `json:"sender_id"`
|
||||
Ts int64 `json:"ts"`
|
||||
}
|
||||
Reference in New Issue
Block a user