1
0
peddlers-of-ketran/PLUGGABLE_ARCHITECTURE.md

376 lines
11 KiB
Markdown

# 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<TMetadata>`: Generic session with app-specific metadata
- `BaseRoom`: Core room data (id, name, sessions, state)
- `Room<TMetadata>`: 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<TMetadata = any> 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<string, Player>; // Game players by color
developmentCards: DevelopmentCard[];
placements: Placements;
turn: Turn;
// ... all game-specific data
}
// Complete game session
type GameSession = Session<GameSessionMetadata>;
// Complete game room
type GameRoom = Room<GameRoomMetadata>;
```
## 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<MySessionMetadata>;
export type MyRoom = Room<MyRoomMetadata>;
```
### 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
<MediaAgent
socketUrl={socketUrl}
session={session}
peers={peers}
setPeers={setPeers}
/>
```
## 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<TodoSessionMetadata>;
type TodoRoom = Room<TodoRoomMetadata>;
// 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
<MediaAgent session={session} peers={peers} setPeers={setPeers} />
```
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