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:

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

typescript
// 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:

typescript
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:

typescript
surf.live.patchState('project-123', { playhead: 43.0 })

emit(event, data, channelId)

Emit a custom event to a channel:

typescript
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:

typescript
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

json
{ "type": "subscribe", "channels": ["project-123"] }

Unsubscribe

json
{ "type": "unsubscribe", "channels": ["project-123"] }

State Event

json
{
"type": "event",
"event": "surf:state",
"data": {
"channel": "project-123",
"state": { "timeline": { "clips": [], "playhead": 42.5 } },
"version": 7
}
}