скрытие статуса online через сокеты и поле isInvisible в профиле

This commit is contained in:
max
2026-04-17 20:47:28 +03:00
parent b346372555
commit 5245e8b7ae
16 changed files with 192 additions and 18 deletions
@@ -21,6 +21,7 @@ using Knot.Modules.Conversations.Application.DTOs;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Contracts.Conversations.Application.Abstractions;
using Knot.Contracts.Profiles.Domain;
namespace Knot.Modules.Conversations.Infrastructure.SignalR;
@@ -51,6 +52,7 @@ public sealed class ChatHub : Hub
private readonly ILogger<ChatHub> _logger;
private readonly IMemoryCache _cache;
private readonly IUserDisplayNameProvider _userProvider;
private readonly IProfileRepository _profileRepository;
public ChatHub(
ISender sender,
@@ -60,7 +62,8 @@ public sealed class ChatHub : Hub
IMessageRepository messageRepository,
ILogger<ChatHub> logger,
IMemoryCache cache,
IUserDisplayNameProvider userProvider)
IUserDisplayNameProvider userProvider,
IProfileRepository profileRepository)
{
_sender = sender;
_userContext = userContext;
@@ -70,6 +73,7 @@ public sealed class ChatHub : Hub
_logger = logger;
_cache = cache;
_userProvider = userProvider;
_profileRepository = profileRepository;
}
public override async Task OnConnectedAsync()
@@ -95,7 +99,11 @@ public sealed class ChatHub : Hub
userId, Context.ConnectionId, userChats.Count);
await Clients.Others.SendAsync("user_online", new { userId });
var isInvisible = await IsUserInvisibleAsync(_userContext.UserId, Context.ConnectionAborted);
if (!isInvisible)
{
await BroadcastPresenceToVisibleUsersAsync("user_online", new { userId }, _userContext.UserId);
}
}
await base.OnConnectedAsync();
}
@@ -111,7 +119,14 @@ public sealed class ChatHub : Hub
if (set.Count == 0)
{
_userConnections.TryRemove(userId, out _);
await Clients.Others.SendAsync("user_offline", new { userId, lastSeen = DateTime.UtcNow });
var isInvisible = await IsUserInvisibleAsync(_userContext.UserId, Context.ConnectionAborted);
if (!isInvisible)
{
await BroadcastPresenceToVisibleUsersAsync(
"user_offline",
new { userId, lastSeen = DateTime.UtcNow },
_userContext.UserId);
}
}
}
_cache.Set("Global_OnlineUsersCount", _userConnections.Count);
@@ -120,6 +135,52 @@ public sealed class ChatHub : Hub
await base.OnDisconnectedAsync(exception);
}
private async Task<bool> IsUserInvisibleAsync(Guid userId, CancellationToken ct)
{
var profile = await _profileRepository.GetAsync(userId, ct);
return profile?.IsInvisible ?? false;
}
private async Task BroadcastPresenceToVisibleUsersAsync(string eventName, object payload, Guid sourceUserId)
{
var recipients = _userConnections.Keys
.Where(id => id != sourceUserId.ToString())
.Select(id => Guid.TryParse(id, out var parsed) ? parsed : Guid.Empty)
.Where(id => id != Guid.Empty)
.Distinct()
.ToList();
if (recipients.Count == 0)
return;
var recipientProfiles = await _profileRepository.GetAsync(recipients, Context.ConnectionAborted);
var invisibleRecipientIds = recipientProfiles
.Where(p => p.IsInvisible)
.Select(p => p.UserId)
.ToHashSet();
foreach (var kvp in _userConnections)
{
if (!Guid.TryParse(kvp.Key, out var recipientId))
continue;
if (recipientId == sourceUserId)
continue;
if (invisibleRecipientIds.Contains(recipientId))
continue;
string[] connectionIds;
lock (kvp.Value)
{
connectionIds = kvp.Value.ToArray();
}
foreach (var connectionId in connectionIds)
{
await Clients.Client(connectionId).SendAsync(eventName, payload);
}
}
}
// ────────────────────────────────────────────────────────────────
// Chat methods
// ────────────────────────────────────────────────────────────────