| @@ -12,212 +12,212 @@ using WebSocketSharp; | |||||
| namespace Discord.Providers.WebSocketSharp | namespace Discord.Providers.WebSocketSharp | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// WebSocket provider using websocket-sharp. | |||||
| /// </summary> | |||||
| internal class WebSocketSharpClient : IWebSocketClient, IDisposable | |||||
| { | |||||
| /// <inheritdoc /> | |||||
| public event Func<byte[], int, int, Task> BinaryMessage; | |||||
| /// <inheritdoc /> | |||||
| public event Func<string, Task> TextMessage; | |||||
| /// <inheritdoc /> | |||||
| public event Func<Exception, Task> Closed; | |||||
| private readonly SemaphoreSlim _lock; | |||||
| private readonly Dictionary<string, string> _headers; | |||||
| private readonly ManualResetEventSlim _waitUntilConnect; | |||||
| private WebSocket _client; | |||||
| private CancellationTokenSource _cancelTokenSource; | |||||
| private CancellationToken _cancelToken; | |||||
| private CancellationToken _parentToken; | |||||
| private bool _isDisposed; | |||||
| /// <summary> | |||||
| /// Initializes a new instance of the <see cref="WebSocketSharpProvider"/> class. | |||||
| /// </summary> | |||||
| public WebSocketSharpClient() | |||||
| { | |||||
| _headers = new Dictionary<string, string>(); | |||||
| _lock = new SemaphoreSlim(1, 1); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| _cancelToken = CancellationToken.None; | |||||
| _parentToken = CancellationToken.None; | |||||
| _waitUntilConnect = new ManualResetEventSlim(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void SetHeader(string key, string value) | |||||
| { | |||||
| _headers[key] = value; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void SetCancelToken(CancellationToken cancelToken) | |||||
| { | |||||
| _parentToken = cancelToken; | |||||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource | |||||
| ( | |||||
| _parentToken, | |||||
| _cancelTokenSource.Token | |||||
| ) | |||||
| .Token; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public async Task ConnectAsync(string host) | |||||
| { | |||||
| await _lock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await ConnectInternalAsync(host).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _lock.Release(); | |||||
| } | |||||
| } | |||||
| private async Task ConnectInternalAsync(string host) | |||||
| { | |||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource | |||||
| ( | |||||
| _parentToken, | |||||
| _cancelTokenSource.Token | |||||
| ) | |||||
| .Token; | |||||
| _client = new WebSocket(host) | |||||
| { | |||||
| CustomHeaders = _headers.ToList() | |||||
| }; | |||||
| _client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; | |||||
| _client.OnMessage += OnMessage; | |||||
| _client.OnOpen += OnConnected; | |||||
| _client.OnClose += OnClosed; | |||||
| _client.Connect(); | |||||
| _waitUntilConnect.Wait(_cancelToken); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public async Task DisconnectAsync() | |||||
| { | |||||
| await _lock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _lock.Release(); | |||||
| } | |||||
| } | |||||
| private Task DisconnectInternalAsync() | |||||
| { | |||||
| _cancelTokenSource.Cancel(); | |||||
| if (_client is null) | |||||
| { | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| if (_client.ReadyState == WebSocketState.Open) | |||||
| { | |||||
| _client.Close(); | |||||
| } | |||||
| _client.OnMessage -= OnMessage; | |||||
| _client.OnOpen -= OnConnected; | |||||
| _client.OnClose -= OnClosed; | |||||
| _client = null; | |||||
| _waitUntilConnect.Reset(); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| private void OnMessage(object sender, MessageEventArgs messageEventArgs) | |||||
| { | |||||
| if (messageEventArgs.IsBinary) | |||||
| { | |||||
| OnBinaryMessage(messageEventArgs); | |||||
| } | |||||
| else if (messageEventArgs.IsText) | |||||
| { | |||||
| OnTextMessage(messageEventArgs); | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public async Task SendAsync(byte[] data, int index, int count, bool isText) | |||||
| { | |||||
| await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (isText) | |||||
| { | |||||
| _client.Send(Encoding.UTF8.GetString(data, index, count)); | |||||
| } | |||||
| else | |||||
| { | |||||
| _client.Send(data.Skip(index).Take(count).ToArray()); | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| _lock.Release(); | |||||
| } | |||||
| } | |||||
| private void OnTextMessage(MessageEventArgs e) | |||||
| { | |||||
| TextMessage?.Invoke(e.Data).GetAwaiter().GetResult(); | |||||
| } | |||||
| private void OnBinaryMessage(MessageEventArgs e) | |||||
| { | |||||
| BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult(); | |||||
| } | |||||
| private void OnConnected(object sender, EventArgs e) | |||||
| { | |||||
| _waitUntilConnect.Set(); | |||||
| } | |||||
| private void OnClosed(object sender, CloseEventArgs e) | |||||
| { | |||||
| if (e.WasClean) | |||||
| { | |||||
| Closed?.Invoke(null).GetAwaiter().GetResult(); | |||||
| return; | |||||
| } | |||||
| var ex = new WebSocketClosedException(e.Code, e.Reason); | |||||
| Closed?.Invoke(ex).GetAwaiter().GetResult(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void Dispose() | |||||
| { | |||||
| if (_isDisposed) | |||||
| { | |||||
| return; | |||||
| } | |||||
| DisconnectInternalAsync().GetAwaiter().GetResult(); | |||||
| ((IDisposable)_client)?.Dispose(); | |||||
| _client = null; | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| /// WebSocket provider using websocket-sharp. | |||||
| /// </summary> | |||||
| internal class WebSocketSharpClient : IWebSocketClient, IDisposable | |||||
| { | |||||
| /// <inheritdoc /> | |||||
| public event Func<byte[], int, int, Task> BinaryMessage; | |||||
| /// <inheritdoc /> | |||||
| public event Func<string, Task> TextMessage; | |||||
| /// <inheritdoc /> | |||||
| public event Func<Exception, Task> Closed; | |||||
| private readonly SemaphoreSlim _lock; | |||||
| private readonly Dictionary<string, string> _headers; | |||||
| private readonly ManualResetEventSlim _waitUntilConnect; | |||||
| private WebSocket _client; | |||||
| private CancellationTokenSource _cancelTokenSource; | |||||
| private CancellationToken _cancelToken; | |||||
| private CancellationToken _parentToken; | |||||
| private bool _isDisposed; | |||||
| /// <summary> | |||||
| /// Initializes a new instance of the <see cref="WebSocketSharpProvider"/> class. | |||||
| /// </summary> | |||||
| public WebSocketSharpClient() | |||||
| { | |||||
| _headers = new Dictionary<string, string>(); | |||||
| _lock = new SemaphoreSlim(1, 1); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| _cancelToken = CancellationToken.None; | |||||
| _parentToken = CancellationToken.None; | |||||
| _waitUntilConnect = new ManualResetEventSlim(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void SetHeader(string key, string value) | |||||
| { | |||||
| _headers[key] = value; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void SetCancelToken(CancellationToken cancelToken) | |||||
| { | |||||
| _parentToken = cancelToken; | |||||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource | |||||
| ( | |||||
| _parentToken, | |||||
| _cancelTokenSource.Token | |||||
| ) | |||||
| .Token; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public async Task ConnectAsync(string host) | |||||
| { | |||||
| await _lock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await ConnectInternalAsync(host).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _lock.Release(); | |||||
| } | |||||
| } | |||||
| private async Task ConnectInternalAsync(string host) | |||||
| { | |||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| _cancelTokenSource = new CancellationTokenSource(); | |||||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource | |||||
| ( | |||||
| _parentToken, | |||||
| _cancelTokenSource.Token | |||||
| ) | |||||
| .Token; | |||||
| _client = new WebSocket(host) | |||||
| { | |||||
| CustomHeaders = _headers.ToList() | |||||
| }; | |||||
| _client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; | |||||
| _client.OnMessage += OnMessage; | |||||
| _client.OnOpen += OnConnected; | |||||
| _client.OnClose += OnClosed; | |||||
| _client.Connect(); | |||||
| _waitUntilConnect.Wait(_cancelToken); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public async Task DisconnectAsync() | |||||
| { | |||||
| await _lock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _lock.Release(); | |||||
| } | |||||
| } | |||||
| private Task DisconnectInternalAsync() | |||||
| { | |||||
| _cancelTokenSource.Cancel(); | |||||
| if (_client is null) | |||||
| { | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| if (_client.ReadyState == WebSocketState.Open) | |||||
| { | |||||
| _client.Close(); | |||||
| } | |||||
| _client.OnMessage -= OnMessage; | |||||
| _client.OnOpen -= OnConnected; | |||||
| _client.OnClose -= OnClosed; | |||||
| _client = null; | |||||
| _waitUntilConnect.Reset(); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| private void OnMessage(object sender, MessageEventArgs messageEventArgs) | |||||
| { | |||||
| if (messageEventArgs.IsBinary) | |||||
| { | |||||
| OnBinaryMessage(messageEventArgs); | |||||
| } | |||||
| else if (messageEventArgs.IsText) | |||||
| { | |||||
| OnTextMessage(messageEventArgs); | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public async Task SendAsync(byte[] data, int index, int count, bool isText) | |||||
| { | |||||
| await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| if (isText) | |||||
| { | |||||
| _client.Send(Encoding.UTF8.GetString(data, index, count)); | |||||
| } | |||||
| else | |||||
| { | |||||
| _client.Send(data.Skip(index).Take(count).ToArray()); | |||||
| } | |||||
| } | |||||
| finally | |||||
| { | |||||
| _lock.Release(); | |||||
| } | |||||
| } | |||||
| private void OnTextMessage(MessageEventArgs e) | |||||
| { | |||||
| TextMessage?.Invoke(e.Data).GetAwaiter().GetResult(); | |||||
| } | |||||
| private void OnBinaryMessage(MessageEventArgs e) | |||||
| { | |||||
| BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult(); | |||||
| } | |||||
| private void OnConnected(object sender, EventArgs e) | |||||
| { | |||||
| _waitUntilConnect.Set(); | |||||
| } | |||||
| private void OnClosed(object sender, CloseEventArgs e) | |||||
| { | |||||
| if (e.WasClean) | |||||
| { | |||||
| Closed?.Invoke(null).GetAwaiter().GetResult(); | |||||
| return; | |||||
| } | |||||
| var ex = new WebSocketClosedException(e.Code, e.Reason); | |||||
| Closed?.Invoke(ex).GetAwaiter().GetResult(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void Dispose() | |||||
| { | |||||
| if (_isDisposed) | |||||
| { | |||||
| return; | |||||
| } | |||||
| DisconnectInternalAsync().GetAwaiter().GetResult(); | |||||
| ((IDisposable)_client)?.Dispose(); | |||||
| _client = null; | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| } | } | ||||