✨ Typing indicator, mark as read server-side
This commit is contained in:
		| @@ -1,16 +1,27 @@ | ||||
| using System.Net.WebSockets; | ||||
| using DysonNetwork.Sphere.Chat; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Caching.Memory; | ||||
| using Microsoft.Extensions.Internal; | ||||
| using SystemClock = NodaTime.SystemClock; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Connection.Handlers; | ||||
|  | ||||
| public class MessageReadHandler(AppDatabase db) : IWebSocketPacketHandler | ||||
| public class MessageReadHandler(AppDatabase db, IMemoryCache cache, ChatRoomService crs) : IWebSocketPacketHandler | ||||
| { | ||||
|     public string PacketType => "message.read"; | ||||
|     public string PacketType => "messages.read"; | ||||
|  | ||||
|     public async Task HandleAsync(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket) | ||||
|     public const string ChatMemberCacheKey = "ChatMember_{0}_{1}"; | ||||
|  | ||||
|     public async Task HandleAsync( | ||||
|         Account.Account currentUser, | ||||
|         string deviceId, | ||||
|         WebSocketPacket packet, | ||||
|         WebSocket socket, | ||||
|         WebSocketService srv | ||||
|     ) | ||||
|     { | ||||
|         var request = packet.GetData<Chat.ChatController.MarkMessageReadRequest>(); | ||||
|         var request = packet.GetData<ChatController.MarkMessageReadRequest>(); | ||||
|         if (request is null) | ||||
|         { | ||||
|             await socket.SendAsync( | ||||
| @@ -26,12 +37,24 @@ public class MessageReadHandler(AppDatabase db) : IWebSocketPacketHandler | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         var existingStatus = await db.ChatStatuses | ||||
|             .FirstOrDefaultAsync(x => x.MessageId == request.MessageId && x.Sender.AccountId == currentUser.Id); | ||||
|         var sender = await db.ChatMembers | ||||
|             .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == request.ChatRoomId) | ||||
|             .FirstOrDefaultAsync(); | ||||
|              | ||||
|         ChatMember? sender = null; | ||||
|         var cacheKey = string.Format(ChatMemberCacheKey, currentUser.Id, request.ChatRoomId); | ||||
|         if (cache.TryGetValue(cacheKey, out ChatMember? cachedMember)) | ||||
|             sender = cachedMember; | ||||
|         else | ||||
|         { | ||||
|             sender = await db.ChatMembers | ||||
|                 .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == request.ChatRoomId) | ||||
|                 .FirstOrDefaultAsync(); | ||||
|  | ||||
|             if (sender != null) | ||||
|             { | ||||
|                 var cacheOptions = new MemoryCacheEntryOptions() | ||||
|                     .SetAbsoluteExpiration(TimeSpan.FromMinutes(5)); | ||||
|                 cache.Set(cacheKey, sender, cacheOptions); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (sender is null) | ||||
|         { | ||||
|             await socket.SendAsync( | ||||
| @@ -47,16 +70,25 @@ public class MessageReadHandler(AppDatabase db) : IWebSocketPacketHandler | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (existingStatus == null) | ||||
|         db.ChatStatuses.Add(new MessageStatus | ||||
|         { | ||||
|             existingStatus = new MessageStatus | ||||
|             { | ||||
|                 MessageId = request.MessageId, | ||||
|                 SenderId = sender.Id, | ||||
|             }; | ||||
|             db.ChatStatuses.Add(existingStatus); | ||||
|         } | ||||
|             MessageId = request.MessageId, | ||||
|             SenderId = sender.Id, | ||||
|             ReadAt = SystemClock.Instance.GetCurrentInstant(), | ||||
|         }); | ||||
|  | ||||
|         await db.SaveChangesAsync(); | ||||
|         try | ||||
|         { | ||||
|             await db.SaveChangesAsync(); | ||||
|              | ||||
|             // Broadcast read statuses | ||||
|             var otherMembers = (await crs.ListRoomMembers(request.ChatRoomId)).Select(m => m.AccountId).ToList(); | ||||
|             foreach (var member in otherMembers) | ||||
|                 srv.SendPacketToAccount(member, packet); | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             // ignored | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,74 @@ | ||||
| using System.Net.WebSockets; | ||||
| using DysonNetwork.Sphere.Chat; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Caching.Memory; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Connection.Handlers; | ||||
|  | ||||
| public class MessageTypingHandler(AppDatabase db, ChatRoomService crs, IMemoryCache cache) : IWebSocketPacketHandler | ||||
| { | ||||
|     public string PacketType => "messages.typing"; | ||||
|  | ||||
|     public async Task HandleAsync( | ||||
|         Account.Account currentUser, | ||||
|         string deviceId, | ||||
|         WebSocketPacket packet, | ||||
|         WebSocket socket, | ||||
|         WebSocketService srv | ||||
|     ) | ||||
|     { | ||||
|         var request = packet.GetData<ChatController.TypingMessageRequest>(); | ||||
|         if (request is null) | ||||
|         { | ||||
|             await socket.SendAsync( | ||||
|                 new ArraySegment<byte>(new WebSocketPacket | ||||
|                 { | ||||
|                     Type = WebSocketPacketType.Error, | ||||
|                     ErrorMessage = "Mark message as read requires you provide the ChatRoomId" | ||||
|                 }.ToBytes()), | ||||
|                 WebSocketMessageType.Binary, | ||||
|                 true, | ||||
|                 CancellationToken.None | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         ChatMember? sender = null; | ||||
|         var cacheKey = string.Format(MessageReadHandler.ChatMemberCacheKey, currentUser.Id, request.ChatRoomId); | ||||
|         if (cache.TryGetValue(cacheKey, out ChatMember? cachedMember)) | ||||
|             sender = cachedMember; | ||||
|         else | ||||
|         { | ||||
|             sender = await db.ChatMembers | ||||
|                 .Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == request.ChatRoomId) | ||||
|                 .FirstOrDefaultAsync(); | ||||
|  | ||||
|             if (sender != null) | ||||
|             { | ||||
|                 var cacheOptions = new MemoryCacheEntryOptions() | ||||
|                     .SetAbsoluteExpiration(TimeSpan.FromMinutes(5)); | ||||
|                 cache.Set(cacheKey, sender, cacheOptions); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (sender is null) | ||||
|         { | ||||
|             await socket.SendAsync( | ||||
|                 new ArraySegment<byte>(new WebSocketPacket | ||||
|                 { | ||||
|                     Type = WebSocketPacketType.Error, | ||||
|                     ErrorMessage = "User is not a member of the chat room." | ||||
|                 }.ToBytes()), | ||||
|                 WebSocketMessageType.Binary, | ||||
|                 true, | ||||
|                 CancellationToken.None | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Broadcast read statuses | ||||
|         var otherMembers = (await crs.ListRoomMembers(request.ChatRoomId)).Select(m => m.AccountId).ToList(); | ||||
|         foreach (var member in otherMembers) | ||||
|             srv.SendPacketToAccount(member, packet); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user