mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-09-02 21:51:27 +00:00
Compare commits
7 Commits
4c953e2c87
...
73764970b0
Author | SHA1 | Date | |
---|---|---|---|
|
73764970b0 | ||
|
9ba29035b2 | ||
|
b215cccda4 | ||
|
3eda488361 | ||
|
993a44de35 | ||
|
99465f99a1 | ||
|
d42de1fe90 |
@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
|
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>Protobuf support for CryptoExchange.Net</Description>
|
<Description>Protobuf support for CryptoExchange.Net</Description>
|
||||||
<PackageVersion>9.5.0</PackageVersion>
|
<PackageVersion>9.6.0</PackageVersion>
|
||||||
<AssemblyVersion>9.5.0</AssemblyVersion>
|
<AssemblyVersion>9.6.0</AssemblyVersion>
|
||||||
<FileVersion>9.5.0</FileVersion>
|
<FileVersion>9.6.0</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
|
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CryptoExchange.Net" Version="9.5.0" />
|
<PackageReference Include="CryptoExchange.Net" Version="9.6.0" />
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -5,6 +5,9 @@
|
|||||||
Protobuf support for CryptoExchange.Net.
|
Protobuf support for CryptoExchange.Net.
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* Version 9.6.0 - 25 Aug 2025
|
||||||
|
* Updated CryptoExchange.Net version to 9.6.0
|
||||||
|
|
||||||
* Version 9.5.0 - 19 Aug 2025
|
* Version 9.5.0 - 19 Aug 2025
|
||||||
* Updated CryptoExchange.Net version to 9.5.0
|
* Updated CryptoExchange.Net version to 9.5.0
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
|
|||||||
return new CallResult(null);
|
return new CallResult(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1);
|
protected override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1);
|
||||||
public override Query GetUnsubQuery() => new TestQuery("unsub", new object(), false, 1);
|
protected override Query GetUnsubQuery(SocketConnection connection) => new TestQuery("unsub", new object(), false, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
|
|||||||
return new CallResult(null);
|
return new CallResult(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1);
|
protected override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1);
|
||||||
public override Query GetUnsubQuery() => new TestChannelQuery(_channel, "unsubscribe", false, 1);
|
protected override Query GetUnsubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "unsubscribe", false, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
//using System;
|
|
||||||
//using System.IO;
|
|
||||||
//using System.Net.WebSockets;
|
|
||||||
//using System.Security.Authentication;
|
|
||||||
//using System.Text;
|
|
||||||
//using System.Threading.Tasks;
|
|
||||||
//using CryptoExchange.Net.Interfaces;
|
|
||||||
//using CryptoExchange.Net.Objects;
|
|
||||||
|
|
||||||
//namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|
||||||
//{
|
|
||||||
// public class TestSocket: IWebsocket
|
|
||||||
// {
|
|
||||||
// public bool CanConnect { get; set; }
|
|
||||||
// public bool Connected { get; set; }
|
|
||||||
|
|
||||||
// public event Func<Task> OnClose;
|
|
||||||
//#pragma warning disable 0067
|
|
||||||
// public event Func<Task> OnReconnected;
|
|
||||||
// public event Func<Task> OnReconnecting;
|
|
||||||
// public event Func<int, Task> OnRequestRateLimited;
|
|
||||||
//#pragma warning restore 0067
|
|
||||||
// public event Func<int, Task> OnRequestSent;
|
|
||||||
// public event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
|
|
||||||
// public event Func<Exception, Task> OnError;
|
|
||||||
// public event Func<Task> OnOpen;
|
|
||||||
// public Func<Task<Uri>> GetReconnectionUrl { get; set; }
|
|
||||||
|
|
||||||
// public int Id { get; }
|
|
||||||
// public bool ShouldReconnect { get; set; }
|
|
||||||
// public TimeSpan Timeout { get; set; }
|
|
||||||
// public Func<string, string> DataInterpreterString { get; set; }
|
|
||||||
// public Func<byte[], string> DataInterpreterBytes { get; set; }
|
|
||||||
// public DateTime? DisconnectTime { get; set; }
|
|
||||||
// public string Url { get; }
|
|
||||||
// public bool IsClosed => !Connected;
|
|
||||||
// public bool IsOpen => Connected;
|
|
||||||
// public bool PingConnection { get; set; }
|
|
||||||
// public TimeSpan PingInterval { get; set; }
|
|
||||||
// public SslProtocols SSLProtocols { get; set; }
|
|
||||||
// public Encoding Encoding { get; set; }
|
|
||||||
|
|
||||||
// public int ConnectCalls { get; private set; }
|
|
||||||
// public bool Reconnecting { get; set; }
|
|
||||||
// public string Origin { get; set; }
|
|
||||||
// public int? RatelimitPerSecond { get; set; }
|
|
||||||
|
|
||||||
// public double IncomingKbps => throw new NotImplementedException();
|
|
||||||
|
|
||||||
// public Uri Uri => new Uri("");
|
|
||||||
|
|
||||||
// public TimeSpan KeepAliveInterval { get; set; }
|
|
||||||
|
|
||||||
// public static int lastId = 0;
|
|
||||||
// public static object lastIdLock = new object();
|
|
||||||
|
|
||||||
// public TestSocket()
|
|
||||||
// {
|
|
||||||
// lock (lastIdLock)
|
|
||||||
// {
|
|
||||||
// Id = lastId + 1;
|
|
||||||
// lastId++;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public Task<CallResult> ConnectAsync()
|
|
||||||
// {
|
|
||||||
// Connected = CanConnect;
|
|
||||||
// ConnectCalls++;
|
|
||||||
// if (CanConnect)
|
|
||||||
// InvokeOpen();
|
|
||||||
// return Task.FromResult(CanConnect ? new CallResult(null) : new CallResult(new CantConnectError()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public bool Send(int requestId, string data, int weight)
|
|
||||||
// {
|
|
||||||
// if(!Connected)
|
|
||||||
// throw new Exception("Socket not connected");
|
|
||||||
// OnRequestSent?.Invoke(requestId);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void Reset()
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public Task CloseAsync()
|
|
||||||
// {
|
|
||||||
// Connected = false;
|
|
||||||
// DisconnectTime = DateTime.UtcNow;
|
|
||||||
// OnClose?.Invoke();
|
|
||||||
// return Task.FromResult(0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void SetProxy(string host, int port)
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException();
|
|
||||||
// }
|
|
||||||
// public void Dispose()
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void InvokeClose()
|
|
||||||
// {
|
|
||||||
// Connected = false;
|
|
||||||
// DisconnectTime = DateTime.UtcNow;
|
|
||||||
// Reconnecting = true;
|
|
||||||
// OnClose?.Invoke();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void InvokeOpen()
|
|
||||||
// {
|
|
||||||
// OnOpen?.Invoke();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void InvokeMessage(string data)
|
|
||||||
// {
|
|
||||||
// OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory<byte>(Encoding.UTF8.GetBytes(data))).Wait();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void SetProxy(ApiProxy proxy)
|
|
||||||
// {
|
|
||||||
// throw new NotImplementedException();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void InvokeError(Exception error)
|
|
||||||
// {
|
|
||||||
// OnError?.Invoke(error);
|
|
||||||
// }
|
|
||||||
// public Task ReconnectAsync() => Task.CompletedTask;
|
|
||||||
// }
|
|
||||||
//}
|
|
@ -7,6 +7,7 @@ indent_style = space
|
|||||||
indent_size = 4
|
indent_size = 4
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
max_line_length = 140
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
# ReSharper code style properties
|
# ReSharper code style properties
|
||||||
|
@ -465,7 +465,11 @@ public abstract class AuthenticationProvider
|
|||||||
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials
|
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
#pragma warning disable IDE1006 // Naming Styles
|
||||||
|
#pragma warning disable CA1707 // Naming Styles
|
||||||
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
|
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
|
||||||
|
#pragma warning restore IDE1006 // Naming Styles
|
||||||
|
#pragma warning restore CA1707 // Naming Styles
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
|
@ -124,7 +124,16 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose
|
/// Dispose
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Dispose()
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
_disposing = true;
|
_disposing = true;
|
||||||
}
|
}
|
||||||
|
@ -119,10 +119,24 @@ public abstract class BaseClient : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose
|
/// Dispose
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Debug, "Disposing client");
|
Dispose(true);
|
||||||
foreach (var client in ApiClients)
|
GC.SuppressFinalize(this);
|
||||||
client.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Debug, "Disposing client");
|
||||||
|
foreach (var client in ApiClients)
|
||||||
|
client.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,11 +56,23 @@ public class CryptoBaseClient : IDisposable
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_serviceCache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose
|
/// Dispose
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_serviceCache.Clear();
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,7 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
|
|||||||
additionalHeaders);
|
additionalHeaders);
|
||||||
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
|
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
|
||||||
TotalRequestsMade++;
|
TotalRequestsMade++;
|
||||||
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
var result = await GetResponseAsync<T>(definition, request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
||||||
if (result.Error is not CancellationRequestedError)
|
if (result.Error is not CancellationRequestedError)
|
||||||
{
|
{
|
||||||
var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]";
|
var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]";
|
||||||
@ -424,11 +424,13 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the request and returns the result deserialized into the type parameter class
|
/// Executes the request and returns the result deserialized into the type parameter class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="requestDefinition">The request definition</param>
|
||||||
/// <param name="request">The request object to execute</param>
|
/// <param name="request">The request object to execute</param>
|
||||||
/// <param name="gate">The ratelimit gate used</param>
|
/// <param name="gate">The ratelimit gate used</param>
|
||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
|
protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
|
||||||
|
RequestDefinition requestDefinition,
|
||||||
IRequest request,
|
IRequest request,
|
||||||
IRateLimitGate? gate,
|
IRateLimitGate? gate,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
@ -448,7 +450,7 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
|
|||||||
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
|
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
|
||||||
|
|
||||||
accessor = CreateAccessor();
|
accessor = CreateAccessor();
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess)
|
||||||
{
|
{
|
||||||
// Error response
|
// Error response
|
||||||
var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false);
|
var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false);
|
||||||
@ -488,7 +490,7 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Json response received
|
// Json response received
|
||||||
var parsedError = TryParseError(response.ResponseHeaders, accessor);
|
var parsedError = TryParseError(requestDefinition, response.ResponseHeaders, accessor);
|
||||||
if (parsedError != null)
|
if (parsedError != null)
|
||||||
{
|
{
|
||||||
if (parsedError is ServerRateLimitError rateError)
|
if (parsedError is ServerRateLimitError rateError)
|
||||||
@ -541,10 +543,11 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
|
|||||||
/// This method will be called for each response to be able to check if the response is an error or not.
|
/// This method will be called for each response to be able to check if the response is an error or not.
|
||||||
/// If the response is an error this method should return the parsed error, else it should return null
|
/// If the response is an error this method should return the parsed error, else it should return null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="requestDefinition">Request definition</param>
|
||||||
/// <param name="accessor">Data accessor</param>
|
/// <param name="accessor">Data accessor</param>
|
||||||
/// <param name="responseHeaders">The response headers</param>
|
/// <param name="responseHeaders">The response headers</param>
|
||||||
/// <returns>Null if not an error, Error otherwise</returns>
|
/// <returns>Null if not an error, Error otherwise</returns>
|
||||||
protected virtual Error? TryParseError(KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null;
|
protected virtual Error? TryParseError(RequestDefinition requestDefinition, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever.
|
/// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever.
|
||||||
@ -634,7 +637,7 @@ public abstract class RestApiClient : BaseApiClient, IRestApiClient
|
|||||||
{
|
{
|
||||||
// Handle retry after header
|
// Handle retry after header
|
||||||
var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("Retry-After", StringComparison.InvariantCultureIgnoreCase));
|
var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("Retry-After", StringComparison.InvariantCultureIgnoreCase));
|
||||||
if (retryAfterHeader.Value?.Any() != true)
|
if (!(retryAfterHeader.Value.Length > 0))
|
||||||
return new ServerRateLimitError();
|
return new ServerRateLimitError();
|
||||||
|
|
||||||
var value = retryAfterHeader.Value.First();
|
var value = retryAfterHeader.Value.First();
|
||||||
|
@ -839,8 +839,11 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose the client
|
/// Dispose the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Dispose()
|
public override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
if (disposing)
|
||||||
|
return;
|
||||||
|
|
||||||
_disposing = true;
|
_disposing = true;
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
{
|
{
|
||||||
@ -855,7 +858,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
semaphoreSlim?.Dispose();
|
semaphoreSlim?.Dispose();
|
||||||
base.Dispose();
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||||
<PackageVersion>9.5.0</PackageVersion>
|
<PackageVersion>9.6.0</PackageVersion>
|
||||||
<AssemblyVersion>9.5.0</AssemblyVersion>
|
<AssemblyVersion>9.6.0</AssemblyVersion>
|
||||||
<FileVersion>9.5.0</FileVersion>
|
<FileVersion>9.6.0</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
@ -118,6 +118,18 @@ public class AsyncResetEvent : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_waits.Clear();
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_waits.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,11 @@ public class RequestDefinition
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PreventCaching { get; set; }
|
public bool PreventCaching { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the response to this requests should attempted to be parsed even when the status indicates failure
|
||||||
|
/// </summary>
|
||||||
|
public bool TryParseOnNonSuccess { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connection id
|
/// Connection id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -46,6 +46,7 @@ public class RequestDefinitionCache
|
|||||||
/// <param name="parameterPosition">Parameter position</param>
|
/// <param name="parameterPosition">Parameter position</param>
|
||||||
/// <param name="arraySerialization">Array serialization type</param>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
/// <param name="preventCaching">Prevent request caching</param>
|
/// <param name="preventCaching">Prevent request caching</param>
|
||||||
|
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public RequestDefinition GetOrCreate(
|
public RequestDefinition GetOrCreate(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
@ -57,8 +58,9 @@ public class RequestDefinitionCache
|
|||||||
RequestBodyFormat? requestBodyFormat = null,
|
RequestBodyFormat? requestBodyFormat = null,
|
||||||
HttpMethodParameterPosition? parameterPosition = null,
|
HttpMethodParameterPosition? parameterPosition = null,
|
||||||
ArrayParametersSerialization? arraySerialization = null,
|
ArrayParametersSerialization? arraySerialization = null,
|
||||||
bool? preventCaching = null)
|
bool? preventCaching = null,
|
||||||
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching);
|
bool? tryParseOnNonSuccess = null)
|
||||||
|
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a definition if it is already in the cache or create a new definition and add it to the cache
|
/// Get a definition if it is already in the cache or create a new definition and add it to the cache
|
||||||
@ -74,6 +76,7 @@ public class RequestDefinitionCache
|
|||||||
/// <param name="parameterPosition">Parameter position</param>
|
/// <param name="parameterPosition">Parameter position</param>
|
||||||
/// <param name="arraySerialization">Array serialization type</param>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
/// <param name="preventCaching">Prevent request caching</param>
|
/// <param name="preventCaching">Prevent request caching</param>
|
||||||
|
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public RequestDefinition GetOrCreate(
|
public RequestDefinition GetOrCreate(
|
||||||
string identifier,
|
string identifier,
|
||||||
@ -86,7 +89,8 @@ public class RequestDefinitionCache
|
|||||||
RequestBodyFormat? requestBodyFormat = null,
|
RequestBodyFormat? requestBodyFormat = null,
|
||||||
HttpMethodParameterPosition? parameterPosition = null,
|
HttpMethodParameterPosition? parameterPosition = null,
|
||||||
ArrayParametersSerialization? arraySerialization = null,
|
ArrayParametersSerialization? arraySerialization = null,
|
||||||
bool? preventCaching = null)
|
bool? preventCaching = null,
|
||||||
|
bool? tryParseOnNonSuccess = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!_definitions.TryGetValue(identifier, out var def))
|
if (!_definitions.TryGetValue(identifier, out var def))
|
||||||
@ -100,7 +104,8 @@ public class RequestDefinitionCache
|
|||||||
ArraySerialization = arraySerialization,
|
ArraySerialization = arraySerialization,
|
||||||
RequestBodyFormat = requestBodyFormat,
|
RequestBodyFormat = requestBodyFormat,
|
||||||
ParameterPosition = parameterPosition,
|
ParameterPosition = parameterPosition,
|
||||||
PreventCaching = preventCaching ?? false
|
PreventCaching = preventCaching ?? false,
|
||||||
|
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false
|
||||||
};
|
};
|
||||||
_definitions.TryAdd(identifier, def);
|
_definitions.TryAdd(identifier, def);
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,22 @@ public class TraceLoggerProvider : ILoggerProvider
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ILogger CreateLogger(string categoryName) => new TraceLogger(categoryName, _logLevel);
|
public ILogger CreateLogger(string categoryName) => new TraceLogger(categoryName, _logLevel);
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose() { }
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public static void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -578,21 +578,24 @@ public abstract class SymbolOrderBook : ISymbolOrderBook, IDisposable
|
|||||||
/// <param name="disposing"></param>
|
/// <param name="disposing"></param>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
Status = OrderBookStatus.Disposing;
|
if (disposing)
|
||||||
|
{
|
||||||
|
Status = OrderBookStatus.Disposing;
|
||||||
|
|
||||||
_cts?.Cancel();
|
_cts?.Cancel();
|
||||||
_queueEvent.Set();
|
_queueEvent.Set();
|
||||||
|
|
||||||
// Clear queue
|
// Clear queue
|
||||||
while (_processQueue.TryDequeue(out _)) { }
|
while (_processQueue.TryDequeue(out _)) { }
|
||||||
|
|
||||||
_processBuffer.Clear();
|
_processBuffer.Clear();
|
||||||
_asks.Clear();
|
_asks.Clear();
|
||||||
_bids.Clear();
|
_bids.Clear();
|
||||||
AskCount = 0;
|
AskCount = 0;
|
||||||
BidCount = 0;
|
BidCount = 0;
|
||||||
|
|
||||||
Status = OrderBookStatus.Disposed;
|
Status = OrderBookStatus.Disposed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -488,9 +488,18 @@ public class CryptoExchangeWebSocketClient : IWebsocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose the socket
|
/// Dispose
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose the socket
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
return;
|
return;
|
||||||
|
@ -169,7 +169,6 @@ public abstract class Query : IMessageProcessor, IDisposable
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
// TODO: dispose managed state (managed objects)
|
|
||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
_event.Dispose();
|
_event.Dispose();
|
||||||
}
|
}
|
||||||
|
@ -709,14 +709,29 @@ public class SocketConnection : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose the connection
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (Status != SocketStatus.Disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Status = SocketStatus.Disposed;
|
||||||
|
periodicEvent?.Set();
|
||||||
|
periodicEvent?.Dispose();
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Status = SocketStatus.Disposed;
|
Dispose(disposing: true);
|
||||||
periodicEvent?.Set();
|
GC.SuppressFinalize(this);
|
||||||
periodicEvent?.Dispose();
|
|
||||||
_socket.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,6 +23,8 @@ public class RestRequestValidator<TClient> where TClient : BaseRestClient
|
|||||||
private readonly string _baseAddress;
|
private readonly string _baseAddress;
|
||||||
private readonly string? _nestedPropertyForCompare;
|
private readonly string? _nestedPropertyForCompare;
|
||||||
|
|
||||||
|
private static readonly char[] _paramSeparator = new char[] { '?' };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -117,8 +119,8 @@ public class RestRequestValidator<TClient> where TClient : BaseRestClient
|
|||||||
throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result.AsDataless())}");
|
throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result.AsDataless())}");
|
||||||
if (result.RequestMethod != new HttpMethod(expectedMethod!))
|
if (result.RequestMethod != new HttpMethod(expectedMethod!))
|
||||||
throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
|
throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
|
||||||
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0])
|
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0])
|
||||||
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0]}");
|
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0]}");
|
||||||
|
|
||||||
if (!skipResponseValidation)
|
if (!skipResponseValidation)
|
||||||
{
|
{
|
||||||
@ -176,8 +178,8 @@ public class RestRequestValidator<TClient> where TClient : BaseRestClient
|
|||||||
throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result)}");
|
throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result)}");
|
||||||
if (result.RequestMethod != new HttpMethod(expectedMethod!))
|
if (result.RequestMethod != new HttpMethod(expectedMethod!))
|
||||||
throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
|
throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
|
||||||
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0])
|
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0])
|
||||||
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0]}");
|
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0]}");
|
||||||
|
|
||||||
Trace.Listeners.Remove(listener);
|
Trace.Listeners.Remove(listener);
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,12 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
|
|||||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* Version 9.6.0 - 25 Aug 2025
|
||||||
|
* Added support for parsing REST response even though status indicates error
|
||||||
|
* Added better support for subscriptions without subscribe confirmation
|
||||||
|
* Added check in websocket for receiving 401 unauthorized http response status when 101 was expected
|
||||||
|
* Removed obsolete attribute on Error.Code property, updated the description
|
||||||
|
|
||||||
* Version 9.5.0 - 19 Aug 2025
|
* Version 9.5.0 - 19 Aug 2025
|
||||||
* Added better error handling support
|
* Added better error handling support
|
||||||
* Added ErrorDescription, ErrorType and IsTransient to Error object
|
* Added ErrorDescription, ErrorType and IsTransient to Error object
|
||||||
|
Loading…
x
Reference in New Issue
Block a user