diff --git a/Discord.Net.sln b/Discord.Net.sln
index 58bfcad86..38b3509f7 100644
--- a/Discord.Net.sln
+++ b/Discord.Net.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
@@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Providers.WebSocketSharp", "src\Discord.Net.Providers.WebSocketSharp\Discord.Net.Providers.WebSocketSharp.csproj", "{0748E83F-87A1-4C07-B458-6E438841925B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -130,6 +132,18 @@ Global
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x64.Build.0 = Debug|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Debug|x86.Build.0 = Debug|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x64.ActiveCfg = Release|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x64.Build.0 = Release|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x86.ActiveCfg = Release|Any CPU
+ {0748E83F-87A1-4C07-B458-6E438841925B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -141,6 +155,7 @@ Global
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
+ {0748E83F-87A1-4C07-B458-6E438841925B} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}
diff --git a/src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj b/src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj
new file mode 100644
index 000000000..50db25efd
--- /dev/null
+++ b/src/Discord.Net.Providers.WebSocketSharp/Discord.Net.Providers.WebSocketSharp.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Discord.Net.Providers.WebSocketSharp
+ Discord.Providers.WebSocketSharp
+ An optional WebSocket client provider for Discord.Net using websocket-sharp
+ netstandard2.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs
new file mode 100644
index 000000000..40aeed644
--- /dev/null
+++ b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpClient.cs
@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Authentication;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Discord.Net;
+using Discord.Net.WebSockets;
+using WebSocketSharp;
+
+namespace Discord.Providers.WebSocketSharp
+{
+ ///
+ /// WebSocket provider using websocket-sharp.
+ ///
+ internal class WebSocketSharpClient : IWebSocketClient, IDisposable
+ {
+ ///
+ public event Func BinaryMessage;
+
+ ///
+ public event Func TextMessage;
+
+ ///
+ public event Func Closed;
+
+ private readonly SemaphoreSlim _lock;
+ private readonly Dictionary _headers;
+ private readonly ManualResetEventSlim _waitUntilConnect;
+
+ private WebSocket _client;
+ private CancellationTokenSource _cancelTokenSource;
+ private CancellationToken _cancelToken;
+ private CancellationToken _parentToken;
+
+ private bool _isDisposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WebSocketSharpClient()
+ {
+ _headers = new Dictionary();
+ _lock = new SemaphoreSlim(1, 1);
+ _cancelTokenSource = new CancellationTokenSource();
+ _cancelToken = CancellationToken.None;
+ _parentToken = CancellationToken.None;
+ _waitUntilConnect = new ManualResetEventSlim();
+ }
+
+ ///
+ public void SetHeader(string key, string value)
+ {
+ _headers[key] = value;
+ }
+
+ ///
+ public void SetCancelToken(CancellationToken cancelToken)
+ {
+ _parentToken = cancelToken;
+ _cancelToken = CancellationTokenSource.CreateLinkedTokenSource
+ (
+ _parentToken,
+ _cancelTokenSource.Token
+ )
+ .Token;
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ 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();
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ DisconnectInternalAsync().GetAwaiter().GetResult();
+
+ ((IDisposable)_client)?.Dispose();
+ _client = null;
+
+ _isDisposed = true;
+ }
+ }
+}
diff --git a/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs
new file mode 100644
index 000000000..e200d3c0c
--- /dev/null
+++ b/src/Discord.Net.Providers.WebSocketSharp/WebSocketSharpProvider.cs
@@ -0,0 +1,9 @@
+using Discord.Net.WebSockets;
+
+namespace Discord.Providers.WebSocketSharp
+{
+ public static class WebSocketSharpProvider
+ {
+ public static readonly WebSocketProvider Instance = () => new WebSocketSharpClient();
+ }
+}