Files
427e7578-d7bf-49c8-aee9-2dd…/specs/002-p2p-networking
2026-03-16 23:03:27 +13:00
..
2026-03-16 23:03:27 +13:00

status, created, tags, priority, created_at, depends_on, updated_at, completed_at, completed, transitions
status created tags priority created_at depends_on updated_at completed_at completed transitions
complete 2026-03-16
p2p
core
high 2026-03-16T07:51:47.888Z
001-project-setup
2026-03-16T10:01:26.362Z 2026-03-16T10:01:26.362Z 2026-03-16
status at
complete 2026-03-16T10:01:26.362Z

P2P Networking Layer

Status: Complete · Priority: High · Created: 2026-03-16 · Tags: p2p, core

Overview

Build the P2P networking layer using PeerJS. This handles connection lifecycle, message passing, and data synchronization between peers. Every poll operates as a "room" where the owner's peer ID is the room identifier.

Design

  • Connection model: Star topology per poll—owner acts as relay hub; participants connect to owner
  • Peer ID: Derived from user's public key (deterministic, resumable)
  • Messages: JSON-based protocol over PeerJS data channels
  • Reconnection: Auto-reconnect with exponential backoff
  • Sync: On connect, owner sends full poll state snapshot; subsequent changes are incremental messages

Message Protocol

type Message =
  | { type: 'poll:state'; payload: Poll }
  | { type: 'poll:vote'; payload: Vote }
  | { type: 'poll:option:add'; payload: Option }
  | { type: 'poll:role:update'; payload: RoleUpdate }
  | { type: 'user:profile'; payload: UserProfile }
  | { type: 'peer:discovery'; payload: PeerInfo[] }
  | { type: 'ack'; payload: { commandId: string; revision: number } }
  | { type: 'error'; payload: { commandId: string; message: string } }
  | { type: 'sync:request'; payload: { pollId: string } }

Offline Behavior

When the poll owner is offline, the poll is unreachable. Participants can view their local cached copy but cannot submit new votes until the owner reconnects.

Outbox & Acknowledgment

  • Every mutation message includes a commandId (UUID) for deduplication
  • Sender persists the command in a local IndexedDB outbox before sending
  • Owner responds with ack { commandId, revision } on success or error { commandId, message } on failure
  • On reconnect, client resends any unacked commands from the outbox
  • Owner deduplicates by commandId
  • UI shows "Pending sync" indicator for unacked mutations

Plan

  • Create PeerJS service (singleton, manages connection lifecycle)
  • Implement message send/receive with typed protocol
  • Add connection state management (connecting, connected, disconnected)
  • Implement auto-reconnect with backoff
  • Add "room" concept—join a poll by connecting to owner's peer ID
  • Handle peer disconnect/cleanup
  • Implement outbox (IndexedDB-backed pending command queue)
  • Implement ack/error response handling
  • Implement resend-on-reconnect for unacked commands

Test

  • Two browser tabs can establish a PeerJS connection
  • Messages round-trip correctly
  • Reconnection works after simulated disconnect
  • Unacked commands are resent after reconnect
  • Owner deduplicates commands by commandId

Notes

  • Star topology keeps it simple—owner must be online for live interaction
  • Could explore gossip/mesh topology later for resilience, but adds complexity