# Pluggable Room/WebRTC Architecture This document describes the layered architecture that separates reusable Room/WebRTC infrastructure from application-specific logic. ## Overview The system is designed with three distinct layers: 1. **Infrastructure Layer** - Reusable Room/Session/WebRTC management 2. **Metadata Layer** - Application-specific data attached to infrastructure 3. **Adapter Layer** - Backward compatibility and migration support This separation allows MediaControl and Room management to be reused across different applications without modification. ## Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────┐ │ Application Layer │ │ (Game Logic - Settlers of Catan specific) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Game Rules, Turns, Resources, Trading, etc. │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ uses metadata ┌────────────────────▼────────────────────────────────────────┐ │ Metadata Layer │ │ (Application-specific data structures) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Session.metadata { color, player, resources } │ │ │ │ Room.metadata { game state, board, cards, etc. } │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ attached to ┌────────────────────▼────────────────────────────────────────┐ │ Infrastructure Layer │ │ (Reusable across applications) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ BaseSession: id, name, ws, live, has_media │ │ │ │ BaseRoom: id, sessions, state │ │ │ │ MediaControl: WebRTC signaling, peer management │ │ │ │ Room Helpers: getParticipants, session management │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Type Hierarchy ### Infrastructure Types (Reusable) Located in: `server/routes/room/types.ts` ```typescript // Core session - no application logic interface BaseSession { id: string; name: string | null; ws?: WebSocket; live: boolean; has_media: boolean; // ... infrastructure fields only } // Core room - no application logic interface BaseRoom { id: string; sessions: Record; state: string; // ... infrastructure fields only } // Generic with metadata support interface Session extends BaseSession { metadata?: TMetadata; // Application data goes here } interface Room extends BaseRoom { metadata?: TMetadata; // Application data goes here } ``` ### Application-Specific Types Located in: `server/routes/games/gameMetadata.ts` ```typescript // Game-specific session data interface GameSessionMetadata { color?: string; // Player color selection player?: Player; // Reference to game player resources?: number; // Temporary resource count } // Game-specific room data interface GameRoomMetadata { players: Record; // Game players turn: Turn; // Current turn placements: Placements; // Board state chat: any[]; // Chat messages // ... all game-specific fields } // Convenience types type GameSession = Session; type GameRoom = Room; ``` ## Key Principles ### 1. Clear Separation of Concerns **Infrastructure Layer Responsibilities:** - WebSocket connection management - Session lifecycle (connect, disconnect, timeout) - WebRTC signaling (join, peer, ICE, SDP) - Participant listing - Room state management **Metadata Layer Responsibilities:** - Application-specific data structures - Business logic - Game rules and validation - Application state ### 2. Metadata Pattern All application-specific data is stored in `metadata` fields: ```typescript // ❌ Old approach - mixed concerns interface Session { id: string; // Infrastructure name: string; // Infrastructure color: string; // APPLICATION SPECIFIC! ❌ player: Player; // APPLICATION SPECIFIC! ❌ } // ✅ New approach - separated concerns interface Session { id: string; // Infrastructure name: string; // Infrastructure metadata?: TMetadata; // Application-specific data } // Usage const session: Session = { id: "abc123", name: "Alice", metadata: { color: "red", // Game-specific player: {...} // Game-specific } }; ``` ### 3. Extensibility Any application can use the infrastructure by defining their own metadata types: ```typescript // Example: Chat application interface ChatSessionMetadata { displayColor: string; status: "available" | "away" | "busy"; lastTyping?: number; } type ChatSession = Session; // Example: Whiteboard application interface WhiteboardSessionMetadata { tool: "pen" | "eraser" | "shape"; color: string; brushSize: number; } type WhiteboardSession = Session; ``` ## Reusable Components ### MediaControl (Client) Located in: `client/src/MediaControl.tsx` **Reusable:** ✅ No application-specific logic ```typescript // MediaControl only needs infrastructure data // MediaAgent only needs infrastructure ``` ### Room Helpers (Server) Located in: `server/routes/room/helpers.ts` **Reusable:** ✅ Generic functions work with any metadata ```typescript import { getParticipants } from './room/helpers'; // Works with any Room const participants = getParticipants(room.sessions); // Returns base participant data (no app-specific fields) // [{ session_id, name, live, has_media, ... }] ``` ### Extending Participants Applications can extend the base participant list: ```typescript // Get base participants (reusable) const baseParticipants = getParticipants(room.sessions); // Add application-specific data const gameParticipants = baseParticipants.map(p => ({ ...p, color: room.sessions[p.session_id].metadata?.color || null })); ``` ## Migration Strategy ### Phase 1: Adapter Layer (Current) Use the adapter layer to maintain backward compatibility: ```typescript import { wrapGame, wrapSession } from './games/gameAdapter'; // Wrap existing game objects const game = wrapGame(loadedGame); // Code still works with old syntax session.color = "red"; // Proxied to session.metadata.color game.turn = {...}; // Proxied to game.metadata.turn ``` ### Phase 2: Gradual Migration Migrate functions one at a time: ```typescript // Old code function doSomething(game: any) { const color = session.color; // Direct access } // New code function doSomething(game: GameRoom) { const color = session.metadata?.color; // Metadata access } ``` ### Phase 3: Pure Architecture Eventually remove adapters and use pure metadata architecture: ```typescript // All application data in metadata const session: GameSession = { id: "123", name: "Alice", has_media: true, live: true, // ... base fields metadata: { color: "red", player: {...}, resources: 5 } }; ``` ## Usage Examples ### Example 1: Creating a New Room Type ```typescript // Define your metadata interface MyAppSessionMetadata { customField: string; } interface MyAppRoomMetadata { appState: any; } // Use the base types type MySession = Session; type MyRoom = Room; // Reuse infrastructure import { getParticipants } from './room/helpers'; function listUsers(room: MyRoom) { return getParticipants(room.sessions); } ``` ### Example 2: Extending Participant Data ```typescript // Start with base participants const participants = getParticipants(room.sessions); // Add app-specific fields const extendedParticipants = participants.map(p => { const session = room.sessions[p.session_id]; return { ...p, // Add your custom fields customField: session.metadata?.customField, }; }); ``` ### Example 3: MediaControl Integration ```typescript // MediaControl is fully reusable import { MediaAgent, MediaControl } from './MediaControl'; function MyApp() { const [peers, setPeers] = useState>({}); return ( <> {participants.map(p => ( ))} ); } ``` ## Benefits ### 1. Reusability - MediaControl can be used in any WebRTC application - Room management works for any multi-user application - WebSocket handling is application-agnostic ### 2. Maintainability - Clear boundaries between infrastructure and application - Easy to test each layer independently - Simpler upgrade paths ### 3. Type Safety - Generic types ensure type safety across layers - TypeScript enforces proper metadata usage - IntelliSense works correctly ### 4. Flexibility - Easy to add new applications using same infrastructure - Can run multiple different applications on same server - Minimal code changes to adapt to new use cases ## File Organization ``` server/ ├── routes/ │ ├── room/ # Reusable infrastructure │ │ ├── types.ts # Base types (BaseSession, BaseRoom, etc.) │ │ └── helpers.ts # Reusable functions │ │ │ └── games/ # Game-specific code │ ├── types.ts # Game domain types (Player, Turn, etc.) │ ├── gameMetadata.ts # Metadata type definitions │ ├── gameAdapter.ts # Backward compatibility │ └── helpers.ts # Game-specific logic client/ └── src/ ├── MediaControl.tsx # Reusable WebRTC component └── PlayerList.tsx # Uses MediaControl + game metadata ``` ## Best Practices ### DO: ✅ Keep infrastructure layer generic and reusable ✅ Put all application logic in metadata ✅ Use type parameters for extensibility ✅ Document what's reusable vs. application-specific ✅ Test infrastructure independently ### DON'T: ❌ Mix application logic into infrastructure types ❌ Hard-code application fields in base types ❌ Duplicate infrastructure code per application ❌ Bypass metadata layer in new code ❌ Create circular dependencies between layers ## Next Steps 1. **Immediate**: Use adapter layer for backward compatibility 2. **Short-term**: Migrate high-traffic functions to use metadata 3. **Long-term**: Remove adapters, use pure metadata architecture 4. **Future**: Extract infrastructure into separate npm package ## See Also - [MEDIACONTROL_API.md](./MEDIACONTROL_API.md) - WebRTC signaling protocol - [server/routes/room/types.ts](./server/routes/room/types.ts) - Infrastructure types - [server/routes/games/gameMetadata.ts](./server/routes/games/gameMetadata.ts) - Game metadata types - [server/routes/games/gameAdapter.ts](./server/routes/games/gameAdapter.ts) - Compatibility adapter