using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using InABox.Clients; using InABox.Core; using WebSocketSharp; using ErrorEventArgs = WebSocketSharp.ErrorEventArgs; namespace InABox.Rpc { public class RpcClientSocketTransport : RpcClientTransport, IDisposable { private WebSocket? _socket; private string[] _urls; public RpcClientSocketTransport(string[] urls) { _urls = urls; } private void Socket_OnOpen(object? sender, EventArgs e) { DoOpen(); } private void Socket_OnMessage(object? sender, MessageEventArgs e) { RpcMessage? message = null; if (e.IsBinary && (e.RawData != null)) message = Serialization.ReadBinary(e.RawData, BinarySerializationSettings.Latest); else if (e.IsText && !string.IsNullOrWhiteSpace(e.Data)) message = Serialization.Deserialize(e.Data); Accept(message); } private void Socket_OnClose(object? sender, CloseEventArgs e) { DoClose(RpcTransportCloseEventType.Closed); } private void Socket_OnError(object? sender, ErrorEventArgs e) { DoException(e.Exception); } private WebSocket? CreateSocket(string url, bool secure) { WebSocket socket = null; var address = $"{(secure ? "wss" : "ws")}://{url}"; try { socket = new WebSocket(address); } catch (Exception e) { return null; } // Time to wait before disconnect - the default meant that the client disconnected during debugging, since the ping would fail socket.WaitTime = TimeSpan.FromSeconds(20); socket.OnOpen -= Socket_OnOpen; socket.OnError -= Socket_OnError; socket.OnClose -= Socket_OnClose; socket.OnMessage -= Socket_OnMessage; socket.Connect(); if (socket.ReadyState == WebSocketState.Open) { DoOpen(); socket.OnOpen += Socket_OnOpen; socket.OnError += Socket_OnError; socket.OnClose += Socket_OnClose; socket.OnMessage += Socket_OnMessage; return socket; } return null; } public override void Connect() { List> tasks = new List>(); foreach (var url in _urls) { tasks.Add(Task.Run(() => CreateSocket(url, true))); tasks.Add(Task.Run(() => CreateSocket(url, false))); } while (tasks.Count > 0) { var result = Task.WhenAny(tasks).Result; if (result.Result == null) tasks.Remove(result); else { _socket = result.Result; return; } } } public override bool IsConnected() => _socket?.ReadyState == WebSocketState.Open; public override bool IsSecure() => _socket?.IsSecure == true; public override void Disconnect() { _socket?.Close(CloseStatusCode.Normal); } public override void Send(RpcMessage message) { var buffer = message.WriteBinary(BinarySerializationSettings.Latest); _socket?.Send(buffer); } protected override RpcClientTransport Clone() => new RpcClientSocketTransport(_urls); public void Dispose() { if (IsConnected()) Disconnect(); } } }