mantimon-tcg/backend/app/socketio/README.md
Cal Corum f512c7b2b3 Refactor to dependency injection pattern - no monkey patching
- ConnectionManager: Add redis_factory constructor parameter
- GameService: Add engine_factory constructor parameter
- AuthHandler: New class replacing standalone functions with
  token_verifier and conn_manager injection
- Update all tests to use constructor DI instead of patch()
- Update CLAUDE.md with factory injection patterns
- Update services README with new patterns
- Add socketio README documenting AuthHandler and events

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 22:54:57 -06:00

121 lines
3.4 KiB
Markdown

# Socket.IO Module
Real-time WebSocket communication for active game sessions.
## Architecture
```
Client <--WebSocket--> Socket.IO Server <--> GameService <--> GameEngine
|
v
ConnectionManager <--> Redis (presence tracking)
```
## Components
| Component | Responsibility | DI Pattern |
|-----------|---------------|------------|
| `server.py` | Socket.IO server setup, event handlers | Uses `auth_handler` |
| `auth.py` | `AuthHandler` class for JWT authentication | `token_verifier`, `conn_manager` |
## AuthHandler
Handles JWT-based authentication with injectable dependencies:
```python
class AuthHandler:
def __init__(
self,
token_verifier: TokenVerifier | None = None, # Callable[[str], UUID | None]
conn_manager: ConnectionManager | None = None,
) -> None:
self._token_verifier = token_verifier or verify_access_token
self._connection_manager = conn_manager or connection_manager
# Global singleton for production
auth_handler = AuthHandler()
# Tests inject mocks via constructor
handler = AuthHandler(
token_verifier=lambda token: user_id,
conn_manager=mock_connection_manager,
)
```
### Methods
| Method | Purpose |
|--------|---------|
| `authenticate_connection(sid, auth)` | Validate JWT, return `AuthResult` |
| `setup_authenticated_session(sio, sid, user_id)` | Save session, register connection |
| `cleanup_authenticated_session(sid)` | Unregister connection, return user_id |
## Event Handlers
All handlers are in the `/game` namespace:
| Event | Direction | Purpose |
|-------|-----------|---------|
| `connect` | Client→Server | Authenticate and establish session |
| `disconnect` | Client→Server | Clean up session and notify opponent |
| `game:join` | Client→Server | Join/rejoin a game session |
| `game:action` | Client→Server | Execute a game action |
| `game:resign` | Client→Server | Resign from game |
| `game:heartbeat` | Client→Server | Keep connection alive |
## Client Connection
```javascript
const socket = io("wss://api.example.com", {
auth: { token: "JWT_ACCESS_TOKEN" }
});
socket.on("connect", () => {
socket.emit("game:join", { game_id: "uuid" });
});
socket.on("game:state", (state) => {
// Full game state update
});
socket.on("auth_error", (error) => {
// { code: "invalid_token", message: "..." }
});
```
## Testing
Use dependency injection - no monkey patching:
```python
@pytest.fixture
def mock_token_verifier():
return MagicMock(return_value=uuid4())
@pytest.fixture
def mock_connection_manager():
cm = AsyncMock()
cm.register_connection = AsyncMock()
return cm
@pytest.fixture
def auth_handler(mock_token_verifier, mock_connection_manager):
return AuthHandler(
token_verifier=mock_token_verifier,
conn_manager=mock_connection_manager,
)
async def test_authenticate_success(auth_handler, mock_token_verifier):
"""Test successful authentication with valid JWT."""
result = await auth_handler.authenticate_connection("sid", {"token": "valid"})
assert result.success
mock_token_verifier.assert_called_once_with("valid")
```
## See Also
- `app/services/connection_manager.py` - Connection presence tracking
- `app/services/game_service.py` - Game orchestration
- `app/schemas/ws_messages.py` - WebSocket message schemas
- `CLAUDE.md` - Architecture guidelines