using Shogi.Contracts.Socket; using Shogi.Api.Extensions; using System.Collections.Concurrent; using System.Net.WebSockets; using System.Text.Json; namespace Shogi.Api.Managers; public interface ISocketConnectionManager { Task BroadcastToAll(ISocketMessage response); void Subscribe(WebSocket socket, string playerName); void Unsubscribe(string playerName); Task BroadcastToPlayers(ISocketMessage response, params string?[] playerNames); } /// /// Retains all active socket connections and provides convenient methods for sending messages to clients. /// public class SocketConnectionManager : ISocketConnectionManager { /// Dictionary key is player name. private readonly ConcurrentDictionary connections; private readonly JsonSerializerOptions serializeOptions; /// Dictionary key is game name. private readonly ILogger logger; public SocketConnectionManager(ILogger logger) { this.logger = logger; this.connections = new ConcurrentDictionary(); this.serializeOptions = new JsonSerializerOptions(JsonSerializerDefaults.General); } public void Subscribe(WebSocket socket, string playerName) { connections.TryRemove(playerName, out var _); connections.TryAdd(playerName, socket); } public void Unsubscribe(string playerName) { connections.TryRemove(playerName, out _); } public async Task BroadcastToPlayers(ISocketMessage response, params string?[] playerNames) { var tasks = new List(playerNames.Length); foreach (var name in playerNames) { if (!string.IsNullOrEmpty(name) && connections.TryGetValue(name, out var socket)) { var serialized = Serialize(response); logger.LogInformation("Response to {0} \n{1}\n", name, serialized); tasks.Add(socket.SendTextAsync(serialized)); } } await Task.WhenAll(tasks); } public Task BroadcastToAll(ISocketMessage response) { var message = Serialize(response); logger.LogInformation("Broadcasting:\n{0}\nDone Broadcasting.", message); var tasks = new List(connections.Count); foreach (var kvp in connections) { var socket = kvp.Value; try { tasks.Add(socket.SendTextAsync(message)); } catch (WebSocketException) { logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key); Unsubscribe(kvp.Key); } catch { logger.LogInformation("Tried sending a message to socket connection for user [{user}], but found the connection has closed.", kvp.Key); Unsubscribe(kvp.Key); } } return Task.WhenAll(tasks); } private string Serialize(object o) => JsonSerializer.Serialize(o, this.serializeOptions); }