Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework PasswordlessClient #45

Merged
merged 8 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 34 additions & 38 deletions src/Passwordless/PasswordlessClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,43 @@ namespace Passwordless;
[DebuggerDisplay("{DebuggerToString(),nq}")]
public class PasswordlessClient : IPasswordlessClient, IDisposable
{
private readonly HttpClient _client;
private readonly bool _disposeClient;
private readonly HttpClient _http;
private readonly PasswordlessOptions _options;

/// <summary>
/// Initializes an instance of <see cref="PasswordlessClient" />.
/// </summary>
public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory)
private PasswordlessClient(HttpClient http, bool disposeClient, PasswordlessOptions options)
{
var client = factory.CreateClient();
client.BaseAddress = new Uri(options.ApiUrl);
client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret);
return new PasswordlessClient(client);
_http = new HttpClient(new PasswordlessHttpHandler(http, disposeClient), true)
{
BaseAddress = new Uri(options.ApiUrl),
DefaultRequestHeaders =
{
{"ApiSecret", options.ApiSecret}
}
};

_options = options;
}

/// <summary>
/// Initializes an instance of <see cref="PasswordlessClient" />.
/// </summary>
public PasswordlessClient(PasswordlessOptions passwordlessOptions)
public PasswordlessClient(HttpClient http, PasswordlessOptions options)
: this(http, false, options)
{
_client = new HttpClient
{
BaseAddress = new Uri(passwordlessOptions.ApiUrl),
};
_client.DefaultRequestHeaders.Add("ApiSecret", passwordlessOptions.ApiSecret);
_disposeClient = true;
}

/// <summary>
/// Initializes an instance of <see cref="PasswordlessClient" />.
/// </summary>
public PasswordlessClient(HttpClient client)
public PasswordlessClient(PasswordlessOptions options)
: this(new HttpClient(), true, options)
{
_client = client;
_disposeClient = false;
}

/// <inheritdoc />
public async Task SetAliasAsync(SetAliasRequest request, CancellationToken cancellationToken)
{
using var response = await _client.PostAsJsonAsync("alias",
using var response = await _http.PostAsJsonAsync("alias",
request,
PasswordlessSerializerContext.Default.SetAliasRequest,
cancellationToken);
Expand All @@ -61,7 +58,7 @@ public async Task SetAliasAsync(SetAliasRequest request, CancellationToken cance
/// <inheritdoc />
public async Task<RegisterTokenResponse> CreateRegisterTokenAsync(RegisterOptions registerOptions, CancellationToken cancellationToken = default)
{
using var response = await _client.PostAsJsonAsync("register/token",
using var response = await _http.PostAsJsonAsync("register/token",
registerOptions,
PasswordlessSerializerContext.Default.RegisterOptions,
cancellationToken);
Expand All @@ -83,7 +80,7 @@ public async Task<RegisterTokenResponse> CreateRegisterTokenAsync(RegisterOption

// We just want to return null if there is a problem.
request.SkipErrorHandling();
using var response = await _client.SendAsync(request, cancellationToken);
using var response = await _http.SendAsync(request, cancellationToken);

if (response.IsSuccessStatusCode)
{
Expand All @@ -100,7 +97,7 @@ public async Task<RegisterTokenResponse> CreateRegisterTokenAsync(RegisterOption
/// <inheritdoc />
public async Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default)
{
using var response = await _client.PostAsJsonAsync("users/delete",
using var response = await _http.PostAsJsonAsync("users/delete",
new DeleteUserRequest(userId),
PasswordlessSerializerContext.Default.DeleteUserRequest,
cancellationToken);
Expand All @@ -109,7 +106,7 @@ public async Task DeleteUserAsync(string userId, CancellationToken cancellationT
/// <inheritdoc />
public async Task<IReadOnlyList<PasswordlessUserSummary>> ListUsersAsync(CancellationToken cancellationToken = default)
{
var response = await _client.GetFromJsonAsync(
var response = await _http.GetFromJsonAsync(
"users/list",
PasswordlessSerializerContext.Default.ListResponsePasswordlessUserSummary,
cancellationToken);
Expand All @@ -120,7 +117,7 @@ public async Task<IReadOnlyList<PasswordlessUserSummary>> ListUsersAsync(Cancell
/// <inheritdoc />
public async Task<IReadOnlyList<AliasPointer>> ListAliasesAsync(string userId, CancellationToken cancellationToken = default)
{
var response = await _client.GetFromJsonAsync(
var response = await _http.GetFromJsonAsync(
$"alias/list?userid={userId}",
PasswordlessSerializerContext.Default.ListResponseAliasPointer,
cancellationToken);
Expand All @@ -131,7 +128,7 @@ public async Task<IReadOnlyList<AliasPointer>> ListAliasesAsync(string userId, C
/// <inheritdoc />
public async Task<IReadOnlyList<Credential>> ListCredentialsAsync(string userId, CancellationToken cancellationToken = default)
{
var response = await _client.GetFromJsonAsync(
var response = await _http.GetFromJsonAsync(
$"credentials/list?userid={userId}",
PasswordlessSerializerContext.Default.ListResponseCredential,
cancellationToken);
Expand All @@ -142,7 +139,7 @@ public async Task<IReadOnlyList<Credential>> ListCredentialsAsync(string userId,
/// <inheritdoc />
public async Task DeleteCredentialAsync(string id, CancellationToken cancellationToken = default)
{
using var response = await _client.PostAsJsonAsync("credentials/delete",
using var response = await _http.PostAsJsonAsync("credentials/delete",
new DeleteCredentialRequest(id),
PasswordlessSerializerContext.Default.DeleteCredentialRequest,
cancellationToken);
Expand All @@ -157,7 +154,7 @@ public async Task DeleteCredentialAsync(byte[] id, CancellationToken cancellatio
/// <inheritdoc />
public async Task<UsersCount> GetUsersCountAsync(CancellationToken cancellationToken = default)
{
return (await _client.GetFromJsonAsync(
return (await _http.GetFromJsonAsync(
"users/count",
PasswordlessSerializerContext.Default.UsersCount,
cancellationToken))!;
Expand All @@ -166,17 +163,18 @@ public async Task<UsersCount> GetUsersCountAsync(CancellationToken cancellationT
private string DebuggerToString()
{
var sb = new StringBuilder();

sb.Append("ApiUrl = ");
sb.Append(_client.BaseAddress);
if (_client.DefaultRequestHeaders.TryGetValues("ApiSecret", out var values))
sb.Append(_options.ApiUrl);

if (!string.IsNullOrEmpty(_options.ApiSecret))
{
var apiSecret = values.First();
if (apiSecret.Length > 5)
if (_options.ApiSecret.Length > 5)
{
sb.Append(' ');
sb.Append("ApiSecret = ");
sb.Append("***");
sb.Append(apiSecret.Substring(apiSecret.Length - 4));
sb.Append(_options.ApiSecret.Substring(_options.ApiSecret.Length - 4));
}
}
else
Expand All @@ -200,9 +198,7 @@ public void Dispose()
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing && _disposeClient)
{
_client.Dispose();
}
if (disposing)
_http.Dispose();
}
}
31 changes: 0 additions & 31 deletions src/Passwordless/PasswordlessDelegatingHandler.cs

This file was deleted.

9 changes: 0 additions & 9 deletions src/Passwordless/PasswordlessExtensions.cs

This file was deleted.

48 changes: 48 additions & 0 deletions src/Passwordless/PasswordlessHttpHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Net.Http.Json;
using Passwordless.Helpers;

namespace Passwordless;

internal class PasswordlessHttpHandler : HttpMessageHandler
{
// Externally provided HTTP Client
private readonly HttpClient _http;
private readonly bool _disposeClient;

public PasswordlessHttpHandler(HttpClient http, bool disposeClient = false)
{
_http = http;
_disposeClient = disposeClient;
}

protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

// On failed requests, check if responded with ProblemDetails and provide a nicer error if so
if (!request.ShouldSkipErrorHandling() &&
!response.IsSuccessStatusCode &&
string.Equals(response.Content.Headers.ContentType?.MediaType,
"application/problem+json",
StringComparison.OrdinalIgnoreCase))
{
var problemDetails = await response.Content.ReadFromJsonAsync(
PasswordlessSerializerContext.Default.PasswordlessProblemDetails,
cancellationToken
);

if (problemDetails is not null)
throw new PasswordlessApiException(problemDetails);
}

return response;
}

protected override void Dispose(bool disposing)
{
if (disposing && _disposeClient)
_http.Dispose();
}
}
Loading
Loading