diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs
index f47c8fd..8d1889b 100644
--- a/CryptoExchange.Net/Clients/BaseApiClient.cs
+++ b/CryptoExchange.Net/Clients/BaseApiClient.cs
@@ -93,6 +93,17 @@ namespace CryptoExchange.Net.Clients
AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy());
}
+ ///
+ public virtual void SetOptions(UpdateOptions options) where T : ApiCredentials
+ {
+ ClientOptions.Proxy = options.Proxy;
+ ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
+
+ ApiOptions.ApiCredentials = options.ApiCredentials ?? ClientOptions.ApiCredentials;
+ if (options.ApiCredentials != null)
+ AuthenticationProvider = CreateAuthenticationProvider(options.ApiCredentials.Copy());
+ }
+
///
/// Dispose
///
diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs
index 41e22d1..91e999a 100644
--- a/CryptoExchange.Net/Clients/RestApiClient.cs
+++ b/CryptoExchange.Net/Clients/RestApiClient.cs
@@ -961,6 +961,14 @@ namespace CryptoExchange.Net.Clients
/// Server time
protected virtual Task> GetServerTimestampAsync() => throw new NotImplementedException();
+ ///
+ public override void SetOptions(UpdateOptions options)
+ {
+ base.SetOptions(options);
+
+ RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout);
+ }
+
internal async Task> SyncTimeAsync()
{
var timeSyncParams = GetTimeSyncInfo();
diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs
index d27ee3d..1adcba6 100644
--- a/CryptoExchange.Net/Clients/SocketApiClient.cs
+++ b/CryptoExchange.Net/Clients/SocketApiClient.cs
@@ -158,7 +158,7 @@ namespace CryptoExchange.Net.Clients
///
///
///
- protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func queryDelegate, Action? callback)
+ protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func queryDelegate, Action? callback)
{
PeriodicTaskRegistrations.Add(new PeriodicTaskRegistration
{
@@ -422,9 +422,10 @@ namespace CryptoExchange.Net.Clients
result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult(result.Error)!;
}
+
+ _logger.Authenticated(socket.SocketId);
}
- _logger.Authenticated(socket.SocketId);
socket.Authenticated = true;
return new CallResult(null);
}
@@ -710,6 +711,25 @@ namespace CryptoExchange.Net.Clients
return new CallResult(null);
}
+ ///
+ public override void SetOptions(UpdateOptions options)
+ {
+ var previousProxyIsSet = ClientOptions.Proxy != null;
+ base.SetOptions(options);
+
+ if ((!previousProxyIsSet && options.Proxy == null)
+ || !socketConnections.Any())
+ {
+ return;
+ }
+
+ _logger.LogInformation("Reconnecting websockets to apply proxy");
+
+ // Update proxy, also triggers reconnect
+ foreach (var connection in socketConnections)
+ _ = connection.Value.UpdateProxy(options.Proxy);
+ }
+
///
/// Log the current state of connections and subscriptions
///
diff --git a/CryptoExchange.Net/Interfaces/IBaseApiClient.cs b/CryptoExchange.Net/Interfaces/IBaseApiClient.cs
index 548a196..9b321b1 100644
--- a/CryptoExchange.Net/Interfaces/IBaseApiClient.cs
+++ b/CryptoExchange.Net/Interfaces/IBaseApiClient.cs
@@ -1,5 +1,6 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects;
+using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis;
using System;
@@ -31,5 +32,12 @@ namespace CryptoExchange.Net.Interfaces
///
///
void SetApiCredentials(T credentials) where T : ApiCredentials;
+
+ ///
+ /// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
+ ///
+ /// Api crentials type
+ /// Options to set
+ void SetOptions(UpdateOptions options) where T : ApiCredentials;
}
}
\ No newline at end of file
diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs
index 66ff906..f3cc827 100644
--- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs
+++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs
@@ -24,6 +24,13 @@ namespace CryptoExchange.Net.Interfaces
/// Request timeout to use
/// Optional shared http client instance
/// Optional proxy to use when no http client is provided
- void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient=null);
+ void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
+
+ ///
+ /// Update settings
+ ///
+ /// Proxy to use
+ /// Request timeout to use
+ void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
}
}
diff --git a/CryptoExchange.Net/Interfaces/IWebsocket.cs b/CryptoExchange.Net/Interfaces/IWebsocket.cs
index b1e75cb..721688d 100644
--- a/CryptoExchange.Net/Interfaces/IWebsocket.cs
+++ b/CryptoExchange.Net/Interfaces/IWebsocket.cs
@@ -93,5 +93,10 @@ namespace CryptoExchange.Net.Interfaces
///
///
Task CloseAsync();
+
+ ///
+ /// Update proxy setting
+ ///
+ void UpdateProxy(ApiProxy? proxy);
}
}
diff --git a/CryptoExchange.Net/Objects/Options/UpdateOptions.cs b/CryptoExchange.Net/Objects/Options/UpdateOptions.cs
new file mode 100644
index 0000000..9fc9ed0
--- /dev/null
+++ b/CryptoExchange.Net/Objects/Options/UpdateOptions.cs
@@ -0,0 +1,29 @@
+using CryptoExchange.Net.Authentication;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CryptoExchange.Net.Objects.Options
+{
+ ///
+ /// Options to update
+ ///
+ public class UpdateOptions where T : ApiCredentials
+ {
+ ///
+ /// Proxy setting. Note that if this is not provided any previously set proxy will be reset
+ ///
+ public ApiProxy? Proxy { get; set; }
+ ///
+ /// Api credentials
+ ///
+ public T? ApiCredentials { get; set; }
+ ///
+ /// Request timeout
+ ///
+ public TimeSpan? RequestTimeout { get; set; }
+ }
+
+ ///
+ public class UpdateOptions : UpdateOptions { }
+}
diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs
index 83ea61d..693e91d 100644
--- a/CryptoExchange.Net/Requests/RequestFactory.cs
+++ b/CryptoExchange.Net/Requests/RequestFactory.cs
@@ -17,28 +17,7 @@ namespace CryptoExchange.Net.Requests
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
{
if (client == null)
- {
- var handler = new HttpClientHandler();
- try
- {
- handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
- }
- catch (PlatformNotSupportedException) { }
-
- if (proxy != null)
- {
- handler.Proxy = new WebProxy
- {
- Address = new Uri($"{proxy.Host}:{proxy.Port}"),
- Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
- };
- }
-
- client = new HttpClient(handler)
- {
- Timeout = requestTimeout
- };
- }
+ client = CreateClient(proxy, requestTimeout);
_httpClient = client;
}
@@ -51,5 +30,37 @@ namespace CryptoExchange.Net.Requests
return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId);
}
+
+ ///
+ public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout)
+ {
+ _httpClient = CreateClient(proxy, requestTimeout);
+ }
+
+ private HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
+ {
+ var handler = new HttpClientHandler();
+ try
+ {
+ handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+ handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
+ }
+ catch (PlatformNotSupportedException) { }
+
+ if (proxy != null)
+ {
+ handler.Proxy = new WebProxy
+ {
+ Address = new Uri($"{proxy.Host}:{proxy.Port}"),
+ Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
+ };
+ }
+
+ var client = new HttpClient(handler)
+ {
+ Timeout = requestTimeout
+ };
+ return client;
+ }
}
}
diff --git a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
index fd3fb65..1d364eb 100644
--- a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
+++ b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
@@ -155,6 +155,12 @@ namespace CryptoExchange.Net.Sockets
_baseAddress = $"{Uri.Scheme}://{Uri.Host}";
}
+ ///
+ public void UpdateProxy(ApiProxy? proxy)
+ {
+ Parameters.Proxy = proxy;
+ }
+
///
public virtual async Task ConnectAsync()
{
@@ -435,8 +441,8 @@ namespace CryptoExchange.Net.Sockets
{
// Wait until we receive close confirmation
await Task.Delay(10).ConfigureAwait(false);
- if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(5))
- break; // Wait for max 5 seconds, then just abort the connection
+ if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(1))
+ break; // Wait for max 1 second, then just abort the connection
}
}
}
diff --git a/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs b/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs
index 8f9ccaf..9c532bb 100644
--- a/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs
+++ b/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs
@@ -23,6 +23,6 @@ namespace CryptoExchange.Net.Sockets
///
/// Callback after query
///
- public Action? Callback { get; set; }
+ public Action? Callback { get; set; }
}
}
diff --git a/CryptoExchange.Net/Sockets/Query.cs b/CryptoExchange.Net/Sockets/Query.cs
index 833abf8..397ae43 100644
--- a/CryptoExchange.Net/Sockets/Query.cs
+++ b/CryptoExchange.Net/Sockets/Query.cs
@@ -23,6 +23,11 @@ namespace CryptoExchange.Net.Sockets
///
public bool Completed { get; set; }
+ ///
+ /// Timeout for the request
+ ///
+ public TimeSpan? RequestTimeout { get; set; }
+
///
/// The number of required responses. Can be more than 1 when for example subscribing multiple symbols streams in a single request,
/// and each symbol receives it's own confirmation response
diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs
index 3e4364f..a1deaaa 100644
--- a/CryptoExchange.Net/Sockets/SocketConnection.cs
+++ b/CryptoExchange.Net/Sockets/SocketConnection.cs
@@ -11,6 +11,8 @@ using System.Diagnostics;
using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Logging.Extensions;
using System.Threading;
+using CryptoExchange.Net.Objects.Options;
+using CryptoExchange.Net.Authentication;
namespace CryptoExchange.Net.Sockets
{
@@ -437,7 +439,7 @@ namespace CryptoExchange.Net.Sockets
return Task.CompletedTask;
}
- query.IsSend(ApiClient.ClientOptions.RequestTimeout);
+ query.IsSend(query.RequestTimeout ?? ApiClient.ClientOptions.RequestTimeout);
return Task.CompletedTask;
}
@@ -583,6 +585,16 @@ namespace CryptoExchange.Net.Sockets
///
public async Task TriggerReconnectAsync() => await _socket.ReconnectAsync().ConfigureAwait(false);
+ ///
+ /// Update the proxy setting and reconnect
+ ///
+ /// New proxy setting
+ public async Task UpdateProxy(ApiProxy? proxy)
+ {
+ _socket.UpdateProxy(proxy);
+ await TriggerReconnectAsync().ConfigureAwait(false);
+ }
+
///
/// Close the connection
///
@@ -988,7 +1000,7 @@ namespace CryptoExchange.Net.Sockets
/// How often
/// Method returning the query to send
/// The callback for processing the response
- public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func queryDelegate, Action? callback)
+ public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func queryDelegate, Action? callback)
{
if (queryDelegate == null)
throw new ArgumentNullException(nameof(queryDelegate));
@@ -1020,7 +1032,7 @@ namespace CryptoExchange.Net.Sockets
try
{
var result = await SendAndWaitQueryAsync(query).ConfigureAwait(false);
- callback?.Invoke(result);
+ callback?.Invoke(this, result);
}
catch (Exception ex)
{
diff --git a/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs b/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs
index 6bcd8c1..293c2ea 100644
--- a/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs
+++ b/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs
@@ -25,5 +25,7 @@ namespace CryptoExchange.Net.Testing.Implementations
_request.RequestId = requestId;
return _request;
}
+
+ public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {}
}
}
diff --git a/CryptoExchange.Net/Testing/Implementations/TestSocket.cs b/CryptoExchange.Net/Testing/Implementations/TestSocket.cs
index b65cfa3..bec8b8d 100644
--- a/CryptoExchange.Net/Testing/Implementations/TestSocket.cs
+++ b/CryptoExchange.Net/Testing/Implementations/TestSocket.cs
@@ -92,5 +92,7 @@ namespace CryptoExchange.Net.Testing.Implementations
public Task ReconnectAsync() => throw new NotImplementedException();
public void Dispose() { }
+
+ public void UpdateProxy(ApiProxy? proxy) => throw new NotImplementedException();
}
}
diff --git a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs
index 8962566..05bc209 100644
--- a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs
+++ b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs
@@ -350,7 +350,8 @@ namespace CryptoExchange.Net.Trackers.Trades
_data.Add(item);
}
- _firstTimestamp = _data.Min(v => v.Timestamp);
+ if (_data.Any())
+ _firstTimestamp = _data.Min(v => v.Timestamp);
ApplyWindow(false);
}