diff --git a/client/src/LobbyChat.tsx b/client/src/LobbyChat.tsx index 3822e9a..992f4bd 100644 --- a/client/src/LobbyChat.tsx +++ b/client/src/LobbyChat.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef, KeyboardEvent } from "react"; -import { Paper, TextField, List, ListItem, ListItemText, Typography, Box, IconButton } from "@mui/material"; +import { Paper, TextField, List, ListItem, ListItemText, Typography, Box, IconButton, Button } from "@mui/material"; import SendIcon from "@mui/icons-material/Send"; +import ClearIcon from "@mui/icons-material/Clear"; import useWebSocket from "react-use-websocket"; import { Session } from "./GlobalContext"; import "./LobbyChat.css"; @@ -23,6 +24,7 @@ type LobbyChatProps = { const LobbyChat: React.FC = ({ socketUrl, session, lobbyId }) => { const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(""); + const [isClearing, setIsClearing] = useState(false); const messagesEndRef = useRef(null); const scrollToBottom = () => { @@ -34,16 +36,20 @@ const LobbyChat: React.FC = ({ socketUrl, session, lobbyId }) => onMessage: (event: MessageEvent) => { const message = JSON.parse(event.data); const data: any = message.data; - + switch (message.type) { case "chat_message": const chatMessage = data as ChatMessage; - setMessages(prev => [...prev, chatMessage]); + setMessages((prev) => [...prev, chatMessage]); break; case "chat_messages": const chatMessages = data.messages as ChatMessage[]; setMessages(chatMessages); break; + case "chat_cleared": + // Clear the messages when receiving chat cleared event + setMessages([]); + break; default: break; } @@ -87,10 +93,10 @@ const LobbyChat: React.FC = ({ socketUrl, session, lobbyId }) => const formatTimestamp = (timestamp: number): string => { const date = new Date(timestamp * 1000); - return date.toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - second: '2-digit' + return date.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", }); }; @@ -98,26 +104,57 @@ const LobbyChat: React.FC = ({ socketUrl, session, lobbyId }) => return message.sender_session_id === session.id; }; + const handleClearChat = async () => { + if (!lobbyId || isClearing) { + return; + } + + try { + setIsClearing(true); + // Use websocket instead of HTTP API to ensure all clients receive the clear event + sendJsonMessage({ + type: "clear_chat_messages", + }); + // Note: We don't clear messages locally here as we'll receive the chat_cleared event + } catch (error) { + console.error("Failed to clear chat messages:", error); + } finally { + setIsClearing(false); + } + }; + return ( - - Chat - - + + + Chat + + {messages.length > 0 && ( + + )} + + {messages.length === 0 ? ( - + ) : ( messages.map((message) => ( - = ({ socketUrl, session, lobbyId }) => multiline maxRows={3} /> - + diff --git a/server/core/lobby_manager.py b/server/core/lobby_manager.py index 6ddae11..7af02ee 100644 --- a/server/core/lobby_manager.py +++ b/server/core/lobby_manager.py @@ -196,6 +196,29 @@ class Lobby: with self.lock: return self.chat_messages[-limit:] if self.chat_messages else [] + def clear_chat_messages(self) -> None: + """Clear all chat messages from the lobby""" + with self.lock: + self.chat_messages.clear() + + async def broadcast_chat_clear(self) -> None: + """Broadcast a chat clear event to all connected sessions in the lobby""" + failed_sessions: List[Session] = [] + for peer in self.sessions.values(): + if peer.ws: + try: + logger.info(f"{self.getName()} -> chat_cleared({peer.getName()})") + await peer.ws.send_json({"type": "chat_cleared", "data": {}}) + except Exception as e: + logger.warning( + f"Failed to send chat clear message to {peer.getName()}: {e}" + ) + failed_sessions.append(peer) + + # Clean up failed sessions + for failed_session in failed_sessions: + failed_session.ws = None + async def broadcast_chat_message(self, chat_message: ChatMessageModel) -> None: """Broadcast a chat message to all connected sessions in the lobby""" failed_sessions: List[Session] = [] diff --git a/server/websocket/message_handlers.py b/server/websocket/message_handlers.py index 049975f..811303b 100644 --- a/server/websocket/message_handlers.py +++ b/server/websocket/message_handlers.py @@ -262,6 +262,35 @@ class SendChatMessageHandler(MessageHandler): await lobby.broadcast_chat_message(chat_message) +class ClearChatMessagesHandler(MessageHandler): + """Handler for clear_chat_messages messages""" + + async def handle( + self, + session: "Session", + lobby: "Lobby", + data: Dict[str, Any], + websocket: WebSocket, + managers: Dict[str, Any], + ) -> None: + if not session.name: + logger.error( + f"{session.getName()} - Cannot clear chat messages without name" + ) + await websocket.send_json( + { + "type": "error", + "data": {"error": "Must set name before clearing chat messages"}, + } + ) + return + + # Clear the messages and broadcast the clear event + lobby.clear_chat_messages() + logger.info(f"{session.getName()} -> clear_chat_messages({lobby.getName()})") + await lobby.broadcast_chat_clear() + + class RelayICECandidateHandler(MessageHandler): """Handler for relayICECandidate messages - WebRTC signaling""" @@ -337,6 +366,7 @@ class MessageRouter: self.register("list_users", ListUsersHandler()) self.register("get_chat_messages", GetChatMessagesHandler()) self.register("send_chat_message", SendChatMessageHandler()) + self.register("clear_chat_messages", ClearChatMessagesHandler()) # WebRTC signaling handlers self.register("relayICECandidate", RelayICECandidateHandler())