A WebSocket-based collaborative editing platform using RGA CRDTs for conflict-free concurrent editing, live cursors, and presence awareness. Supports 500+ concurrent users.
npm install
npm start
# → http://localhost:3001Open the URL in two or more browser tabs, enter a name and the same room ID, and start editing together.
Keyo/
├── server/
│ ├── server.js # Express + WebSocket server
│ ├── room-manager.js # Room lifecycle + disk persistence
│ └── crdt.js # Server-side RGA CRDT (authoritative)
├── client/
│ ├── index.html # App shell (join screen + editor)
│ ├── style.css # Dark glassmorphism design system
│ ├── crdt.js # Browser RGA CRDT (same algorithm)
│ ├── ws-client.js # WebSocket + reconnection wrapper
│ ├── cursors.js # Remote cursor overlay (mirror-div)
│ └── app.js # Main orchestrator
├── data/rooms/ # Persisted room documents (auto-created)
└── package.json
Every character holds a globally unique ID { clock, siteId }. Concurrent inserts at the same position are deterministically ordered by (higher clock first, then siteId descending) — so all clients always converge to the same document regardless of message arrival order.
| Op | Description |
|---|---|
insert |
Inserts char after a target character ID |
delete |
Tombstones a character (stays in structure, invisible) |
Both operations are commutative and idempotent — duplicate or reordered messages are safe.
| Message | Direction | Purpose |
|---|---|---|
join |
C→S | Join a room with name + optional userId |
init |
S→C | Full document snapshot + presence list |
op |
C↔S | CRDT operation (insert / delete) |
cursor |
C↔S | Cursor index update (throttled 30fps) |
join |
S→C | New user arrived notification |
leave |
S→C | User disconnected notification |
ping/pong |
C↔S | Keepalive every 20s |
Documents are saved to data/rooms/<roomId>.json automatically, debounced 2 seconds after each operation. They are loaded back on server restart. Empty rooms are evicted from memory (but kept on disk) after 10 seconds.
- Room isolation — ops are broadcast only within a room, never globally
- Return-path skip — server never echoes an op back to its sender
- Cursor throttle — client rate-limits cursor broadcasts to ~30fps
- Heartbeat TTL — WebSocket ping/pong every 20s; stale connections auto-close
- Memory eviction — idle empty rooms are flushed from memory
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
HTTP + WebSocket port |