package tools import ( "context" "encoding/json" "errors" "fmt" "net/http" "sync" "time" ) var ( ErrEventsUnsupported = errors.New("events unsupported") ) type EventHelper struct { m sync.Mutex c context.Context w http.ResponseWriter r *http.Request f http.Flusher } func NewEventHelper(ctx context.Context, w http.ResponseWriter, r *http.Request) (*EventHelper, error) { // Setup Connection flusher, ok := w.(http.Flusher) if !ok { return nil, ErrEventsUnsupported } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") sse := &EventHelper{f: flusher, c: ctx, w: w, r: r} // Heartbeat Generator go func(h *EventHelper) { t := time.NewTicker(5 * time.Second) defer t.Stop() for { select { case <-h.c.Done(): return case <-t.C: h.m.Lock() fmt.Fprintf(h.w, ": ping\n\n") h.f.Flush() h.m.Unlock() } } }(sse) return sse, nil } func (h *EventHelper) SendJSON(eventName string, eventData any) { b, err := json.Marshal(map[string]any{ "name": eventName, "data": eventData, }) if err != nil { return } h.m.Lock() fmt.Fprintf(h.w, "data: %s\n\n", b) h.f.Flush() h.m.Unlock() } func (h *EventHelper) SendServerError(err error) { SendServerError(nil, h.r, err) h.SendClientError(ERROR_GENERIC_SERVER) } func (h *EventHelper) SendClientError(err APIError) { h.SendJSON("error", err) }