A real-time collaborative text editor built with Go and WebSockets. Multiple users can edit the same document simultaneously with operational transformation for conflict resolution.
- Real-time Collaboration: Multiple clients can edit the same document simultaneously via WebSocket connections
- Operational Transformation: Automatically resolves conflicting insert and delete operations
- Document Rooms: Isolated document spaces identified by unique IDs (e.g.,
/ws/documents/document-123) - Version Tracking: Each document has a monotonically increasing version number for every operation applied
- Idempotent Operations: Duplicate operations are detected and ignored to prevent data corruption
- Document Snapshots: New clients receive the full current state upon joining a room
- Missing Operation Sync: Clients detect version gaps and automatically request missing operations
- Auto Cleanup: Empty rooms are removed when all clients disconnect
βββββββββββββββ βββββββββββββββββββββββββββββββ
β Client A ββββββΆβ β
β (Browser) β β HTTP Server β
βββββββββββββββ€ β - Serves static files β
β Client B ββββββΆβ - WebSocket upgrade β
β (Browser) β β - /health endpoint β
βββββββββββββββ βββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Hub β
β - Manages WebSocket β
β connections β
β - Routes to rooms β
β - Broadcasts messages β
βββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β RoomManager β
β - Creates rooms β
β - Joins/leaves clientsβ
β - Cleans up empty β
β rooms β
βββββββββββββ¬ββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
β Room A β β Room B β β Room C β
β - Doc content β β - Doc content β β - Doc content β
β - Version β β - Version β β - Version β
β - History β β - History β β - History β
β - Lock β β - Lock β β - Lock β
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
- Server (
cmd/server/main.go): Starts HTTP server, serves frontend, upgrades to WebSocket, graceful shutdown - Hub (
internal/websocket/handler.go): Accepts WebSocket connections, routes clients to document rooms, handles operation processing and broadcasting - Room (
internal/websocket/room.go): Holds document state, version history, and client list for a single document - Operation Processor (
internal/websocket/processor.go): Validates, applies, and idempotently tracks operations - Transformer (
internal/websocket/transform.go): Implements operational transformation for all conflict cases - Client (
internal/websocket/client.go): Wraps WebSocket connection with send buffer and base version tracking
{
"id": "op-123",
"type": "insert",
"position": 5,
"text": "Hello",
"base_version": 5
}For delete operations:
{
"id": "op-124",
"type": "delete",
"position": 5,
"length": 6,
"base_version": 5
}{
"type": "request_missing_operations",
"base_version": 5
}{
"type": "document_snapshot",
"content": "current document text",
"version": 10
}{
"type": "operation",
"operation": {
"id": "op-125",
"type": "insert",
"position": 0,
"text": "Hi",
"base_version": 10
},
"version": 11
}{
"type": "error",
"message": "stale operation"
}syncpad/
βββ cmd/
β βββ server/
β β βββ main.go # HTTP server with WebSocket support
β βββ client/
β βββ client.go # Demo client for testing
βββ internal/
β βββ websocket/
β βββ client.go # Client connection wrapper
β βββ handler.go # WebSocket connection handler
β βββ room.go # Room management and document state
β βββ operation.go # Operation validation and types
β βββ processor.go # Operation processing pipeline
β βββ transform.go # Operational transformation logic
β βββ message.go # Message envelope types
β βββ stale_error.go # Stale operation error
β βββ *_test.go # Unit tests
β βββ VERSIONING.md # Version system documentation
βββ web/
β βββ index.html # Frontend demo
βββ go.mod
βββ go.sum
βββ README.md
- Go 1.25.0 or later
# Clone the repository
git clone git@github.com:Smitbafna/collaborative-editor.git
cd syncpad
# Install dependencies
go mod download
# Run tests (optional)
go test ./...# Start the server (defaults to port 8080)
PORT=8080 go run ./cmd/server
# The server is now running at http://localhost:8080/
# Health check: http://localhost:8080/health
# WebSocket endpoint: ws://localhost:8080/ws/documents/{document-id}# In a separate terminal
go run ./cmd/client
# The demo client will:
# 1. Connect to the server
# 2. Insert "Hello World" at position 0
# 3. Delete 5 characters at position 0
# 4. Display server responsesOpen web/index.html in multiple browser tabs to see real-time collaboration between tabs.
- Client sends operation with a
base_versionrepresenting the document version it was based on - Server validates the operation under the room write lock:
- Checks for duplicate operation IDs
- Verifies the
base_versionmatches the current server version - Validates position/length bounds against current content
- If stale: Operation version conflicts with current version. Server transforms the operation against missing operations and applies it as-isolated. If transformation produces an invalid operation, it's converted to a no-op but still assigned a new version.
- If valid: Operation is applied, version is incremented, and broadcast to all clients in the room.
- Clients update their local document and version number from the broadcast.
When two clients edit the same document concurrently, operations may conflict. The server uses operational transformation to resolve conflicts:
Client A (v5) Client B (v5)
β β
insert "X" at 3 insert "Y" at 3
β β
βΌ βΌ
Server receives first operation successfully
β
βΌ
Server transforms second operation
insert "Y" at 3 β insert "Y" at 4
(because "X" now occupies position 3)
β
βΌ
Second operation applied successfully
Supported transformations:
- Insert after insert: Adjust positions for inserted text
- Insert after delete: Adjust positions for removed range
- Delete after insert: Adjust positions for inserted text
- Delete after delete: Compute remaining range, or no-op if delete was already removed
- Versions start at 0
- Every successful operation increments the version by 1
- Clients include their version in every operation (
base_version) - Version gaps trigger missing operation sync requests
- Sentry: Because transforming a stale operation can produce invalid results (positions out of bounds, etc.), the processor applies the following safeguard: after transformation against all missing operations, it validates the result against the current document content. If validation fails, the operation is downgraded to a no-op before being applied, preventing crashes or data corruption.
# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run specific test package
go test -v ./internal/websocket/| File | Purpose |
|---|---|
transform.go |
Core operational transformation logic |
handler.go |
WebSocket message routing and stale operation handling |
room.go |
Document state, version tracking, operation history |
processor.go |
Operation pipeline: validation, atomic apply, versioning |
/ws/documents/{document-id}
- Protocol:
ws://orwss:// - Path:
/ws/documents/{document-id}where{document-id}is any string identifying the document - Close: Server may close connections for slow clients (write buffer full)
| Path | Method | Description |
|---|---|---|
/health |
GET | Returns JSON {"status": "ok"} |
/ws/documents/{id} |
GET (upgrade) | WebSocket endpoint for document collaboration |
/ |
GET | Serves static frontend files from /web/ |
- No persistence: Document state is lost when server restarts
- No authentication: Any client can join any document ID
- No undo/redo: Operations are append-only
- No document creation: Documents are auto-created when first client joins
- No message size limit: Connections are closed on write buffer saturation
- Fork the repository
- Create a feature branch
- Run tests to verify your changes
- Submit a pull request
MIT