feat: integrate d3-org-chart for network visualization and add SignalR token notification service
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing SignalR connection and handling token-related notifications from BFF.
|
||||
/// This service connects to the BFF SignalR Hub and notifies the application when token refresh is needed.
|
||||
/// </summary>
|
||||
public class TokenNotificationService : IAsyncDisposable
|
||||
{
|
||||
private readonly ILocalStorageService _localStorage;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<TokenNotificationService> _logger;
|
||||
private HubConnection? _hubConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a token revoked notification is received from server
|
||||
/// </summary>
|
||||
public event Func<TokenRevokedEventArgs, Task>? OnTokenRevoked;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a force refresh token notification is received from server
|
||||
/// </summary>
|
||||
public event Func<ForceRefreshEventArgs, Task>? OnForceRefreshToken;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a broadcast message is received from server
|
||||
/// </summary>
|
||||
public event Func<BroadcastMessageEventArgs, Task>? OnBroadcastMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when connection state changes
|
||||
/// </summary>
|
||||
public event Action<HubConnectionState>? OnConnectionStateChanged;
|
||||
|
||||
public TokenNotificationService(
|
||||
ILocalStorageService localStorage,
|
||||
IConfiguration configuration,
|
||||
ILogger<TokenNotificationService> logger)
|
||||
{
|
||||
_localStorage = localStorage;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current connection state
|
||||
/// </summary>
|
||||
public HubConnectionState ConnectionState => _hubConnection?.State ?? HubConnectionState.Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// Connect to the SignalR Hub with the user's authentication token
|
||||
/// </summary>
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (_hubConnection?.State == HubConnectionState.Connected)
|
||||
{
|
||||
_logger.LogWarning("Already connected to SignalR Hub");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var token = await _localStorage.GetItemAsync<string>("authToken");
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
_logger.LogWarning("No auth token found, cannot connect to SignalR Hub");
|
||||
return;
|
||||
}
|
||||
|
||||
var gwUrl = _configuration["GwUrl"]?.TrimEnd('/') ?? "https://localhost:5002";
|
||||
var hubPath = _configuration["SignalR:HubPath"] ?? "/hubs/token-relay";
|
||||
var hubUrl = $"{gwUrl}{hubPath}";
|
||||
|
||||
_logger.LogInformation("Connecting to SignalR Hub at {HubUrl}", hubUrl);
|
||||
|
||||
_hubConnection = new HubConnectionBuilder()
|
||||
.WithUrl(hubUrl, options =>
|
||||
{
|
||||
options.AccessTokenProvider = () => Task.FromResult<string?>(token);
|
||||
})
|
||||
.WithAutomaticReconnect(new[]
|
||||
{
|
||||
TimeSpan.FromSeconds(0),
|
||||
TimeSpan.FromSeconds(2),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromSeconds(30)
|
||||
})
|
||||
.Build();
|
||||
|
||||
RegisterEventHandlers();
|
||||
|
||||
await _hubConnection.StartAsync();
|
||||
|
||||
_logger.LogInformation("Successfully connected to SignalR Hub");
|
||||
OnConnectionStateChanged?.Invoke(HubConnectionState.Connected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to connect to SignalR Hub");
|
||||
OnConnectionStateChanged?.Invoke(HubConnectionState.Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from the SignalR Hub
|
||||
/// </summary>
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_hubConnection != null)
|
||||
{
|
||||
await _hubConnection.StopAsync();
|
||||
_logger.LogInformation("Disconnected from SignalR Hub");
|
||||
OnConnectionStateChanged?.Invoke(HubConnectionState.Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterEventHandlers()
|
||||
{
|
||||
if (_hubConnection == null) return;
|
||||
|
||||
// Handle TokenRevoked event
|
||||
_hubConnection.On<TokenRevokedEventArgs>("TokenRevoked", async args =>
|
||||
{
|
||||
_logger.LogInformation("Received TokenRevoked notification. UserId: {UserId}, Reason: {Reason}",
|
||||
args.UserId, args.Reason);
|
||||
|
||||
if (OnTokenRevoked != null)
|
||||
{
|
||||
await OnTokenRevoked.Invoke(args);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle ForceRefreshToken event
|
||||
_hubConnection.On<ForceRefreshEventArgs>("ForceRefreshToken", async args =>
|
||||
{
|
||||
_logger.LogInformation("Received ForceRefreshToken notification. UserId: {UserId}", args.UserId);
|
||||
|
||||
if (OnForceRefreshToken != null)
|
||||
{
|
||||
await OnForceRefreshToken.Invoke(args);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle BroadcastMessage event
|
||||
_hubConnection.On<BroadcastMessageEventArgs>("BroadcastMessage", async args =>
|
||||
{
|
||||
_logger.LogInformation("Received BroadcastMessage: {Message}", args.Message);
|
||||
|
||||
if (OnBroadcastMessage != null)
|
||||
{
|
||||
await OnBroadcastMessage.Invoke(args);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle connection state changes
|
||||
_hubConnection.Reconnecting += error =>
|
||||
{
|
||||
_logger.LogWarning(error, "SignalR connection lost. Attempting to reconnect...");
|
||||
OnConnectionStateChanged?.Invoke(HubConnectionState.Reconnecting);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_hubConnection.Reconnected += connectionId =>
|
||||
{
|
||||
_logger.LogInformation("SignalR reconnected. ConnectionId: {ConnectionId}", connectionId);
|
||||
OnConnectionStateChanged?.Invoke(HubConnectionState.Connected);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_hubConnection.Closed += async error =>
|
||||
{
|
||||
_logger.LogWarning(error, "SignalR connection closed");
|
||||
OnConnectionStateChanged?.Invoke(HubConnectionState.Disconnected);
|
||||
await Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_hubConnection != null)
|
||||
{
|
||||
await _hubConnection.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for token revoked notification
|
||||
/// </summary>
|
||||
public class TokenRevokedEventArgs
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for force refresh token notification
|
||||
/// </summary>
|
||||
public class ForceRefreshEventArgs
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for broadcast message notification
|
||||
/// </summary>
|
||||
public class BroadcastMessageEventArgs
|
||||
{
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user