package handler import ( "encoding/json" "net/http" "sync" "github.com/gorilla/websocket" "git.yeij.top/AskaEth/Cyrene/platform-bridge/internal/logging" ) var wsUpgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } // LogWSHub broadcasts log entries to connected WebSocket clients. type LogWSHub struct { mu sync.Mutex clients map[*websocket.Conn]chan logging.LogEntry } // NewLogWSHub creates a LogWSHub and subscribes to the logger. func NewLogWSHub(logger *logging.Logger) *LogWSHub { h := &LogWSHub{ clients: make(map[*websocket.Conn]chan logging.LogEntry), } logger.OnLog(func(entry logging.LogEntry) { h.broadcast(entry) }) return h } func (h *LogWSHub) broadcast(entry logging.LogEntry) { h.mu.Lock() defer h.mu.Unlock() for _, ch := range h.clients { select { case ch <- entry: default: } } } // ServeWS handles WebSocket upgrade and streams log entries to the client. func (h *LogWSHub) ServeWS(w http.ResponseWriter, r *http.Request) { conn, err := wsUpgrader.Upgrade(w, r, nil) if err != nil { return } ch := make(chan logging.LogEntry, 64) h.mu.Lock() h.clients[conn] = ch h.mu.Unlock() // Write goroutine: drains ch until it is closed. done := make(chan struct{}) go func() { defer close(done) for entry := range ch { data, _ := json.Marshal(entry) if err := conn.WriteMessage(websocket.TextMessage, data); err != nil { return } } }() // Read goroutine: detect client disconnect. // (websocket requires a reader to detect close frames.) go func() { for { if _, _, err := conn.ReadMessage(); err != nil { break } } // Client disconnected — stop broadcasting, close channel. h.mu.Lock() delete(h.clients, conn) h.mu.Unlock() close(ch) }() <-done conn.Close() }