using FLSBLL.Instance; using GB5Shared.DTO.Framework.Login; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace FLSSL.Hubs; public class FlsMonitorHub( IFlsInstanceBLL _instanceBll, ILogger _logger ) : Hub { // ── Group naming: fls:instance:{flsInstanceId} ──────────────── private static string InstanceGroup(int flsInstanceId) => $"fls:instance:{flsInstanceId}"; // ── Connection lifecycle ────────────────────────────────────── public override async Task OnConnectedAsync() { _logger.LogInformation("FlsMonitorHub connected: {ConnectionId}", Context.ConnectionId); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { if (exception is not null) _logger.LogWarning(exception, "FlsMonitorHub disconnected with error: {ConnectionId}", Context.ConnectionId); await base.OnDisconnectedAsync(exception); } // ── Hub methods ─────────────────────────────────────────────── /// /// Incharge joins the monitor group for a specific instance to receive live updates. /// public async Task JoinInstanceMonitor(int flsInstanceId) { var login = GetLoginDTO(); try { await Groups.AddToGroupAsync(Context.ConnectionId, InstanceGroup(flsInstanceId)); _logger.LogInformation("User {UserId} joined FLS monitor for instance {InstanceId}", login.UserId, flsInstanceId); // Push current summary immediately on join var summaryJson = await _instanceBll.GetSummaryAsync(flsInstanceId, login, CancellationToken.None) .ConfigureAwait(false); await Clients.Caller.ReceiveError(string.Empty); // ping to confirm connection } catch (Exception ex) { _logger.LogError(ex, "JoinInstanceMonitor failed for instance {Id}", flsInstanceId); await Clients.Caller.ReceiveError("JOIN_FAILED"); } } /// /// Incharge leaves the monitor group for a specific instance. /// public async Task LeaveInstanceMonitor(int flsInstanceId) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, InstanceGroup(flsInstanceId)); _logger.LogInformation("Connection {ConnectionId} left FLS monitor for instance {InstanceId}", Context.ConnectionId, flsInstanceId); } // ── Helper ──────────────────────────────────────────────────── private LoginDTO GetLoginDTO() { var http = Context.GetHttpContext() ?? throw new InvalidOperationException("Hub invoked outside HTTP context."); var raw = http.Request.Headers["Login"].FirstOrDefault() ?? throw new InvalidOperationException("Login header missing."); return System.Text.Json.JsonSerializer.Deserialize(raw) ?? throw new InvalidOperationException("Login header is invalid JSON."); } }