forked from quic-issues/427e7578-d7bf-49c8-aee9-2dd999e25316
87 lines
3.2 KiB
Markdown
87 lines
3.2 KiB
Markdown
---
|
|
status: complete
|
|
created: '2026-03-16'
|
|
tags:
|
|
- p2p
|
|
- core
|
|
priority: high
|
|
created_at: '2026-03-16T07:51:47.888Z'
|
|
depends_on:
|
|
- 001-project-setup
|
|
updated_at: '2026-03-16T10:01:26.362Z'
|
|
completed_at: '2026-03-16T10:01:26.362Z'
|
|
completed: '2026-03-16'
|
|
transitions:
|
|
- status: complete
|
|
at: '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
|
|
|
|
```typescript
|
|
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
|