ceaf6de226
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 3m43s
- 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.
217 lines
7.1 KiB
C#
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; }
|
|
}
|