events

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package events is the in-process pub/sub for presence, device, audit and hbbs health events, exposed to the panel as Server-Sent Events at /api/v1/events.

Stream format (text/event-stream):

retry: 5000

id: 42
event: presence.changed
data: {"id":"123456789","online":true,"since":"...","source":"hbbs"}

event: resync
data: {"dropped":17}

: keepalive

"resync" tells the client that events were dropped for it (slow consumer) and it should refetch state over the REST API. Last-Event-ID is not supported: on reconnect clients refetch state.

Optional query parameter types=presence.changed,hbbs.health restricts the stream to the given event types.

Index

Constants

View Source
const DefaultBuffer = 256

DefaultBuffer is the per-subscriber queue length.

View Source
const (
	DefaultKeepalive = 15 * time.Second
)

Defaults for the SSE handler.

View Source
const RoleAdmin = "admin"

Role values understood by Principal.IsAdmin.

Variables

View Source
var Admins = Audience{AdminOnly: true}

Admins is the audience of admin-only events.

View Source
var ErrUnauthenticated = errors.New("events: unauthenticated")

ErrUnauthenticated is the conventional Authenticator error for "no valid session".

Functions

func Handler

func Handler(d Deps) http.Handler

Handler returns the SSE handler.

func Register

func Register(r chi.Router, d Deps)

Register mounts the SSE endpoint on r (already scoped to /api/v1/events). Note: the HTTP server has no WriteTimeout so long-lived streams work.

Types

type Audience

type Audience struct {
	// AdminOnly events are delivered to admins only.
	AdminOnly bool
	// UserIDs, when non-empty, limits delivery of a non-admin event to these
	// users (admins always receive it). Empty means every authenticated user.
	UserIDs []int64
}

Audience restricts who may receive an event.

type Authenticator

type Authenticator interface {
	Authenticate(r *http.Request) (Principal, error)
}

Authenticator resolves the panel session of a request (stream B implements it on top of the session cookie). It is called once when the stream opens and again on every keepalive, so a revoked session ends the stream.

DenyAll is the default Authenticator: every request is rejected.

type AuthenticatorFunc

type AuthenticatorFunc func(r *http.Request) (Principal, error)

AuthenticatorFunc adapts a function to Authenticator.

func (AuthenticatorFunc) Authenticate

func (f AuthenticatorFunc) Authenticate(r *http.Request) (Principal, error)

Authenticate implements Authenticator.

type Bus

type Bus struct {
	// contains filtered or unexported fields
}

Bus is an in-process fan-out pub/sub. Publish never blocks: each subscriber has a bounded queue and, when it is full, the oldest queued event is dropped and counted.

func NewBus

func NewBus(reg prometheus.Registerer) *Bus

NewBus creates a bus; reg may be nil.

func (*Bus) Publish

func (b *Bus) Publish(t Type, data any, aud Audience)

Publish stamps and delivers an event to every matching subscriber. A nil *Bus is a valid no-op publisher.

func (*Bus) Subscribe

func (b *Bus) Subscribe(filter func(Event) bool, buffer int) *Subscription

Subscribe registers a subscriber. filter (may be nil) selects events; buffer <= 0 means DefaultBuffer. Call Close when done.

func (*Bus) Subscribers

func (b *Bus) Subscribers() int

Subscribers returns the current number of subscribers.

type Deps

type Deps struct {
	Log   *slog.Logger
	Store *store.Store
	Bus   *Bus
	// Auth authenticates the stream; nil means DenyAll.
	Auth Authenticator
	// Keepalive is the comment/heartbeat interval (default 15 s).
	Keepalive time.Duration
	// Buffer is the per-stream queue length (default DefaultBuffer).
	Buffer int
}

Deps are the collaborators the SSE endpoint needs.

type DeviceUpdate

type DeviceUpdate struct {
	ID     string   `json:"id"`
	Fields []string `json:"fields,omitempty"`
}

DeviceUpdate is the payload of DeviceUpdated.

type Event

type Event struct {
	// ID is a process-local, monotonically increasing sequence number.
	ID       uint64    `json:"id"`
	Type     Type      `json:"type"`
	Time     time.Time `json:"time"`
	Data     any       `json:"data"`
	Audience Audience  `json:"-"`
}

Event is one published message.

func (Event) VisibleTo

func (e Event) VisibleTo(p Principal) bool

VisibleTo reports whether p may receive e.

type Health

type Health struct {
	// Component: "hbbs" (rendezvous port, presence poller), "hbbs-admin" or "hbbr-admin".
	Component string `json:"component"`
	Up        bool   `json:"up"`
	// RTTMillis is the round-trip time of the probe that observed the state.
	RTTMillis float64 `json:"rtt_ms,omitempty"`
	Error     string  `json:"error,omitempty"`
}

Health is the payload of HBBSHealth.

type Presence

type Presence struct {
	ID     string `json:"id"`
	Online bool   `json:"online"`
	// Since is when the device entered its current state (as observed by the panel).
	Since time.Time `json:"since"`
	// LastSeen is the last time the device was observed online (zero if never).
	LastSeen time.Time `json:"last_seen,omitempty"`
	// Source is "hbbs" (OnlineRequest) or "heartbeat" (fallback while hbbs is down).
	Source string `json:"source"`
}

Presence is the payload of PresenceChanged.

type Principal

type Principal struct {
	UserID int64
	Role   string // "admin" or "user"
}

Principal is the authenticated panel user of a stream.

func (Principal) IsAdmin

func (p Principal) IsAdmin() bool

IsAdmin reports whether p has the admin role.

type RelayState added in v0.3.0

type RelayState struct {
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	Health    string `json:"health"`
	Previous  string `json:"previous,omitempty"`
	Connected bool   `json:"connected"`
	Error     string `json:"error,omitempty"`
}

RelayState is the payload of RelayHealth (internal/relays).

type Subscription

type Subscription struct {
	// contains filtered or unexported fields
}

Subscription is one consumer's bounded queue (a ring buffer).

func (*Subscription) Close

func (s *Subscription) Close()

Close unregisters the subscription. It is idempotent.

func (*Subscription) Drain

func (s *Subscription) Drain(dst []Event) ([]Event, uint64)

Drain appends all queued events to dst and returns it together with the number of events dropped since the previous Drain.

func (*Subscription) Dropped

func (s *Subscription) Dropped() uint64

Dropped returns the total number of events dropped for this subscriber.

func (*Subscription) Next

func (s *Subscription) Next(ctx context.Context) (Event, error)

Next blocks until an event is available or ctx ends.

func (*Subscription) Ready

func (s *Subscription) Ready() <-chan struct{}

Ready is signalled (level-triggered, coalesced) when events may be queued.

type Type

type Type string

Type names an event kind. It is used as the SSE "event:" field.

const (
	// PresenceChanged: a device went online or offline (data: Presence).
	PresenceChanged Type = "presence.changed"
	// DeviceUpdated: a device row changed (data: DeviceUpdate).
	DeviceUpdated Type = "device.updated"
	// AuditConnection: a connection audit row was recorded (data: free-form object).
	AuditConnection Type = "audit.connection"
	// HBBSHealth: reachability of hbbs/hbbr changed (data: Health).
	HBBSHealth Type = "hbbs.health"
	// RelayHealth: a relay's health, agent connection or config state
	// changed (data: RelayState). Admins only.
	RelayHealth Type = "relay.health"
)

Event kinds published by the panel.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL