package api import ( "encoding/json" "fmt" "net/http" "sync" ) // Event is a change notification pushed to SSE clients. type Event struct { Type string `json:"type"` ProjectID string `json:"projectId,omitempty"` } type hub struct { mu sync.Mutex clients map[chan Event]struct{} } func newHub() *hub { return &hub{clients: map[chan Event]struct{}{}} } func (h *hub) subscribe() chan Event { ch := make(chan Event, 16) h.mu.Lock() h.clients[ch] = struct{}{} h.mu.Unlock() return ch } func (h *hub) unsubscribe(ch chan Event) { h.mu.Lock() delete(h.clients, ch) h.mu.Unlock() close(ch) } func (h *hub) publish(ev Event) { h.mu.Lock() defer h.mu.Unlock() for ch := range h.clients { select { case ch <- ev: default: } } } func (s *Server) handleEvents(w http.ResponseWriter, r *http.Request) { flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming unsupported", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") ch := s.hub.subscribe() defer s.hub.unsubscribe(ch) fmt.Fprintf(w, ": ok\n\n") flusher.Flush() ctx := r.Context() for { select { case <-ctx.Done(): return case ev, ok := <-ch: if !ok { return } data, _ := json.Marshal(ev) fmt.Fprintf(w, "data: %s\n\n", data) flusher.Flush() } } }