mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-10-27 08:27:19 +00:00
Compare commits
No commits in common. "a832f0e4d40599da4d2d04541cfdd6dcf9182968" and "d88fb0d35676daef52eed94bfb7b034181fca1cd" have entirely different histories.
a832f0e4d4
...
d88fb0d356
@ -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.8.0</PackageVersion>
|
<PackageVersion>9.7.0</PackageVersion>
|
||||||
<AssemblyVersion>9.8.0</AssemblyVersion>
|
<AssemblyVersion>9.7.0</AssemblyVersion>
|
||||||
<FileVersion>9.8.0</FileVersion>
|
<FileVersion>9.7.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.8.0" />
|
<PackageReference Include="CryptoExchange.Net" Version="9.7.0" />
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -5,9 +5,6 @@
|
|||||||
Protobuf support for CryptoExchange.Net.
|
Protobuf support for CryptoExchange.Net.
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
* Version 9.8.0 - 30 Sep 2025
|
|
||||||
* Updated CryptoExchange.Net version to 9.8.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
|
||||||
|
|
||||||
* Version 9.7.0 - 01 Sep 2025
|
* Version 9.7.0 - 01 Sep 2025
|
||||||
* Updated CryptoExchange.Net version to 9.7.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
* Updated CryptoExchange.Net version to 9.7.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,6 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.532, 0.532)]
|
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.532, 0.532)]
|
||||||
[TestCase(0.1, 1, 0.0001, RoundingType.Down, 0.5516592, 0.5516)]
|
[TestCase(0.1, 1, 0.0001, RoundingType.Down, 0.5516592, 0.5516)]
|
||||||
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.5516592, 0.5517)]
|
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.5516592, 0.5517)]
|
||||||
[TestCase(0, 1, 0.000000001, RoundingType.Closest, 0.0000097232, 0.000009723)]
|
|
||||||
public void AdjustValueStepTests(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal input, decimal expected)
|
public void AdjustValueStepTests(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal input, decimal expected)
|
||||||
{
|
{
|
||||||
var result = ExchangeHelpers.AdjustValueStep(min, max, step, roundingType, input);
|
var result = ExchangeHelpers.AdjustValueStep(min, max, step, roundingType, input);
|
||||||
|
|||||||
@ -227,7 +227,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Get a new or existing socket connection
|
// Get a new or existing socket connection
|
||||||
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, ct, subscription.Topic).ConfigureAwait(false);
|
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, subscription.Topic).ConfigureAwait(false);
|
||||||
if (!socketResult)
|
if (!socketResult)
|
||||||
return socketResult.As<UpdateSubscription>(null);
|
return socketResult.As<UpdateSubscription>(null);
|
||||||
|
|
||||||
@ -343,7 +343,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var socketResult = await GetSocketConnection(url, query.Authenticated, true, ct).ConfigureAwait(false);
|
var socketResult = await GetSocketConnection(url, query.Authenticated, true).ConfigureAwait(false);
|
||||||
if (!socketResult)
|
if (!socketResult)
|
||||||
return socketResult.As<THandlerResponse>(default);
|
return socketResult.As<THandlerResponse>(default);
|
||||||
|
|
||||||
@ -494,56 +494,25 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="address">The address the socket is for</param>
|
/// <param name="address">The address the socket is for</param>
|
||||||
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
||||||
/// <param name="dedicatedRequestConnection">Whether a dedicated request connection should be returned</param>
|
/// <param name="dedicatedRequestConnection">Whether a dedicated request connection should be returned</param>
|
||||||
/// <param name="ct">Cancellation token</param>
|
|
||||||
/// <param name="topic">The subscription topic, can be provided when multiple of the same topics are not allowed on a connection</param>
|
/// <param name="topic">The subscription topic, can be provided when multiple of the same topics are not allowed on a connection</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(string address, bool authenticated, bool dedicatedRequestConnection, CancellationToken ct, string? topic = null)
|
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(string address, bool authenticated, bool dedicatedRequestConnection, string? topic = null)
|
||||||
{
|
{
|
||||||
var socketQuery = socketConnections.Where(s => s.Value.Tag.TrimEnd('/') == address.TrimEnd('/')
|
var socketQuery = socketConnections.Where(s => (s.Value.Status == SocketConnection.SocketStatus.None || s.Value.Status == SocketConnection.SocketStatus.Connected)
|
||||||
|
&& s.Value.Tag.TrimEnd('/') == address.TrimEnd('/')
|
||||||
&& s.Value.ApiClient.GetType() == GetType()
|
&& s.Value.ApiClient.GetType() == GetType()
|
||||||
&& (AllowTopicsOnTheSameConnection || !s.Value.Topics.Contains(topic)))
|
&& (s.Value.Authenticated == authenticated || !authenticated)
|
||||||
.Select(x => x.Value)
|
&& (AllowTopicsOnTheSameConnection || !s.Value.Topics.Contains(topic))
|
||||||
.ToList();
|
&& s.Value.Connected);
|
||||||
|
|
||||||
// If all current socket connections are reconnecting or resubscribing wait for that to finish as we can probably use the existing connection
|
SocketConnection connection;
|
||||||
var delayStart = DateTime.UtcNow;
|
|
||||||
var delayed = false;
|
|
||||||
while (socketQuery.Count >= 1 && socketQuery.All(x => x.Status == SocketConnection.SocketStatus.Reconnecting || x.Status == SocketConnection.SocketStatus.Resubscribing))
|
|
||||||
{
|
|
||||||
if (DateTime.UtcNow - delayStart > TimeSpan.FromSeconds(10))
|
|
||||||
{
|
|
||||||
if (socketQuery.Count >= 1 && socketQuery.All(x => x.Status == SocketConnection.SocketStatus.Reconnecting || x.Status == SocketConnection.SocketStatus.Resubscribing))
|
|
||||||
{
|
|
||||||
// If after this time we still trying to reconnect/reprocess there is some issue in the connection
|
|
||||||
_logger.TimeoutWaitingForReconnectingSocket();
|
|
||||||
return new CallResult<SocketConnection>(new CantConnectError());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
delayed = true;
|
|
||||||
try { await Task.Delay(50, ct).ConfigureAwait(false); } catch (Exception) { }
|
|
||||||
|
|
||||||
if (ct.IsCancellationRequested)
|
|
||||||
return new CallResult<SocketConnection>(new CancellationRequestedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delayed)
|
|
||||||
_logger.WaitedForReconnectingSocket((long)(DateTime.UtcNow - delayStart).TotalMilliseconds);
|
|
||||||
|
|
||||||
socketQuery = socketQuery.Where(s => (s.Status == SocketConnection.SocketStatus.None || s.Status == SocketConnection.SocketStatus.Connected)
|
|
||||||
&& (s.Authenticated == authenticated || !authenticated)
|
|
||||||
&& s.Connected).ToList();
|
|
||||||
|
|
||||||
SocketConnection? connection;
|
|
||||||
if (!dedicatedRequestConnection)
|
if (!dedicatedRequestConnection)
|
||||||
{
|
{
|
||||||
connection = socketQuery.Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection).OrderBy(s => s.UserSubscriptionCount).FirstOrDefault();
|
connection = socketQuery.Where(s => !s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).OrderBy(s => s.Value.UserSubscriptionCount).FirstOrDefault().Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
connection = socketQuery.Where(s => s.DedicatedRequestConnection.IsDedicatedRequestConnection).FirstOrDefault();
|
connection = socketQuery.Where(s => s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).FirstOrDefault().Value;
|
||||||
if (connection != null && !connection.DedicatedRequestConnection.Authenticated)
|
if (connection != null && !connection.DedicatedRequestConnection.Authenticated)
|
||||||
// Mark dedicated request connection as authenticated if the request is authenticated
|
// Mark dedicated request connection as authenticated if the request is authenticated
|
||||||
connection.DedicatedRequestConnection.Authenticated = authenticated;
|
connection.DedicatedRequestConnection.Authenticated = authenticated;
|
||||||
@ -551,12 +520,9 @@ namespace CryptoExchange.Net.Clients
|
|||||||
|
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
if (connection.UserSubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget
|
if (connection.UserSubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || (socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections) && socketConnections.All(s => s.Value.UserSubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
|
||||||
|| (socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections) && socketConnections.All(s => s.Value.UserSubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
|
|
||||||
{
|
|
||||||
// Use existing socket if it has less than target connections OR it has the least connections and we can't make new
|
// Use existing socket if it has less than target connections OR it has the least connections and we can't make new
|
||||||
return new CallResult<SocketConnection>(connection);
|
return new CallResult<SocketConnection>(connection);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectionAddress = await GetConnectionUrlAsync(address, authenticated).ConfigureAwait(false);
|
var connectionAddress = await GetConnectionUrlAsync(address, authenticated).ConfigureAwait(false);
|
||||||
@ -750,7 +716,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
foreach (var item in DedicatedConnectionConfigs)
|
foreach (var item in DedicatedConnectionConfigs)
|
||||||
{
|
{
|
||||||
var socketResult = await GetSocketConnection(item.SocketAddress, item.Authenticated, true, CancellationToken.None).ConfigureAwait(false);
|
var socketResult = await GetSocketConnection(item.SocketAddress, item.Authenticated, true).ConfigureAwait(false);
|
||||||
if (!socketResult)
|
if (!socketResult)
|
||||||
return socketResult.AsDataless();
|
return socketResult.AsDataless();
|
||||||
|
|
||||||
|
|||||||
@ -140,10 +140,10 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
_ => throw new Exception("Invalid token type for enum deserialization: " + reader.TokenType)
|
_ => throw new Exception("Invalid token type for enum deserialization: " + reader.TokenType)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (stringValue is null)
|
if (string.IsNullOrEmpty(stringValue))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (!GetValue(enumType, stringValue, out var result))
|
if (!GetValue(enumType, stringValue!, out var result))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(stringValue))
|
if (string.IsNullOrWhiteSpace(stringValue))
|
||||||
{
|
{
|
||||||
@ -204,13 +204,6 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(value))
|
|
||||||
{
|
|
||||||
// An empty/null value will always fail when parsing, so just return here
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// If no explicit mapping is found try to parse string
|
// If no explicit mapping is found try to parse string
|
||||||
|
|||||||
@ -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.8.0</PackageVersion>
|
<PackageVersion>9.7.0</PackageVersion>
|
||||||
<AssemblyVersion>9.8.0</AssemblyVersion>
|
<AssemblyVersion>9.7.0</AssemblyVersion>
|
||||||
<FileVersion>9.8.0</FileVersion>
|
<FileVersion>9.7.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>
|
||||||
|
|||||||
@ -89,6 +89,8 @@ namespace CryptoExchange.Net
|
|||||||
else value += (step.Value - offset);
|
else value += (step.Value - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value = RoundDown(value, 8);
|
||||||
|
|
||||||
return value.Normalize();
|
return value.Normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
using CryptoExchange.Net.SharedApis;
|
|
||||||
using CryptoExchange.Net.Trackers.Klines;
|
|
||||||
using CryptoExchange.Net.Trackers.Trades;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Interfaces
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Tracker factory
|
|
||||||
/// </summary>
|
|
||||||
public interface ITrackerFactory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the factory supports creating a KlineTracker instance for this symbol and interval
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="symbol">The symbol</param>
|
|
||||||
/// <param name="interval">The kline interval</param>
|
|
||||||
bool CanCreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new kline tracker
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="symbol">The symbol</param>
|
|
||||||
/// <param name="interval">Kline interval</param>
|
|
||||||
/// <param name="limit">The max amount of klines to retain</param>
|
|
||||||
/// <param name="period">The max period the data should be retained</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the factory supports creating a TradeTracker instance for this symbol
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="symbol">The symbol</param>
|
|
||||||
bool CanCreateTradeTracker(SharedSymbol symbol);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new trade tracker for a symbol
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="symbol">The symbol</param>
|
|
||||||
/// <param name="limit">The max amount of trades to retain</param>
|
|
||||||
/// <param name="period">The max period the data should be retained</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -23,8 +23,6 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
private static readonly Action<ILogger, int, int, Exception?> _unsubscribingSubscription;
|
private static readonly Action<ILogger, int, int, Exception?> _unsubscribingSubscription;
|
||||||
private static readonly Action<ILogger, int, Exception?> _reconnectingAllConnections;
|
private static readonly Action<ILogger, int, Exception?> _reconnectingAllConnections;
|
||||||
private static readonly Action<ILogger, DateTime, Exception?> _addingRetryAfterGuard;
|
private static readonly Action<ILogger, DateTime, Exception?> _addingRetryAfterGuard;
|
||||||
private static readonly Action<ILogger, Exception?> _timeoutWaitingForReconnectingSocket;
|
|
||||||
private static readonly Action<ILogger, long, Exception?> _waitedForReconnectingSocket;
|
|
||||||
|
|
||||||
static SocketApiClientLoggingExtension()
|
static SocketApiClientLoggingExtension()
|
||||||
{
|
{
|
||||||
@ -112,16 +110,6 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(3018, "AddRetryAfterGuard"),
|
new EventId(3018, "AddRetryAfterGuard"),
|
||||||
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
|
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
|
||||||
|
|
||||||
_timeoutWaitingForReconnectingSocket = LoggerMessage.Define(
|
|
||||||
LogLevel.Debug,
|
|
||||||
new EventId(3019, "TimeoutWaitingForReconnectingSocket"),
|
|
||||||
"Timeout while waiting for existing socket reconnection, failing request");
|
|
||||||
|
|
||||||
_waitedForReconnectingSocket = LoggerMessage.Define<long>(
|
|
||||||
LogLevel.Trace,
|
|
||||||
new EventId(3020, "WaitedForReconnectingSocket"),
|
|
||||||
"Waited for reconnecting socket for {Timespan}ms");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
|
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
|
||||||
@ -208,14 +196,5 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
{
|
{
|
||||||
_addingRetryAfterGuard(logger, retryAfter, null);
|
_addingRetryAfterGuard(logger, retryAfter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TimeoutWaitingForReconnectingSocket(this ILogger logger)
|
|
||||||
{
|
|
||||||
_timeoutWaitingForReconnectingSocket(logger, null);
|
|
||||||
}
|
|
||||||
public static void WaitedForReconnectingSocket(this ILogger logger, long milliseconds)
|
|
||||||
{
|
|
||||||
_waitedForReconnectingSocket(logger, milliseconds, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
using CryptoExchange.Net.Sockets;
|
using CryptoExchange.Net.Sockets;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Objects.Sockets
|
namespace CryptoExchange.Net.Objects.Sockets
|
||||||
@ -14,21 +12,13 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
private readonly SocketConnection _connection;
|
private readonly SocketConnection _connection;
|
||||||
private readonly Subscription _listener;
|
private readonly Subscription _listener;
|
||||||
|
|
||||||
private object _eventLock = new object();
|
|
||||||
private List<Action> _connectionClosedEventHandlers = new List<Action>();
|
|
||||||
private List<Action> _connectionLostEventHandlers = new List<Action>();
|
|
||||||
private List<Action<Error>> _resubscribeFailedEventHandlers = new List<Action<Error>>();
|
|
||||||
private List<Action<TimeSpan>> _connectionRestoredEventHandlers = new List<Action<TimeSpan>>();
|
|
||||||
private List<Action> _activityPausedEventHandlers = new List<Action>();
|
|
||||||
private List<Action> _activityUnpausedEventHandlers = new List<Action>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event when the connection is lost. The socket will automatically reconnect when possible.
|
/// Event when the connection is lost. The socket will automatically reconnect when possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action ConnectionLost
|
public event Action ConnectionLost
|
||||||
{
|
{
|
||||||
add { lock (_eventLock) _connectionLostEventHandlers.Add(value); }
|
add => _connection.ConnectionLost += value;
|
||||||
remove { lock (_eventLock) _connectionLostEventHandlers.Remove(value); }
|
remove => _connection.ConnectionLost -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,8 +26,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action ConnectionClosed
|
public event Action ConnectionClosed
|
||||||
{
|
{
|
||||||
add { lock (_eventLock) _connectionClosedEventHandlers.Add(value); }
|
add => _connection.ConnectionClosed += value;
|
||||||
remove { lock (_eventLock) _connectionClosedEventHandlers.Remove(value); }
|
remove => _connection.ConnectionClosed -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -45,8 +35,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<Error> ResubscribingFailed
|
public event Action<Error> ResubscribingFailed
|
||||||
{
|
{
|
||||||
add { lock (_eventLock) _resubscribeFailedEventHandlers.Add(value); }
|
add => _connection.ResubscribingFailed += value;
|
||||||
remove { lock (_eventLock) _resubscribeFailedEventHandlers.Remove(value); }
|
remove => _connection.ResubscribingFailed -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -56,8 +46,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TimeSpan> ConnectionRestored
|
public event Action<TimeSpan> ConnectionRestored
|
||||||
{
|
{
|
||||||
add { lock (_eventLock) _connectionRestoredEventHandlers.Add(value); }
|
add => _connection.ConnectionRestored += value;
|
||||||
remove { lock (_eventLock) _connectionRestoredEventHandlers.Remove(value); }
|
remove => _connection.ConnectionRestored -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -65,8 +55,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action ActivityPaused
|
public event Action ActivityPaused
|
||||||
{
|
{
|
||||||
add { lock (_eventLock) _activityPausedEventHandlers.Add(value); }
|
add => _connection.ActivityPaused += value;
|
||||||
remove { lock (_eventLock) _activityPausedEventHandlers.Remove(value); }
|
remove => _connection.ActivityPaused -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -74,8 +64,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action ActivityUnpaused
|
public event Action ActivityUnpaused
|
||||||
{
|
{
|
||||||
add { lock (_eventLock) _activityUnpausedEventHandlers.Add(value); }
|
add => _connection.ActivityUnpaused += value;
|
||||||
remove { lock (_eventLock) _activityUnpausedEventHandlers.Remove(value); }
|
remove => _connection.ActivityUnpaused -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -105,85 +95,7 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
public UpdateSubscription(SocketConnection connection, Subscription subscription)
|
public UpdateSubscription(SocketConnection connection, Subscription subscription)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
_connection.ConnectionClosed += HandleConnectionClosedEvent;
|
|
||||||
_connection.ConnectionLost += HandleConnectionLostEvent;
|
|
||||||
_connection.ConnectionRestored += HandleConnectionRestoredEvent;
|
|
||||||
_connection.ResubscribingFailed += HandleResubscribeFailedEvent;
|
|
||||||
_connection.ActivityPaused += HandlePausedEvent;
|
|
||||||
_connection.ActivityUnpaused += HandleUnpausedEvent;
|
|
||||||
|
|
||||||
_listener = subscription;
|
_listener = subscription;
|
||||||
_listener.Unsubscribed += HandleUnsubscribed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleUnsubscribed()
|
|
||||||
{
|
|
||||||
_connection.ConnectionClosed -= HandleConnectionClosedEvent;
|
|
||||||
_connection.ConnectionLost -= HandleConnectionLostEvent;
|
|
||||||
_connection.ConnectionRestored -= HandleConnectionRestoredEvent;
|
|
||||||
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
|
|
||||||
_connection.ActivityPaused -= HandlePausedEvent;
|
|
||||||
_connection.ActivityUnpaused -= HandleUnpausedEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleConnectionClosedEvent()
|
|
||||||
{
|
|
||||||
List<Action> handlers;
|
|
||||||
lock (_eventLock)
|
|
||||||
handlers = _connectionClosedEventHandlers.ToList();
|
|
||||||
|
|
||||||
foreach(var callback in handlers)
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleConnectionLostEvent()
|
|
||||||
{
|
|
||||||
List<Action> handlers;
|
|
||||||
lock (_eventLock)
|
|
||||||
handlers = _connectionLostEventHandlers.ToList();
|
|
||||||
|
|
||||||
foreach (var callback in handlers)
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleConnectionRestoredEvent(TimeSpan period)
|
|
||||||
{
|
|
||||||
List<Action<TimeSpan>> handlers;
|
|
||||||
lock (_eventLock)
|
|
||||||
handlers = _connectionRestoredEventHandlers.ToList();
|
|
||||||
|
|
||||||
foreach (var callback in handlers)
|
|
||||||
callback(period);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleResubscribeFailedEvent(Error error)
|
|
||||||
{
|
|
||||||
List<Action<Error>> handlers;
|
|
||||||
lock (_eventLock)
|
|
||||||
handlers = _resubscribeFailedEventHandlers.ToList();
|
|
||||||
|
|
||||||
foreach (var callback in handlers)
|
|
||||||
callback(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandlePausedEvent()
|
|
||||||
{
|
|
||||||
List<Action> handlers;
|
|
||||||
lock (_eventLock)
|
|
||||||
handlers = _activityPausedEventHandlers.ToList();
|
|
||||||
|
|
||||||
foreach (var callback in handlers)
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleUnpausedEvent()
|
|
||||||
{
|
|
||||||
List<Action> handlers;
|
|
||||||
lock (_eventLock)
|
|
||||||
handlers = _activityUnpausedEventHandlers.ToList();
|
|
||||||
|
|
||||||
foreach (var callback in handlers)
|
|
||||||
callback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -67,10 +67,6 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// Min number of confirmations
|
/// Min number of confirmations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MinConfirmations { get; set; }
|
public int? MinConfirmations { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// The contract address
|
|
||||||
/// </summary>
|
|
||||||
public string? ContractAddress { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
|
|||||||
@ -706,8 +706,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
lock (_listenersLock)
|
lock (_listenersLock)
|
||||||
_listeners.Remove(subscription);
|
_listeners.Remove(subscription);
|
||||||
|
|
||||||
subscription.InvokeUnsubscribedHandler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -74,10 +74,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// Exception event
|
/// Exception event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<Exception>? Exception;
|
public event Action<Exception>? Exception;
|
||||||
/// <summary>
|
|
||||||
/// Listener unsubscribed event
|
|
||||||
/// </summary>
|
|
||||||
public event Action? Unsubscribed;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscription topic
|
/// Subscription topic
|
||||||
@ -185,14 +181,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
Exception?.Invoke(e);
|
Exception?.Invoke(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke the unsubscribed event
|
|
||||||
/// </summary>
|
|
||||||
public void InvokeUnsubscribedHandler()
|
|
||||||
{
|
|
||||||
Unsubscribed?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// State of this subscription
|
/// State of this subscription
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -64,14 +64,6 @@ 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.8.0 - 30 Sep 2025
|
|
||||||
* Added ContractAddress to SharedAsset model
|
|
||||||
* Added ITrackerFactory interface
|
|
||||||
* Fixed UpdateSubscription still propagating connection events even though the specific listener is unsubscribed
|
|
||||||
* Fixed ExchangeHelpers.AdjustValueStep high precision calculation
|
|
||||||
* Fixed issue increasing the number of websocket connections increasing when sending a query when a previous connection was attempting to reconnect
|
|
||||||
* Fixed EnumConverter to allow mapping empty string values
|
|
||||||
|
|
||||||
* Version 9.7.0 - 01 Sep 2025
|
* Version 9.7.0 - 01 Sep 2025
|
||||||
* Added LibraryHelpers.CreateHttpClientMessageHandle to standardize HttpMessageHandler creation
|
* Added LibraryHelpers.CreateHttpClientMessageHandle to standardize HttpMessageHandler creation
|
||||||
* Added REST client option for selecting HTTP protocol version
|
* Added REST client option for selecting HTTP protocol version
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user