streaming

package module
v1.9.5 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 35 Imported by: 2

README

Streaming Extension

Real-time streaming extension for Forge with WebSocket/SSE support, rooms, channels, presence tracking, typing indicators, and distributed coordination.

Features

  • WebSocket & SSE - Built on top of Forge's core router streaming support
  • Rooms - Create chat rooms with members, roles, and permissions
  • Channels - Pub/sub channels with filters and subscriptions
  • Presence - Track online/offline/away status across users
  • Typing Indicators - Real-time typing indicators per room
  • Message History - Persist and retrieve message history
  • Distributed - Redis/NATS backends for multi-node deployments
  • Authorization - Room and message policies wired by default, not opt-in
  • Gap-free reconnect - Per-room sequences and cursor-based replay over SSE
  • Backpressure - Bounded per-connection send queue; one slow client cannot stall a broadcast
  • Interface-First - All major components are interfaces for testability

Installation

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

Quick Start

Basic Setup (Local Backend)
package main

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

func main() {
    // Create container and app
    container := forge.NewContainer()
    app := forge.NewApp(container)
    router := forge.NewRouter(forge.WithContainer(container))

    // Create streaming extension with local backend
    streamExt := streaming.NewExtension(
        streaming.WithLocalBackend(),
        streaming.WithFeatures(true, true, true, true, true), // rooms, channels, presence, typing, history
    )

    // Register and start
    app.Use(streamExt)
    app.Start(context.Background())

    // Register streaming routes
    streamExt.RegisterRoutes(router, "/ws", "/sse")

    // Start server
    http.ListenAndServe(":8080", router)
}
With Redis Backend (Distributed)
streamExt := streaming.NewExtension(
    streaming.WithRedisBackend("redis://localhost:6379"),
    streaming.WithFeatures(true, true, true, true, true),
    streaming.WithNodeID("node-1"), // Optional, auto-generated if not set
)

Architecture

Interface-First Design

All major components are defined as interfaces with multiple implementations:

streaming (interfaces)
├── Manager              - Central orchestrator
├── EnhancedConnection   - WebSocket connection with metadata
├── Room                 - Room management
├── RoomStore           - Room persistence backend
├── Channel             - Pub/sub channel
├── ChannelStore        - Channel persistence backend
├── PresenceTracker     - Presence tracking
├── PresenceStore       - Presence persistence backend
├── TypingTracker       - Typing indicators
├── TypingStore         - Typing persistence backend
├── MessageStore        - Message history
└── DistributedBackend  - Cross-node coordination
Package Structure
v2/extensions/streaming/
├── streaming.go         # Core interfaces (Manager, EnhancedConnection)
├── room.go             # Room interfaces
├── channel.go          # Channel interfaces
├── presence.go         # Presence interfaces
├── typing.go           # Typing interfaces
├── persistence.go      # Message store interfaces
├── distributed.go      # Distributed backend interfaces
├── config.go           # Configuration
├── errors.go           # Domain errors
├── manager.go          # Manager implementation
├── connection.go       # Enhanced connection implementation
├── extension.go        # Extension entry point
├── backends/
│   ├── factory.go      # Store factory
│   ├── local/          # In-memory implementations
│   ├── redis/          # Redis implementations (TODO)
│   └── nats/           # NATS implementations (TODO)
└── trackers/
    ├── presence_tracker.go  # Presence tracker implementation
    └── typing_tracker.go    # Typing tracker implementation

Usage Examples

Custom WebSocket Handler
router.WebSocket("/chat", func(ctx forge.Context, conn forge.Connection) error {
    // Get streaming manager from DI
    var manager streaming.Manager
    ctx.Container().Resolve(&manager)

    // Get user from auth
    userID := ctx.Get("user_id").(string)

    // Create enhanced connection
    enhanced := streaming.NewEnhancedConnection(conn)
    enhanced.SetUserID(userID)
    enhanced.SetSessionID(uuid.New().String())

    // Register
    manager.Register(enhanced)
    defer manager.Unregister(conn.ID())

    // Set online
    manager.SetPresence(ctx.Request().Context(), userID, streaming.StatusOnline)
    defer manager.SetPresence(ctx.Request().Context(), userID, streaming.StatusOffline)

    // Message loop
    for {
        var msg streaming.Message
        if err := conn.ReadJSON(&msg); err != nil {
            return err
        }

        reqCtx := ctx.Request().Context()

        // Identity comes from the connection, never from the client. Without
        // this a client can set user_id to anything and have the server
        // broadcast and persist it under that name.
        msg.UserID = enhanced.GetUserID()

        // The inbound gate: size cap, per-user rate limit, target
        // authorization, content validation. Skipping it leaves an unmetered
        // path from any socket straight to a broadcast.
        gated, err := manager.ProcessInbound(reqCtx, &msg, enhanced)
        if err != nil {
            // Tell the client why, so it can back off rather than retrying
            // into the same limit forever. NewLifecycleMessage rather than a
            // literal Event: a lifecycle name in Event is a name no generated
            // manifest binds, and the client reports it as an unknown message
            // instead of dropping it. The name rides in Metadata instead.
            rejected := streaming.NewLifecycleMessage(streaming.MessageTypeError, "message.rejected")
            rejected.Data = map[string]any{"error": err.Error()}

            _ = conn.WriteJSON(rejected)

            continue
        }

        switch gated.Type {
        case streaming.MessageTypeMessage:
            if gated.RoomID != "" {
                // Save first: this assigns gated.Sequence, which is what lets a
                // reconnecting client resume from exactly this point.
                manager.SaveMessage(reqCtx, gated)
                manager.BroadcastToRoom(reqCtx, gated.RoomID, gated)
            }
        case streaming.MessageTypeJoin:
            manager.JoinRoom(reqCtx, conn.ID(), gated.RoomID)
        case streaming.MessageTypeLeave:
            manager.LeaveRoom(reqCtx, conn.ID(), gated.RoomID)
        }
    }
})

Using RegisterRoutes instead of a hand-written handler does all of the above for you.

Room Management REST API
api := router.Group("/api/v1")

// Create room
api.POST("/rooms", func(ctx forge.Context, req *CreateRoomRequest) error {
    var manager streaming.Manager
    ctx.Container().Resolve(&manager)

    userID := ctx.Get("user_id").(string)

    room := streaming.RoomOptions{
        ID:          uuid.New().String(),
        Name:        req.Name,
        Description: req.Description,
        Owner:       userID,
    }

    if err := manager.CreateRoom(ctx.Request().Context(), room); err != nil {
        return err
    }

    return ctx.JSON(200, room)
})

// Get room history
api.GET("/rooms/:id/history", func(ctx forge.Context) error {
    var manager streaming.Manager
    ctx.Container().Resolve(&manager)

    roomID := ctx.Param("id")

    messages, err := manager.GetHistory(ctx.Request().Context(), roomID, streaming.HistoryQuery{
        Limit: 100,
    })
    if err != nil {
        return err
    }

    return ctx.JSON(200, messages)
})

Configuration

Complete Configuration Example
streamExt := streaming.NewExtension(
    // Backend
    streaming.WithBackend("redis"),
    streaming.WithBackendURLs("redis://localhost:6379"),
    streaming.WithAuthentication("username", "password"),

    // Features
    streaming.WithFeatures(true, true, true, true, true),

    // Limits
    streaming.WithConnectionLimits(5, 50, 100), // conns/user, rooms/user, channels/user
    streaming.WithMessageLimits(64*1024, 100),  // max size, max/second

    // Timeouts
    streaming.WithTimeouts(30*time.Second, 10*time.Second, 10*time.Second), // ping, pong, write

    // Retention
    streaming.WithMessageRetention(30 * 24 * time.Hour), // 30 days

    // Distributed
    streaming.WithNodeID("node-1"),

    // TLS
    streaming.WithTLS("cert.pem", "key.pem", "ca.pem"),
)
Configuration from File
# config.yaml
extensions:
  streaming:
    backend: redis
    backend_urls:
      - redis://localhost:6379
    enable_rooms: true
    enable_channels: true
    enable_presence: true
    enable_typing_indicators: true
    enable_message_history: true
    max_connections_per_user: 5
    max_rooms_per_user: 50
    max_message_size: 65536
    message_retention: 720h # 30 days
// Automatically loads from config
streamExt := streaming.NewExtension()

Message Protocol

Message Structure
type Message struct {
    ID        string         `json:"id"`
    Type      string         `json:"type"`      // "message", "presence", "typing", "system"
    Event     string         `json:"event,omitempty"`
    RoomID    string         `json:"room_id,omitempty"`
    ChannelID string         `json:"channel_id,omitempty"`
    UserID    string         `json:"user_id"`
    Data      any            `json:"data"`
    RawData   []byte         `json:"-"`                      // Binary payload
    ContentType string       `json:"content_type,omitempty"` // MIME type of Data
    Metadata  map[string]any `json:"metadata,omitempty"`
    Timestamp time.Time      `json:"timestamp"`
    ThreadID  string         `json:"thread_id,omitempty"`
    Sequence  int64          `json:"sequence,omitempty"`     // Per-room, assigned on save
}

Type is the transport kind; Event is the domain name (order.created). The generated TypeScript client binds on Event, so a frame an application is expected to handle must set it — build those with NewEventMessage rather than by hand.

Sequence is assigned by the message store when a room message is saved, and is what lets a reconnecting client be sent exactly what it missed.

Message Types
  • MessageTypeMessage - Regular chat message
  • MessageTypePresence - Presence update (online/offline/away)
  • MessageTypeTyping - Typing indicator
  • MessageTypeSystem - System notification
  • MessageTypeJoin - User joined room
  • MessageTypeLeave - User left room
  • MessageTypeError - Error message

Backend Comparison

Feature Local Redis NATS
Single Node
Multi-Node
Persistence Memory Disk Disk
Message History Limited Full Full
Presence Sync
Performance Fastest Fast Fastest
Setup None Redis NATS Server

Security

Authorization is wired by default rather than offered as an opt-in.

  • Room joins consult a RoomAuthorizer. The default admits members, admits anyone to a public room, and refuses non-members entry to a private one. Replace it with WithRoomAuthorizer.
  • Sends require the connection to have joined the target room, pass the MessageAuthorizer, and not be muted or banned there.
  • Inbound messages must go through Manager.ProcessInbound before broadcast — size cap, per-user rate limit, target authorization, then content validation. RegisterRoutes does this for you; a custom socket handler must call it, or it leaves an unmetered path from any socket to a broadcast.
  • Identity is stamped from the authenticated connection. A client cannot set user_id.
  • Session resumption binds a snapshot to the user who created it.
  • Anonymous connections are capped separately, since no per-user limit can bound them.

Migration

Several previously inert settings are now enforced, and a few semantics changed. See the migration guide — in particular StartTyping/StopTyping take (ctx, userID, roomID), which is the one change that compiles either way.

Production Considerations

Scaling

For distributed deployments:

  1. Use Redis or NATS backend
  2. Set unique node IDs per instance
  3. Configure proper timeouts and limits
  4. Enable message persistence
  5. Monitor metrics
Security
  • Always use authentication middleware before WebSocket routes
  • Validate user permissions for room/channel access
  • Rate limit connections per user
  • Use TLS in production
  • Never log sensitive message content
Monitoring

Key metrics to monitor:

  • streaming.connections.active - Active connections
  • streaming.connections.total - Total connections created
  • streaming.messages.broadcast - Messages broadcast
  • streaming.rooms.joins - Room joins
  • streaming.presence.updates - Presence updates
Health Checks
// Extension provides health check
if err := streamExt.Health(ctx); err != nil {
    log.Error("streaming unhealthy", err)
}

Testing

Mock Implementations

All interfaces can be easily mocked for testing:

type mockManager struct {
    streaming.Manager
    registerCalls int
}

func (m *mockManager) Register(conn streaming.EnhancedConnection) error {
    m.registerCalls++
    return nil
}

func TestMyHandler(t *testing.T) {
    manager := &mockManager{}
    // Test with mock manager
}
Integration Tests
// Use local backend for tests
streamExt := streaming.NewExtension(streaming.WithLocalBackend())
// Run tests against real implementation

Roadmap

  • Redis backend implementation
  • NATS backend implementation
  • Message compression for old messages
  • Advanced filtering and search
  • WebRTC signaling support
  • GraphQL subscriptions integration
  • Admin dashboard for monitoring

License

Part of Forge framework.

Documentation

Index

Constants

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

Transport 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 ContentTypeBinary = internal.ContentTypeBinary
View Source
const ContentTypeJSON = internal.ContentTypeJSON

Content type constants.

View Source
const ContentTypeMsgPack = internal.ContentTypeMsgPack
View Source
const ContentTypeProtobuf = internal.ContentTypeProtobuf
View Source
const ContentTypeText = internal.ContentTypeText
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.

View Source
const MessageTypeError = internal.MessageTypeError
View Source
const MessageTypeJoin = internal.MessageTypeJoin
View Source
const MessageTypeLeave = internal.MessageTypeLeave
View Source
const MessageTypeMessage = internal.MessageTypeMessage

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 MessageTypePresence = internal.MessageTypePresence
View Source
const MessageTypeSystem = internal.MessageTypeSystem
View Source
const MessageTypeTyping = internal.MessageTypeTyping
View Source
const StatusAway = internal.StatusAway
View Source
const StatusBusy = internal.StatusBusy
View Source
const StatusOffline = internal.StatusOffline
View Source
const StatusOnline = internal.StatusOnline

Status constants.

Variables

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 DefaultPresenceOptions = internal.DefaultPresenceOptions

Default option functions.

View Source
var DefaultTypingOptions = internal.DefaultTypingOptions
View Source
var ErrAlreadyRoomMember = internal.ErrAlreadyRoomMember
View Source
var ErrAlreadySubscribed = internal.ErrAlreadySubscribed
View Source
var ErrBackendNotFound = internal.ErrBackendNotConnected
View Source
var ErrBackendTimeout = internal.ErrBackendTimeout
View Source
var ErrBackendUnavailable = internal.ErrBackendUnavailable
View Source
var ErrChannelAccessDenied = internal.ErrChannelAccessDenied
View Source
var ErrChannelAlreadyExists = internal.ErrChannelAlreadyExists
View Source
var ErrChannelLimitReached = internal.ErrChannelLimitReached
View Source
var ErrChannelNotFound = internal.ErrChannelNotFound

Channel errors.

View Source
var ErrChannelsDisabled = internal.ErrChannelsDisabled
View Source
var ErrConnectionClosed = internal.ErrConnectionClosed
View Source
var ErrConnectionLimitReached = internal.ErrConnectionLimitReached
View Source
var ErrConnectionNotFound = internal.ErrConnectionNotFound

Connection errors.

View Source
var ErrHistoryDisabled = internal.ErrHistoryDisabled
View Source
var ErrInsufficientRole = internal.ErrInsufficientRole
View Source
var ErrInvalidChannel = internal.ErrInvalidChannel
View Source
var ErrInvalidConfig = internal.ErrInvalidConfig
View Source
var ErrInvalidConnection = internal.ErrInvalidConnection
View Source
var ErrInvalidMessage = internal.ErrInvalidMessage
View Source
var ErrInvalidPermission = internal.ErrInvalidPermission
View Source
var ErrInvalidRoom = internal.ErrInvalidRoom
View Source
var ErrInvalidStatus = internal.ErrInvalidStatus
View Source
var ErrInviteExpired = internal.ErrInviteExpired
View Source
var ErrInviteNotFound = internal.ErrInviteNotFound

Invite errors.

View Source
var ErrLockAcquisitionFailed = internal.ErrLockAcquisitionFailed
View Source
var ErrLockNotHeld = internal.ErrLockNotHeld
View Source
var ErrMessageNotFound = internal.ErrMessageNotFound
View Source
var ErrMessageTooLarge = internal.ErrMessageTooLarge
View Source
var ErrNodeNotFound = internal.ErrNodeNotFound
View Source
var ErrNotRoomMember = internal.ErrNotRoomMember
View Source
var ErrNotSubscribed = internal.ErrNotSubscribed
View Source
var ErrPermissionDenied = internal.ErrPermissionDenied

Permission errors.

View Source
var ErrPresenceDisabled = internal.ErrPresenceDisabled
View Source
var ErrPresenceNotFound = internal.ErrPresenceNotFound
View Source
var ErrRateLimitExceeded = internal.ErrRateLimitExceeded

Rate limiting.

View Source
var ErrRoomAccessDenied = internal.ErrRoomAccessDenied

Authorization errors.

View Source
var ErrRoomAlreadyExists = internal.ErrRoomAlreadyExists
View Source
var ErrRoomFull = internal.ErrRoomFull
View Source
var ErrRoomLimitReached = internal.ErrRoomLimitReached
View Source
var ErrRoomNotFound = internal.ErrRoomNotFound

Room errors.

View Source
var ErrRoomsDisabled = internal.ErrRoomsDisabled

Feature-flag errors.

View Source
var ErrSendDenied = internal.ErrSendDenied
View Source
var ErrSessionNotOwned = internal.ErrSessionNotOwned
View Source
var ErrTypingDisabled = internal.ErrTypingDisabled
View Source
var ErrUserBanned = internal.ErrUserBanned
View Source
var ErrUserMuted = internal.ErrUserMuted
View Source
var NewBackendError = internal.NewBackendError
View Source
var NewChannelError = internal.NewChannelError
View Source
var NewConnectionError = internal.NewConnectionError

Error constructors.

View Source
var NewMessageError = internal.NewMessageError
View Source
var NewRoomError = internal.NewRoomError

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

Room creation.

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

type AnalyticsEvent

type AnalyticsEvent = internal.AnalyticsEvent

type AnalyticsQuery

type AnalyticsQuery = internal.AnalyticsQuery

type AnalyticsResult

type AnalyticsResult = internal.AnalyticsResult

Analytics types.

type Availability

type Availability = internal.Availability

type BackendError

type BackendError = internal.BackendError

Backend error.

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

type ChannelStore

type ChannelStore = internal.ChannelStore

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

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

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

Connection types.

type DeviceInfo

type DeviceInfo = internal.DeviceInfo

type DistributedBackend

type DistributedBackend = internal.DistributedBackend

Distributed backend.

type DistributedBackendOptions

type DistributedBackendOptions = internal.DistributedBackendOptions

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) DashboardContributor added in v1.3.0

func (e *Extension) DashboardContributor() contributor.LocalContributor

DashboardContributor implements dashboard.DashboardAware. Returns a streaming dashboard contributor for auto-registration. Uses resolver closures so the manager/config are resolved at render time, not at discovery time (when they may not yet be initialized).

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-contract handlers (slice f migration target) into the dashboard's contract dispatcher and registers the embedded YAML manifest. Coexists with DashboardContributor: both are registered during dashboard startup, so the legacy /dashboard/ext/streaming/* and the new /dashboard/contract/streaming-contract/* paths both stay live during the migration window. See extensions/dashboard/contract/SLICE_F_DESIGN.md.

func (*Extension) RegisterDashboardBridge added in v1.3.0

func (e *Extension) RegisterDashboardBridge(b *bridge.Bridge) error

RegisterDashboardBridge implements dashboard.BridgeAware. Registers streaming bridge functions for Go↔JS communication. Uses resolver closures so the manager/config are resolved at request time.

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

type FileQuery

type FileQuery = internal.FileQuery

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

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).

type Lock

type Lock = internal.Lock

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

WithNodeID 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

type Member

type Member = internal.Member

type MemberOptions

type MemberOptions = internal.MemberOptions

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

type MessageHandler

type MessageHandler = internal.MessageHandler

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

type MessageSearchQuery

type MessageSearchQuery = internal.MessageSearchQuery

type MessageStore

type MessageStore = internal.MessageStore

type ModerationEvent

type ModerationEvent = internal.ModerationEvent

type ModerationStatus

type ModerationStatus = internal.ModerationStatus

Moderation types.

type NodeChangeEvent

type NodeChangeEvent = internal.NodeChangeEvent

type NodeChangeHandler

type NodeChangeHandler = internal.NodeChangeHandler

type NodeInfo

type NodeInfo = internal.NodeInfo

type OnlineStats

type OnlineStats = internal.OnlineStats

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

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

type PresenceStore

type PresenceStore = internal.PresenceStore

type PresenceTracker

type PresenceTracker = internal.PresenceTracker

Tracker interfaces.

type RateLimitStatus

type RateLimitStatus = internal.RateLimitStatus

Rate limiting.

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

type RoomBan

type RoomBan = internal.RoomBan

type RoomEvent

type RoomEvent = internal.RoomEvent

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

type RoomStats

type RoomStats = internal.RoomStats

Statistics types.

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

Typing.

type TypingStore

type TypingStore = internal.TypingStore

type TypingTracker

type TypingTracker = internal.TypingTracker

type UserPresence

type UserPresence = internal.UserPresence

type UserPresenceStats

type UserPresenceStats = internal.UserPresenceStats

type UserStats

type UserStats = internal.UserStats

type WebhookConfig

type WebhookConfig = internal.WebhookConfig

Webhook types.

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