using DigitalData.Auth.Abstractions; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace DigitalData.Auth.Client; public class AuthClient : IAuthClient, IHostedService { private readonly HubConnection _connection; private readonly ILogger? _logger; private readonly ClientParams _params; public AuthClient(IOptions paramsOptions, HubConnectionBuilder connectionBuilder, ILogger? logger = null) { _params = paramsOptions.Value; var cnnBuilder = connectionBuilder.WithUrl(_params.Url); // set RetryPolicy if it exists if (_params.RetryPolicy is not null) cnnBuilder = cnnBuilder.WithAutomaticReconnect(_params.RetryPolicy); _connection = cnnBuilder.Build(); _connection.On(nameof(ReceivePublicKeyAsync), ReceivePublicKeyAsync); _connection.Reconnected += async cnnId => { _logger?.LogInformation("Auth-client reconnected. Number of connection attempts {nOfAttempts}.", _nOfAttempts); await GetAllPublicKeysAsync(); _nOfAttempts = 0; }; _connection.Reconnecting += (ex) => { logger?.LogError(ex, "Auth-client disconnected. Attempt to reconnect every {time} seconds.", _params.RetryDelay!.Value.TotalSeconds); _nOfAttempts += 1; return Task.CompletedTask; }; _logger = logger; } public bool IsConnected { get; private set; } = false; public IEnumerable PublicKeys => _params.PublicKeys; public async Task StartAsync(CancellationToken cancellationToken = default) { while(!await TryStartConnectionAsync(cancellationToken)) { if (_params.RetryDelay is not null) await Task.Delay(_params.RetryDelay.Value.Milliseconds, cancellationToken); else return; } IsConnected = true; await GetAllPublicKeysAsync(); } private int _nOfAttempts = 0; private async Task TryStartConnectionAsync(CancellationToken cancellationToken = default) { try { _nOfAttempts += 1; await _connection.StartAsync(cancellationToken); _logger?.LogInformation("Auth-client connection successful. Number of connection attempts {nOfAttempts}.", _nOfAttempts); _nOfAttempts = 0; return true; } catch(HttpRequestException ex) { if(_nOfAttempts < 2) { if (_params.RetryDelay is null) _logger?.LogError(ex, "Auth-client connection failed. {message}", ex.Message); else _logger?.LogError(ex, "Auth-client connection failed and will be retried every {time} seconds. The status of being successful can be followed from the information logs.\n{message}", _params.RetryDelay.Value.TotalSeconds, ex.Message); } return false; } } public async Task StopAsync(CancellationToken cancellationToken) { await _connection.StopAsync(cancellationToken); IsConnected = false; } public Task ReceivePublicKeyAsync(string issuer, string audience, string message) => Task.Run(() => _params.TriggerOnPublicReceivedEvent(this, issuer, audience, message, _logger)); public Task SendPublicKeyAsync(string issuer, string audience, string message) => _connection.InvokeAsync(nameof(SendPublicKeyAsync), issuer, audience, message); public Task GetPublicKeyAsync(string issuer, string audience) => _connection.InvokeAsync(nameof(GetPublicKeyAsync), issuer, audience); public async Task GetAllPublicKeysAsync() { foreach (var publicKey in PublicKeys) await GetPublicKeyAsync(publicKey.Issuer, publicKey.Audience); } }