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) {
console.log(`media-agent - Initiating media join for ${session.name}`);
setJoinStatus({ status: "Joining" });
sendJsonMessage({ type: "join" });
sendJsonMessage({ type: "join", data: {} });
}
}, [media, joinStatus.status, sendJsonMessage, readyState, session.name]);

View File

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

View File

@ -194,7 +194,7 @@ class WebSocketMessageModel(BaseModel):
"""Base model for all WebSocket messages"""
type: str
data: (
data: Optional[
JoinStatusModel
| UserJoinedModel
| LobbyStateModel
@ -209,7 +209,7 @@ class WebSocketMessageModel(BaseModel):
| ChatMessagesListModel
| WebSocketErrorModel
| 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
from typing import Optional, Tuple
#logging_level = os.getenv("LOGGING_LEVEL", "INFO").upper()
logging_level = os.getenv("LOGGING_LEVEL", "DEBUG").upper()
logging_level = os.getenv("LOGGING_LEVEL", "INFO").upper()
#logging_level = os.getenv("LOGGING_LEVEL", "DEBUG").upper()
class RelativePathFormatter(logging.Formatter):

View File

@ -469,9 +469,11 @@ class WebRTCSignalingClient:
ws = cast(WebSocketProtocol, self.websocket)
# Build message with explicit type to avoid type narrowing
message: dict[str, object] = {"type": message_type}
if data is not None:
message["data"] = data
# Always include data field to match WebSocketMessageModel
message: dict[str, object] = {
"type": message_type,
"data": data if data is not None else {}
}
try:
logger.debug(f"_send_message: Sending {message_type} with data: {data}")
@ -563,6 +565,9 @@ class WebRTCSignalingClient:
)
if msg_type == "addPeer":
if data is None:
logger.error("addPeer message missing required data")
return
try:
validated = AddPeerModel.model_validate(data)
except ValidationError as e:
@ -623,11 +628,11 @@ class WebRTCSignalingClient:
logger.info(f"Received update message: {validated}")
elif msg_type == "status_check":
# 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
elif msg_type == "status_ok":
# 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
elif msg_type == "chat_message":
logger.info("Received chat message")