Skip to content

WebRTC Peer-to-Peer Mesh for Offline Field Device Sync with CRDT Merge and QR Fallback Discovery #65

Description

@elizabetheonoja-art

Problem Statement / Feature Objective

Field teams operating in remote areas with no cellular connectivity cannot sync their collected meter data and inspection reports back to the central server. A WebRTC-based peer-to-peer mesh network must enable direct device-to-device synchronization without any infrastructure. Devices discover each other via mDNS (local network) or Bluetooth LE (proximity), negotiate a WebRTC connection using a relay signaling server when available (fallback to manual QR code exchange), and sync pending outbox entries using a CRDT-based merge strategy to resolve concurrent edits. The mesh must support up to 50 devices in a single session.

Technical Invariants & Bounds

  • Mesh topology: fully connected peer-to-peer (each peer connects to all others). For 50 peers, this is 1,225 connections. Use an upper limit of 20 direct connections; beyond that, route through relay peers (spanning tree).
  • Signaling: WebSocket signaling server at wss://signal.utilityprotocol.com. Fallback: manual QR code encoding the SDP offer/answer (max 4 KB per exchange).
  • Sync payload: each peer's outbox contains SyncEntry { id, resourceType, action, data, timestamp, peerId, vectorClock }. Entries are batched: max 500 KB per sync message.
  • CRDT merge: each peer maintains a Lamport vector clock. Conflicting edits to the same resource are resolved by last-writer-wins (by timestamp) with peerId tiebreaker.
  • Data channels: single SCTP data channel per peer connection, ordered and reliable. Channel label: "utility-sync". Max message size: 256 KB (split larger payloads).
  • Connection lifecycle: automatic reconnection with exponential backoff up to 30 seconds. If a peer disconnects, its entries remain in the outbox; they are re-sent on reconnection.

Codebase Navigation Guide

  • src/services/webrtc/SyncPeer.ts - Represents a single peer connection: manages RTCPeerConnection lifecycle, data channel, and message framing.
  • src/services/webrtc/SyncMesh.ts - Mesh manager: tracks connected peers, handles peer discovery, maintains spanning tree for relay.
  • src/services/webrtc/SignalingClient.ts - WebSocket-based signaling client for exchanging SDP offers/answers and ICE candidates.
  • src/services/webrtc/QRDiscovery.ts - QR code generation/scanning for manual SDP exchange fallback (uses qrcode and jsQR libraries).
  • src/hooks/useOfflineSync.ts - React hook providing sync status: connected peers, pending outbox count, last sync timestamp.
  • src/store/slices/syncSlice.ts - Redux slice tracking sync state: peer list, outbox queue, vector clock for each peer.
  • src/utils/vectorClock.ts - Lamport vector clock implementation for CRDT conflict resolution.

Implementation Blueprint

  1. Implement VectorClock in src/utils/vectorClock.ts: Map<peerId, number>. Methods: tick(peerId), merge(other), compare(other) returns Before | After | Concurrent.
  2. Build SyncPeer class: constructor(peerId, config) creates RTCPeerConnection with STUN/TURN servers. createDataChannel('utility-sync', { ordered: true }). On message, parse SyncEntry batch, apply CRDT merge via vector clock, dispatch SYNC_ENTRY_RECEIVED action.
  3. SyncMesh class manages a Map<peerId, SyncPeer>. Methods: join(peerId, sdp?), broadcast(entries), leave(peerId). For mesh with > 20 peers, compute a spanning tree (minimum spanning tree based on latency estimates) and route entries through the tree.
  4. SignalingClient connects to wss://signal.utilityprotocol.com, sends { type: 'join', roomId, peerId }, receives peer joined/left events and SDP offers. On receiving offer, create a new SyncPeer and setRemoteDescription.
  5. QRDiscovery fallback: generate a QR code with the local SDP offer as a data URL, display on screen. Scanning mode uses getUserMedia + jsQR to read another device's QR, extract SDP, and complete the handshake.
  6. useOfflineSync hook orchestrates: initialize SyncMesh, connect to signaling (or start QR discovery), subscribe to syncSlice for UI state. Exposes { peers, outboxCount, sync(entries), status }.
  7. syncSlice stores: peers (array of { id, connected, latency, lastSync }), outbox (SyncEntry[]), vectorClocks (Record<peerId, Record<peerId, number>>). On SYNC_ENTRY_RECEIVED, merge into the local resource state via the CRDT logic.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions