// Package handlers provides HTTP handlers for the rdev API. package handlers import ( "encoding/json" "fmt" "net/http" "sync" ) // streamManager manages SSE event streams. type streamManager struct { mu sync.RWMutex streams map[string][]chan streamEvent } type streamEvent struct { Type string Data map[string]any } func newStreamManager() *streamManager { return &streamManager{ streams: make(map[string][]chan streamEvent), } } func (sm *streamManager) Subscribe(streamID string) chan streamEvent { sm.mu.Lock() defer sm.mu.Unlock() ch := make(chan streamEvent, 100) sm.streams[streamID] = append(sm.streams[streamID], ch) return ch } func (sm *streamManager) Unsubscribe(streamID string, ch chan streamEvent) { sm.mu.Lock() defer sm.mu.Unlock() channels := sm.streams[streamID] for i, c := range channels { if c == ch { sm.streams[streamID] = append(channels[:i], channels[i+1:]...) close(ch) break } } } func (sm *streamManager) Send(streamID, eventType string, data map[string]any) { sm.mu.RLock() defer sm.mu.RUnlock() for _, ch := range sm.streams[streamID] { select { case ch <- streamEvent{Type: eventType, Data: data}: default: // Channel full, skip } } } func (sm *streamManager) Close(streamID string) { sm.mu.Lock() defer sm.mu.Unlock() for _, ch := range sm.streams[streamID] { close(ch) } delete(sm.streams, streamID) } // writeSSE writes a Server-Sent Event. func writeSSE(w http.ResponseWriter, flusher http.Flusher, event string, data map[string]any) { writeSSEWithID(w, flusher, "", event, data) } // writeSSEWithID writes a Server-Sent Event with an optional event ID. func writeSSEWithID(w http.ResponseWriter, flusher http.Flusher, id, event string, data map[string]any) { dataBytes, _ := json.Marshal(data) if id != "" { _, _ = fmt.Fprintf(w, "id: %s\n", id) } _, _ = fmt.Fprintf(w, "event: %s\n", event) _, _ = fmt.Fprintf(w, "data: %s\n\n", dataBytes) flusher.Flush() }