realtime

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package realtime is an in-process WebSocket pub/sub hub for pushing server-side events (logs, sync progress) to the browser. It's mounted directly on the existing Fiber app — no separate listener/port, and no dependency beyond github.com/gofiber/contrib/v3/websocket (itself a thin fasthttp-native wrapper, so this adds nothing indirect beyond what Fiber already pulls in).

Mounting:

node := realtime.New(logger)
app.Get("/ws", node.Handler())

Publishing:

// pre-marshaled JSON, e.g. from logger.BrowserHandler
go node.DrainLogs(ctx, browserHandler.Chan())

// any JSON-marshalable type
progressCh := make(chan service.ProgressEvent, 1024)
go realtime.Drain(node, ctx, realtime.ProgressChannel, progressCh)

Wire protocol (JSON, one message per WebSocket frame, ws://<host>/ws — same origin/port as the rest of the app):

Client -> Server:
  {"type":"subscribe","channel":"logs"}
  {"type":"unsubscribe","channel":"logs"}

Server -> Client:
  {"type":"subscribed","channel":"logs"}
  {"type":"unsubscribed","channel":"logs"}
  {"type":"publication","channel":"logs","data":{...}}
  {"type":"error","error":"..."}

A client that never subscribes to anything just sits idle (ping/pong keepalive only) — subscribing is required to receive any "publication".

Index

Constants

View Source
const (
	LogChannel      = "logs"
	ProgressChannel = "sync_progress"
)

Channel names in use by this application. Publish/Drain accept any string as a channel — these two are just what's currently published.

Variables

This section is empty.

Functions

func Drain

func Drain[T any](n *Node, ctx context.Context, channel string, ch <-chan T)

Drain reads typed values from ch, JSON-marshals each one, and Publishes the result on channel. Runs until ctx is cancelled or ch is closed; intended to be called in a goroutine. It's a free function rather than a method because Go doesn't allow generic methods on a non-generic receiver. Marshal failures are logged and skipped rather than aborting the drain loop — one bad event shouldn't kill the feed.

Don't use this for already-marshaled []byte payloads (like log entries) — json.Marshal on a []byte base64-encodes it instead of passing it through. Use DrainLogs for those.

Types

type Node

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

Node is the pub/sub hub. Create one with New and mount Handler() on a route; it needs no Start/Stop of its own since it owns no listener — call Close() during graceful shutdown to disconnect clients.

func New

func New(logger *slog.Logger) *Node

New creates a Node. It doesn't open any network resources itself — the Fiber app the returned Handler is mounted on does that.

func (*Node) ClientCount

func (n *Node) ClientCount() int

ClientCount returns the number of currently connected WebSocket clients. Handy for a health/debug endpoint.

func (*Node) Close

func (n *Node) Close()

Close disconnects every currently connected client. Call this during graceful shutdown, before or alongside app.Shutdown().

func (*Node) DrainLogs

func (n *Node) DrainLogs(ctx context.Context, ch <-chan []byte)

DrainLogs reads from ch and publishes each entry to LogChannel. ch already carries pre-marshaled JSON (see logger.BrowserHandler.Handle), so this forwards the bytes as-is instead of going through Drain/json.Marshal (which would base64-encode them). Runs until ctx is cancelled or ch is closed; intended to be called in a goroutine.

func (*Node) Handler

func (n *Node) Handler() fiber.Handler

Handler returns the fiber.Handler to mount on your WebSocket route, e.g. app.Get("/ws", node.Handler()). Origins are intentionally left at the contrib package's default (allow all): if this stops being localhost-only reachable, add websocket.Config{Origins: [...]}.

func (*Node) Publish

func (n *Node) Publish(channel string, data []byte) error

Publish sends raw JSON bytes to every client currently subscribed to channel. A channel with no subscribers is a no-op; a slow client is dropped-for rather than allowed to stall delivery to everyone else (see hub.broadcast).

Jump to

Keyboard shortcuts

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