Plugin Real-time

Transmit — Server-Sent Events

Transmit provides Server-Sent Events (SSE) for real-time server-to-client push. Subscribe to channels with authorization, get lifecycle hooks, and scale to multiple instances with Redis transport. Included by default when creating a new app with nimbus new.

§ Overview

SSE Streaming
HTTP-native push
Channel Auth
Pattern-based guards
Lifecycle Hooks
5 event callbacks
Redis Transport
Multi-instance sync
Keep-alive
Configurable ping
Targeted Send
Broadcast / Exclude

For bidirectional WebSockets and channel subscriptions, use the Reverb plugin instead of Transmit.

§ Installation

$ nimbus plugin:install transmit

Or add manually:

import "github.com/CodeSyncr/nimbus/plugins/transmit"

app.Use(transmit.New(nil))

§ Configuration

Environment Variable Description Default
TRANSMIT_PATHRoute prefix for SSE endpoints__transmit
TRANSMIT_PING_INTERVALKeep-alive ping frequency (e.g. 30s, 1m)disabled
TRANSMIT_TRANSPORTMulti-instance transport (redis)none (in-memory)
REDIS_URLRedis connection URL for transportredis://localhost:6379

§ Auto-Registered Routes

Method Route Purpose
GET/__transmit/eventsEstablish SSE connection, receive UID
POST/__transmit/subscribeSubscribe to a channel
POST/__transmit/unsubscribeUnsubscribe from a channel

§ Broadcasting

Push data to all subscribers of a channel from anywhere in your application:

import "github.com/CodeSyncr/nimbus/plugins/transmit"

func (ctrl *ChatController) Send(c *http.Context) error {
    message := c.Body("message")
    userID := auth.User(c).ID

    data := map[string]any{
        "message": message,
        "user_id": userID,
        "sent_at": time.Now(),
    }

    // Broadcast to all subscribers
    transmit.Broadcast("chats/1/messages", data)

    // Broadcast to all EXCEPT the sender (exclude by UID)
    transmit.BroadcastExcept("chats/1/messages", data, c.Get("transmit_uid"))

    return c.JSON(data)
}

You can also query subscribers:

// Get all subscriber UIDs for a channel
subscribers := transmit.GetSubscribers("chats/1/messages")
fmt.Printf("Active listeners: %d\n", len(subscribers))

§ Channel Authorization

Guard channels with pattern-based authorization. The pattern uses :param syntax to extract dynamic segments:

import "github.com/CodeSyncr/nimbus/plugins/transmit"

// Private user channel — only the owner can subscribe
transmit.Authorize("users/:id/notifications", 
    func(ctx *http.Context, params map[string]string) bool {
        user := auth.User(ctx)
        return user != nil && fmt.Sprint(user.ID) == params["id"]
    },
)

// Team channel — check membership
transmit.Authorize("teams/:teamId/updates",
    func(ctx *http.Context, params map[string]string) bool {
        user := auth.User(ctx)
        if user == nil {
            return false
        }
        return models.IsTeamMember(user.ID, params["teamId"])
    },
)

// Public channel — no authorization needed (default behavior)
// Channels without an Authorize rule are open to all subscribers

How it works: When a client POSTs to /__transmit/subscribe, the channel name is matched against registered authorization patterns. If a matching pattern is found, the callback decides whether to allow the subscription. Channels without any authorization rule are public.

§ Lifecycle Hooks

React to connection events for analytics, presence tracking, or cleanup:

import "github.com/CodeSyncr/nimbus/plugins/transmit"

// Client connects — receives a unique UID
transmit.OnConnect(func(uid string) {
    log.Printf("Client connected: %s", uid)
    // Track online users, increment stats, etc.
})

// Client disconnects (connection closed / network drop)  
transmit.OnDisconnect(func(uid string) {
    log.Printf("Client disconnected: %s", uid)
    // Mark user offline, cleanup presence
})

// Client subscribes to a channel (after auth passes)
transmit.OnSubscribe(func(uid, channel string) {
    log.Printf("UID %s subscribed to %s", uid, channel)
})

// Client unsubscribes from a channel
transmit.OnUnsubscribe(func(uid, channel string) {
    log.Printf("UID %s unsubscribed from %s", uid, channel)
})

// Data broadcasted to a channel
transmit.OnBroadcast(func(channel string, payload any) {
    log.Printf("Broadcast on %s: %v", channel, payload)
    // Audit log, trigger side effects, etc.
})

§ Redis Transport (Multi-Instance)

By default, Transmit stores connections in-memory — so broadcasts only reach clients connected to the same server instance. For multi-instance deployments, enable the Redis transport:

TRANSMIT_TRANSPORT=redis
REDIS_URL=redis://localhost:6379

With Redis transport enabled:

  • Broadcasts are published to a Redis Pub/Sub channel
  • All connected instances receive and relay messages to local subscribers
  • Subscribe/unsubscribe operations are synchronized across instances
  • Each instance uses a unique instanceID to avoid echo

§ Client Setup — @codesyncr/echo

Install the official Nimbus real-time client SDK:

npm install @codesyncr/echo

Connect and subscribe to channels:

import { Echo } from '@codesyncr/echo'

const echo = new Echo({
  baseURL: 'http://localhost:3333',
})

// Public channel
echo.channel('notifications')
  .listen('NewMessage', (data) => {
    console.log('New message:', data)
  })

// Private channel (requires auth)
echo.private('projects.1')
  .listen('RenderComplete', (data) => {
    console.log('Render done:', data)
  })

// Presence channel
echo.join('room.1')
  .here((users) => console.log('Online:', users))
  .joining((user) => console.log('Joined:', user))
  .leaving((user) => console.log('Left:', user))
  .listen('ChatMessage', (data) => console.log(data))

// Connection events
echo.onConnect(() => console.log('Connected!'))
echo.onDisconnect(() => console.log('Disconnected'))

// Leave / disconnect
echo.leave('notifications')
echo.disconnect()

Echo Configuration

Option Type Default Description
baseURLstringNimbus server URL
pathstring__transmitTransmit route prefix
bearerTokenstringBearer token for private channels
csrfTokenstringCSRF token for POST requests
autoReconnectbooleantrueAuto-reconnect on disconnect
reconnectDelaynumber1000Reconnect delay (ms)
maxReconnectAttemptsnumberInfinityMax reconnect attempts

Manual protocol (without Echo):

  1. Connect to GET /__transmit/events — receive your UID in the first SSE message
  2. Subscribe: POST /__transmit/subscribe with {"uid": "...", "channel": "chats/1"}
  3. Receive events on the SSE stream as JSON payloads
  4. Unsubscribe: POST /__transmit/unsubscribe with the same body

§ Production Notes

  • Disable response compression for text/event-stream in your reverse proxy (Nginx: proxy_buffering off; Traefik: disable compress middleware for SSE routes)
  • Enable TRANSMIT_PING_INTERVAL=30s to keep connections alive through load balancers with idle timeouts
  • Use Redis transport when running more than one server instance
  • Monitor active connections via transmit.GetSubscribers() for capacity planning