Files
KoalaSync/docs/host-control-mode.md
T
Skrockle 463f871958 feat: Add protocol spec, rate limiting, vitest framework, and documentation
## Added
- **WebSocket Protocol Specification** (docs/PROTOCOL.md) - Complete reference
  for all 20+ events with payload schemas, rate limits, and edge cases
- **LEAVE_ROOM Rate Limiting** - 10 requests/socket/minute to prevent abuse
- **Vitest Testing Framework** - Modern test setup with coverage reporting
- **Host Control Mode Documentation** - Consolidated EN documentation
  (docs/host-control-mode.md) with references to internal implementation

## Changed
- **Graceful Shutdown** - Socket.IO clients properly disconnected during
  server shutdown (server/index.js)
- **CI Workflow** - Added test step with continue-on-error for gradual rollout
- **README** - Added protocol documentation link
- **CHANGELOG** - Updated with all new features and improvements

## Moved
- Internal documentation moved to docs/internal/ for better organization
- Host Control Mode docs consolidated from 4 files to 1 comprehensive guide

## Technical Details
- Protocol spec: 495 lines, covers all events from shared/constants.js
- Rate limiter: Follows existing pattern (checkAuthRate, checkEventRate)
- Vitest: 6 tests for rate limiter, all passing
- Documentation: Host Control Mode doc includes edge cases, testing checklist,
  architecture decisions
2026-07-01 23:27:30 +02:00

287 lines
9.0 KiB
Markdown

# Host Control Mode
## Overview
Host Control Mode allows the room host to control playback for all participants. Guests can attempt to play/pause/seek, but their actions are not broadcast to the room unless they explicitly choose to desync and watch on their own.
## Modes
### `everyone` (Default)
- Anyone in the room can control playback
- All play/pause/seek actions are broadcast to all peers
- Traditional KoalaSync behavior
### `host-only`
- Only the host (and promoted controllers) can control the room
- Guest actions are blocked and they are snapped back to the host's position
- Guests can choose to desync and watch independently
## Protocol Events
### `SET_CONTROL_MODE` (Client → Server)
Sent by the host to change the room's control mode.
**Payload:**
```json
{
"controlMode": "everyone" | "host-only"
}
```
**Authorization:** Only the host (room.hostPeerId) can send this event.
**Server Response:** Broadcasts `CONTROL_MODE` to all peers in the room.
### `CONTROL_MODE` (Server → Client)
Broadcast when the control mode changes or when a peer joins a room.
**Payload:**
```json
{
"controlMode": "everyone" | "host-only",
"hostPeerId": "string",
"controllers": ["string"]
}
```
**Triggered by:**
- Host changing the control mode
- Host leaving the room (fallback to 'everyone')
- Controller promotion/demotion
### `SET_PEER_ROLE` (Client → Server)
Sent by the host to promote or demote a peer to/from controller status.
**Payload:**
```json
{
"peerId": "string",
"controller": "boolean"
}
```
**Authorization:** Only the host (room.hostPeerId) can send this event.
**Server Response:** Broadcasts `CONTROL_MODE` to all peers in the room.
## Server-Side Implementation
### Room Object
```javascript
room = {
hostPeerId: "peer-id", // First peer to join the room
controlMode: "everyone", // 'everyone' | 'host-only'
controllers: ["peer-id"], // Peers allowed to control in host-only mode
lastControlModeChangeAt: 0, // Timestamp for rate limiting
lastRoleChangeAt: 0 // Timestamp for rate limiting
}
```
### Key Behaviors
1. **Host Migration:** When the host leaves, the room falls back to 'everyone' mode
2. **Controller Set:** Always includes the host, plus any promoted peers
3. **Rate Limiting:** Control mode changes are debounced to 500ms per room
## Client-Side Implementation
### State Management (background.js)
```javascript
let controlMode = CONTROL_MODES.EVERYONE;
let hostPeerId = null;
let controllers = [];
function amHost() { return hostPeerId === peerId; }
function amController() { return amHost() || controllers.includes(peerId); }
```
### Event Gating
**Sender-side gate (background.js):**
- Blocks `PLAY`, `PAUSE`, `SEEK`, `FORCE_SYNC_*`, `EPISODE_LOBBY_*` from non-controllers
- Sends `HOST_BLOCKED` message to content script instead
**Receiver-side gate (background.js):**
- Ignores gated events from non-controllers in host-only mode
- Prevents malicious or outdated clients from bypassing controls
### Snap-Back Logic (content.js)
When a guest's action is blocked:
1. Show dialog: "Stay in sync" (default) or "Watch on my own"
2. If "Stay in sync": snap player back to host's position
3. If "Watch on my own": set `hcmDesynced = true`, show resync button
### Host Sync Target Calculation
```javascript
function getHostSyncTarget() {
const host = currentRoom.peers.find(p => p.peerId === hostPeerId);
if (!host) return null;
let targetTime = host.currentTime;
// Extrapolate if host is playing
if (host.playbackState === 'playing' && host.lastHeartbeat) {
const elapsedSec = (Date.now() - host.lastHeartbeat) / 1000;
if (elapsedSec > 0 && elapsedSec <= 30) { // Max 30s extrapolation
targetTime += elapsedSec;
}
}
return { playbackState: host.playbackState, targetTime };
}
```
## Edge Cases
### 1. Host Leaves Room
- **Behavior:** Room falls back to 'everyone' mode
- **Implementation:** `removePeerFromRoom` checks if leaving peer is host
- **Broadcast:** `CONTROL_MODE` event sent to all remaining peers
### 2. Controller Promoted During Force Sync
- **Problem:** Controller's `FORCE_SYNC_EXECUTE` would be blocked
- **Solution:** `forceSyncInitiator` field tracks who started the sync
- **Behavior:** Initiator's EXECUTE is allowed even if demoted mid-sync
### 3. Guest Seek Spam
- **Problem:** Guest could spam seeks to disrupt
- **Solution:** Cooldown period after snap-back (1000ms)
- **Behavior:** Subsequent actions within cooldown are ignored
### 4. Host State Stale
- **Problem:** Host heartbeat hasn't arrived recently
- **Solution:** Max 30s extrapolation, then use last known position
- **Behavior:** Prevents overshooting if host paused but heartbeat delayed
### 5. Old Client Without Feature
- **Problem:** Client doesn't know about host-only mode
- **Solution:** Receiver-side gate in all clients
- **Behavior:** Modern clients ignore events from non-controllers
## User Experience
### Host UI
- Toggle: "Only I can control" (visible only to host)
- Role badges: "Host", "Controller", or "Guest"
- Guest notice: "The host controls playback" (when host-only active)
- Controller management: Promote/demote peers in participant list
### Guest UI
- **Blocked Action:** Dialog with choices:
- "Stay in sync" (default, auto-closes after 8s)
- "Watch on my own" (enters desync mode)
- **Desync Mode:** "Solo" badge with "Resync" button
- **Resync:** Returns to synchronized state with host
### Co-Host UI
- Same controls as host (can play/pause/seek/force-sync)
- "Controller" badge instead of "Host"
- Cannot promote/demote other controllers
## Testing Checklist
### Basic Functionality
1. ✅ Host can toggle between 'everyone' and 'host-only'
2. ✅ Guests see "host controls playback" notice in host-only
3. ✅ Guest play/pause/seek blocked in host-only
4. ✅ Guest snapped back to host position when blocked
5. ✅ Guest can choose "watch on my own" and desync
6. ✅ Desynced guest can resync with host
### Edge Cases
7. ✅ Host leaving room falls back to 'everyone'
8. ✅ Multiple rapid guest actions don't cause loop
9. ✅ Guest can desync during buffering/throttling
10. ✅ Old client events ignored by modern clients
11. ✅ Force sync initiated by controller works
12. ✅ Episode lobby initiated by controller works
### UI/UX
13. ✅ Host toggle only visible to host
14. ✅ Role badges show correct roles
15. ✅ Desync dialog auto-closes after timeout
16. ✅ Resync button visible when desynced
17. ✅ Controller promotion UI visible to host
## Architecture Decisions
### Why Double Gating?
**Sender-side + Receiver-side gates provide defense in depth:**
- Sender-side: Clean UX with dialog for guests
- Receiver-side: Protects against old/buggy/malicious clients
- Server-side: Central enforcement point (optional but recommended)
### Why Extrapolation?
**Linear extrapolation from last heartbeat provides ~±1s accuracy:**
- Better than freezing at last known position
- Handles minor network jitter gracefully
- Capped at 30s to avoid overshooting on stale data
### Why Cooldown?
**600-1000ms cooldown prevents control loops:**
- Guest pause → snap-back → pause → snap-back...
- Allows legitimate desync after cooldown expires
- Doesn't block deliberate user actions
## Migration Path
### From 'everyone' to 'host-only'
1. Host toggles mode to 'host-only'
2. Server validates host authority
3. Server broadcasts `CONTROL_MODE` to all peers
4. All clients update local state
5. Existing playback continues uninterrupted
6. Future guest actions are gated
### From 'host-only' to 'everyone'
1. Host toggles mode to 'everyone'
2. Server validates host authority
3. Server broadcasts `CONTROL_MODE` to all peers
4. All clients update local state
5. All peers regain control immediately
## Performance Considerations
- **Memory:** Minimal overhead (~100 bytes per room for HCM state)
- **CPU:** Snap-back calculation is O(1), negligible impact
- **Network:** No additional traffic beyond existing heartbeats
- **Storage:** Control mode persisted in room state, no additional storage
## Security Considerations
- **No Authentication:** Trust model is client-enforced (no tokens)
- **No Encryption:** Feature doesn't handle sensitive data
- **Rate Limiting:** Control mode changes debounced to prevent spam
- **Validation:** All mode changes validated server-side
## Future Enhancements
### Planned
- Host transfer button (manual host migration)
- Temporary controller promotion (time-limited)
- Guest request control (notification to host)
### Considered but Rejected
- Password-protected host transfer (too complex)
- Vote-based control mode (social complexity)
- Per-action permissions (UI overload)
## Changelog
- **2.5.0**: Initial implementation (July 2026)
- **2.5.1**: Added co-host support and controller promotion
- **2.5.2**: Improved desync UI with resync button
- **2.6.0**: Added server-side rate limiting for mode changes
## See Also
- [Protocol Specification](PROTOCOL.md) — Technical event details
- [Architecture](ARCHITECTURE.md) — System overview
- [Internal Documentation](internal/host-control-mode-plan.md) — Original implementation plan (German)