From e8c9a55fe9cc8930c589473e775889ce023a8163 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 17 Apr 2026 23:03:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B1=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0=D1=81?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=BE=D0=B1=D0=B5=D1=80=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=20IsUserInvisibleAsync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Infrastructure/SignalR/ChatHub.cs | 35 ++++++++++++++----- .../modules/chats/application/chatStore.ts | 6 +++- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/backend/src/Modules/Conversations/Infrastructure/SignalR/ChatHub.cs b/backend/src/Modules/Conversations/Infrastructure/SignalR/ChatHub.cs index 56f2132..d965f3a 100644 --- a/backend/src/Modules/Conversations/Infrastructure/SignalR/ChatHub.cs +++ b/backend/src/Modules/Conversations/Infrastructure/SignalR/ChatHub.cs @@ -102,7 +102,11 @@ public sealed class ChatHub : Hub var isInvisible = await IsUserInvisibleAsync(_userContext.UserId, Context.ConnectionAborted); if (!isInvisible) { - await BroadcastPresenceToVisibleUsersAsync("user_online", new { userId }, _userContext.UserId); + await BroadcastPresenceToVisibleUsersAsync( + "user_online", + new { userId }, + _userContext.UserId, + Context.ConnectionAborted); } } await base.OnConnectedAsync(); @@ -119,13 +123,22 @@ public sealed class ChatHub : Hub if (set.Count == 0) { _userConnections.TryRemove(userId, out _); - var isInvisible = await IsUserInvisibleAsync(_userContext.UserId, Context.ConnectionAborted); - if (!isInvisible) + try { - await BroadcastPresenceToVisibleUsersAsync( - "user_offline", - new { userId, lastSeen = DateTime.UtcNow }, - _userContext.UserId); + // Connection is tearing down: ConnectionAborted is already signaled and will cancel I/O. + var isInvisible = await IsUserInvisibleAsync(_userContext.UserId, CancellationToken.None); + if (!isInvisible) + { + await BroadcastPresenceToVisibleUsersAsync( + "user_offline", + new { userId, lastSeen = DateTime.UtcNow }, + _userContext.UserId, + CancellationToken.None); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Presence broadcast on disconnect failed for {UserId}", userId); } } } @@ -141,7 +154,11 @@ public sealed class ChatHub : Hub return profile?.IsInvisible ?? false; } - private async Task BroadcastPresenceToVisibleUsersAsync(string eventName, object payload, Guid sourceUserId) + private async Task BroadcastPresenceToVisibleUsersAsync( + string eventName, + object payload, + Guid sourceUserId, + CancellationToken cancellationToken) { var recipients = _userConnections.Keys .Where(id => id != sourceUserId.ToString()) @@ -153,7 +170,7 @@ public sealed class ChatHub : Hub if (recipients.Count == 0) return; - var recipientProfiles = await _profileRepository.GetAsync(recipients, Context.ConnectionAborted); + var recipientProfiles = await _profileRepository.GetAsync(recipients, cancellationToken); var invisibleRecipientIds = recipientProfiles .Where(p => p.IsInvisible) .Select(p => p.UserId) diff --git a/client-web/src/modules/chats/application/chatStore.ts b/client-web/src/modules/chats/application/chatStore.ts index 1350b20..e30858a 100644 --- a/client-web/src/modules/chats/application/chatStore.ts +++ b/client-web/src/modules/chats/application/chatStore.ts @@ -125,7 +125,11 @@ export const useChatStore = create((set, get) => ({ set({ isLoadingMessages: true }); const currentMessages = state.messages[chatId] || []; - const cursor = !reset && currentMessages.length > 0 ? currentMessages[0].sequenceId.toString() : undefined; + const firstSeq = currentMessages[0]?.sequenceId; + const cursor = + !reset && currentMessages.length > 0 && firstSeq != null + ? String(firstSeq) + : undefined; const fetched = await ChatApi.getMessages(chatId, cursor);