Fixed WebSocket message validation by ensuring all messages include data field

- Updated server to send status_ok messages with data field instead of direct timestamp
- Modified voicebot _send_message to always include data field (empty dict if no data)
- Fixed all error message formats in server to use proper {type, data: {error}} structure
- Updated client join message to include empty data field for consistency
- All WebSocket messages now comply with WebSocketMessageModel requiring data field
- Resolved Pydantic validation errors for status_ok and error messages
This commit is contained in:
James Ketr 2025-09-03 18:14:59 -07:00
parent a3b9e7fa39
commit bd5e5e4d8f
5 changed files with 27 additions and 22 deletions

View File

@ -953,7 +953,7 @@ const MediaAgent = (props: MediaAgentProps) => {
if (media && joinStatus.status === "Not joined" && readyState === ReadyState.OPEN) { if (media && joinStatus.status === "Not joined" && readyState === ReadyState.OPEN) {
console.log(`media-agent - Initiating media join for ${session.name}`); console.log(`media-agent - Initiating media join for ${session.name}`);
setJoinStatus({ status: "Joining" }); setJoinStatus({ status: "Joining" });
sendJsonMessage({ type: "join" }); sendJsonMessage({ type: "join", data: {} });
} }
}, [media, joinStatus.status, sendJsonMessage, readyState, session.name]); }, [media, joinStatus.status, sendJsonMessage, readyState, session.name]);

View File

@ -1727,13 +1727,13 @@ async def lobby_join(
await websocket.accept() await websocket.accept()
if lobby_id is None: if lobby_id is None:
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "Invalid or missing lobby"} {"type": "error", "data": {"error": "Invalid or missing lobby"}}
) )
await websocket.close() await websocket.close()
return return
if session_id is None: if session_id is None:
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "Invalid or missing session"} {"type": "error", "data": {"error": "Invalid or missing session"}}
) )
await websocket.close() await websocket.close()
return return
@ -1741,7 +1741,7 @@ async def lobby_join(
if not session: if not session:
# logger.error(f"Invalid session ID {session_id}") # logger.error(f"Invalid session ID {session_id}")
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": f"Invalid session ID {session_id}"} {"type": "error", "data": {"error": f"Invalid session ID {session_id}"}}
) )
await websocket.close() await websocket.close()
return return
@ -1750,7 +1750,7 @@ async def lobby_join(
try: try:
lobby = getLobby(lobby_id) lobby = getLobby(lobby_id)
except Exception as e: except Exception as e:
await websocket.send_json({"type": "error", "error": str(e)}) await websocket.send_json({"type": "error", "data": {"error": str(e)}})
await websocket.close() await websocket.close()
return return
@ -1815,7 +1815,7 @@ async def lobby_join(
data: dict[str, Any] | None = packet.get("data", None) data: dict[str, Any] | None = packet.get("data", None)
if not type: if not type:
logger.error(f"{session.getName()} - Invalid request: {packet}") logger.error(f"{session.getName()} - Invalid request: {packet}")
await websocket.send_json({"type": "error", "error": "Invalid request"}) await websocket.send_json({"type": "error", "data": {"error": "Invalid request"}})
continue continue
# logger.info(f"{session.getName()} <- RAW Rx: {data}") # logger.info(f"{session.getName()} <- RAW Rx: {data}")
match type: match type:
@ -1823,7 +1823,7 @@ async def lobby_join(
if not data: if not data:
logger.error(f"{session.getName()} - set_name missing data") logger.error(f"{session.getName()} - set_name missing data")
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "set_name missing data"} {"type": "error", "data": {"error": "set_name missing data"}}
) )
continue continue
name = data.get("name") name = data.get("name")
@ -1832,7 +1832,7 @@ async def lobby_join(
if not name: if not name:
logger.error(f"{session.getName()} - Name required") logger.error(f"{session.getName()} - Name required")
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "Name required"} {"type": "error", "data": {"error": "Name required"}}
) )
continue continue
# Name takeover / password logic # Name takeover / password logic
@ -1868,7 +1868,7 @@ async def lobby_join(
f"{session.getName()} - Name already taken (no password set)" f"{session.getName()} - Name already taken (no password set)"
) )
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "Name already taken"} {"type": "error", "data": {"error": "Name already taken"}}
) )
continue continue
@ -2030,7 +2030,7 @@ async def lobby_join(
f"{session.getName()} - relayICECandidate missing data" f"{session.getName()} - relayICECandidate missing data"
) )
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "relayICECandidate missing data"} {"type": "error", "data": {"error": "relayICECandidate missing data"}}
) )
continue continue
@ -2043,7 +2043,7 @@ async def lobby_join(
f"{session.short}:{session.name} <- relayICECandidate - Not an RTC peer ({session.id})" f"{session.short}:{session.name} <- relayICECandidate - Not an RTC peer ({session.id})"
) )
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "Not joined to lobby"} {"type": "error", "data": {"error": "Not joined to lobby"}}
) )
continue continue
session_peers = session.lobby_peers[lobby.id] session_peers = session.lobby_peers[lobby.id]
@ -2109,7 +2109,7 @@ async def lobby_join(
f"{session.short}:{session.name} <- relaySessionDescription - Not an RTC peer ({session.id})" f"{session.short}:{session.name} <- relaySessionDescription - Not an RTC peer ({session.id})"
) )
await websocket.send_json( await websocket.send_json(
{"type": "error", "error": "Not joined to lobby"} {"type": "error", "data": {"error": "Not joined to lobby"}}
) )
continue continue
@ -2168,7 +2168,7 @@ async def lobby_join(
# Simple status check - just respond with success to keep connection alive # Simple status check - just respond with success to keep connection alive
logger.debug(f"{session.getName()} <- status_check") logger.debug(f"{session.getName()} <- status_check")
await websocket.send_json( await websocket.send_json(
{"type": "status_ok", "timestamp": time.time()} {"type": "status_ok", "data": {"timestamp": time.time()}}
) )
case _: case _:

View File

@ -194,7 +194,7 @@ class WebSocketMessageModel(BaseModel):
"""Base model for all WebSocket messages""" """Base model for all WebSocket messages"""
type: str type: str
data: ( data: Optional[
JoinStatusModel JoinStatusModel
| UserJoinedModel | UserJoinedModel
| LobbyStateModel | LobbyStateModel
@ -209,7 +209,7 @@ class WebSocketMessageModel(BaseModel):
| ChatMessagesListModel | ChatMessagesListModel
| WebSocketErrorModel | WebSocketErrorModel
| Dict[str, str] | Dict[str, str]
) # Generic dict for simple messages ] = None # Generic dict for simple messages, optional for status messages
# ============================================================================= # =============================================================================

View File

@ -4,8 +4,8 @@ import logging
import time import time
from typing import Optional, Tuple from typing import Optional, Tuple
#logging_level = os.getenv("LOGGING_LEVEL", "INFO").upper() logging_level = os.getenv("LOGGING_LEVEL", "INFO").upper()
logging_level = os.getenv("LOGGING_LEVEL", "DEBUG").upper() #logging_level = os.getenv("LOGGING_LEVEL", "DEBUG").upper()
class RelativePathFormatter(logging.Formatter): class RelativePathFormatter(logging.Formatter):

View File

@ -469,9 +469,11 @@ class WebRTCSignalingClient:
ws = cast(WebSocketProtocol, self.websocket) ws = cast(WebSocketProtocol, self.websocket)
# Build message with explicit type to avoid type narrowing # Build message with explicit type to avoid type narrowing
message: dict[str, object] = {"type": message_type} # Always include data field to match WebSocketMessageModel
if data is not None: message: dict[str, object] = {
message["data"] = data "type": message_type,
"data": data if data is not None else {}
}
try: try:
logger.debug(f"_send_message: Sending {message_type} with data: {data}") logger.debug(f"_send_message: Sending {message_type} with data: {data}")
@ -563,6 +565,9 @@ class WebRTCSignalingClient:
) )
if msg_type == "addPeer": if msg_type == "addPeer":
if data is None:
logger.error("addPeer message missing required data")
return
try: try:
validated = AddPeerModel.model_validate(data) validated = AddPeerModel.model_validate(data)
except ValidationError as e: except ValidationError as e:
@ -623,11 +628,11 @@ class WebRTCSignalingClient:
logger.info(f"Received update message: {validated}") logger.info(f"Received update message: {validated}")
elif msg_type == "status_check": elif msg_type == "status_check":
# Handle status check messages - these are used to verify connection # Handle status check messages - these are used to verify connection
logger.debug(f"Received status check message: {data}") logger.debug(f"Received status check message with data: {data}")
# No special processing needed for status checks, just acknowledge receipt # No special processing needed for status checks, just acknowledge receipt
elif msg_type == "status_ok": elif msg_type == "status_ok":
# Handle status_ok response from server # Handle status_ok response from server
logger.debug(f"Received status_ok from server: {data}") logger.debug(f"Received status_ok from server with data: {data}")
# This confirms the connection is healthy # This confirms the connection is healthy
elif msg_type == "chat_message": elif msg_type == "chat_message":
logger.info("Received chat message") logger.info("Received chat message")