#nullable enable using System; using System.Net.Http; using System.Net.Http.Json; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Wino.Core.Domain.Interfaces; using Wino.Mail.Api.Contracts.Auth; using Wino.Mail.Api.Contracts.Common; namespace Wino.Services; public sealed class WinoAccountApiClient : IWinoAccountApiClient, IDisposable { private readonly HttpClient _httpClient; private readonly bool _ownsHttpClient; public WinoAccountApiClient(HttpClient? httpClient = null) { if (httpClient != null) { _httpClient = httpClient; return; } var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = ValidateCertificate }; _httpClient = new HttpClient(handler) { BaseAddress = new Uri("https://api.winomail.app/") }; _ownsHttpClient = true; } public Task> RegisterAsync(string email, string password, CancellationToken cancellationToken = default) => SendAuthRequestAsync("api/v1/auth/register", new RegisterRequest(email, password), WinoAccountApiJsonContext.Default.RegisterRequest, cancellationToken); public Task> LoginAsync(string email, string password, CancellationToken cancellationToken = default) => SendAuthRequestAsync("api/v1/auth/login", new LoginRequest(email, password), WinoAccountApiJsonContext.Default.LoginRequest, cancellationToken); public Task> RefreshAsync(string refreshToken, CancellationToken cancellationToken = default) => SendAuthRequestAsync("api/v1/auth/refresh", new RefreshRequest(refreshToken), WinoAccountApiJsonContext.Default.RefreshRequest, cancellationToken); public async Task> LogoutAsync(string refreshToken, CancellationToken cancellationToken = default) { try { using var response = await _httpClient.PostAsJsonAsync( "api/v1/auth/logout", new LogoutRequest(refreshToken), WinoAccountApiJsonContext.Default.LogoutRequest, cancellationToken).ConfigureAwait(false); var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); var envelope = string.IsNullOrWhiteSpace(payload) ? null : JsonSerializer.Deserialize(payload, WinoAccountApiJsonContext.Default.ApiEnvelopeJsonElement); return envelope ?? ApiEnvelope.Failure($"HTTP {(int)response.StatusCode} {response.ReasonPhrase}".Trim()); } catch (Exception ex) { return ApiEnvelope.Failure(ex.Message); } } private async Task> SendAuthRequestAsync(string endpoint, TRequest request, JsonTypeInfo typeInfo, CancellationToken cancellationToken) { try { using var response = await _httpClient.PostAsJsonAsync( endpoint, request, typeInfo, cancellationToken).ConfigureAwait(false); var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); var envelope = string.IsNullOrWhiteSpace(payload) ? null : JsonSerializer.Deserialize(payload, WinoAccountApiJsonContext.Default.ApiEnvelopeAuthResultDto); return envelope ?? ApiEnvelope.Failure($"HTTP {(int)response.StatusCode} {response.ReasonPhrase}".Trim()); } catch (Exception ex) { return ApiEnvelope.Failure(ex.Message); } } private static bool ValidateCertificate(HttpRequestMessage requestMessage, X509Certificate2? certificate, X509Chain? chain, System.Net.Security.SslPolicyErrors sslPolicyErrors) { if (requestMessage.RequestUri?.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) == true) { return true; } return sslPolicyErrors == System.Net.Security.SslPolicyErrors.None; } public void Dispose() { if (_ownsHttpClient) { _httpClient.Dispose(); } } } [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)] [JsonSerializable(typeof(RegisterRequest))] [JsonSerializable(typeof(LoginRequest))] [JsonSerializable(typeof(RefreshRequest))] [JsonSerializable(typeof(LogoutRequest))] [JsonSerializable(typeof(ApiEnvelope))] [JsonSerializable(typeof(ApiEnvelope))] internal sealed partial class WinoAccountApiJsonContext : JsonSerializerContext;