- 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> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| auth.py | ||
| README.md | ||
| server.py | ||
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:
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
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:
@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 trackingapp/services/game_service.py- Game orchestrationapp/schemas/ws_messages.py- WebSocket message schemasCLAUDE.md- Architecture guidelines