# Pluggable Room/WebRTC Architecture This document describes the clean separation between reusable Room/WebRTC infrastructure and application-specific game logic. ## Architecture Overview The system is now organized in three layers: ``` ┌─────────────────────────────────────────────────────┐ │ Application Layer (Game-Specific) │ │ - Game rules, player colors, resources │ │ - Stored in metadata fields │ └─────────────────────────────────────────────────────┘ │ ├── uses metadata ↓ ┌─────────────────────────────────────────────────────┐ │ Adapter Layer (Backward Compatibility) │ │ - Proxies for transparent access │ │ - Maps old API to new architecture │ └─────────────────────────────────────────────────────┘ │ ├── wraps ↓ ┌─────────────────────────────────────────────────────┐ │ Infrastructure Layer (Reusable) │ │ - Room/Session management │ │ - WebSocket handling │ │ - WebRTC signaling (MediaControl) │ │ - Participant lists │ └─────────────────────────────────────────────────────┘ ``` ## File Organization ### Infrastructure Layer (Reusable) **[server/routes/room/types.ts](server/routes/room/types.ts)** - `BaseSession`: Core session data (id, name, ws, live, has_media) - `Session`: Generic session with app-specific metadata - `BaseRoom`: Core room data (id, name, sessions, state) - `Room`: Generic room with app-specific metadata - `Participant`: Minimal info for participant lists - `PeerConfig`, `PeerRegistry`: WebRTC peer management **[server/routes/room/helpers.ts](server/routes/room/helpers.ts)** - `getParticipants()`: Get participant list from sessions - `createBaseSession()`: Create new session - `getSessionName()`: Get display name - `updateSessionActivity()`: Update last active timestamp - `isSessionActive()`: Check if session is active - `getActiveSessions()`: Filter active sessions - `cleanupInactiveSessions()`: Remove stale sessions **[client/src/MediaControl.tsx](client/src/MediaControl.tsx)** - `MediaAgent`: WebRTC signaling and connection management - `MediaControl`: Video feed and controls UI - Works with `Participant` type from any application ### Application Layer (Game-Specific) **[server/routes/games/gameMetadata.ts](server/routes/games/gameMetadata.ts)** - `GameSessionMetadata`: Game-specific session data (color, player, resources) - `GameRoomMetadata`: Game-specific room data (players, board, rules) - `GameSession`: Session type with game metadata - `GameRoom`: Room type with game metadata - Migration helpers between old and new formats **[server/routes/games/types.ts](server/routes/games/types.ts)** - `Player`: Player game state - `Turn`, `Placements`, `DevelopmentCard`: Game-specific types - (Legacy `Session` and `Game` types - will be deprecated) ### Adapter Layer (Backward Compatibility) **[server/routes/games/gameAdapter.ts](server/routes/games/gameAdapter.ts)** - `wrapSession()`: Proxy for transparent metadata access - `wrapGame()`: Proxy for transparent metadata access - Allows code to use `session.color` instead of `session.metadata.color` - Enables gradual migration without breaking existing code ## Type Definitions ### Infrastructure Types ```typescript // Base Session (reusable) interface BaseSession { // Identity id: string; userId?: number; name: string | null; // Connection ws?: WebSocket; live: boolean; lastActive: number; connected: boolean; // Media has_media: boolean; // Security/Bot protected?: boolean; bot_run_id?: string | null; bot_provider_id?: string | null; bot_instance_id?: string | null; } // Generic Session with metadata interface Session extends BaseSession { metadata?: TMetadata; // Your app data goes here } // Participant (for UI lists) interface Participant { name: string | null; session_id: string; live: boolean; has_media: boolean; protected?: boolean; // ... bot fields } ``` ### Game-Specific Types ```typescript // Game session metadata interface GameSessionMetadata { color?: string; // Player color in game player?: Player; // Reference to player object resources?: number; // Temporary resource count } // Game room metadata interface GameRoomMetadata { players: Record; // Game players by color developmentCards: DevelopmentCard[]; placements: Placements; turn: Turn; // ... all game-specific data } // Complete game session type GameSession = Session; // Complete game room type GameRoom = Room; ``` ## Usage Examples ### Creating a New Session (Infrastructure) ```typescript import { createBaseSession } from './room/helpers'; // Create base session (no game data) const baseSession = createBaseSession('session-123', 'Alice'); // Add game-specific data const gameSession: GameSession = { ...baseSession, metadata: { color: 'red', player: gamePlayerObject, } }; ``` ### Getting Participants (Reusable) ```typescript import { getParticipants } from './room/helpers'; // Get base participant list (works for any app) const baseParticipants = getParticipants(room.sessions); // Extend with game-specific data const gameParticipants = baseParticipants.map(p => ({ ...p, color: room.sessions[p.session_id].metadata?.color, })); ``` ### Using the Adapter (Backward Compatibility) ```typescript import { wrapSession, wrapGame } from './games/gameAdapter'; // Old code can still work const session = wrapSession(gameSession); console.log(session.color); // Accesses session.metadata.color transparently const game = wrapGame(gameRoom); console.log(game.players); // Accesses game.metadata.players transparently ``` ## Reusing for Another Application To use the Room/WebRTC infrastructure for a different application: ### 1. Define Your Metadata Types ```typescript // myapp/metadata.ts export interface MySessionMetadata { score: number; team: string; role: string; } export interface MyRoomMetadata { gameMode: string; maxPlayers: number; settings: any; } export type MySession = Session; export type MyRoom = Room; ``` ### 2. Use Infrastructure Helpers ```typescript import { createBaseSession, getParticipants } from './room/helpers'; // Create session with your metadata const session: MySession = { ...createBaseSession('user-456', 'Bob'), metadata: { score: 0, team: 'blue', role: 'defender', } }; // Get participants (works out of the box) const participants = getParticipants(room.sessions); ``` ### 3. Extend Participants with Your Data ```typescript function getMyAppParticipants(room: MyRoom) { const base = getParticipants(room.sessions); return base.map(p => ({ ...p, team: room.sessions[p.session_id].metadata?.team, role: room.sessions[p.session_id].metadata?.role, })); } ``` ### 4. Use MediaControl As-Is ```typescript // client/src/MyApp.tsx import { MediaAgent, MediaControl } from './MediaControl'; // Works with your participant type ``` ## Migration Path ### Phase 1: Add Metadata Layer (Current) - ✅ Create new types with metadata separation - ✅ Create adapter layer for backward compatibility - ✅ Document architecture - Existing code continues to work unchanged ### Phase 2: Gradual Migration (Future) - Update `getParticipants()` to use `import { getParticipants } from './room/helpers'` - Move game logic to use `session.metadata.color` explicitly - Update session creation to use new format - Remove adapter proxies where code is migrated ### Phase 3: Complete Separation (Future) - Extract room/WebRTC code to separate package - Publish as reusable library - Game code only depends on metadata types - Full separation achieved ## Benefits ### For Current Game 1. **Cleaner Code**: Game logic is clearly separated from infrastructure 2. **Easier Testing**: Can mock sessions without game data 3. **Better Type Safety**: Explicit metadata types 4. **Maintainability**: Clear boundaries between layers ### For Reusability 1. **Drop-In WebRTC**: MediaControl works with any app 2. **Flexible Metadata**: Easy to add app-specific data 3. **Minimal Coupling**: Infrastructure has zero game dependencies 4. **Proven Patterns**: Well-documented extension points ## Current Implementation Status - ✅ **Infrastructure types defined** ([server/routes/room/types.ts](server/routes/room/types.ts)) - ✅ **Helper functions created** ([server/routes/room/helpers.ts](server/routes/room/helpers.ts)) - ✅ **Game metadata types defined** ([server/routes/games/gameMetadata.ts](server/routes/games/gameMetadata.ts)) - ✅ **Adapter layer implemented** ([server/routes/games/gameAdapter.ts](server/routes/games/gameAdapter.ts)) - ✅ **Documentation complete** (this file) - ⏳ **Existing code uses adapter** (backward compatible, no changes needed) - ⏳ **Gradual migration** (future work, optional) ## Example: Todo List App Using This Infrastructure ```typescript // todo-metadata.ts interface TodoSessionMetadata { completedCount: number; role: 'viewer' | 'editor' | 'admin'; } interface TodoRoomMetadata { todos: Array<{ id: string; text: string; done: boolean }>; createdBy: string; } // todo-app.ts import { createBaseSession, getParticipants } from './room/helpers'; import { MediaAgent } from './MediaControl'; type TodoSession = Session; type TodoRoom = Room; // Create room const room: TodoRoom = { ...createBaseRoom('room-123'), metadata: { todos: [], createdBy: 'user-1', } }; // Add session room.sessions['user-1'] = { ...createBaseSession('user-1', 'Alice'), metadata: { completedCount: 0, role: 'admin', } }; // Get participants with WebRTC (works out of box) const participants = getParticipants(room.sessions); // Use MediaControl for video chat while working on todos ``` The Todo app gets WebRTC/video for free, with clean separation! ## Summary This architecture provides: - ✅ **Clean separation** between infrastructure and application - ✅ **Reusable components** (Room, Session, MediaControl, WebRTC) - ✅ **Type-safe metadata** for application-specific data - ✅ **Backward compatibility** via adapter layer - ✅ **Easy migration path** with no breaking changes - ✅ **Well-documented** extension points for new applications