Real-time Collaboration Example
This example demonstrates building a collaborative document editor with Bindra's real-time features.
Features Demonstrated
- ✅ WebSocket real-time synchronization
- ✅ Optimistic updates for instant UI feedback
- ✅ Auto-save with debounce and max wait
- ✅ Presence awareness (who's editing)
- ✅ Conflict detection and resolution
- ✅ Connection status handling
- ✅ XSS protection with field sanitization
Architecture
┌─────────────┐ WebSocket ┌─────────────┐
│ Client A │◄───────────────────────────►│ Server │
└─────────────┘ └─────────────┘
▲
│
┌─────────────┐ WebSocket │
│ Client B │◄───────────────────────────────────┘
└─────────────┘Setup
1. Install Dependencies
bash
pnpm install2. Start WebSocket Server
You'll need a WebSocket server that broadcasts document updates. Example using Node.js:
javascript
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (message) => {
// Broadcast to all clients
clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.on('close', () => {
clients.delete(ws);
});
});3. Run the Example
bash
npx ts-node index.tsKey Concepts
Real-time Configuration
typescript
const documents = new DataSource<Document>({
url: 'https://api.example.com/documents',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
reconnect: true,
reconnectInterval: 5000
},
optimisticUpdates: true
});Event Handling
typescript
// Listen for remote changes
documents.on('created', (doc) => {
console.log('New document:', doc);
});
documents.on('updated', (doc) => {
if (doc.lastEditedBy !== currentUser) {
// Handle conflict
}
});
// Connection status
documents.on('ws:connected', () => {
updateUI('connected');
});Auto-save with Debounce
typescript
import { debounceWithMaxWait } from 'bindra';
const autoSave = debounceWithMaxWait(
(id, changes) => documents.update(id, changes),
2000, // Wait 2s after last edit
10000 // But save at least every 10s
);
editor.on('change', () => {
autoSave(docId, { content: editor.getContent() });
});Optimistic Updates
typescript
// With optimisticUpdates: true
await documents.update(id, { content: 'New text' });
// UI updates immediately (optimistic)
// If server fails, automatically rolls backPresence Broadcasting
typescript
// Send presence through WebSocket
documents.sendWebSocketMessage({
type: 'presence',
data: {
userId: currentUser.id,
cursorPosition: 42,
color: '#3b82f6'
}
});
// Receive presence from others
documents.on('realtime:message', (message) => {
if (message.type === 'presence') {
showUserCursor(message.data);
}
});Conflict Resolution Strategies
1. Last Write Wins (Simple)
typescript
if (remoteDoc.version > localDoc.version) {
// Accept remote changes
return remoteDoc;
}2. Operational Transformation (Advanced)
typescript
function resolveConflict(local, remote) {
// Apply OT algorithm to merge changes
return mergeOperations(local.ops, remote.ops);
}3. Manual Resolution
typescript
// Show conflict dialog to user
showConflictDialog({
local: localDoc,
remote: remoteDoc,
onResolve: (resolved) => {
documents.update(id, resolved);
}
});Best Practices
Always handle connection errors
typescriptdocuments.on('ws:error', handleError); documents.on('ws:disconnected', showOfflineMode);Use version numbers or timestamps
typescriptinterface Document { version: number; lastEditedAt: Date; }Sanitize user input
typescriptsecurity: { sanitizeFields: ['title', 'content'] }Implement proper debouncing
typescript// Don't save on every keystroke debounceWithMaxWait(save, 1000, 10000);Clean up on unmount
typescriptonUnmount(() => { documents.disconnectWebSocket(); documents.off('updated', handler); });