How GeoLibre Real-Time Collaboration Synchronization Works: WebSocket Architecture Explained
GeoLibre uses a lightweight WebSocket relay built on Cloudflare Durable Objects to synchronize live edits, presence cursors, and chat across multiple users in real time.
Real-time collaboration in GeoLibre enables multiple users to edit geographic projects simultaneously without conflicts. The system, as implemented in opengeos/GeoLibre, separates concerns into three distinct layers: client-side transport, server-side relay, and durable persistence. This article breaks down exactly how synchronization works across these layers, from session creation to live cursor updates.
The Three-Layer Architecture
GeoLibre's real-time collaboration synchronization relies on a clean separation between client, relay, and storage. Each layer has specific responsibilities and error-handling strategies.
| Layer | Key Responsibility | Primary File |
|---|---|---|
| Client | WebSocket connection, local state observation, UI integration | apps/geolibre-desktop/src/lib/collab-client.ts |
| Relay | Connection management, permission enforcement, broadcast coordination | workers/collab/src/session.ts |
| Storage | Durable snapshot persistence, revision tracking, bounded chat history | workers/collab/src/session.ts (Durable Object storage) |
The client layer watches the Zustand store for local edits and publishes snapshot, presence, chat, and mode frames. The relay layer enforces monotonic revision numbers, byte-size limits (~1 MiB for snapshots), and rate-limits for chat messages. The storage layer maintains the latest project snapshot and revision in Cloudflare's Durable Object storage, ensuring durability across server restarts.
Session Initialization and WebSocket Handshake
Collaboration begins with session creation and the join handshake. Understanding this flow explains how GeoLibre establishes trust and state synchronization.
Creating a Session
A host initiates collaboration via POST /sessions, which triggers the Durable Object's /init endpoint in workers/collab/src/session.ts. This stores:
- The session mode (
co-editorview-only) - A cryptographically random host token for subsequent authority claims
The Join Handshake
When a client connects to /ws, the Durable Object accepts the WebSocket upgrade via this.ctx.acceptWebSocket. The client then sends a join frame:
// Client sends
{
"type": "join",
"displayName": "Alice",
"color": "#4a90e2",
"hostToken": "optional-token-if-host"
}
The server responds with a welcome frame containing:
- A server-generated
clientId(unique to this connection) - Current session mode
- The latest project snapshot (parsed safely, never executed)
- Live presence map of all participants
- Recent chat history (trimmed to limits)
- Current revision number
This handshake ensures late joiners immediately receive the newest state. The handleJoin function in session.ts coordinates this exchange.
Snapshot Synchronization: The Core Edit Flow
Project edits follow a strict validation and broadcast pattern. This is where GeoLibre's real-time collaboration synchronization prevents conflicts and data corruption.
When a participant makes a local edit, the client:
- Serializes the new project to JSON
- Increments its local revision tracker
- Sends a
snapshotframe
The server validates before accepting:
// Simplified validation in session.ts
if (!canEdit(clientId)) throw 'Not authorized';
if (byteSize > MAX_SNAPSHOT_BYTES) throw 'Snapshot too large';
if (rev <= storedRevision) throw 'Stale revision';
On validation success, the server:
- Persists the new snapshot:
this.ctx.storage.put('snapshot', project) - Updates stored revision:
this.ctx.storage.put('revision', rev) - Broadcasts to all other sockets:
this.broadcast({ type: 'snapshot', project, rev })
The monotonic revision requirement ensures that out-of-order messages or stale edits are rejected, preventing conflicting state merges.
Presence Updates: Cursor and Viewport Tracking
Presence data—cursors, viewports, and participant activity—uses a separate, lighter-weight path. Unlike snapshots, presence lives only in memory for fast updates.
When a mouse moves or the map pans, the client sends:
collab.sendPresence({
cursor: { lng: -122.4194, lat: 37.7749 },
view: { bounds: [[sw], [ne]], zoom: 12 }
});
The server sanitizes via sanitizeCursor and sanitizeView, updates the in-memory presence Map, and forwards to peers. No disk write occurs, keeping latency under 50ms for cursor movements.
Chat System: Rate-Limited and Bounded
Chat messages persist to durable storage but with strict resource protection:
- Rate-limiting: Per-socket tokens prevent spam
- Size validation:
isValidChatMessagechecks structure and length - Dual trimming:
CHAT_HISTORY_LIMIT(count) andMAX_CHAT_STORAGE_BYTES(size)
The server appends valid messages, trims the log, persists with this.ctx.storage.put('chat', trimmedLog), then broadcasts.
Permission Model: Host Controls and Participant Overrides
GeoLibre's collaboration synchronization includes fine-grained permission controls. The host—the session creator—maintains special authority through the stored host token.
Host capabilities include:
set-mode: Change global session mode betweenco-editandview-onlyset-participant-mode: Toggle individual guest edit permissions
When permissions change, the server:
- Updates durable storage:
this.ctx.storage.put('mode', newMode) - Clears stale overrides to ensure immediate effect
- Broadcasts the change to all connected clients
Guests without the host token can only view and chat, not modify session configuration.
Client Integration: React Hooks and Zustand Store
The desktop application integrates collaboration through apps/geolibre-desktop/src/hooks/useCollaboration.ts. This React hook:
- Instantiates the
CollabClientfromcollab-client.ts - Subscribes to store changes and dispatches snapshots
- Exposes UI state: participant list, chat messages, remote cursors, session mode
The Zustand store slice in packages/core/src/store.ts holds runtime collaboration state separate from persistent project data, enabling clean separation between local editing and remote synchronization.
Security and Defensive Design
The relay's agnostic design—never parsing project contents beyond JSON validation—provides defense in depth:
- Type strictness: Protocol definitions in
workers/collab/src/protocol.tsenforce frame structure - Sanitization: All incoming data passes through validation functions (colors must match regex, coordinates must be finite numbers)
- Size enforcement: Hard caps prevent storage and memory exhaustion
- Revision monotonicity: Prevents rollback attacks or stale overwrites
A compromised client cannot corrupt session state beyond what permissions allow.
Summary
- GeoLibre's real-time collaboration synchronization uses Cloudflare Durable Objects as a WebSocket relay, not a game server—clients maintain authoritative state
- Snapshots carry full project state with monotonic revisions; presence is ephemeral memory-only
- The join handshake ensures late joiners receive current state immediately
- Host tokens enable centralized permission control without complex ACL systems
- Rate limits and size bounds protect against resource exhaustion
- The codebase separates protocol (
protocol.ts), transport (collab-client.ts), and UI (useCollaboration.ts) concerns cleanly
Frequently Asked Questions
What happens when two users edit simultaneously in GeoLibre?
The server enforces monotonic revision numbers. The second snapshot to arrive with a stale revision is rejected, and the client receives the newer snapshot from the server. The client must then merge or rebase its local changes. This is a last-write-wins strategy with explicit conflict detection, not operational transforms.
Can the collaboration server scale horizontally?
No—by design. Each CollabSession Durable Object is single-tenant and single-threaded, ensuring consistent state without distributed consensus. Cloudflare's infrastructure migrates Durable Objects to handle load, but a single session runs on one instance. This trades horizontal scaling per-session for simplicity and correctness.
Does GeoLibre support peer-to-peer collaboration without Cloudflare?
The current implementation requires Cloudflare Durable Objects for the relay and persistent storage. The protocol is generic enough that a self-hosted WebSocket server could implement the same interface, but no such adapter exists in the repository as of the analyzed version.
How large can collaborative projects be?
Snapshots are limited to approximately 1 MiB uncompressed. This accommodates complex GeoJSON projects with thousands of features while protecting storage costs and transmission latency. The server rejects oversized snapshots with an explicit error.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →