1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-17 19:33:07 +00:00

Authentication update (#275)

Updated API credential logic, exchange implementation are expected to provide their own credentials implementation with ApiCredentials as base class
Removed ApiCredentials implementation used by most exchanges
Removed ApiCredentialsType Enum
Added CredentialSet base class and implementations for defining different API credentials
Added optional type param to AuthenticationProvider for the specific API credential type to improve type safety
Moved AuthenticationProvider/ApiCredentials from BaseApiClient to RestApiClient/SocketApiClient base classes
Added optional type params to RestApiClient/SocketApiClient base class to specify the AuthenticationProvider type and credentials type to improve type safety
Moved SetOptions/SetApiCredentials from BaseApiClient to RestApiClient/SocketApiClient
Extracted LibraryOptions<TRestOptions, TSocketOptions, TEnvironment> without TApiCredentials for libraries without API credentials
Removed ApiCredentials from ApiOptions, credentials can only be configured at library, rest or socket level
Added EnvironmentName to RestApiClient/SocketApiClient property
Added Unknown enum value to Shared interfaces SharedOrderStatus, SharedTransferStatus and SharedTriggerOrderStatus enums
Updated Enum converter to map value to an undefined Enum value instead of the first Enum value
Added support for checking for missing fields on RestIntegrationTest
Added BytesToHexString and HexToBytesString to ExchangeHelpers static class
Fixed bug where WebSocket connections are not reconnected when configuring Proxy with SetUpdates
Removed legacy CryptoBaseClient, CryptoRestClient and CryptoSocketClient
This commit is contained in:
Jan Korf
2026-03-23 14:28:00 +01:00
committed by GitHub
parent 875696a73a
commit 2f9f97f4e8
47 changed files with 1615 additions and 771 deletions
+12 -43
View File
@@ -1,5 +1,4 @@
using System;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces.Clients;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options;
@@ -28,6 +27,11 @@ namespace CryptoExchange.Net.Clients
/// </summary>
protected bool _disposing;
/// <summary>
/// Whether a proxy is configured
/// </summary>
protected bool _proxyConfigured;
/// <summary>
/// Name of the client
/// </summary>
@@ -43,11 +47,6 @@ namespace CryptoExchange.Net.Clients
}
}
/// <summary>
/// The authentication provider for this API client. (null if no credentials are set)
/// </summary>
public AuthenticationProvider? AuthenticationProvider { get; private set; }
/// <summary>
/// The environment this client communicates to
/// </summary>
@@ -58,12 +57,6 @@ namespace CryptoExchange.Net.Clients
/// </summary>
public bool OutputOriginalData { get; }
/// <inheritdoc />
public bool Authenticated => ApiCredentials != null;
/// <inheritdoc />
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// Api options
/// </summary>
@@ -85,10 +78,14 @@ namespace CryptoExchange.Net.Clients
/// <param name="logger">Logger</param>
/// <param name="outputOriginalData">Should data from this client include the original data in the call result</param>
/// <param name="baseAddress">Base address for this API client</param>
/// <param name="apiCredentials">Api credentials</param>
/// <param name="clientOptions">Client options</param>
/// <param name="apiOptions">Api options</param>
protected BaseApiClient(ILogger logger, bool outputOriginalData, ApiCredentials? apiCredentials, string baseAddress, ExchangeOptions clientOptions, ApiOptions apiOptions)
protected BaseApiClient(
ILogger logger,
bool outputOriginalData,
string baseAddress,
ExchangeOptions clientOptions,
ApiOptions apiOptions)
{
_logger = logger;
@@ -96,19 +93,10 @@ namespace CryptoExchange.Net.Clients
ApiOptions = apiOptions;
OutputOriginalData = outputOriginalData;
BaseAddress = baseAddress;
ApiCredentials = apiCredentials?.Copy();
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
_proxyConfigured = ClientOptions.Proxy != null;
}
/// <summary>
/// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
/// <inheritdoc />
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
@@ -122,25 +110,6 @@ namespace CryptoExchange.Net.Clients
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
/// <inheritdoc />
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
ApiCredentials = credentials?.Copy();
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <inheritdoc />
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
{
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials;
if (ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
}
/// <summary>
/// Dispose
/// </summary>
-10
View File
@@ -92,16 +92,6 @@ namespace CryptoExchange.Net.Clients
_logger.Log(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{CryptoExchangeLibVersion}, {Exchange}.Net: v{ExchangeLibVersion}");
}
/// <summary>
/// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
/// </summary>
/// <param name="credentials">The credentials to set</param>
protected virtual void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{
foreach (var apiClient in ApiClients)
apiClient.SetApiCredentials(credentials);
}
/// <summary>
/// Register an API client
/// </summary>
+47 -2
View File
@@ -1,7 +1,11 @@
using System.Linq;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces.Clients;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Collections.Generic;
using System.Linq;
namespace CryptoExchange.Net.Clients
{
@@ -24,5 +28,46 @@ namespace CryptoExchange.Net.Clients
LibraryHelpers.StaticLogger = loggerFactory?.CreateLogger("CryptoExchange");
}
}
}
/// <inheritdoc />
public abstract class BaseRestClient<TEnvironment, TApiCredentials> : BaseRestClient, IRestClient<TApiCredentials>
where TEnvironment : TradeEnvironment
where TApiCredentials : ApiCredentials
{
/// <summary>
/// Api clients in this client
/// </summary>
internal new List<RestApiClient<TEnvironment, TApiCredentials>> ApiClients => base.ApiClients.OfType<RestApiClient<TEnvironment, TApiCredentials>>().ToList();
/// <summary>
/// ctor
/// </summary>
/// <param name="loggerFactory">Logger factory</param>
/// <param name="name">The name of the API this client is for</param>
protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{
}
/// <summary>
/// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
/// </summary>
/// <param name="credentials">The credentials to set</param>
public void SetApiCredentials(TApiCredentials credentials)
{
foreach (var apiClient in ApiClients)
apiClient.SetApiCredentials(credentials);
}
/// <summary>
/// Update options
/// </summary>
public virtual void SetOptions(UpdateOptions<TApiCredentials> options)
{
foreach (var apiClient in ApiClients)
apiClient.SetOptions(options);
}
}
}
+42 -1
View File
@@ -1,5 +1,7 @@
using CryptoExchange.Net.Interfaces.Clients;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces.Clients;
using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging;
@@ -134,4 +136,43 @@ namespace CryptoExchange.Net.Clients
return result;
}
}
/// <inheritdoc />
public abstract class BaseSocketClient<TEnvironment, TApiCredentials> : BaseSocketClient, ISocketClient<TApiCredentials>
where TEnvironment : TradeEnvironment
where TApiCredentials : ApiCredentials
{
/// <summary>
/// Api clients in this client
/// </summary>
internal new List<SocketApiClient<TEnvironment, TApiCredentials>> ApiClients => base.ApiClients.OfType<SocketApiClient<TEnvironment, TApiCredentials>>().ToList();
/// <summary>
/// ctor
/// </summary>
/// <param name="loggerFactory">Logger factory</param>
/// <param name="name">The name of the API this client is for</param>
protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{
}
/// <summary>
/// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
/// </summary>
/// <param name="credentials">The credentials to set</param>
public virtual void SetApiCredentials(TApiCredentials credentials)
{
foreach (var apiClient in ApiClients)
apiClient.SetApiCredentials(credentials);
}
/// <summary>
/// Update options
/// </summary>
public virtual void SetOptions(UpdateOptions<TApiCredentials> options)
{
foreach (var apiClient in ApiClients)
apiClient.SetOptions(options);
}
}
}
@@ -1,67 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.Clients
{
/// <summary>
/// Base crypto client
/// </summary>
public class CryptoBaseClient : IDisposable
{
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
/// <summary>
/// Service provider
/// </summary>
protected readonly IServiceProvider? _serviceProvider;
/// <summary>
/// ctor
/// </summary>
public CryptoBaseClient() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoBaseClient(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_serviceCache = new Dictionary<Type, object>();
}
/// <summary>
/// Try get a client by type for the service collection
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T TryGet<T>(Func<T> createFunc)
{
var type = typeof(T);
if (_serviceCache.TryGetValue(type, out var value))
return (T)value;
if (_serviceProvider == null)
{
// Create with default options
var createResult = createFunc();
_serviceCache.Add(typeof(T), createResult!);
return createResult;
}
var result = _serviceProvider.GetService<T>()
?? throw new InvalidOperationException($"No service was found for {typeof(T).Name}, make sure the exchange is registered in dependency injection with the `services.Add[Exchange]()` method");
_serviceCache.Add(type, result!);
return result;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
_serviceCache.Clear();
}
}
}
@@ -1,24 +0,0 @@
using CryptoExchange.Net.Interfaces.Clients;
using System;
namespace CryptoExchange.Net.Clients
{
/// <inheritdoc />
public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
{
/// <summary>
/// ctor
/// </summary>
public CryptoRestClient()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}
@@ -1,24 +0,0 @@
using CryptoExchange.Net.Interfaces.Clients;
using System;
namespace CryptoExchange.Net.Clients
{
/// <inheritdoc />
public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
{
/// <summary>
/// ctor
/// </summary>
public CryptoSocketClient()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="serviceProvider"></param>
public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}
+199 -14
View File
@@ -1,3 +1,4 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Caching;
using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters;
using CryptoExchange.Net.Interfaces;
@@ -102,7 +103,17 @@ namespace CryptoExchange.Net.Clients
/// The message handler
/// </summary>
protected abstract IRestMessageHandler MessageHandler { get; }
/// <summary>
/// Get the AuthenticationProvider implementation, or null if no ApiCredentials are set
/// </summary>
public virtual AuthenticationProvider? GetAuthenticationProvider() => null;
/// <summary>
/// Configured environment name
/// </summary>
public abstract string EnvironmentName { get; }
/// <summary>
/// ctor
/// </summary>
@@ -111,10 +122,13 @@ namespace CryptoExchange.Net.Clients
/// <param name="baseAddress">Base address for this API client</param>
/// <param name="options">The base client options</param>
/// <param name="apiOptions">The Api client options</param>
public RestApiClient(ILogger logger, HttpClient? httpClient, string baseAddress, RestExchangeOptions options, RestApiOptions apiOptions)
public RestApiClient(ILogger logger,
HttpClient? httpClient,
string baseAddress,
RestExchangeOptions options,
RestApiOptions apiOptions)
: base(logger,
apiOptions.OutputOriginalData ?? options.OutputOriginalData,
apiOptions.ApiCredentials ?? options.ApiCredentials,
baseAddress,
options,
apiOptions)
@@ -214,7 +228,7 @@ namespace CryptoExchange.Net.Clients
string? rateLimitKeySuffix = null)
{
var requestId = ExchangeHelpers.NextId();
if (definition.Authenticated && AuthenticationProvider == null)
if (definition.Authenticated && GetAuthenticationProvider() == null)
{
_logger.RestApiNoApiCredentials(requestId, definition.Path);
return new WebCallResult<T>(new NoApiCredentialsError());
@@ -319,7 +333,17 @@ namespace CryptoExchange.Net.Clients
if (ClientOptions.RateLimiterEnabled)
{
var limitResult = await definition.RateLimitGate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, definition, host, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, cancellationToken).ConfigureAwait(false);
var limitResult = await definition.RateLimitGate.ProcessAsync(
_logger,
requestId,
RateLimitItemType.Request,
definition,
host,
GetAuthenticationProvider()?.Key,
requestWeight,
ClientOptions.RateLimitingBehaviour,
rateLimitKeySuffix,
cancellationToken).ConfigureAwait(false);
if (!limitResult)
return limitResult.Error!;
}
@@ -334,7 +358,18 @@ namespace CryptoExchange.Net.Clients
if (ClientOptions.RateLimiterEnabled)
{
var singleRequestWeight = weightSingleLimiter ?? 1;
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, host, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, cancellationToken).ConfigureAwait(false);
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(
_logger,
requestId,
definition.LimitGuard,
RateLimitItemType.Request,
definition,
host,
GetAuthenticationProvider()?.Key,
singleRequestWeight,
ClientOptions.RateLimitingBehaviour,
rateLimitKeySuffix,
cancellationToken).ConfigureAwait(false);
if (!limitResult)
return limitResult.Error!;
}
@@ -373,7 +408,7 @@ namespace CryptoExchange.Net.Clients
try
{
AuthenticationProvider?.ProcessRequest(this, requestConfiguration);
GetAuthenticationProvider()?.ProcessRequest(this, requestConfiguration);
}
catch (Exception ex)
{
@@ -689,14 +724,6 @@ namespace CryptoExchange.Net.Clients
/// <returns>Server time</returns>
protected virtual Task<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();
/// <inheritdoc />
public override void SetOptions<T>(UpdateOptions<T> options)
{
base.SetOptions(options);
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout, ClientOptions.HttpKeepAliveInterval);
}
private async ValueTask CheckTimeSync(int requestId, RequestDefinition definition)
{
if (!definition.Authenticated)
@@ -793,4 +820,162 @@ namespace CryptoExchange.Net.Clients
&& !definition.PreventCaching;
}
/// <inheritdoc />
public abstract class RestApiClient<TEnvironment> : RestApiClient, IRestApiClient
where TEnvironment : TradeEnvironment
{
/// <inheritdoc />
public new RestExchangeOptions<TEnvironment> ClientOptions => (RestExchangeOptions<TEnvironment>)base.ClientOptions;
/// <inheritdoc />
public override string EnvironmentName => ClientOptions.Environment.Name;
/// <summary>
/// ctor
/// </summary>
protected RestApiClient(
ILogger logger,
HttpClient? httpClient,
string baseAddress,
RestExchangeOptions options,
RestApiOptions apiOptions) : base(
logger,
httpClient,
baseAddress,
options,
apiOptions)
{
}
}
/// <inheritdoc />
public abstract class RestApiClient<TEnvironment, TApiCredentials> : RestApiClient<TEnvironment>, IRestApiClient<TApiCredentials>
where TApiCredentials : ApiCredentials
where TEnvironment : TradeEnvironment
{
/// <inheritdoc />
public TApiCredentials? ApiCredentials { get; set; }
/// <inheritdoc />
public bool Authenticated => ApiCredentials != null;
/// <inheritdoc />
public new RestExchangeOptions<TEnvironment, TApiCredentials> ClientOptions => (RestExchangeOptions<TEnvironment, TApiCredentials>)base.ClientOptions;
/// <summary>
/// ctor
/// </summary>
protected RestApiClient(
ILogger logger,
HttpClient? httpClient,
string baseAddress,
RestExchangeOptions<TEnvironment, TApiCredentials> options,
RestApiOptions apiOptions) : base(
logger,
httpClient,
baseAddress,
options,
apiOptions)
{
ApiCredentials = options.ApiCredentials;
}
/// <inheritdoc />
public virtual void SetApiCredentials(TApiCredentials credentials)
{
ApiCredentials = (TApiCredentials)credentials.Copy();
}
/// <inheritdoc />
public virtual void SetOptions(UpdateOptions<TApiCredentials> options)
{
_proxyConfigured = options.Proxy != null;
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiCredentials = (TApiCredentials?)options.ApiCredentials?.Copy() ?? ApiCredentials;
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout, ClientOptions.HttpKeepAliveInterval);
}
}
/// <inheritdoc />
public abstract class RestApiClient<TEnvironment, TAuthenticationProvider, TApiCredentials> : RestApiClient<TEnvironment, TApiCredentials>
where TApiCredentials : ApiCredentials
where TAuthenticationProvider : AuthenticationProvider<TApiCredentials>
where TEnvironment : TradeEnvironment
{
private bool _authProviderInitialized = false;
private TAuthenticationProvider? _authenticationProvider;
/// <summary>
/// The authentication provider for this API client. (null if no credentials are set)
/// </summary>
public TAuthenticationProvider? AuthenticationProvider
{
get
{
if (!_authProviderInitialized)
{
if (ApiCredentials != null)
_authenticationProvider = CreateAuthenticationProvider(ApiCredentials);
_authProviderInitialized = true;
}
return _authenticationProvider;
}
internal set => _authenticationProvider = value;
}
/// <inheritdoc />
public override AuthenticationProvider? GetAuthenticationProvider() => AuthenticationProvider;
/// <summary>
/// ctor
/// </summary>
protected RestApiClient(
ILogger logger,
HttpClient? httpClient,
string baseAddress,
RestExchangeOptions<TEnvironment, TApiCredentials> options,
RestApiOptions apiOptions) : base(
logger,
httpClient,
baseAddress,
options,
apiOptions)
{
}
/// <summary>
/// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
protected abstract TAuthenticationProvider CreateAuthenticationProvider(TApiCredentials credentials);
/// <inheritdoc />
public override void SetApiCredentials(TApiCredentials credentials)
{
base.SetApiCredentials(credentials);
AuthenticationProvider = null;
_authProviderInitialized = false;
ApiCredentials = credentials;
}
/// <inheritdoc />
public override void SetOptions(UpdateOptions<TApiCredentials> options)
{
base.SetOptions(options);
if (options.ApiCredentials != null)
{
AuthenticationProvider = null;
_authProviderInitialized = false;
ApiCredentials = options.ApiCredentials;
}
}
}
}
+185 -24
View File
@@ -1,3 +1,4 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Interfaces.Clients;
@@ -19,6 +20,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
@@ -141,6 +143,16 @@ namespace CryptoExchange.Net.Clients
/// Whether or not to enforce that sequence number updates are always (lastSequenceNumber + 1)
/// </summary>
public bool EnforceSequenceNumbers { get; set; }
/// <summary>
/// Get the AuthenticationProvider implementation, or null if no ApiCredentials are set
/// </summary>
public virtual AuthenticationProvider? GetAuthenticationProvider() => null;
/// <summary>
/// Configured environment name
/// </summary>
public abstract string EnvironmentName { get; }
#endregion
/// <summary>
@@ -150,10 +162,13 @@ namespace CryptoExchange.Net.Clients
/// <param name="options">Client options</param>
/// <param name="baseAddress">Base address for this API client</param>
/// <param name="apiOptions">The Api client options</param>
public SocketApiClient(ILogger logger, string baseAddress, SocketExchangeOptions options, SocketApiOptions apiOptions)
public SocketApiClient(
ILogger logger,
string baseAddress,
SocketExchangeOptions options,
SocketApiOptions apiOptions)
: base(logger,
apiOptions.OutputOriginalData ?? options.OutputOriginalData,
apiOptions.ApiCredentials ?? options.ApiCredentials,
baseAddress,
options,
apiOptions)
@@ -235,7 +250,7 @@ namespace CryptoExchange.Net.Clients
if (_disposing)
return new CallResult<UpdateSubscription>(new InvalidOperationError("Client disposed, can't subscribe"));
if (subscription.Authenticated && AuthenticationProvider == null)
if (subscription.Authenticated && GetAuthenticationProvider() == null)
{
_logger.LogWarning("Failed to subscribe, private subscription but no API credentials set");
return new CallResult<UpdateSubscription>(new NoApiCredentialsError());
@@ -515,7 +530,7 @@ namespace CryptoExchange.Net.Clients
/// <returns></returns>
public virtual async Task<CallResult> AuthenticateSocketAsync(SocketConnection socket)
{
if (AuthenticationProvider == null)
if (GetAuthenticationProvider() == null)
return new CallResult(new NoApiCredentialsError());
_logger.AttemptingToAuthenticate(socket.SocketId);
@@ -546,7 +561,7 @@ namespace CryptoExchange.Net.Clients
/// </summary>
/// <returns></returns>
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection) =>
Task.FromResult(AuthenticationProvider!.GetAuthenticationQuery(this, connection));
Task.FromResult(GetAuthenticationProvider()!.GetAuthenticationQuery(this, connection));
/// <summary>
/// Adds a system subscription. Used for example to reply to ping requests
@@ -912,25 +927,6 @@ namespace CryptoExchange.Net.Clients
return CallResult.SuccessResult;
}
/// <inheritdoc />
public override void SetOptions<T>(UpdateOptions<T> options)
{
var previousProxyIsSet = ClientOptions.Proxy != null;
base.SetOptions(options);
if ((!previousProxyIsSet && options.Proxy == null)
|| _socketConnections.IsEmpty)
{
return;
}
_logger.LogInformation("Reconnecting websockets to apply proxy");
// Update proxy, also triggers reconnect
foreach (var connection in _socketConnections)
_ = connection.Value.UpdateProxy(options.Proxy);
}
/// <summary>
/// Log the current state of connections and subscriptions
/// </summary>
@@ -1040,4 +1036,169 @@ namespace CryptoExchange.Net.Clients
/// <returns></returns>
public abstract ISocketMessageHandler CreateMessageConverter(WebSocketMessageType messageType);
}
/// <inheritdoc />
public abstract class SocketApiClient<TEnvironment> : SocketApiClient, ISocketApiClient
where TEnvironment : TradeEnvironment
{
/// <inheritdoc />
public new SocketExchangeOptions<TEnvironment> ClientOptions => (SocketExchangeOptions<TEnvironment>)base.ClientOptions;
/// <inheritdoc />
public override string EnvironmentName => ClientOptions.Environment.Name;
/// <summary>
/// ctor
/// </summary>
protected SocketApiClient(
ILogger logger,
string baseAddress,
SocketExchangeOptions<TEnvironment> options,
SocketApiOptions apiOptions) : base(
logger,
baseAddress,
options,
apiOptions)
{
}
}
/// <inheritdoc />
public abstract class SocketApiClient<TEnvironment, TApiCredentials> : SocketApiClient<TEnvironment>, ISocketApiClient<TApiCredentials>
where TApiCredentials : ApiCredentials
where TEnvironment : TradeEnvironment
{
/// <inheritdoc />
public TApiCredentials? ApiCredentials { get; set; }
/// <inheritdoc />
public bool Authenticated => ApiCredentials != null;
/// <inheritdoc />
public new SocketExchangeOptions<TEnvironment, TApiCredentials> ClientOptions => (SocketExchangeOptions<TEnvironment, TApiCredentials>)base.ClientOptions;
/// <summary>
/// ctor
/// </summary>
protected SocketApiClient(
ILogger logger,
string baseAddress,
SocketExchangeOptions<TEnvironment, TApiCredentials> options,
SocketApiOptions apiOptions) : base(
logger,
baseAddress,
options,
apiOptions)
{
ApiCredentials = options.ApiCredentials;
}
/// <inheritdoc />
public virtual void SetApiCredentials(TApiCredentials credentials)
{
ApiCredentials = (TApiCredentials)credentials.Copy();
}
/// <inheritdoc />
public virtual void SetOptions(UpdateOptions<TApiCredentials> options)
{
var previousProxyIsSet = _proxyConfigured;
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiCredentials = (TApiCredentials?)options.ApiCredentials?.Copy() ?? ApiCredentials;
_proxyConfigured = options.Proxy != null;
if ((!previousProxyIsSet && options.Proxy == null)
|| _socketConnections.IsEmpty)
{
return;
}
_logger.LogInformation("Reconnecting websockets to apply proxy");
// Update proxy, also triggers reconnect
foreach (var connection in _socketConnections)
_ = connection.Value.UpdateProxy(options.Proxy);
}
}
/// <inheritdoc />
public abstract class SocketApiClient<TEnvironment, TAuthenticationProvider, TApiCredentials> : SocketApiClient<TEnvironment, TApiCredentials>
where TAuthenticationProvider : AuthenticationProvider<TApiCredentials>
where TApiCredentials : ApiCredentials
where TEnvironment : TradeEnvironment
{
private bool _authProviderInitialized = false;
private TAuthenticationProvider? _authenticationProvider;
/// <summary>
/// The authentication provider for this API client. (null if no credentials are set)
/// </summary>
public TAuthenticationProvider? AuthenticationProvider
{
get
{
if (!_authProviderInitialized)
{
if (ApiCredentials != null)
_authenticationProvider = CreateAuthenticationProvider(ApiCredentials);
_authProviderInitialized = true;
}
return _authenticationProvider;
}
internal set => _authenticationProvider = value;
}
/// <inheritdoc />
public override AuthenticationProvider? GetAuthenticationProvider() => AuthenticationProvider;
/// <summary>
/// ctor
/// </summary>
protected SocketApiClient(
ILogger logger,
string baseAddress,
SocketExchangeOptions<TEnvironment, TApiCredentials> options,
SocketApiOptions apiOptions) : base(
logger,
baseAddress,
options,
apiOptions)
{
}
/// <summary>
/// Create an AuthenticationProvider implementation instance based on the provided credentials
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
protected abstract TAuthenticationProvider CreateAuthenticationProvider(TApiCredentials credentials);
/// <inheritdoc />
public override void SetApiCredentials(TApiCredentials credentials)
{
AuthenticationProvider = null;
_authProviderInitialized = false;
ApiCredentials = credentials;
base.SetApiCredentials(credentials);
}
/// <inheritdoc />
public override void SetOptions(UpdateOptions<TApiCredentials> options)
{
if (options.ApiCredentials != null)
{
AuthenticationProvider = null;
_authProviderInitialized = false;
ApiCredentials = options.ApiCredentials;
}
base.SetOptions(options);
}
}
}