"""WebSocket proxy: authenticated web users ↔ Tracker WS.""" import asyncio import json import logging from fastapi import APIRouter, WebSocket, WebSocketDisconnect import websockets from auth import verify_token from config import TRACKER_WS_URL, TRACKER_TOKEN logger = logging.getLogger("bff.ws") router = APIRouter() @router.websocket("/ws") async def ws_proxy(ws: WebSocket, token: str = ""): # Authenticate user if not token: await ws.close(code=4001, reason="No token") return user = verify_token(token) if not user: await ws.close(code=4001, reason="Invalid token") return await ws.accept() user_name = user.get("name", "unknown") user_slug = user.get("sub", "admin") logger.info("WS connected: %s (%s)", user_name, user_slug) try: async with websockets.connect(TRACKER_WS_URL) as tracker_ws: # Send auth to tracker on behalf of user auth_msg = json.dumps({ "type": "auth", "token": TRACKER_TOKEN, }) await tracker_ws.send(auth_msg) # Wait for auth.ok auth_resp = await tracker_ws.recv() auth_data = json.loads(auth_resp) if auth_data.get("type") != "auth.ok": logger.error("Tracker auth failed: %s", auth_data) await ws.close(code=4002, reason="Tracker auth failed") return # Forward auth.ok to client await ws.send_text(auth_resp) async def forward_to_tracker(): try: while True: data = await ws.receive_text() await tracker_ws.send(data) except WebSocketDisconnect: pass async def forward_to_client(): try: async for msg in tracker_ws: await ws.send_text(msg) except Exception: pass await asyncio.gather(forward_to_tracker(), forward_to_client()) except Exception as e: logger.error("WS proxy error: %s", e) finally: logger.info("WS disconnected: %s", user_name)