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-edit or view-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:

  1. Serializes the new project to JSON
  2. Increments its local revision tracker
  3. Sends a snapshot frame

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: isValidChatMessage checks structure and length
  • Dual trimming: CHAT_HISTORY_LIMIT (count) and MAX_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 between co-edit and view-only
  • set-participant-mode: Toggle individual guest edit permissions

When permissions change, the server:

  1. Updates durable storage: this.ctx.storage.put('mode', newMode)
  2. Clears stale overrides to ensure immediate effect
  3. 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 CollabClient from collab-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.ts enforce 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →