433 lines
13 KiB
Markdown
433 lines
13 KiB
Markdown
# 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<string, BaseSession>;
|
|
state: string;
|
|
// ... infrastructure fields only
|
|
}
|
|
|
|
// Generic with metadata support
|
|
interface Session<TMetadata = any> extends BaseSession {
|
|
metadata?: TMetadata; // Application data goes here
|
|
}
|
|
|
|
interface Room<TMetadata = any> 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<string, Player>; // Game players
|
|
turn: Turn; // Current turn
|
|
placements: Placements; // Board state
|
|
chat: any[]; // Chat messages
|
|
// ... all game-specific fields
|
|
}
|
|
|
|
// Convenience types
|
|
type GameSession = Session<GameSessionMetadata>;
|
|
type GameRoom = Room<GameRoomMetadata>;
|
|
```
|
|
|
|
## 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<TMetadata> {
|
|
id: string; // Infrastructure
|
|
name: string; // Infrastructure
|
|
metadata?: TMetadata; // Application-specific data
|
|
}
|
|
|
|
// Usage
|
|
const session: Session<GameSessionMetadata> = {
|
|
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<ChatSessionMetadata>;
|
|
|
|
// Example: Whiteboard application
|
|
interface WhiteboardSessionMetadata {
|
|
tool: "pen" | "eraser" | "shape";
|
|
color: string;
|
|
brushSize: number;
|
|
}
|
|
|
|
type WhiteboardSession = Session<WhiteboardSessionMetadata>;
|
|
```
|
|
|
|
## Reusable Components
|
|
|
|
### MediaControl (Client)
|
|
|
|
Located in: `client/src/MediaControl.tsx`
|
|
|
|
**Reusable:** ✅ No application-specific logic
|
|
|
|
```typescript
|
|
// MediaControl only needs infrastructure data
|
|
<MediaControl
|
|
peer={peer} // Has: session_id, peer_name, srcObject
|
|
isSelf={isLocal}
|
|
sendJsonMessage={send}
|
|
/>
|
|
|
|
// MediaAgent only needs infrastructure
|
|
<MediaAgent
|
|
session={session} // Has: id, name, has_media
|
|
socketUrl={url}
|
|
peers={peers}
|
|
setPeers={setPeers}
|
|
/>
|
|
```
|
|
|
|
### 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<T>
|
|
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<MyAppSessionMetadata>;
|
|
type MyRoom = Room<MyAppRoomMetadata>;
|
|
|
|
// 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<Record<string, Peer>>({});
|
|
|
|
return (
|
|
<>
|
|
<MediaAgent
|
|
session={session} // Base session data
|
|
socketUrl={socketUrl}
|
|
peers={peers}
|
|
setPeers={setPeers}
|
|
/>
|
|
|
|
{participants.map(p => (
|
|
<MediaControl
|
|
peer={peers[p.session_id]}
|
|
isSelf={p.session_id === session.id}
|
|
/>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
## 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
|