Real-Time Collaboration App
Scenario: Building Figma-like Collaboration
You’re building a real-time collaborative design tool with:
- WebSocket connections for live updates
- Canvas rendering with WebGL
- Conflict resolution for concurrent edits
- Presence awareness
- Offline support with sync
CLAUDE.md for Real-Time Features
## Real-Time Collaboration Architecture
### Core Types
```typescript
// Collaboration Types
interface CollaborationSession {
id: string;
documentId: string;
participants: Map<string, Participant>;
operations: OperationalTransform[];
version: number;
}
interface Participant {
id: string;
user: User;
cursor: CursorPosition;
selection: Selection | null;
color: string;
lastSeen: number;
permissions: Permission[];
}
interface CursorPosition {
x: number;
y: number;
viewport: Viewport;
}
// Operational Transform Types
type Operation =
| InsertOperation
| DeleteOperation
| MoveOperation
| StyleOperation
| GroupOperation;
interface InsertOperation {
type: 'insert';
id: string;
parentId: string;
index: number;
element: DesignElement;
timestamp: number;
userId: string;
}
interface DeleteOperation {
type: 'delete';
id: string;
timestamp: number;
userId: string;
}
// Conflict Resolution
interface ConflictResolver {
resolve(
local: Operation,
remote: Operation,
baseVersion: number
): ResolvedOperation[];
}
// WebSocket Message Types
type WSMessage =
| { type: 'operation'; operation: Operation; version: number }
| { type: 'cursor'; position: CursorPosition; userId: string }
| { type: 'selection'; selection: Selection; userId: string }
| { type: 'presence'; status: 'join' | 'leave'; participant: Participant }
| { type: 'sync'; operations: Operation[]; version: number };WebSocket Client Implementation
export class CollaborationClient {
private ws: WebSocket | null = null;
private messageQueue: WSMessage[] = [];
private reconnectTimer: NodeJS.Timeout | null = null;
private eventHandlers = new Map<string, Set<Function>>();
constructor(
private url: string,
private documentId: string,
private userId: string
) {}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.flushMessageQueue();
this.emit('connected');
resolve();
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data) as WSMessage;
this.handleMessage(message);
};
this.ws.onerror = (error) => {
this.emit('error', error);
reject(error);
};
this.ws.onclose = () => {
this.emit('disconnected');
this.scheduleReconnect();
};
});
}
private handleMessage(message: WSMessage): void {
switch (message.type) {
case 'operation':
this.handleOperation(message.operation, message.version);
break;
case 'cursor':
this.emit('cursor', { userId: message.userId, position: message.position });
break;
case 'selection':
this.emit('selection', { userId: message.userId, selection: message.selection });
break;
case 'presence':
this.emit('presence', message);
break;
case 'sync':
this.handleSync(message.operations, message.version);
break;
}
}
sendOperation(operation: Operation): void {
const message: WSMessage = {
type: 'operation',
operation,
version: this.currentVersion,
};
this.send(message);
}
private send(message: WSMessage): void {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
this.messageQueue.push(message);
}
}
private flushMessageQueue(): void {
while (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
const message = this.messageQueue.shift()!;
this.ws.send(JSON.stringify(message));
}
}
on<T extends keyof CollaborationEvents>(
event: T,
handler: CollaborationEvents[T]
): void {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, new Set());
}
this.eventHandlers.get(event)!.add(handler);
}
off<T extends keyof CollaborationEvents>(
event: T,
handler: CollaborationEvents[T]
): void {
this.eventHandlers.get(event)?.delete(handler);
}
private emit(event: string, ...args: any[]): void {
this.eventHandlers.get(event)?.forEach(handler => handler(...args));
}
}
// React Hook for Collaboration
export function useCollaboration(documentId: string) {
const [participants, setParticipants] = useState<Map<string, Participant>>(new Map());
const [connected, setConnected] = useState(false);
const clientRef = useRef<CollaborationClient>();
useEffect(() => {
const client = new CollaborationClient(
process.env.NEXT_PUBLIC_WS_URL!,
documentId,
getCurrentUserId()
);
clientRef.current = client;
client.on('connected', () => setConnected(true));
client.on('disconnected', () => setConnected(false));
client.on('presence', ({ status, participant }) => {
setParticipants(prev => {
const next = new Map(prev);
if (status === 'join') {
next.set(participant.id, participant);
} else {
next.delete(participant.id);
}
return next;
});
});
client.on('cursor', ({ userId, position }) => {
setParticipants(prev => {
const next = new Map(prev);
const participant = next.get(userId);
if (participant) {
next.set(userId, { ...participant, cursor: position });
}
return next;
});
});
client.connect();
return () => {
client.disconnect();
};
}, [documentId]);
return {
participants,
connected,
sendOperation: (op: Operation) => clientRef.current?.sendOperation(op),
updateCursor: (pos: CursorPosition) => clientRef.current?.updateCursor(pos),
};
}Offline Support with IndexedDB
// Offline storage for operations
export class OfflineStore {
private db: IDBDatabase | null = null;
async initialize(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open('collaboration', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains('operations')) {
const store = db.createObjectStore('operations', {
keyPath: 'id',
autoIncrement: true
});
store.createIndex('documentId', 'documentId', { unique: false });
store.createIndex('timestamp', 'timestamp', { unique: false });
}
if (!db.objectStoreNames.contains('documents')) {
db.createObjectStore('documents', { keyPath: 'id' });
}
};
});
}
async saveOperation(
documentId: string,
operation: Operation
): Promise<void> {
const tx = this.db!.transaction(['operations'], 'readwrite');
const store = tx.objectStore('operations');
await store.add({
documentId,
operation,
timestamp: Date.now(),
synced: false,
});
}
async getUnsyncedOperations(
documentId: string
): Promise<Operation[]> {
const tx = this.db!.transaction(['operations'], 'readonly');
const store = tx.objectStore('operations');
const index = store.index('documentId');
const operations: Operation[] = [];
return new Promise((resolve, reject) => {
const request = index.openCursor(IDBKeyRange.only(documentId));
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
if (!cursor.value.synced) {
operations.push(cursor.value.operation);
}
cursor.continue();
} else {
resolve(operations);
}
};
request.onerror = () => reject(request.error);
});
}
async markOperationsSynced(
documentId: string,
operationIds: string[]
): Promise<void> {
const tx = this.db!.transaction(['operations'], 'readwrite');
const store = tx.objectStore('operations');
for (const id of operationIds) {
const request = store.get(id);
request.onsuccess = () => {
const record = request.result;
if (record) {
record.synced = true;
store.put(record);
}
};
}
}
}
## Key Implementation Patterns
1. **Event-Driven Architecture**: Use WebSocket events for all real-time updates
2. **Operational Transforms**: Ensure consistency with concurrent edits
3. **Optimistic UI**: Update locally first, then sync
4. **Presence System**: Show who's working where
5. **Offline Queue**: Store operations when disconnected
## Performance Considerations
- Use WebSocket message batching
- Implement cursor position throttling
- Cache frequently accessed data
- Use WebWorkers for heavy computations
- Implement virtual scrolling for large canvases
## Related Resources
- [[../interactive-typescript|Interactive TypeScript Guide]]
- [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
- [Operational Transformation](http://ot.substance.io/visualization/)
## 🧭 Quick Navigation
[← E-Commerce](e-commerce-migration) | [Back to Scenarios](../index) | [Financial Trading →](/docs/reference/templates/real-world-scenarios/financial-trading-platform)