#!/usr/bin/env python3.12 from fastapi import FastAPI, WebSocket, WebSocketDisconnect import time import uuid import json import random from cah.constructors import CAHClient, CAHGame from cah.websocket_conn import ConnectionManager class CAH(FastAPI): """CAH Endpoint(s)""" def __init__(self, app: FastAPI, util, constants, glob_state): # pylint: disable=super-init-not-called self.app = app self.util = util self.constants = constants self.glob_state = glob_state self.games: list[dict] = [] self.ws_endpoints = { "cah": self.cah_handler, #tbd } self.endpoints = { #tbd if any non-WS endpoints } self.connection_manager = ConnectionManager() for endpoint, handler in self.ws_endpoints.items(): print(f"Adding websocket route: {endpoint} @ {handler}") app.add_api_websocket_route(f"/{endpoint}/", handler) for endpoint, handler in self.endpoints.items(): app.add_api_route(f"/{endpoint}/", handler, methods=["POST"]) async def cah_handler(self, websocket: WebSocket): """/cah WebSocket""" await self.connection_manager.connect(websocket) await websocket.send_json({ "event": "connection_established", "ts": int(time.time()), }) try: while True: data = await websocket.receive_json() event = data.get('event') if not event: await websocket.send_json({ 'err': True, 'errorText': 'Invalid data received, closing connection.' }) return await websocket.close() print(f"Event: {event}") match event: case 'handshake': await self.cah_handshake(websocket, data) case 'create_game': await self.create_game(websocket, data) case _: sender = self.connection_manager.get_connection_by_ws(websocket) await self.connection_manager.broadcast({ "event": "echo", "ts": int(time.time()), "from": sender.get('client').resource, "data": data, }) except WebSocketDisconnect: disconnected = self.connection_manager.get_connection_by_ws(websocket) self.connection_manager.disconnect(websocket) await self.connection_manager.broadcast({ "event": "client_disconnected", "ts": int(time.time()), "data": { "disconnected_resource": disconnected.get('client').resource, } }) async def cah_handshake(self, websocket: WebSocket, data): """Handshake""" data = data.get('data') if not data: await websocket.send_json({ "err": "WTF", }) return await websocket.close() csid = str(data.get('csid')) resource = data.get('resource') platform = data.get('platform') if not csid in self.constants.VALID_CSIDS: await websocket.send_json({ "err": "Unauthorized", }) return await websocket.close() client = CAHClient( resource=resource, platform=platform, csid=csid, connected_at=int(time.time()), current_game=None, ) await self.connection_manager.handshake_complete(self, websocket, csid, client) await websocket.send_json({ "event": "handshake_response", "ts": int(time.time()), "data": { "success": True, "resource": resource, "platform": platform, "games": [str(game.id) for game in self.games], }, }) async def create_game(self, websocket, data: str): data = data.get('data') if not data.get('rounds') or not str(data.get('rounds')).isnumeric(): return await websocket.send_json({ "event": "create_game_response", "ts": int(time.time()), "data": { "success": False, "errorText": "Invalid data", "recvdData": data, } }) client = self.connection_manager.get_connection_by_ws(websocket).get('client') rounds = int(data.get('rounds')) game_uuid = str(uuid.uuid4()) game = CAHGame(id=game_uuid, rounds=rounds, players=[client,], created_at=int(time.time()), state=-1, started_at=0, state_changed_at=int(time.time())) self.games.append(game) client.current_game = game await websocket.send_json({ "event": "create_game_response", "ts": int(time.time()), "data": { "success": True, "createdGame": client.current_game.id, } })