@@ -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 | # Visual Studio 15 | ||||
VisualStudioVersion = 15.0.26730.12 | VisualStudioVersion = 15.0.26730.12 | ||||
MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
@@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" | ||||
EndProject | 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 | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | 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|x64.Build.0 = Release|Any CPU | ||||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = 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 | {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 | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -141,6 +155,7 @@ Global | |||||
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | {688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | ||||
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | ||||
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | ||||
{0748E83F-87A1-4C07-B458-6E438841925B} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | ||||
@@ -0,0 +1,15 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<Import Project="../../Discord.Net.targets" /> | |||||
<PropertyGroup> | |||||
<AssemblyName>Discord.Net.Providers.WebSocketSharp</AssemblyName> | |||||
<RootNamespace>Discord.Providers.WebSocketSharp</RootNamespace> | |||||
<Description>An optional WebSocket client provider for Discord.Net using websocket-sharp</Description> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="websocket-sharp-customheaders" Version="1.0.2.31869" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -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 | |||||
{ | |||||
/// <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; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
using Discord.Net.WebSockets; | |||||
namespace Discord.Providers.WebSocketSharp | |||||
{ | |||||
public static class WebSocketSharpProvider | |||||
{ | |||||
public static readonly WebSocketProvider Instance = () => new WebSocketSharpClient(); | |||||
} | |||||
} |