mantimon-tcg/backend/app/socketio
Cal Corum 52c8edcf93 Fix WebSocket game:join to emit game:state event
Backend was returning the state but not emitting the game:state
event that the frontend listens for. Added explicit emit call
to send game:state to the client after successful join.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 12:27:01 -06:00
..
__init__.py Refactor to dependency injection pattern - no monkey patching 2026-01-28 22:54:57 -06:00
auth.py Refactor to dependency injection pattern - no monkey patching 2026-01-28 22:54:57 -06:00
game_namespace.py Fix WebSocket game:join to emit game:state event 2026-02-01 12:27:01 -06:00
README.md Refactor to dependency injection pattern - no monkey patching 2026-01-28 22:54:57 -06:00
server.py Complete Phase 4 implementation files 2026-01-30 08:03:43 -06:00

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 tracking
  • app/services/game_service.py - Game orchestration
  • app/schemas/ws_messages.py - WebSocket message schemas
  • CLAUDE.md - Architecture guidelines