using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text.Json; using Fleck; using WirelessTextSyncer.Windows.Models; namespace WirelessTextSyncer.Windows.Services; public sealed class WebSocketServerService : IDisposable { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true }; private readonly SyncMessageHandler messageHandler; private readonly List clients = []; private readonly object clientsLock = new(); private WebSocketServer? server; public WebSocketServerService(SyncMessageHandler messageHandler) { this.messageHandler = messageHandler; } public int Port { get; private set; } = 8181; public string LocalIpAddress { get; private set; } = "127.0.0.1"; public bool HasClient { get { lock (clientsLock) { return clients.Count > 0; } } } public event EventHandler? StatusChanged; public void Start(int port) { Port = port; LocalIpAddress = ResolveLocalIpAddress(); AppLogger.Info($"Starting WebSocket server on 0.0.0.0:{Port}. Local IP: {LocalIpAddress}"); server = new WebSocketServer($"ws://0.0.0.0:{Port}"); server.Start(socket => { socket.OnOpen = () => { lock (clientsLock) { clients.Add(socket); } AppLogger.Info($"Client connected: {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}"); StatusChanged?.Invoke(this, EventArgs.Empty); }; socket.OnClose = () => { lock (clientsLock) { clients.Remove(socket); } AppLogger.Info($"Client disconnected: {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}"); StatusChanged?.Invoke(this, EventArgs.Empty); }; socket.OnError = exception => { AppLogger.Error("WebSocket connection error.", exception); StatusChanged?.Invoke(this, EventArgs.Empty); }; socket.OnMessage = HandleRawMessage; }); } public void Dispose() { IWebSocketConnection[] currentClients; lock (clientsLock) { currentClients = clients.ToArray(); clients.Clear(); } foreach (var client in currentClients) { client.Close(); } server?.Dispose(); } private void HandleRawMessage(string rawMessage) { try { AppLogger.Info($"Received message: {rawMessage}"); var message = JsonSerializer.Deserialize(rawMessage, JsonOptions); if (message is null) { AppLogger.Info("Ignored empty sync message."); return; } messageHandler.Handle(message); } catch (JsonException exception) { AppLogger.Error($"Invalid sync message: {rawMessage}", exception); } catch (Exception exception) { AppLogger.Error($"Failed to handle sync message: {rawMessage}", exception); } } private static string ResolveLocalIpAddress() { var address = NetworkInterface.GetAllNetworkInterfaces() .Where(networkInterface => networkInterface.OperationalStatus == OperationalStatus.Up && networkInterface.NetworkInterfaceType != NetworkInterfaceType.Loopback) .Select(networkInterface => networkInterface.GetIPProperties()) .Where(properties => properties.GatewayAddresses.Any( gateway => gateway.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.Any.Equals(gateway.Address))) .SelectMany(properties => properties.UnicastAddresses) .Select(unicast => unicast.Address) .FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(address) && !address.ToString().StartsWith("169.254.", StringComparison.Ordinal)); return address?.ToString() ?? "127.0.0.1"; } }