using System.Net.WebSockets; using System.Text.Json; using System.Text.RegularExpressions; using MauMau_Server.Room.Messages; using MauMau_Server.Websockets; namespace MauMau_Server.Room; public class Room { private readonly IRoomManager _roomManager; private readonly string _roomId; public readonly List Connections = []; public ConnectionInstance? Host; public RoomType RoomType; public Room(IRoomManager roomManager, string roomId) { _roomManager = roomManager; RoomType = new Lobby(this); _roomId = roomId; } public async Task InstantiateConnection(WebSocket socket, string name) { var connectionId = Guid.NewGuid(); // If the name is empty, set it to "Mau" + the first part of the connection ID, otherwise strip potential HTML from the name and use it var validatedName = !string.IsNullOrWhiteSpace(name) ? StripHTML(name) : "Mau" + connectionId.ToString().Split('-')[0]; var connection = new ConnectionInstance(validatedName, connectionId, socket); if (IsEmpty()) Host = connection; Connections.Add(connection); RoomType.OnConnect(connection); await HandleConnection(connection); } private async Task HandleConnection(ConnectionInstance connection) { var webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); while (!webSocketResponse.Result!.CloseStatus.HasValue) { var message = JsonSerializer.Deserialize>(webSocketResponse.SlicedBuffer); switch (message.Type) { case "CHAT": HandleChatMessage(connection, message.Data); break; case "KICK": await HandleKick(Guid.Parse(message.Data)); break; default: RoomType.OnMessage(connection, message); break; } webSocketResponse = await WebsocketManager.ReceiveAsync(connection.Socket); } WebsocketManager.CloseAsync(connection.Socket, webSocketResponse.Result); HandleDisconnect(connection); } private void HandleChatMessage(ConnectionInstance sender, string chatMessage) { // Remove HTML from chat message to prevent HTML injection var cleanedMessage = StripHTML(chatMessage); // If the message is empty, set it to "Mau!" if (string.IsNullOrWhiteSpace(cleanedMessage)) cleanedMessage = "Mau!"; // Create a new chat message object with the sender and the message var envelope = new ChatMessage(sender.Name, cleanedMessage); // Broadcast the chat message to all connections in the room BroadCast(new RoomMessage("CHAT", envelope)); } private async Task HandleKick(Guid connectionId) { // Search for the connection with the given ID var connection = Connections.FirstOrDefault(connection => connection.Id == connectionId); // If the connection could not be found, return if (connection == null) return; // Handle the disconnect of the connection HandleDisconnect(connection); // Close the connection with the reason "You have been kicked" await connection.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "You have been kicked", CancellationToken.None); } private void HandleDisconnect(ConnectionInstance connection) { Connections.Remove(connection); RoomType.OnDisconnect(connection); if (IsEmpty()) { _roomManager.RemoveRoom(_roomId); return; } if (connection == Host) { Host = Connections.First(); } } public void BroadCast(RoomMessage message) { foreach (var connection in Connections) { connection.SendMessageAsync(JsonSerializer.Serialize(message)); } } public bool IsEmpty() { return Connections.Count == 0; } private static string StripHTML(string input) { return Regex.Replace(input, "<.*?>", string.Empty); } }