streaming

package module
v1.12.9 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 34 Imported by: 2

README

Forge Streaming, a WebSocket and Server-Sent Events extension for Go with rooms, channels, presence and message replay

Forge Streaming: WebSocket and SSE real-time messaging for Go

Forge Streaming is the real-time messaging extension for Forge, the Go backend framework. It gives your app ready-made WebSocket and Server-Sent Events (SSE) endpoints and a Manager that handles chat rooms, pub/sub channels, presence, typing indicators, message history and gap-free replay after a reconnect. You can run it on one node with the local backend, or across several with Redis. Whatever your clients send goes through size limits, per-user rate limiting and room authorization before you or anyone else sees it broadcast.

Go CI Go Reference Apache 2.0

Documentation · Quick start · Features · Wire protocol · Rooms and identity · Backends · Configuration · FAQ

Quick start

You need Go 1.26 or later and a Go module:

go get github.com/xraph/forge@latest
go get github.com/xraph/forge/extensions/streaming@latest

Save this as main.go:

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/xraph/forge"
	"github.com/xraph/forge/extensions/streaming"
)

func main() {
	stream := streaming.NewExtension(streaming.WithLocalBackend())

	app := forge.NewApp(forge.AppConfig{
		Name:        "live-updates",
		HTTPAddress: ":8080",
		Extensions:  []forge.Extension{stream},
	})

	// The extension builds its manager when the app registers it, so mount
	// the built-in handlers from a start hook: WebSocket on /ws, SSE on
	// /events, plus POST /events/subscribe and /events/unsubscribe.
	err := forge.OnStarted(app, "streaming-routes", func(_ context.Context, a forge.App) error {
		return stream.RegisterRoutes(a.Router(), "/ws", "/events")
	})
	if err != nil {
		log.Fatal(err)
	}

	// Send a message to every open WebSocket and SSE connection.
	err = app.Router().POST("/announce", func(ctx forge.Context) error {
		msg := streaming.NewEventMessage("announcement", ctx.Query("text"))

		if err := stream.Manager().Broadcast(ctx.Context(), msg); err != nil {
			return err
		}

		return ctx.JSON(http.StatusAccepted, map[string]string{"status": "sent"})
	})
	if err != nil {
		log.Fatal(err)
	}

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

Run it, open an SSE stream in one terminal and send from another:

go mod tidy
go run .

curl -N http://localhost:8080/events
curl -X POST 'http://localhost:8080/announce?text=deploy+finished'

The stream first prints a connected event with your connection and session IDs, then the announcement:

event: connected
data: {"conn_id":"a5ef...","resumed":false,"session_id":"389f..."}

event: message
data: {"id":"","type":"message","event":"announcement","user_id":"","data":"deploy finished","timestamp":"..."}

If you connect a WebSocket client to ws://localhost:8080/ws, it gets the same JSON as a text frame. Call RegisterRoutes, RegisterHook and RegisterCodec from a start hook like this one. The extension creates its logger, hook registry and manager during registration, so if you call them before the app starts you get a nil pointer panic.

Features

You need Forge Streaming gives you
WebSocket and SSE endpoints RegisterRoutes(router, wsPath, ssePath), built on Forge's core router streaming
Chat rooms Create, join, leave, archive and search rooms, member roles, invites, mute and ban
Pub/sub channels Channel subscriptions per connection, broadcast to subscribers
Presence and typing indicators Online, away, busy and offline status that tracks every tab a user has open, and typing state with a timeout
Message history and replay Per-room sequence numbers, history queries, and replay of missed messages from an SSE Last-Event-ID
Protection on the inbound path Message size cap (64 KB), per-user rate limit (100 messages a second), room and send authorization, and identity stamped by the server
Backpressure A bounded send queue per connection, so one slow client cannot stall a broadcast
Multiple nodes A Redis backend with a cross-node coordinator, plus load balancer strategies
Graceful shutdown Connections are drained (15 seconds by default) before the manager stops
Extension points Connection, message, room, presence and error hooks, pluggable codecs, and an AsyncAPI spec from AsyncAPISpec()

Wire protocol

Every frame is a JSON streaming.Message:

Field Meaning
type Transport kind: message, join, leave, typing, presence, system or error
event Your domain event name, such as order.created. Build these with streaming.NewEventMessage
room_id, channel_id Where the message goes
user_id, id, timestamp Set by the server on inbound messages. Whatever the client sends is overwritten
data The payload
sequence Position within the room, assigned when a room message is saved

A WebSocket client sends one of these:

{"type": "join", "room_id": "lobby"}
{"type": "message", "room_id": "lobby", "data": {"text": "hi"}}
{"type": "typing", "room_id": "lobby", "data": true}
{"type": "presence", "data": "away"}
{"type": "leave", "room_id": "lobby"}

When you send a room message, the server saves it to history (if history is on) and broadcasts it to the room. A message with a channel_id goes to that channel's subscribers. When the server rejects one of your frames, it sends back a type: "error" frame whose data has a stable code (rate_limited, message_too_large, send_denied, room_access_denied, muted, banned, feature_disabled and a few more) and, for rate limits, retry_after_ms.

The SSE endpoint takes query parameters: rooms=a,b and channels=x,y to join on connect, session_id to resume a session, and last_event_id (or the standard Last-Event-ID header, which EventSource sends for you) to replay what you missed. If the server cannot honour your cursor, the stream sends a resync_required event so your client knows to reload. A browser EventSource can't send frames, so it changes subscriptions with POST {ssePath}/subscribe and /unsubscribe, passing {"conn_id": "...", "rooms": [...], "channels": [...]}.

Rooms and identity

Rooms must exist before anyone joins them, so you create them from your own code:

err := stream.Manager().CreateRoom(ctx, streaming.NewLocalRoom(streaming.RoomOptions{
	ID:    "lobby",
	Name:  "Lobby",
	Owner: "system",
}))

RoomOptions also takes Private, MaxMembers, Description and Metadata. The default room authorizer lets anyone into a public room and only members into a private one. From the server you can push into a room with Manager().BroadcastToRoom, read history with GetHistory(ctx, roomID, streaming.HistoryQuery{Limit: 50}), and reach one user on every device with SendToUser.

Joining a room needs a user. The built-in handlers read the caller's ID from the user_id value on the Forge context, and a connection without one is anonymous: it can receive broadcasts, but JoinRoom refuses it and the SSE subscribe endpoints answer 404. Put middleware on the group or the routes that authenticates the caller and calls ctx.Set("user_id", ...), and the built-in handlers pick it up. If you'd rather own the handler, call router.WebSocket yourself, wrap the connection with streaming.NewConnection(conn), call SetUserID, then Manager().Register it and pass every inbound message through Manager().ProcessInbound before you act on it.

Hooks and codecs

Hooks let you reject connections, transform or drop messages, and watch room and presence changes as they happen. A hook implements Name() plus any of ConnectionHook, MessageHook, RawMessageHook, RoomHook, PresenceHook or ErrorHook:

type auditHook struct{ log forge.Logger }

func (h auditHook) Name() string { return "audit" }

func (h auditHook) OnConnect(ctx context.Context, conn streaming.Connection) error {
	h.log.Info("stream connected", forge.F("conn_id", conn.ID()))
	return nil // return an error to refuse the connection
}

func (h auditHook) OnDisconnect(ctx context.Context, conn streaming.Connection) {}

Register it with stream.RegisterHook(auditHook{log: app.Logger()}) from a start hook. JSON is the default codec. Binary and text codecs are built in, and RegisterCodec adds your own for a content type that clients ask for with ?content_type= or an X-Content-Type header.

Backends

Backend Option Status
local WithLocalBackend() In-memory stores on one node. The default
redis WithRedisBackend(url) Redis stores for rooms, channels, history, presence and typing, plus a coordinator that relays messages between nodes. Turns distributed mode on
nats WithNATSBackend(urls...) Not implemented. Registration fails with a backend error

With more than one node, give each a stable WithNodeID (one is generated otherwise) and consider WithLoadBalancer("consistent_hash") or "sticky". least_connections is also available, and round_robin currently uses least connections too. WithSessionResumption(ttl) keeps a disconnected session's rooms and channels for the TTL so a client that comes back with the same session_id gets them restored. Sessions are kept in memory on the node.

Configuration

You can configure the extension in code with options, or in YAML under extensions.streaming (the extension also reads a top-level streaming key).

Option Sets
WithLocalBackend(), WithRedisBackend(url), WithNATSBackend(urls...) Backend, URLs and distributed mode
WithBackend(string), WithBackendURLs(...string), WithAuthentication(user, pass) The same, field by field
WithFeatures(rooms, channels, presence, typing, history) Feature switches, all on by default
WithConnectionLimits(perUser, roomsPerUser, channelsPerUser) 5, 50, 100 by default
WithMessageLimits(maxSize, maxPerSecond) 65536 bytes, 100 a second
WithTimeouts(ping, pong, write) 30s, 10s, 10s
WithBufferSizes(read, write) 4096, 4096
WithMessageRetention(d), WithPresenceTimeout(d), WithTypingTimeout(d) 720h, 5m, 3s
WithNodeID(string), WithLoadBalancer(strategy), WithSessionResumption(ttl) Multi-node settings
WithTLS(cert, key, ca) TLS for the backend connection
WithRequireConfig(bool), WithConfig(streaming.Config) Require YAML, or replace the whole config

A few settings have no option and go through WithConfig or YAML: max_anonymous_connections (1000), max_total_connections (0, unbounded), drain_timeout (15s) and max_messages_per_room (100000).

Two of these are only reported, never enforced: message_retention (WithMessageRetention) and max_messages_per_room show up on the dashboard, but nothing trims history by age or count, so room history grows until you delete it.

extensions:
  streaming:
    backend_urls: ["localhost:6379"]   # host:port, not a redis:// URL
    node_id: api-1
    max_total_connections: 20000
    enable_session_resumption: true

The YAML keys are the snake_case names from the struct tags. The file overrides DefaultConfig() and your options override the file, so backend: redis or max_message_size works there too. Fields you leave out keep their defaults.

Forge reads config.yaml only when EnableConfigAutoDiscovery is on. forge.DefaultAppConfig() turns it on, but an AppConfig literal like the one in the quick start leaves it off.

Observability

Counters and gauges go through Forge's metrics under streaming.: connections.active, connections.total, messages.broadcast, messages.room_broadcast, messages.rate_limited, messages.oversize, rooms.joins, rooms.joins_denied, sendqueue.dropped, sessions.resumed, replay.messages and more. Manager().GetStats(ctx) returns a snapshot, and the extension's health check fails when the manager is unhealthy.

If you run the Forge dashboard, the extension implements RegisterContractContributor, which serves the streaming pages of the dashboard contract.

Documentation

Topic Where
Overview Streaming docs
Features and usage Streaming features
Configuration reference Streaming configuration
Function reference Streaming functions
Go API pkg.go.dev
Core router streaming Forge streaming guide

Examples

The chat example serves a single-page chat UI on / with WebSocket and SSE routes and a small REST API for rooms, history and members. It's part of this module, so GOWORK=off go run ./examples/chat from this directory starts it on port 8080. Treat it as a sketch: its create-room endpoint doesn't create a room yet, and nothing in front of its routes sets user_id, so its connections arrive anonymous. Add middleware as shown in Rooms and identity if you want to try rooms with it.

FAQ

How do I add WebSockets to a Go app built on Forge?

Add streaming.NewExtension() to your app and call RegisterRoutes(router, "/ws", "/events") from a start hook. You get a WebSocket endpoint and an SSE endpoint that share one connection manager. The quick start is a complete program.

Should I use WebSocket or Server-Sent Events?

Use SSE when the browser only listens: it reconnects on its own and replays missed room messages from Last-Event-ID. Use WebSocket when clients also send, for chat, typing indicators or presence updates. Both transports receive the same broadcasts.

Can I scale Forge Streaming across several servers?

Yes, with the Redis backend. WithRedisBackend("localhost:6379") stores rooms, history and presence in Redis and relays broadcasts between nodes. Pass a plain host:port: the backend hands the string to go-redis as an address, so a redis:// URL fails on every call, and only the first address is used. The NATS backend is not implemented yet.

How do clients catch up after a dropped connection?

Over SSE, room messages carry a sequence number as the event ID. When the browser reconnects with Last-Event-ID, the server replays what that connection's rooms missed, or sends resync_required when it cannot. Over WebSocket, reconnect with the same session_id and turn on WithSessionResumption to get your rooms and channels back.

How do I authenticate WebSocket connections?

Put middleware on the routes (or their group) that checks the caller's token and calls ctx.Set("user_id", id). The built-in WebSocket and SSE handlers read that value, so the connection joins rooms as that user. Rooms and identity has the details, and the alternative of writing your own handler. Keep in mind that a WebSocket client has to answer the server's JSON ping: one that only listens is closed after about 40 seconds with the default timings.

Contributing

Streaming is its own Go module inside the Forge repository. Build, vet and test it from its directory the way CI does:

cd extensions/streaming
GOWORK=off go build ./...
GOWORK=off go vet ./...
GOWORK=off go test -short -count=1 ./...
golangci-lint run ./...

Forge's AGENTS.md has the pinned lint version and the rest of the repository conventions. Commits follow Conventional Commits, for example fix(streaming): ....

License

Forge Streaming is part of Forge and is available under the Apache License 2.0.

Documentation

Index

Constants

View Source
const (
	TransportWebSocket = "websocket"
	TransportSSE       = "sse"
)

Transport type constants.

View Source
const (
	StatusOnline  = internal.StatusOnline
	StatusAway    = internal.StatusAway
	StatusBusy    = internal.StatusBusy
	StatusOffline = internal.StatusOffline
)

Status constants.

View Source
const (
	MessageTypeMessage  = internal.MessageTypeMessage
	MessageTypeJoin     = internal.MessageTypeJoin
	MessageTypeLeave    = internal.MessageTypeLeave
	MessageTypeTyping   = internal.MessageTypeTyping
	MessageTypePresence = internal.MessageTypePresence
	MessageTypeSystem   = internal.MessageTypeSystem
	MessageTypeError    = internal.MessageTypeError
)

Message type constants.

All seven kinds `internal` declares, deliberately: `TransportKinds` needs the complete set, and a reserved set that silently listed only some of them would be wrong in the direction that matters. A kind missing from it is a frame the client reports as an unknown message forever. Exporting them all also spares a caller outside this package spelling any value of `Message.Type` as a string literal.

View Source
const (
	ContentTypeJSON     = internal.ContentTypeJSON
	ContentTypeBinary   = internal.ContentTypeBinary
	ContentTypeText     = internal.ContentTypeText
	ContentTypeMsgPack  = internal.ContentTypeMsgPack
	ContentTypeProtobuf = internal.ContentTypeProtobuf
)

Content type constants.

View Source
const (
	// DefaultSendQueueCapacity is the number of frames a connection may have
	// queued for the socket before the overflow policy kicks in.
	DefaultSendQueueCapacity = 256

	// DefaultSendQueueFlushTimeout bounds how long Close waits for already
	// queued frames to reach the socket before the connection is torn down.
	// It exists so a final frame (a kick notice, a close reason) is not
	// discarded, without letting one slow consumer stall connection cleanup.
	DefaultSendQueueFlushTimeout = time.Second

	// DefaultSendQueueExitTimeout bounds how long Close waits for the writer
	// goroutine to exit after the underlying connection has been closed.
	DefaultSendQueueExitTimeout = 5 * time.Second
)

Send queue defaults.

View Source
const LifecycleMetadataKey = "lifecycle"

LifecycleMetadataKey is the Metadata key under which NewLifecycleMessage records the lifecycle name.

Exported so neither the client nor any other consumer has to match a bare string literal against an envelope this package owns. A literal spelled in three places is a rename away from a reader that silently finds nothing, and a lifecycle frame whose name cannot be read is indistinguishable from one that never carried a name at all.

View Source
const (
	// ManagerKey is the DI key for the streaming manager.
	ManagerKey = "streaming"
)

DI container keys for streaming extension services.

Variables

View Source
var (
	ErrInvalidConfig         = internal.ErrInvalidConfig
	ErrBackendNotFound       = internal.ErrBackendNotConnected
	ErrBackendTimeout        = internal.ErrBackendTimeout
	ErrBackendUnavailable    = internal.ErrBackendUnavailable
	ErrLockAcquisitionFailed = internal.ErrLockAcquisitionFailed
	ErrLockNotHeld           = internal.ErrLockNotHeld
	ErrNodeNotFound          = internal.ErrNodeNotFound
	ErrInvalidChannel        = internal.ErrInvalidChannel
	ErrInvalidRoom           = internal.ErrInvalidRoom
	ErrInvalidMessage        = internal.ErrInvalidMessage
	ErrPresenceNotFound      = internal.ErrPresenceNotFound
	ErrInvalidStatus         = internal.ErrInvalidStatus
	ErrInvalidPermission     = internal.ErrInvalidPermission
	ErrInsufficientRole      = internal.ErrInsufficientRole
	ErrMessageTooLarge       = internal.ErrMessageTooLarge
	ErrMessageNotFound       = internal.ErrMessageNotFound
)

Backend, validation and lookup errors.

View Source
var (
	ErrConnectionNotFound     = internal.ErrConnectionNotFound
	ErrConnectionClosed       = internal.ErrConnectionClosed
	ErrConnectionLimitReached = internal.ErrConnectionLimitReached
	ErrInvalidConnection      = internal.ErrInvalidConnection
)

Connection errors.

View Source
var (
	ErrRoomNotFound      = internal.ErrRoomNotFound
	ErrRoomAlreadyExists = internal.ErrRoomAlreadyExists
	ErrRoomFull          = internal.ErrRoomFull
	ErrNotRoomMember     = internal.ErrNotRoomMember
	ErrAlreadyRoomMember = internal.ErrAlreadyRoomMember
	ErrRoomLimitReached  = internal.ErrRoomLimitReached
)

Room errors.

View Source
var (
	ErrChannelNotFound      = internal.ErrChannelNotFound
	ErrChannelAlreadyExists = internal.ErrChannelAlreadyExists
	ErrNotSubscribed        = internal.ErrNotSubscribed
	ErrAlreadySubscribed    = internal.ErrAlreadySubscribed
	ErrChannelLimitReached  = internal.ErrChannelLimitReached
)

Channel errors.

View Source
var (
	ErrRoomAccessDenied    = internal.ErrRoomAccessDenied
	ErrChannelAccessDenied = internal.ErrChannelAccessDenied
	ErrSendDenied          = internal.ErrSendDenied
	ErrUserMuted           = internal.ErrUserMuted
	ErrUserBanned          = internal.ErrUserBanned
	ErrSessionNotOwned     = internal.ErrSessionNotOwned
)

Authorization errors.

View Source
var (
	ErrRoomsDisabled    = internal.ErrRoomsDisabled
	ErrChannelsDisabled = internal.ErrChannelsDisabled
	ErrHistoryDisabled  = internal.ErrHistoryDisabled
	ErrPresenceDisabled = internal.ErrPresenceDisabled
	ErrTypingDisabled   = internal.ErrTypingDisabled
)

Feature-flag errors.

View Source
var (
	ErrInviteNotFound = internal.ErrInviteNotFound
	ErrInviteExpired  = internal.ErrInviteExpired
)

Invite errors.

View Source
var (
	NewConnectionError = internal.NewConnectionError
	NewRoomError       = internal.NewRoomError
	NewChannelError    = internal.NewChannelError
	NewMessageError    = internal.NewMessageError
	NewBackendError    = internal.NewBackendError
)

Error constructors.

View Source
var (
	DefaultPresenceOptions = internal.DefaultPresenceOptions
	DefaultTypingOptions   = internal.DefaultTypingOptions
)

Default option functions.

View Source
var (
	// ErrSendQueueClosed is returned when a frame is enqueued on a connection
	// whose writer has already stopped.
	ErrSendQueueClosed = errors.New("streaming: connection send queue closed")

	// ErrSendQueueOverflow is returned when a non-droppable frame arrives on a
	// full queue. The connection is disconnected; the client is expected to
	// reconnect and resynchronise.
	ErrSendQueueOverflow = errors.New("streaming: connection send queue overflow")
)

Send queue errors.

View Source
var ErrPermissionDenied = internal.ErrPermissionDenied

ErrPermissionDenied is returned when the caller may not perform an action.

View Source
var ErrRateLimitExceeded = internal.ErrRateLimitExceeded

ErrRateLimitExceeded is returned when a connection exceeds its rate limit.

Functions

func IsTransportKind added in v1.9.5

func IsTransportKind(kind string) bool

IsTransportKind reports whether kind is one of the reserved transport kinds.

Useful to a producer choosing an Event name: a domain event that collides with a reserved kind is unbindable on the client, because a frame naming it cannot be told apart from the transport frame of the same name.

func NewLocalRoom

func NewLocalRoom(opts RoomOptions) *local.LocalRoom

NewLocalRoom creates a room backed by the in-process local store.

func NewMember added in v1.9.5

func NewMember(opts streaming.MemberOptions) streaming.Member

NewMember creates a Member suitable for handing to any RoomStore.

func NewSSEConnection added in v0.10.0

func NewSSEConnection(stream forge.Stream, remoteAddr, localAddr string) forge.Connection

NewSSEConnection creates a new SSE connection adapter.

func NewSubscription added in v1.9.5

NewSubscription creates a Subscription suitable for handing to any ChannelStore.

func TransportKinds added in v1.9.5

func TransportKinds() []string

TransportKinds returns the reserved values of Message.Type, in the order they are declared.

Mirrored by TRANSPORT_KINDS in packages/client-core/src/streaming.ts, which drops a frame whose name resolves to one of these through the Type fallback. The mirror is asserted in frame_test.go rather than trusted: an eighth kind added to this package and not to that set would arrive at the client as a frame name no binding can claim, and be reported as an unknown message on every channel for as long as it existed.

A fresh slice per call, so a caller ranging over it cannot reorder the set every other consumer reads.

Types

type ActivityInfo

type ActivityInfo = internal.ActivityInfo

Presence types.

type AnalyticsEvent

type AnalyticsEvent = internal.AnalyticsEvent

Analytics types.

type AnalyticsQuery

type AnalyticsQuery = internal.AnalyticsQuery

Query types.

type AnalyticsResult

type AnalyticsResult = internal.AnalyticsResult

Analytics types.

type Availability

type Availability = internal.Availability

Presence types.

type BackendError

type BackendError = internal.BackendError

BackendError wraps backend-related errors with context.

type BinaryCodec added in v1.3.0

type BinaryCodec struct{}

BinaryCodec handles raw binary data. On decode, it stores the raw bytes in msg.RawData and sets the content type. On encode, it returns msg.RawData directly.

func (*BinaryCodec) ContentType added in v1.3.0

func (c *BinaryCodec) ContentType() string

func (*BinaryCodec) Decode added in v1.3.0

func (c *BinaryCodec) Decode(data []byte, msg *streaming.Message) error

func (*BinaryCodec) Encode added in v1.3.0

func (c *BinaryCodec) Encode(msg *streaming.Message) ([]byte, error)

type Channel

type Channel = internal.Channel

Core interfaces.

type ChannelStore

type ChannelStore = internal.ChannelStore

Store interfaces.

type Codec added in v1.3.0

type Codec interface {
	// ContentType returns the MIME type this codec handles (e.g. "application/json").
	ContentType() string
	// Encode serializes a message to bytes.
	Encode(msg *streaming.Message) ([]byte, error)
	// Decode deserializes bytes into a message.
	Decode(data []byte, msg *streaming.Message) error
}

Codec handles encoding and decoding messages for a specific content type.

type CodecRegistry added in v1.3.0

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

CodecRegistry manages codecs by content type and provides encode/decode dispatch based on message content type.

func NewCodecRegistry added in v1.3.0

func NewCodecRegistry() *CodecRegistry

NewCodecRegistry creates a new codec registry pre-loaded with a JSON codec as default.

func (*CodecRegistry) Decode added in v1.3.0

func (r *CodecRegistry) Decode(data []byte, msg *streaming.Message) error

Decode deserializes bytes into a message using the default codec. If decoding fails with the default codec, it returns the error.

func (*CodecRegistry) DecodeWithType added in v1.3.0

func (r *CodecRegistry) DecodeWithType(contentType string, data []byte, msg *streaming.Message) error

DecodeWithType deserializes bytes into a message using the codec for the given content type.

func (*CodecRegistry) Default added in v1.3.0

func (r *CodecRegistry) Default() Codec

Default returns the default codec (JSON).

func (*CodecRegistry) Encode added in v1.3.0

func (r *CodecRegistry) Encode(msg *streaming.Message) ([]byte, error)

Encode serializes a message using the codec matching msg.ContentType, or the default codec if ContentType is empty.

Callers that resolve a content type from somewhere other than the message — notably a connection's SetContentType preference — must use EncodeWithType instead, or the resolved type is discarded here.

func (*CodecRegistry) EncodeWithType added in v1.9.5

func (r *CodecRegistry) EncodeWithType(contentType string, msg *streaming.Message) ([]byte, error)

EncodeWithType serializes a message using the codec for the given content type, falling back to the default codec when contentType is empty. It mirrors DecodeWithType and lets the caller supply a type resolved from the connection rather than from the message itself.

func (*CodecRegistry) Get added in v1.3.0

func (r *CodecRegistry) Get(contentType string) (Codec, bool)

Get returns the codec for the given content type.

func (*CodecRegistry) Register added in v1.3.0

func (r *CodecRegistry) Register(codec Codec)

Register adds a codec. If a codec for the same content type already exists, it is replaced.

func (*CodecRegistry) SetDefault added in v1.3.0

func (r *CodecRegistry) SetDefault(contentType string) error

SetDefault changes the default codec to the one registered for the given content type.

type Config

type Config = internal.Config

Configuration.

func DefaultConfig

func DefaultConfig() Config

type ConfigOption

type ConfigOption = internal.ConfigOption

Configuration.

func WithAuthentication

func WithAuthentication(username, password string) ConfigOption

func WithBackend

func WithBackend(backend string) ConfigOption

func WithBackendURLs

func WithBackendURLs(urls ...string) ConfigOption

func WithBufferSizes

func WithBufferSizes(read, write int) ConfigOption

func WithConfig

func WithConfig(config Config) ConfigOption

func WithConnectionLimits

func WithConnectionLimits(perUser, roomsPerUser, channelsPerUser int) ConfigOption

func WithFeatures

func WithFeatures(rooms, channels, presence, typing, history bool) ConfigOption

func WithLoadBalancer added in v1.9.5

func WithLoadBalancer(strategy string) ConfigOption

func WithLocalBackend

func WithLocalBackend() ConfigOption

func WithMessageLimits

func WithMessageLimits(maxSize, maxPerSecond int) ConfigOption

func WithMessageRetention

func WithMessageRetention(retention time.Duration) ConfigOption

func WithNATSBackend

func WithNATSBackend(urls ...string) ConfigOption

func WithNodeID

func WithNodeID(nodeID string) ConfigOption

func WithPresenceTimeout

func WithPresenceTimeout(timeout time.Duration) ConfigOption

func WithRedisBackend

func WithRedisBackend(url string) ConfigOption

func WithRequireConfig

func WithRequireConfig(require bool) ConfigOption

func WithSessionResumption added in v1.9.5

func WithSessionResumption(ttl time.Duration) ConfigOption

func WithTLS

func WithTLS(certFile, keyFile, caFile string) ConfigOption

func WithTimeouts

func WithTimeouts(ping, pong, write time.Duration) ConfigOption

func WithTypingTimeout

func WithTypingTimeout(timeout time.Duration) ConfigOption

type ConnOption added in v1.9.5

type ConnOption func(*connOptions)

ConnOption configures an enhanced connection.

func WithConnectionLogger added in v1.9.5

func WithConnectionLogger(logger forge.Logger) ConnOption

WithConnectionLogger gives the connection a logger for writer-goroutine teardown reasons.

func WithConnectionMetrics added in v1.9.5

func WithConnectionMetrics(metrics forge.Metrics) ConnOption

WithConnectionMetrics gives the connection a metrics handle for send queue depth, drops, and overflow disconnects. Without one the same numbers remain readable through SendQueueStats.

func WithSendQueueCapacity added in v1.9.5

func WithSendQueueCapacity(capacity int) ConnOption

WithSendQueueCapacity sets how many frames may be queued for the socket before the per-message-type overflow policy applies. Zero or negative selects DefaultSendQueueCapacity.

func WithSendQueueTimeouts added in v1.9.5

func WithSendQueueTimeouts(flush, exit time.Duration) ConnOption

WithSendQueueTimeouts bounds connection teardown: flush is how long Close waits for queued frames to reach the socket, exit is how long it then waits for the writer goroutine to unwind after the socket has been closed.

type Connection

type Connection = internal.EnhancedConnection

Core interfaces.

func NewConnection

func NewConnection(conn forge.Connection, opts ...ConnOption) Connection

NewConnection creates a new enhanced connection with default transport "websocket".

func NewConnectionWithTransport added in v1.3.0

func NewConnectionWithTransport(conn forge.Connection, transport string, opts ...ConnOption) Connection

NewConnectionWithTransport creates a new enhanced connection with a specified transport type.

type ConnectionHook added in v1.3.0

type ConnectionHook interface {
	StreamingHook
	// OnConnect is called before registration. Return error to reject connection.
	OnConnect(ctx context.Context, conn Connection) error
	// OnDisconnect is called after unregistration.
	OnDisconnect(ctx context.Context, conn Connection)
}

ConnectionHook fires on connection lifecycle events.

type ConnectionInfo

type ConnectionInfo = internal.ConnectionInfo

ConnectionInfo provides detailed information about a connection.

type DeviceInfo

type DeviceInfo = internal.DeviceInfo

Presence types.

type DistributedBackend

type DistributedBackend = internal.DistributedBackend

Distributed backend.

type DistributedBackendOptions

type DistributedBackendOptions = internal.DistributedBackendOptions

Configuration.

type ErrorHook added in v1.3.0

type ErrorHook interface {
	StreamingHook
	// OnError is called when a message handling error occurs.
	OnError(ctx context.Context, conn Connection, err error)
}

ErrorHook fires on message handling errors.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension implements forge.Extension for streaming functionality.

func NewExtension

func NewExtension(opts ...ConfigOption) *Extension

NewExtension creates a new streaming extension with functional options.

Returns the concrete *Extension rather than forge.Extension because the type carries surface the interface does not — RegisterRoutes, RegisterHook, RegisterCodec, Manager — and every one of those is needed in ordinary use. Returning the interface made the documented quick-start unable to compile.

This widens the return type rather than narrowing it: *Extension satisfies forge.Extension, so `var e forge.Extension = streaming.NewExtension(...)` still works.

func NewExtensionWithConfig

func NewExtensionWithConfig(config Config) *Extension

NewExtensionWithConfig creates a new streaming extension with a complete config.

func (*Extension) AsyncAPISpec

func (e *Extension) AsyncAPISpec() *forge.AsyncAPISpec

AsyncAPISpec generates AsyncAPI 3.0.0 specification for the streaming extension This documents all streaming channels, operations, and message types.

func (*Extension) Codecs added in v1.3.0

func (e *Extension) Codecs() *CodecRegistry

Codecs returns the codec registry for direct access.

func (*Extension) Health

func (e *Extension) Health(ctx context.Context) error

Health checks if the streaming extension is healthy.

func (*Extension) Hooks added in v1.3.0

func (e *Extension) Hooks() *HookRegistry

Hooks returns the hook registry for direct access.

func (*Extension) Manager

func (e *Extension) Manager() Manager

Manager returns the streaming manager (for advanced usage).

func (*Extension) Register

func (e *Extension) Register(app forge.App) error

Register registers the streaming extension with the app.

func (*Extension) RegisterCodec added in v1.3.0

func (e *Extension) RegisterCodec(codec Codec)

RegisterCodec adds a message codec for a specific content type.

func (*Extension) RegisterContractContributor added in v1.6.4

func (e *Extension) RegisterContractContributor(
	disp *dispatcher.Dispatcher,
	reg dashcontract.Registry,
	wreg dashcontract.WardenRegistry,
) error

RegisterContractContributor implements dashboard.ContractContributorAware. Wires the streaming contributor handlers into the dashboard's contract dispatcher and registers the embedded YAML manifest, serving /dashboard/contract/streaming/*. This is the only dashboard surface streaming exposes: the server-rendered contributor that used to sit beside it on /dashboard/ext/streaming/* is gone, and the UI is now the @forge-go/dashboard-plugin-streaming React package, which joins to this contributor by its manifest name.

func (*Extension) RegisterHook added in v1.3.0

func (e *Extension) RegisterHook(hook StreamingHook)

RegisterHook adds a streaming hook for lifecycle events. Hooks can implement one or more hook interfaces (ConnectionHook, MessageHook, RawMessageHook, RoomHook, PresenceHook, ErrorHook).

func (*Extension) RegisterRoutes

func (e *Extension) RegisterRoutes(router forge.Router, wsPath, ssePath string) error

RegisterRoutes is a helper to register WebSocket and SSE routes with the router.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start starts the streaming extension.

func (*Extension) Stop

func (e *Extension) Stop(ctx context.Context) error

Stop stops the streaming extension.

Connections are drained before the manager is torn down. Drain was implemented but never called from anywhere, so shutdown dropped every live socket without notice — clients saw an abrupt close and could not tell a deploy from a crash, which matters because the two deserve different reconnect behaviour.

func (*Extension) UnregisterHook added in v1.3.0

func (e *Extension) UnregisterHook(name string)

UnregisterHook removes a streaming hook by name.

type FileInfo

type FileInfo = internal.FileInfo

File types.

type FileQuery

type FileQuery = internal.FileQuery

Query types.

type FileUpload

type FileUpload = internal.FileUpload

File types.

type HistoryQuery

type HistoryQuery = internal.HistoryQuery

Query types.

type HookRegistry added in v1.3.0

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

HookRegistry manages streaming hooks and dispatches events.

Hooks fire in registration order. Several of the fire methods are a pipeline — FireOnMessageReceived threads the message through each hook in turn, and FireOnConnect stops at the first rejection — so the order has to be defined and stable, not whatever a map range happens to produce.

func NewHookRegistry added in v1.3.0

func NewHookRegistry() *HookRegistry

NewHookRegistry creates a new hook registry.

func (*HookRegistry) Add added in v1.3.0

func (r *HookRegistry) Add(hook StreamingHook)

Add registers a hook. The hook is type-asserted to categorize it into the appropriate dispatch lists.

Registering a name that already exists replaces the hook in place, keeping its original position in the dispatch order.

func (*HookRegistry) Close added in v1.9.5

func (r *HookRegistry) Close() error

Close shuts down the delivery worker pool and waits for in-flight delivery hooks to finish. It is safe to call concurrently and more than once; calls after the first are no-ops. Deliveries fired after Close are dropped.

func (*HookRegistry) DroppedDeliveries added in v1.9.5

func (r *HookRegistry) DroppedDeliveries() uint64

DroppedDeliveries returns the number of delivery-hook batches that were never run, either because the pool's queue was saturated or because the registry was already closed. Delivery hooks are non-blocking by contract, so overload is shed here rather than pushed back onto the delivery path — this counter is how that shedding becomes visible. A steadily climbing value means hooks are too slow for the message rate, and any hook doing billing or audit work is losing events.

func (*HookRegistry) FireOnConnect added in v1.3.0

func (r *HookRegistry) FireOnConnect(ctx context.Context, conn streaming.EnhancedConnection) error

FireOnConnect fires ConnectionHook.OnConnect for all registered hooks. Returns the first error encountered, which should be used to reject the connection.

func (*HookRegistry) FireOnDisconnect added in v1.3.0

func (r *HookRegistry) FireOnDisconnect(ctx context.Context, conn streaming.EnhancedConnection)

FireOnDisconnect fires ConnectionHook.OnDisconnect for all registered hooks. Errors are ignored (post-hook).

func (*HookRegistry) FireOnError added in v1.3.0

func (r *HookRegistry) FireOnError(ctx context.Context, conn streaming.EnhancedConnection, err error)

FireOnError fires ErrorHook.OnError for all hooks (post-hook).

func (*HookRegistry) FireOnMessageDelivered added in v1.3.0

func (r *HookRegistry) FireOnMessageDelivered(ctx context.Context, conn streaming.EnhancedConnection, msg *streaming.Message)

FireOnMessageDelivered fires MessageHook.OnMessageDelivered on a bounded worker pool. This is non-blocking to avoid slowing down message delivery: the call is made once per recipient, so a broadcast to a large room reaches this path thousands of times for a single message.

If the pool's queue is saturated the batch is dropped rather than queued or awaited, because blocking here would stall delivery itself.

func (*HookRegistry) FireOnMessageReceived added in v1.3.0

func (r *HookRegistry) FireOnMessageReceived(ctx context.Context, conn streaming.EnhancedConnection, msg *streaming.Message) (*streaming.Message, error)

FireOnMessageReceived fires MessageHook.OnMessageReceived for all hooks in sequence. Each hook can transform or block (return nil) the message.

func (*HookRegistry) FireOnPresenceChange added in v1.3.0

func (r *HookRegistry) FireOnPresenceChange(ctx context.Context, userID, oldStatus, newStatus string)

FireOnPresenceChange fires PresenceHook.OnPresenceChange for all hooks (post-hook).

func (*HookRegistry) FireOnRawMessage added in v1.3.0

func (r *HookRegistry) FireOnRawMessage(ctx context.Context, conn streaming.EnhancedConnection, data []byte) ([]byte, error)

FireOnRawMessage fires RawMessageHook.OnRawMessage for all hooks in sequence. Each hook can transform the bytes or block (return error) the message.

func (*HookRegistry) FireOnRoomCreate added in v1.3.0

func (r *HookRegistry) FireOnRoomCreate(ctx context.Context, room streaming.Room) error

FireOnRoomCreate fires RoomHook.OnRoomCreate for all hooks. Returns the first error encountered, which should be used to reject creation.

func (*HookRegistry) FireOnRoomDelete added in v1.3.0

func (r *HookRegistry) FireOnRoomDelete(ctx context.Context, roomID string)

FireOnRoomDelete fires RoomHook.OnRoomDelete for all hooks (post-hook).

func (*HookRegistry) FireOnRoomJoin added in v1.3.0

func (r *HookRegistry) FireOnRoomJoin(ctx context.Context, conn streaming.EnhancedConnection, roomID string) error

FireOnRoomJoin fires RoomHook.OnRoomJoin for all hooks. Returns the first error encountered, which should be used to reject the join.

func (*HookRegistry) FireOnRoomLeave added in v1.3.0

func (r *HookRegistry) FireOnRoomLeave(ctx context.Context, conn streaming.EnhancedConnection, roomID string)

FireOnRoomLeave fires RoomHook.OnRoomLeave for all hooks (post-hook).

func (*HookRegistry) List added in v1.3.0

func (r *HookRegistry) List() []StreamingHook

List returns all registered hooks in registration order.

func (*HookRegistry) Remove added in v1.3.0

func (r *HookRegistry) Remove(name string)

Remove unregisters a hook by name.

type Invite

type Invite = internal.Invite

Room types.

type InviteOptions

type InviteOptions = internal.InviteOptions

Room types.

type JSONCodec added in v1.3.0

type JSONCodec struct{}

JSONCodec encodes/decodes messages as JSON. This is the default codec.

func (*JSONCodec) ContentType added in v1.3.0

func (c *JSONCodec) ContentType() string

func (*JSONCodec) Decode added in v1.3.0

func (c *JSONCodec) Decode(data []byte, msg *streaming.Message) error

func (*JSONCodec) Encode added in v1.3.0

func (c *JSONCodec) Encode(msg *streaming.Message) ([]byte, error)

type LocalRoom

type LocalRoom = *local.LocalRoom

Room creation (local backend) and statistics types.

type Lock

type Lock = internal.Lock

Distributed backend.

type Manager

type Manager = internal.Manager

Core interfaces.

func GetManager

func GetManager(c forge.Container) (Manager, error)

GetManager retrieves the streaming Manager from the container. Returns error if not found or type assertion fails.

func GetManagerFromApp

func GetManagerFromApp(app forge.App) (Manager, error)

GetManagerFromApp retrieves the streaming Manager from the app. Returns error if not found or type assertion fails.

func MustGetManager

func MustGetManager(c forge.Container) Manager

MustGetManager retrieves the streaming Manager from the container. Panics if not found or type assertion fails.

func MustGetManagerFromApp

func MustGetManagerFromApp(app forge.App) Manager

MustGetManagerFromApp retrieves the streaming Manager from the app. Panics if not found or type assertion fails.

func NewManager

func NewManager(
	config Config,
	roomStore RoomStore,
	channelStore ChannelStore,
	messageStore MessageStore,
	presenceTracker PresenceTracker,
	typingTracker TypingTracker,
	distributed DistributedBackend,
	logger forge.Logger,
	metrics forge.Metrics,
	opts ...ManagerOption,
) Manager

NewManager creates a new streaming manager.

type ManagerOption added in v0.10.0

type ManagerOption func(*manager)

ManagerOption configures the manager.

func WithCodecRegistry added in v1.3.0

func WithCodecRegistry(cr *CodecRegistry) ManagerOption

WithCodecRegistry sets the codec registry for message encoding/decoding.

func WithCoordinator added in v0.10.0

func WithCoordinator(c coordinator.StreamCoordinator) ManagerOption

WithCoordinator sets the distributed coordinator.

func WithFilterChain added in v0.10.0

func WithFilterChain(fc filters.FilterChain) ManagerOption

WithFilterChain sets the message filter chain.

func WithHookRegistry added in v1.3.0

func WithHookRegistry(hr *HookRegistry) ManagerOption

WithHookRegistry sets the hook registry for lifecycle hooks.

func WithManagerHealthChecker added in v0.10.0

func WithManagerHealthChecker(hc lb.HealthChecker) ManagerOption

WithManagerHealthChecker sets the health checker.

func WithManagerLoadBalancer added in v0.10.0

func WithManagerLoadBalancer(l lb.LoadBalancer) ManagerOption

WithManagerLoadBalancer sets the load balancer.

func WithManagerNodeID added in v0.10.0

func WithManagerNodeID(id string) ManagerOption

WithManagerNodeID sets the node ID for distributed mode.

func WithMessageAuthorizer added in v1.9.5

func WithMessageAuthorizer(ma streamauth.MessageAuthorizer) ManagerOption

WithMessageAuthorizer sets the message authorizer. Sends, edits, deletes and reactions are checked against it.

func WithRateLimiter added in v0.10.0

func WithRateLimiter(rl ratelimit.RateLimiter) ManagerOption

WithRateLimiter sets the rate limiter.

func WithRoomAuthorizer added in v1.9.5

func WithRoomAuthorizer(ra streamauth.RoomAuthorizer) ManagerOption

WithRoomAuthorizer sets the room authorizer. Join, leave and room-targeted sends are checked against it.

func WithSessionStore added in v0.10.0

func WithSessionStore(ss SessionStore) ManagerOption

WithSessionStore sets the session store for session resumption.

func WithValidator added in v0.10.0

func WithValidator(v validation.MessageValidator) ManagerOption

WithValidator sets the message validator.

type ManagerStats

type ManagerStats = internal.ManagerStats

Room creation (local backend) and statistics types.

type Member

type Member = internal.Member

Core interfaces.

type MemberOptions

type MemberOptions = internal.MemberOptions

Room types.

type Message

type Message = internal.Message

Message types.

func NewEventMessage added in v1.9.5

func NewEventMessage(event string, data any) *Message

NewEventMessage builds a frame carrying a domain event, named so the generated client can bind it.

Type is MessageTypeMessage and Event is the domain name -- never the reverse, which is the mistake this constructor exists to make unavailable. Callers wanting a different transport kind may set Type afterwards; the client honours Event regardless of the kind it rides on, which is what makes the existing system-kind events in manager.go (message.deleted, message.edited) bindable without being reclassified.

ID, UserID and the routing fields are deliberately left alone. They are the producer's, and identity in particular has semantics -- deduplication, history -- that a wire-contract helper has no business inventing. Timestamp is set because a frame without one marshals as the zero time, which is a wrong answer rather than an absent one.

An event whose name collides with a reserved transport kind is accepted and is a mistake: the client will look for a binding named "presence" and find none. It is not rejected here because the failure is visible -- an event name always takes the client's event branch, so the frame is reported rather than dropped -- and because a constructor that can fail is a worse trade than a caller running IsTransportKind when the name is not a literal.

func NewLifecycleMessage added in v1.9.5

func NewLifecycleMessage(kind, lifecycle string) *Message

NewLifecycleMessage builds a transport frame announcing a connection lifecycle moment -- a ping, a kick, a shutdown -- named in Metadata rather than in Event.

Event is left empty, and that is the entire point of this constructor. Event is the binding key: it is what the generated client's manifest is keyed on, one row per AsyncAPI domain message, and what forgeStreamingDecoder reads first. A lifecycle name sitting in Event is therefore a name no manifest binds, and worse, it is non-empty, so the decoder takes its event branch and never reaches the reserved-kind filter that exists to drop transport frames quietly. The frame is passed through to the runtime, no slot matches it, and it surfaces through onUnknown as an unknown message on a channel that is working exactly as designed. For a heartbeat firing on a ticker for every connection that is a permanent recurring false signal -- into an application's own onUnknown, which is typically wired to metrics or Sentry.

The alternatives were both worse. Leaving the name in Event costs every heartbeat a spurious unknown-message report and cannot be fixed on the decoder side, because a lifecycle name and a genuine system-kind domain event (message.deleted, message.edited in manager.go) have the same shape on the wire and no decoder-side rule can separate them. Dropping the name entirely would make the frames clean but would throw away information a client may legitimately want -- a client that wants to show "you were removed by a moderator" needs to know a kicked frame from an idle_cleanup one. Metadata carries the name where a curious client can still read it, while the frame stays a pure transport frame by shape: it sets only Type, so the client drops it silently, which is the correct outcome for a frame no manifest was ever going to bind.

This is a deliberate wire change. A consumer previously reading event: "kicked" must now read metadata.lifecycle.

ID, UserID, Data and the routing fields are left to the caller for the same reason NewEventMessage leaves them: they are the producer's, and identity in particular carries deduplication and history semantics a wire-contract helper has no business inventing. Timestamp is set because a frame without one marshals as the zero time, which is a wrong answer rather than an absent one.

type MessageEdit

type MessageEdit = internal.MessageEdit

Message types.

type MessageHandler

type MessageHandler = internal.MessageHandler

Distributed backend.

type MessageHook added in v1.3.0

type MessageHook interface {
	StreamingHook
	// OnMessageReceived is called before message processing. Can transform or block (return nil).
	OnMessageReceived(ctx context.Context, conn Connection, msg *Message) (*Message, error)
	// OnMessageDelivered is called after delivery (non-blocking, runs async).
	OnMessageDelivered(ctx context.Context, conn Connection, msg *Message)
}

MessageHook fires on message events.

type MessageReaction

type MessageReaction = internal.MessageReaction

Message types.

type MessageSearchQuery

type MessageSearchQuery = internal.MessageSearchQuery

Query types.

type MessageStore

type MessageStore = internal.MessageStore

Store interfaces.

type ModerationEvent

type ModerationEvent = internal.ModerationEvent

Room types.

type ModerationStatus

type ModerationStatus = internal.ModerationStatus

ModerationStatus represents the status of a moderation action.

type NodeChangeEvent

type NodeChangeEvent = internal.NodeChangeEvent

Distributed backend.

type NodeChangeHandler

type NodeChangeHandler = internal.NodeChangeHandler

Distributed backend.

type NodeInfo

type NodeInfo = internal.NodeInfo

Distributed backend.

type OnlineStats

type OnlineStats = internal.OnlineStats

Room creation (local backend) and statistics types.

type OutboundFrame added in v1.9.5

type OutboundFrame struct {
	// Data is the encoded payload. The queue retains it until it is written, so
	// callers must not reuse the slice.
	Data []byte

	// Type is the streaming message type, one of the MessageType* constants. It
	// selects the overflow policy; empty means unknown, which is treated
	// conservatively.
	Type string

	// Binary requests a binary frame. A text frame promises valid UTF-8, and a
	// browser fails the connection with close code 1007 when that is broken.
	Binary bool
}

OutboundFrame is a pre-encoded frame plus the two things its bytes no longer carry: the streaming message type it was encoded from, and whether the wire needs a binary frame rather than a text one.

type PresenceEvent

type PresenceEvent = internal.PresenceEvent

Presence types.

type PresenceFilters

type PresenceFilters = internal.PresenceFilters

Presence types.

type PresenceHook added in v1.3.0

type PresenceHook interface {
	StreamingHook
	// OnPresenceChange is called after a user's presence status changes.
	OnPresenceChange(ctx context.Context, userID, oldStatus, newStatus string)
}

PresenceHook fires on presence changes.

type PresenceOptions

type PresenceOptions = internal.PresenceOptions

Presence types.

type PresenceStore

type PresenceStore = internal.PresenceStore

Store interfaces.

type PresenceTracker

type PresenceTracker = internal.PresenceTracker

Tracker interfaces.

type RateLimitStatus

type RateLimitStatus = internal.RateLimitStatus

RateLimitStatus represents rate limiting status for a user.

type RawMessageHook added in v1.3.0

type RawMessageHook interface {
	StreamingHook
	// OnRawMessage processes raw bytes before decoding. Return error to drop the message.
	OnRawMessage(ctx context.Context, conn Connection, data []byte) ([]byte, error)
}

RawMessageHook fires before deserialization on raw bytes from the connection.

type Room

type Room = internal.Room

Core interfaces.

type RoomBan

type RoomBan = internal.RoomBan

Room types.

type RoomEvent

type RoomEvent = internal.RoomEvent

Room types.

type RoomHook added in v1.3.0

type RoomHook interface {
	StreamingHook
	// OnRoomJoin is called before join. Return error to reject.
	OnRoomJoin(ctx context.Context, conn Connection, roomID string) error
	// OnRoomLeave is called after leave.
	OnRoomLeave(ctx context.Context, conn Connection, roomID string)
	// OnRoomCreate is called before room creation. Return error to reject.
	OnRoomCreate(ctx context.Context, room Room) error
	// OnRoomDelete is called after room deletion.
	OnRoomDelete(ctx context.Context, roomID string)
}

RoomHook fires on room lifecycle events.

type RoomOptions

type RoomOptions = internal.RoomOptions

Room types.

type RoomStats

type RoomStats = internal.RoomStats

RoomStats provides statistics for a specific room.

type RoomStore

type RoomStore = internal.RoomStore

Store interfaces.

type SendQueueStats added in v1.9.5

type SendQueueStats struct {
	Capacity            int
	Depth               int
	Enqueued            uint64
	Written             uint64
	Dropped             uint64
	OverflowDisconnects uint64
	WriteErrors         uint64
	Closed              bool
}

SendQueueStats is a snapshot of one connection's outbound queue. The manager can read it off a connection with a type assertion:

if s, ok := conn.(interface{ SendQueueStats() streaming.SendQueueStats }); ok {
	depth := s.SendQueueStats().Depth
}

type SessionSnapshot added in v0.10.0

type SessionSnapshot struct {
	SessionID      string            `json:"session_id"`
	UserID         string            `json:"user_id"`
	Rooms          []string          `json:"rooms"`
	Channels       []string          `json:"channels"`
	Metadata       map[string]string `json:"metadata,omitempty"`
	DisconnectedAt time.Time         `json:"disconnected_at"`

	// LastEventIDs is the position each channel had reached when the session
	// dropped, so a resumption can ask for the gap instead of resynchronising.
	LastEventIDs map[string]string `json:"last_event_ids,omitempty"`
}

SessionSnapshot captures the state of a connection for resumption.

type SessionStore added in v0.10.0

type SessionStore interface {
	// Save stores a session snapshot with a TTL.
	Save(ctx context.Context, snapshot *SessionSnapshot, ttl time.Duration) error

	// Get retrieves a session snapshot by session ID.
	Get(ctx context.Context, sessionID string) (*SessionSnapshot, error)

	// Delete removes a session snapshot.
	Delete(ctx context.Context, sessionID string) error

	// Close releases any background resources held by the store. It must be
	// safe to call more than once.
	Close() error
}

SessionStore stores session snapshots for resumption.

func NewInMemorySessionStore added in v0.10.0

func NewInMemorySessionStore() SessionStore

NewInMemorySessionStore creates an in-memory session store.

type StreamingHook added in v1.3.0

type StreamingHook interface {
	Name() string
}

StreamingHook is the base interface for all streaming hooks. Hooks implement one or more of the optional hook interfaces below.

type TextCodec added in v1.3.0

type TextCodec struct{}

TextCodec handles plain text data. On decode, it stores the text in msg.Data as a string. On encode, it converts msg.Data to a string and returns bytes.

func (*TextCodec) ContentType added in v1.3.0

func (c *TextCodec) ContentType() string

func (*TextCodec) Decode added in v1.3.0

func (c *TextCodec) Decode(data []byte, msg *streaming.Message) error

func (*TextCodec) Encode added in v1.3.0

func (c *TextCodec) Encode(msg *streaming.Message) ([]byte, error)

type TypingOptions

type TypingOptions = internal.TypingOptions

TypingOptions contains configuration for typing indicators.

type TypingStore

type TypingStore = internal.TypingStore

Store interfaces.

type TypingTracker

type TypingTracker = internal.TypingTracker

Tracker interfaces.

type UserPresence

type UserPresence = internal.UserPresence

Core interfaces.

type UserPresenceStats

type UserPresenceStats = internal.UserPresenceStats

Room creation (local backend) and statistics types.

type UserStats

type UserStats = internal.UserStats

Room creation (local backend) and statistics types.

type WebhookConfig

type WebhookConfig = internal.WebhookConfig

WebhookConfig represents webhook configuration.

Directories

Path Synopsis
storetest
Package storetest holds the behavioural contract every MessageStore backend must satisfy, so the backends can be proven to agree rather than merely to each work.
Package storetest holds the behavioural contract every MessageStore backend must satisfy, so the backends can be proven to agree rather than merely to each work.
Package contract is the streaming extension's dashboard contract contributor.
Package contract is the streaming extension's dashboard contract contributor.
examples
chat command

Jump to

Keyboard shortcuts

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