import json from datetime import datetime, timezone from fastapi import APIRouter, WebSocket, WebSocketDisconnect from app.websocket.manager import manager router = APIRouter() def now_iso() -> str: """Return an ISO timestamp so browsers can format message time locally.""" return datetime.now(timezone.utc).isoformat() def event(event_type: str, room_id: str, client_id: str, content: str): """Create the shared JSON shape sent from the server to the browser.""" return { "type": event_type, "room_id": room_id, "client_id": client_id, "content": content, "timestamp": now_iso(), } def clean_value(value: str, fallback: str) -> str: """Use a default value when a route parameter is empty after trimming.""" value = value.strip() return value if value else fallback @router.websocket("/ws/{room_id}/{client_id}") async def websocket_endpoint(websocket: WebSocket, room_id: str, client_id: str): """Handle one browser's full WebSocket lifecycle.""" room_id = clean_value(room_id, "عمومی") client_id = clean_value(client_id, "ناشناس") # FastAPI accepts the HTTP Upgrade request here and switches to WebSocket. await manager.connect(websocket, room_id, client_id) # Notify everyone in the room that the user joined, then refresh presence. await manager.broadcast_json( event("system", room_id, "سیستم", f"{client_id} وارد اتاق {room_id} شد."), room_id, ) await manager.broadcast_json( { "type": "presence", "room_id": room_id, "client_id": "سیستم", "content": "presence-updated", "users": manager.room_users(room_id), "timestamp": now_iso(), }, room_id, ) try: while True: # Keep the socket open and wait for the next message from this client. raw_data = await websocket.receive_text() # The browser sends JSON, but plain text is accepted for manual tests. try: data = json.loads(raw_data) message = str(data.get("content", "")).strip() except json.JSONDecodeError: message = raw_data.strip() if not message: continue await manager.broadcast_json( event("message", room_id, client_id, message), room_id, ) except WebSocketDisconnect: # When the browser tab closes or disconnects, remove it and update the room. manager.disconnect(websocket, room_id) await manager.broadcast_json( event("system", room_id, "سیستم", f"{client_id} از اتاق {room_id} خارج شد."), room_id, ) await manager.broadcast_json( { "type": "presence", "room_id": room_id, "client_id": "سیستم", "content": "presence-updated", "users": manager.room_users(room_id), "timestamp": now_iso(), }, room_id, )