Files
Extrudex/backend/internal/sse/events.go

93 lines
2.8 KiB
Go
Raw Normal View History

// Package sse provides Server-Sent Events infrastructure for real-time updates.
// Includes event types, a central broadcaster, and an HTTP handler.
package sse
import (
"encoding/json"
"time"
)
// EventType identifies the category of an SSE event.
type EventType string
const (
EventPrinterStatus EventType = "printer.status"
EventJobStarted EventType = "job.started"
EventJobCompleted EventType = "job.completed"
EventFilamentLow EventType = "filament.low"
)
// Event is a JSON-serializable SSE event pushed through the broadcaster.
type Event struct {
Type EventType `json:"type"`
Payload json.RawMessage `json:"payload"`
Timestamp time.Time `json:"timestamp"`
}
// PrinterStatusPayload carries printer online/offline/printing state.
type PrinterStatusPayload struct {
PrinterID int `json:"printer_id"`
PrinterName string `json:"printer_name"`
Status string `json:"status"` // "online", "offline", "printing"
}
// JobStartedPayload carries initial print job info.
type JobStartedPayload struct {
JobID int `json:"job_id"`
JobName string `json:"job_name"`
PrinterID int `json:"printer_id"`
SpoolID *int `json:"spool_id,omitempty"`
}
// JobCompletedPayload carries final print job data including usage.
type JobCompletedPayload struct {
JobID int `json:"job_id"`
JobName string `json:"job_name"`
PrinterID int `json:"printer_id"`
DurationSeconds *int `json:"duration_seconds,omitempty"`
TotalGramsUsed *float64 `json:"total_grams_used,omitempty"`
TotalCostUSD *float64 `json:"total_cost_usd,omitempty"`
}
// FilamentLowPayload alerts that a spool is below its threshold.
type FilamentLowPayload struct {
SpoolID int `json:"spool_id"`
SpoolName string `json:"spool_name"`
RemainingGrams int `json:"remaining_grams"`
ThresholdGrams int `json:"threshold_grams"`
}
// NewEvent creates an Event with the current timestamp from a typed payload.
func NewEvent(eventType EventType, payload any) (Event, error) {
raw, err := json.Marshal(payload)
if err != nil {
return Event{}, err
}
return Event{
Type: eventType,
Payload: raw,
Timestamp: time.Now().UTC(),
}, nil
}
// MustEvent creates an Event and panics on marshal failure (for use with
// known-good payloads in tests and internal wiring).
func MustEvent(eventType EventType, payload any) Event {
ev, err := NewEvent(eventType, payload)
if err != nil {
panic("sse.MustEvent: failed to marshal payload: " + err.Error())
}
return ev
}
// toSSE formats this Event as a standard SSE message string ready to be
// written to a response writer. The format is:
//
// event: <type>
// data: <json>
//
func (e Event) toSSE() string {
data, _ := json.Marshal(e)
return "event: " + string(e.Type) + "\n" + "data: " + string(data) + "\n\n"
}