Integration
Multiplayer with Liveblocks
Synchronize Telva documents and presence through Liveblocks using onChangePage and presence callbacks.
Multiplayer with Liveblocks
The reference example in examples/telva-example/src/multiplayer uses Liveblocks as transport/storage while Telva remains the editor runtime.
Core integration idea
- send local shape/binding/asset patches from
onChangePage - mirror cursor/selection presence through
onChangePresence - rehydrate remote updates into the editor using
replacePageContent
Minimal mount pattern
import { TVUserStatus, Telva } from 'telva'
import { RoomProvider } from './liveblocks.config'
import { useMultiplayerState } from './useMultiplayerState'
const roomId = 'mp-room'
export function Multiplayer() {
return (
<RoomProvider
id={roomId}
initialPresence={{
id: 'DEFAULT_ID',
user: {
id: 'DEFAULT_ID',
status: TVUserStatus.Connecting,
activeShapes: [],
color: 'black',
point: [0, 0],
selectedIds: [],
},
}}
>
<Editor roomId={roomId} />
</RoomProvider>
)
}
function Editor({ roomId }: { roomId: string }) {
const { error, ...events } = useMultiplayerState(roomId)
if (error) return <div>Error: {error.message}</div>
return <Telva {...events} disableAssets showPages={false} />
}Why disableAssets is often enabled
In multiplayer, unmanaged media may write large base64 payloads to shared storage.
For production rooms:
- keep
disableAssets={true}by default, or - implement object storage and return URL-backed assets via
onAssetCreate/onAssetUpload
Session-aware history management
The example pauses room history on session start and resumes it on session end:
const onSessionStart = () => {
room.history.pause()
}
const onSessionEnd = () => {
room.history.resume()
}This prevents noisy intermediate transforms from polluting collaborative history.
Data model used in the example
Liveblocks storage keys:
shapes: LiveMap<string, TVShape>bindings: LiveMap<string, TVBinding>assets: LiveMap<string, TVAsset>version: numberfor migration logic
On startup, the hook initializes maps, runs migration fallback if needed, subscribes to map changes, and calls app.replacePageContent(...).