Skip to content
324 changes: 231 additions & 93 deletions Flagsmith.Client.Test/FlagsmithTest.cs

Large diffs are not rendered by default.

668 changes: 668 additions & 0 deletions Flagsmith.Client.Test/FlagsmithTestDeprecated.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Flagsmith.Client.Test/PollingManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void TestPollingManagerCallsUpdateEnvironmentOnStart()
isCalled = true;
return Task.CompletedTask;
};
var x = new PollingManager(callback);
var x = new PollingManager(callback, TimeSpan.Zero);
_ = x.StartPoll();
Assert.True(isCalled);
x.StopPoll();
Expand All @@ -32,7 +32,7 @@ public async Task TestPollingManagerCallsUpdateEnvironmentOnEachRefresh()
calledCount += 1;
return Task.CompletedTask;
};
var x = new PollingManager(callback, 1);
var x = new PollingManager(callback, TimeSpan.FromSeconds(1));
_ = x.StartPoll();
await Task.Delay(3000);
Assert.True(calledCount >= 3); // check pollingmanager polls atlease 3 times.
Expand Down
247 changes: 118 additions & 129 deletions Flagsmith.FlagsmithClient/FlagsmithClient.cs

Large diffs are not rendered by default.

91 changes: 78 additions & 13 deletions Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,72 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using OfflineHandler;
using System.Net.Http;

namespace Flagsmith
{
public class FlagsmithConfiguration : IFlagsmithConfiguration
public class FlagsmithConfiguration
{
public FlagsmithConfiguration()
private static readonly Uri DefaultApiUri = new Uri("https://edge.api.flagsmith.com/api/v1/");
private TimeSpan _timeout;

/// <summary>
/// <para>Override the URL of the Flagsmith API to communicate with.</para>
/// <para>Deprecated since 7.1.0. Use <see cref="ApiUri"/> instead.</para>
/// </summary>
[Obsolete("Use ApiUri instead.")]
public string ApiUrl
{
ApiUrl = "https://edge.api.flagsmith.com/api/v1/";
EnvironmentKey = string.Empty;
EnvironmentRefreshIntervalSeconds = 60;
get => ApiUri.ToString();
set => ApiUri = value.EndsWith("/") ? new Uri(value) : new Uri($"{value}/");
}

/// <summary>
/// Override the URL of the Flagsmith API to communicate with.
/// Versioned base Flagsmith API URI to use for all requests. Defaults to
/// <c>https://edge.api.flagsmith.com/api/v1/</c>.
/// <example><code>new Uri("https://flagsmith.example.com/api/v1/")</code></example>
/// </summary>
public string ApiUrl { get; set; }
public Uri ApiUri { get; set; } = DefaultApiUri;

/// <summary>
/// The environment key obtained from Flagsmith interface.
/// </summary>
public string EnvironmentKey { get; set; }

/// <summary>
/// Enables local evaluation of flags.
/// </summary>
public bool EnableClientSideEvaluation { get; set; }
[Obsolete("Use EnableLocalEvaluation instead.")]
public bool EnableClientSideEvaluation
{
get => EnableLocalEvaluation;
set => EnableLocalEvaluation = value;
}

/// <summary>
/// Enables local evaluation of flags.
/// </summary>
public bool EnableLocalEvaluation { get; set; }

/// <summary>
/// <para>If using local evaluation, specify the interval period between refreshes of local environment data.</para>
/// <para>Deprecated since 7.1.0. Use <see cref="EnvironmentRefreshInterval"/> instead.</para>
/// </summary>
[Obsolete("Use EnvironmentRefreshInterval instead.")]
public int EnvironmentRefreshIntervalSeconds
{
get => EnvironmentRefreshInterval.Seconds;
set => EnvironmentRefreshInterval = TimeSpan.FromSeconds(value);
}
/// <summary>
/// If using local evaluation, specify the interval period between refreshes of local environment data.
/// </summary>
public int EnvironmentRefreshIntervalSeconds { get; set; }
public TimeSpan EnvironmentRefreshInterval { get; set; } = TimeSpan.FromSeconds(60);
/// <summary>
/// Callable which will be used in the case where flags cannot be retrieved from the API or a non existent feature is requested.
/// </summary>
public Func<string, Flag> DefaultFlagHandler { get; set; }
public Func<string, Flag>? DefaultFlagHandler { get; set; }

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.FlagsmithClient)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Test (Flagsmith.Client.Test, 6.0.x)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.Engine)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.Client.Test)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Test (Flagsmith.Client.Test, 7.0.x)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.EngineTest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 69 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Test (Flagsmith.Client.Test, 8.0.x)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Provide logger for logging polling info & errors which is only applicable when client side evalution is enabled and analytics errors.
/// </summary>
Expand All @@ -40,10 +75,24 @@
/// if enabled, sends additional requests to the Flagsmith API to power flag analytics charts.
/// </summary>
public bool EnableAnalytics { get; set; }

/// <summary>
/// Number of seconds to wait for a request to complete before terminating the request
/// </summary>
public Double? RequestTimeout { get; set; }
public Double? RequestTimeout
{
get => _timeout.Seconds;
set => _timeout = TimeSpan.FromSeconds(value ?? 100);
}

/// <summary>
/// Timeout duration to use for HTTP requests.
/// </summary>
public TimeSpan Timeout
{
get => _timeout;
set => _timeout = value;
}
/// <summary>
/// Total http retries for every failing request before throwing the final error.
/// </summary>
Expand All @@ -56,11 +105,27 @@
/// <summary>
/// If enabled, the SDK will cache the flags for the duration specified in the CacheConfig
/// </summary>
public CacheConfig CacheConfig { get; set; }
public CacheConfig CacheConfig { get; set; } = new CacheConfig(false);

/// <summary>
/// Indicates whether the client is in offline mode.
/// </summary>
public bool OfflineMode { get; set; }

/// <summary>
/// Handler for offline mode operations.
/// </summary>
public BaseOfflineHandler? OfflineHandler { get; set; }

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.FlagsmithClient)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Test (Flagsmith.Client.Test, 6.0.x)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.Engine)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.Client.Test)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Test (Flagsmith.Client.Test, 7.0.x)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Check Build and formatting (Flagsmith.EngineTest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 118 in Flagsmith.FlagsmithClient/FlagsmithConfiguration.cs

View workflow job for this annotation

GitHub Actions / Test (Flagsmith.Client.Test, 8.0.x)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

/// <summary>
/// Http client used for flagsmith-API requests.
/// </summary>
public HttpClient HttpClient { get; set; } = new HttpClient();

[Obsolete("This method will be removed in a future release.")]
public bool IsValid()
{
return !string.IsNullOrEmpty(ApiUrl) && !string.IsNullOrEmpty(EnvironmentKey);
return !string.IsNullOrEmpty(ApiUri.ToString()) && !string.IsNullOrEmpty(EnvironmentKey);
}
}
}
20 changes: 19 additions & 1 deletion Flagsmith.FlagsmithClient/IFlagsmithConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using OfflineHandler;
using System.Net.Http;

namespace Flagsmith
{
[Obsolete("Use FlagsmithConfiguration instead.")]
public interface IFlagsmithConfiguration
{
/// <summary>
Expand Down Expand Up @@ -61,6 +64,21 @@ public interface IFlagsmithConfiguration
/// </summary>
CacheConfig CacheConfig { get; set; }

/// <summary>
/// Indicates whether the client is in offline mode.
/// </summary>
bool OfflineMode { get; set; }

/// <summary>
/// Handler for offline mode operations.
/// </summary>
BaseOfflineHandler OfflineHandler { get; set; }

/// <summary>
/// HTTP client used for Flagsmith API requests.
/// </summary>
HttpClient HttpClient { get; set; }

bool IsValid();
}
}
}
41 changes: 25 additions & 16 deletions Flagsmith.FlagsmithClient/PollingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@ namespace Flagsmith
public class PollingManager : IPollingManager
{
private Timer _timer;
private readonly CancellationTokenSource _CancellationTokenSource = new CancellationTokenSource();
readonly Func<Task> _CallBack;
readonly int _Interval;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly Func<Task> _callback;
private readonly TimeSpan _interval;
/// <summary>
///
/// </summary>
/// <param name="callBack">Awaitable function that will be polled.</param>
/// <param name="intervalSeconds">Total delay in seconds between continous exection of callback.</param>
public PollingManager(Func<Task> callBack, int intervalSeconds = 10)
/// <param name="callback">Awaitable function that will be polled.</param>
/// <param name="intervalSeconds">Total delay in seconds between continuous execution of callback.</param>
[Obsolete("Use PollingManager(Func<Task>, TimeSpan) instead.")]
public PollingManager(Func<Task> callback, int intervalSeconds = 10)
{
_CallBack = callBack;
_Interval = intervalSeconds * 1000; //convert to milliseconds
this._callback = callback;
_interval = TimeSpan.FromSeconds(intervalSeconds);
}

/// <param name="callback">Awaitable function that will be polled.</param>
/// <param name="timespan">Polling interval.</param>
public PollingManager(Func<Task> callback, TimeSpan timespan)
{
this._callback = callback;
_interval = timespan;
}
/// <summary>
/// Start calling callback continuously after provided interval
Expand All @@ -29,24 +38,24 @@ public PollingManager(Func<Task> callBack, int intervalSeconds = 10)
public async Task StartPoll()
{
// Force a first call of the callback at least once and synchronously if it is awaited.
await _CallBack.Invoke();
_CancellationTokenSource.Token.ThrowIfCancellationRequested();
_timer = new Timer(async (object state) =>
await _callback.Invoke();
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
_timer = new Timer(state =>
{
if (_CancellationTokenSource.Token.IsCancellationRequested)
if (_cancellationTokenSource.Token.IsCancellationRequested)
{
_timer.Dispose();
return;
}
await _CallBack.Invoke();
}, null, _Interval, _Interval);
_callback.Invoke().GetAwaiter().GetResult();
}, null, _interval, _interval);
}
/// <summary>
/// Stop continously exectuing callback
/// Stop continuously executing callback
/// </summary>
public void StopPoll()
{
_CancellationTokenSource.Cancel();
_cancellationTokenSource.Cancel();
}
}
}
Loading