package api import ( "log/slog" "net/http" "sync" "golang.org/x/net/websocket" ) // Hub manages WebSocket connections and broadcasts messages. type Hub struct { mu sync.RWMutex clients map[*websocket.Conn]bool logger *slog.Logger } func NewHub() *Hub { return &Hub{ clients: make(map[*websocket.Conn]bool), logger: slog.Default(), } } // Run is a no-op loop kept for future cleanup/heartbeat logic. func (h *Hub) Run() {} func (h *Hub) Register(ws *websocket.Conn) { h.mu.Lock() h.clients[ws] = true h.mu.Unlock() } func (h *Hub) Unregister(ws *websocket.Conn) { h.mu.Lock() delete(h.clients, ws) h.mu.Unlock() } // Broadcast sends a message to all connected WebSocket clients. func (h *Hub) Broadcast(msg []byte) { h.mu.RLock() defer h.mu.RUnlock() for conn := range h.clients { if _, err := conn.Write(msg); err != nil { h.logger.Error("websocket write error", "error", err) } } } // ClientCount returns the number of connected clients. func (h *Hub) ClientCount() int { h.mu.RLock() defer h.mu.RUnlock() return len(h.clients) } func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { handler := websocket.Handler(func(ws *websocket.Conn) { s.hub.Register(ws) defer s.hub.Unregister(ws) // Keep connection alive until client disconnects. buf := make([]byte, 1024) for { if _, err := ws.Read(buf); err != nil { break } } }) handler.ServeHTTP(w, r) }