Real-Time Collaboration Tool
A real-time collaborative editing tool built with React, WebSockets, and Operational Transformation, enabling multiple users to edit documents simultaneously.
ReactWebSocketNode.jsRedisMongoDBOperational Transformation
Real-Time Collaboration Tool
A sophisticated real-time collaboration platform that allows multiple users to edit documents simultaneously with conflict-free merging using Operational Transformation.
Project Overview
This project explores the challenges of building real-time collaborative editing, similar to Google Docs. It implements Operational Transformation (OT) to handle concurrent edits from multiple users.
Core Challenges
1. Concurrency Control
When multiple users edit the same document simultaneously, we need to ensure:
- All users see the same final state
- No edits are lost
- Changes are applied in a consistent order
2. Operational Transformation
interface Operation {
type: 'insert' | 'delete'
position: number
content?: string
length?: number
userId: string
timestamp: number
}
function transform(op1: Operation, op2: Operation): Operation {
if (op1.type === 'insert' && op2.type === 'insert') {
if (op1.position < op2.position) {
return op2
} else if (op1.position > op2.position) {
return { ...op2, position: op2.position + (op1.content?.length || 0) }
} else {
// Handle concurrent inserts at same position
return op1.userId < op2.userId
? op2
: { ...op2, position: op2.position + (op1.content?.length || 0) }
}
}
// Handle other operation type combinations
// ...
}
3. WebSocket Communication
// Server-side WebSocket handling
io.on('connection', (socket) => {
socket.on('join-document', async (documentId) => {
socket.join(documentId)
const document = await getDocument(documentId)
socket.emit('document-loaded', document)
// Notify others
socket.to(documentId).emit('user-joined', {
userId: socket.id,
userName: socket.data.userName
})
})
socket.on('operation', async (data) => {
const { documentId, operation } = data
// Transform operation against pending operations
const transformedOp = await transformOperation(operation)
// Apply to document
await applyOperation(documentId, transformedOp)
// Broadcast to other users
socket.to(documentId).emit('operation', transformedOp)
})
})
Architecture
Client Layer
- React: UI components and local state
- Editor: Custom text editor with rich formatting
- WebSocket Client: Real-time communication
- Local Buffer: Queue operations during network issues
Server Layer
- Node.js: WebSocket server with Socket.io
- Redis: Pub/sub for horizontal scaling
- MongoDB: Document storage
- Operation Queue: Process and transform operations
Data Flow
User A Types → Operation Created → Transform Queue →
→ Broadcast to Users → Apply Locally → Update UI
Features Implemented
✅ Core Functionality
- Real-time text editing
- Cursor position synchronization
- User presence indicators
- Conflict-free operation merging
✅ Rich Text Support
- Bold, italic, underline formatting
- Lists and headings
- Code blocks
- Links and images
✅ Collaboration Features
- Active user list
- User cursors with names
- Change history
- Document versioning
🚧 In Progress
- Offline support with sync
- Mobile app
- Voice comments
- Advanced permissions
Performance Considerations
Network Optimization
// Batch operations to reduce network calls
class OperationBatcher {
private operations: Operation[] = []
private timeout: NodeJS.Timeout | null = null
addOperation(op: Operation) {
this.operations.push(op)
if (this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.flush()
}, 50) // Batch operations within 50ms
}
flush() {
if (this.operations.length > 0) {
socket.emit('operations-batch', this.operations)
this.operations = []
}
}
}
Scalability
- Horizontal Scaling: Redis pub/sub for multi-server deployment
- Load Balancing: Sticky sessions for WebSocket connections
- Database Sharding: Documents sharded by ID
- Caching: Frequently accessed documents cached in Redis
Testing Strategy
Unit Tests
describe('Operational Transformation', () => {
it('should transform concurrent inserts correctly', () => {
const op1 = { type: 'insert', position: 5, content: 'hello' }
const op2 = { type: 'insert', position: 5, content: 'world' }
const transformed = transform(op1, op2)
expect(transformed.position).toBe(10)
})
})
Integration Tests
- WebSocket connection handling
- Operation transformation pipeline
- Database persistence
- User session management
End-to-End Tests
- Multi-user editing scenarios
- Network interruption handling
- Conflict resolution
- Performance under load
Current Status
The project is in active development with the core OT engine complete and working. Current focus is on:
- Improving mobile experience
- Adding offline support
- Performance optimization for large documents
- Enhanced rich text features
Technical Insights
What Worked Well
- Operational Transformation proved reliable for conflict resolution
- WebSocket with Socket.io provided excellent real-time performance
- Redis pub/sub enabled seamless horizontal scaling
Challenges Faced
- OT implementation complexity for rich text operations
- Handling network interruptions gracefully
- Managing memory for large documents with long histories
- Cursor synchronization across different screen sizes