Packages
@surfjs/react
React SDK for Surf Live โ real-time state sync hooks and components.
@surfjs/react
React SDK for Surf Live โ connect your React app to a Surf WebSocket server and receive real-time state updates from AI agents.
Installation#
npm install @surfjs/reactPeer dependencies: react ^18.0.0 || ^19.0.0
SurfProvider#
Wrap your app with SurfProvider to establish a WebSocket connection:
import { SurfProvider } from '@surfjs/react'ย function App() { return ( <SurfProvider url="wss://myapp.com/surf/ws" auth="your-auth-token" channels={['project-123']} > <MyComponent /> </SurfProvider> )}Props
| Prop | Type | Description |
|------|------|-------------|
| url | string | WebSocket URL to connect to |
| auth | string? | Auth token sent on connect |
| channels | string[]? | Channels to subscribe to on connect |
Auto-Reconnect
The provider automatically reconnects with exponential backoff: 1s โ 2s โ 4s โ 8s โ max 30s.
Channel subscriptions are restored on reconnect.
useSurf#
Access the core Surf context โ execute commands and check connection status:
import { useSurf } from '@surfjs/react'ย function Controls() { const { execute, connected, status } = useSurf()ย const addClip = async () => { const result = await execute('video.addClip', { url: 'clip.mp4' }) if (result.ok) console.log('Added:', result.result) }ย return ( <button onClick={addClip} disabled={!connected}> Add Clip </button> )}Returns
| Property | Type | Description |
|----------|------|-------------|
| execute | (command, params?) => Promise<SurfResult> | Execute a Surf command |
| connected | boolean | Whether WebSocket is connected |
| status | ConnectionStatus | 'connecting' \| 'connected' \| 'disconnected' \| 'reconnecting' |
| sessionId | string? | Current session ID |
useSurfEvent#
Subscribe to real-time events. Automatically cleans up on unmount:
import { useSurfEvent } from '@surfjs/react'ย function Timeline() { const [clips, setClips] = useState([])ย useSurfEvent('timeline.updated', (data) => { setClips(data.clips) })ย return <ClipList clips={clips} />}useSurfState#
Synced state that auto-updates from Surf Live broadcast events:
import { useSurfState } from '@surfjs/react'ย function Editor() { const [state, setState] = useSurfState('project-123', { timeline: { clips: [], playhead: 0 }, selectedClip: null, })ย // `state` updates automatically when the server calls: // surf.live.setState('project-123', newState) // surf.live.patchState('project-123', partialUpdate)ย return ( <div> <p>Playhead: {state.timeline.playhead}s</p> <p>Clips: {state.timeline.clips.length}</p> </div> )}useSurfState handles both full state updates (surf:state) and patches (surf:patch). It uses version numbers to prevent stale updates.
useSurfChannel#
Dynamically manage channel subscriptions:
import { useSurfChannel } from '@surfjs/react'ย function ProjectSwitcher({ projectId }) { const { subscribe, unsubscribe, channels } = useSurfChannel()ย useEffect(() => { subscribe(projectId) return () => unsubscribe(projectId) }, [projectId])ย return <p>Subscribed to: {[...channels].join(', ')}</p>}Connection Status#
import { useSurf } from '@surfjs/react'ย function StatusIndicator() { const { status } = useSurf()ย const colors = { connecting: 'yellow', connected: 'green', disconnected: 'red', reconnecting: 'orange', }ย return ( <span style={{ color: colors[status] }}> {status} </span> )}TypeScript#
All hooks and components are fully typed. The SurfResult type:
interface SurfResult { ok: boolean result?: unknown error?: { code: string; message: string } state?: Record<string, unknown>}