using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Data; namespace API.SignalR.Presence { public interface IPresenceTracker { Task UserConnected(string username, string connectionId); Task UserDisconnected(string username, string connectionId); Task GetOnlineAdmins(); Task> GetConnectionsForUser(string username); } internal class ConnectionDetail { public List ConnectionIds { get; set; } public bool IsAdmin { get; set; } } // TODO: This can respond to UserRoleUpdate events to handle online users /// /// This is a singleton service for tracking what users have a SignalR connection and their difference connectionIds /// public class PresenceTracker : IPresenceTracker { private readonly IUnitOfWork _unitOfWork; private static readonly Dictionary OnlineUsers = new Dictionary(); public PresenceTracker(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task UserConnected(string username, string connectionId) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username); if (user == null) return; var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user); lock (OnlineUsers) { if (OnlineUsers.ContainsKey(username)) { OnlineUsers[username].ConnectionIds.Add(connectionId); } else { OnlineUsers.Add(username, new ConnectionDetail() { ConnectionIds = new List() {connectionId}, IsAdmin = isAdmin }); } } // Update the last active for the user user.LastActive = DateTime.Now; await _unitOfWork.CommitAsync(); } public Task UserDisconnected(string username, string connectionId) { lock (OnlineUsers) { if (!OnlineUsers.ContainsKey(username)) return Task.CompletedTask; OnlineUsers[username].ConnectionIds.Remove(connectionId); if (OnlineUsers[username].ConnectionIds.Count == 0) { OnlineUsers.Remove(username); } } return Task.CompletedTask; } public static Task GetOnlineUsers() { string[] onlineUsers; lock (OnlineUsers) { onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray(); } return Task.FromResult(onlineUsers); } public Task GetOnlineAdmins() { // TODO: This might end in stale data, we want to get the online users, query against DB to check if they are admins then return string[] onlineUsers; lock (OnlineUsers) { onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin).OrderBy(k => k.Key).Select(k => k.Key).ToArray(); } return Task.FromResult(onlineUsers); } public Task> GetConnectionsForUser(string username) { List connectionIds; lock (OnlineUsers) { connectionIds = OnlineUsers.GetValueOrDefault(username)?.ConnectionIds; } return Task.FromResult(connectionIds ?? new List()); } } }