using interfaces; using Microsoft.Extensions.Options; using options; namespace services; internal class SmappeeTokenService : IBearerTokenService { private readonly ChargerOptions options; private readonly IHttpClientFactory httpClientFactory; private TokenData? tokenData = null; public SmappeeTokenService( IOptions options, IHttpClientFactory httpClientFactory ) { this.options = options?.Value ?? throw new ArgumentNullException(nameof(options)); this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); } public async ValueTask GetToken(bool forceNewToken) { if ( tokenData == null || tokenData.Expiration < DateTime.UtcNow.AddSeconds(-10) || forceNewToken ) { tokenData = await RequestNewToken(); } return tokenData.Token; } private async Task RequestNewToken() { var httpClient = httpClientFactory.CreateClient("smappeeTokenService"); httpClient.BaseAddress = options.Url; var result = await httpClient.PostAsync( "/dev/v3/oauth2/token", new FormUrlEncodedContent( new Dictionary { ["grant_type"] = "password", ["client_id"] = options.ClientId, ["client_secret"] = options.ClientSecret, ["username"] = options.UserName, ["password"] = options.Password, } ) ); result.EnsureSuccessStatusCode(); var tokenResponse = await result.Content.ReadFromJsonAsync() ?? throw new InvalidOperationException("Token could not be fetched"); return new TokenData( tokenResponse.access_token, DateTime.UtcNow.AddSeconds(tokenResponse.expires_in) ); } record TokenResponse(string access_token, int expires_in); record TokenData(string Token, DateTime Expiration); }