Files
FrontOffice/src/FrontOffice.Main/Utilities/TokenNotificationService.cs
T
masoodafar-web ceaf6de226
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 3m43s
chore: enhance configuration for gateway URL handling
- Added GW_URL and GwUrl environment variables in frontoffice-deployment.yaml and Dockerfile for consistent gateway URL configuration.
- Updated AddGrpcServices method to resolve gateway URL from both environment variables, ensuring proper error handling for missing values.
- Modified TokenNotificationService to prioritize GW_URL over GwUrl for improved configuration flexibility.
2026-05-14 00:27:16 +03:30

217 lines
7.1 KiB
C#

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["GW_URL"] ?? _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; }
}