Real-time
Surf Live
Real-time state sync between AI agents and your UI via WebSocket channels.
Surf Live
Surf Live enables real-time state broadcasting from your server to all connected browser clients. When an AI agent executes commands that change state, those changes automatically propagate to every connected UI โ no polling, no manual refresh.
Use Cases#
- An AI agent editing a video timeline while the user watches live
- Collaborative editing where an agent makes changes visible to all viewers
- Real-time dashboards updated by agent workflows
Setup#
Enable Surf Live in your server config:
import { createSurf } from '@surfjs/core'ย const surf = await createSurf({ name: 'My App', commands: { /* your commands */ }, live: { enabled: true, maxChannelsPerConnection: 10, },})Channels#
A channel is a string identifier that groups connections. Clients subscribe to channels, and the server emits events scoped to those channels.
// In a command handler:surf.live.setState('project-123', { timeline: { clips: [...], playhead: 42.5 }, selectedClip: 'clip-7',})This emits a surf:state event to all clients subscribed to project-123.
State Methods#
setState(channelId, state)
Push full state to all subscribers on a channel:
surf.live.setState('project-123', { timeline: { clips: updatedClips, playhead: 42.5 },})patchState(channelId, patch)
Push a partial update โ clients merge it into their current state:
surf.live.patchState('project-123', { playhead: 43.0 })emit(event, data, channelId)
Emit a custom event to a channel:
surf.live.emit('cursor.moved', { x: 100, y: 200 }, 'project-123')Security#
Channel Auth
Optionally verify if a token has access to subscribe to a channel:
const surf = await createSurf({ // ... live: { enabled: true, channelAuth: async (token, channelId) => { const user = await verifyToken(token) return user.hasAccessTo(channelId) }, },})If channelAuth is configured, clients must authenticate before subscribing.
Limits
- Off by default โ must set
live.enabled: true - Max channels per connection โ default 10, configurable
- Isolation โ channel events never leak to session-scoped listeners
Version Ordering#
Each setState and patchState call increments a version counter. Clients use this for ordering and deduplication โ events with a version โค the last applied version are discarded.
WebSocket Protocol#
Subscribe
{ "type": "subscribe", "channels": ["project-123"] }Unsubscribe
{ "type": "unsubscribe", "channels": ["project-123"] }State Event
{ "type": "event", "event": "surf:state", "data": { "channel": "project-123", "state": { "timeline": { "clips": [], "playhead": 42.5 } }, "version": 7 }}