Real-Time Sync
Sync cursors, edits, and shared state across multiple clients using CloudSignal's pub/sub messaging.
Real-time sync keeps shared state consistent across multiple clients. When one user moves a cursor, edits a field, or updates a value, every other participant sees the change instantly. CloudSignal's pub/sub model makes this straightforward - clients publish changes to shared topics, and all subscribers receive updates in real time.
Architecture
Multiple clients subscribe to the same room topics. When any client publishes a change, the broker fans it out to all other subscribers immediately.
Each client both publishes and subscribes, creating a bidirectional sync loop. The broker handles fan-out - no server-side relay is needed.
Topic Design
Organize sync topics by room and field to keep updates granular:
sync/{room_id}/{field}Examples:
| Topic | Purpose |
|---|---|
sync/doc-123/content | Document body changes |
sync/doc-123/cursors | Live cursor positions |
sync/doc-123/selections | Text selection highlights |
sync/whiteboard-9/objects | Whiteboard object state |
Splitting by field prevents unnecessary re-renders. A cursor movement does not trigger a content update handler, and vice versa.
How It Works
- Client connects to CloudSignal and subscribes to all topics for its room (
sync/doc-123/#) - User makes a change - the client publishes the delta to the relevant topic
- Broker fans out the message to all other subscribers on that topic
- Each client applies the incoming delta to its local state
Messages are typically published at QoS 0 for lowest latency. For collaborative editing where every keystroke matters, QoS 0 is acceptable because the next update will correct any missed frame.
For document editing, send operational transforms or CRDTs as the message payload rather than the full document. This minimizes bandwidth and avoids overwrite conflicts.
Implementation with @cloudsignal/collaborate
The @cloudsignal/collaborate SDK provides React components that handle connection management, cursor rendering, and state synchronization out of the box.
Wrap Your App with Space
import { CloudSignalProvider, Space } from '@cloudsignal/collaborate'
function App() {
return (
<CloudSignalProvider
host="wss://connect.cloudsignal.app:18885/"
token="your-token"
>
<Space id="doc-123">
<CollaborativeEditor />
</Space>
</CloudSignalProvider>
)
}Add Live Cursors
import { CursorOverlay, useSelf } from '@cloudsignal/collaborate'
function CollaborativeEditor() {
const self = useSelf()
return (
<div style={{ position: 'relative' }}>
<CursorOverlay />
<textarea
onPointerMove={(e) => {
self.updatePresence({
cursor: { x: e.clientX, y: e.clientY }
})
}}
/>
</div>
)
}Sync Shared State
import { useSharedState } from '@cloudsignal/collaborate'
function Counter() {
const [count, setCount] = useSharedState('counter', 0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
)
}Every call to setCount publishes the new value to the sync topic. All other clients in the same Space receive the update and re-render.
Raw MQTT Example
If you are not using React or prefer direct MQTT control:
import { connect } from '@cloudsignal/mqtt-client'
const client = await connect({
host: 'wss://connect.cloudsignal.app:18885/',
username: 'sync-user',
password: 'your-token'
})
// Subscribe to all fields in the room
await client.subscribe('sync/doc-123/#')
// Listen for updates from other clients
client.on('message', (topic, payload) => {
const field = topic.split('/').pop()
const data = JSON.parse(payload.toString())
applyUpdate(field, data)
})
// Publish a change
client.publish('sync/doc-123/content', JSON.stringify({
user: 'alice',
delta: { position: 42, insert: 'hello' }
}))Next Steps
- Collaborate SDK reference - full API for the React collaboration components
- Presence - add online/offline indicators and typing status
- QoS Levels - choose the right delivery guarantee for your sync messages