2025-08-19 03:08:30 -05:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2025-10-28 22:21:16 -05:00
|
|
|
using System.Linq;
|
2025-08-19 03:08:30 -05:00
|
|
|
using System.Threading.Tasks;
|
2025-12-23 21:12:06 -06:00
|
|
|
using LANCommander.SDK.Abstractions;
|
|
|
|
|
using LANCommander.SDK.Extensions;
|
|
|
|
|
using HubChatClient = LANCommander.SDK.Hubs.IChatClient;
|
|
|
|
|
using LANCommander.SDK.Hubs;
|
2025-08-19 03:08:30 -05:00
|
|
|
using LANCommander.SDK.Models;
|
2025-12-23 21:12:06 -06:00
|
|
|
using LANCommander.SDK.Services;
|
|
|
|
|
using Microsoft.AspNetCore.SignalR.Client;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2025-08-19 03:08:30 -05:00
|
|
|
|
|
|
|
|
namespace LANCommander.SDK.Services;
|
|
|
|
|
|
2026-05-09 19:42:41 -05:00
|
|
|
public class ChatClient : IChatClient, HubChatClient, IDisposable
|
2025-08-19 03:08:30 -05:00
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
private HubConnection? _connection;
|
|
|
|
|
private IChatHub? _hub;
|
|
|
|
|
private readonly ITokenProvider _tokenProvider;
|
|
|
|
|
private readonly IConnectionClient _connectionClient;
|
|
|
|
|
private readonly ILogger<ChatClient> _logger;
|
|
|
|
|
private readonly Dictionary<Guid, ChatThread> _threads = new();
|
2026-05-09 19:42:41 -05:00
|
|
|
private readonly EventHandler _onConnect;
|
|
|
|
|
private readonly EventHandler _onDisconnect;
|
2025-10-25 22:00:50 -05:00
|
|
|
|
2025-12-23 21:12:06 -06:00
|
|
|
public ChatClient(
|
|
|
|
|
ITokenProvider tokenProvider,
|
|
|
|
|
IConnectionClient connectionClient,
|
|
|
|
|
ILogger<ChatClient> logger)
|
|
|
|
|
{
|
|
|
|
|
_tokenProvider = tokenProvider;
|
|
|
|
|
_connectionClient = connectionClient;
|
|
|
|
|
_logger = logger;
|
2025-10-28 22:21:16 -05:00
|
|
|
|
2025-12-23 21:12:06 -06:00
|
|
|
// Subscribe to connection events to manage chat hub connection
|
2026-05-09 19:42:41 -05:00
|
|
|
_onConnect = async (sender, e) => await EnsureConnectedAsync();
|
|
|
|
|
_onDisconnect = async (sender, e) => await DisconnectAsync();
|
|
|
|
|
_connectionClient.OnConnect += _onConnect;
|
|
|
|
|
_connectionClient.OnDisconnect += _onDisconnect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_connectionClient.OnConnect -= _onConnect;
|
|
|
|
|
_connectionClient.OnDisconnect -= _onDisconnect;
|
2025-12-23 21:12:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task EnsureConnectedAsync()
|
2025-10-25 22:00:50 -05:00
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
if (_connection?.State == HubConnectionState.Connected)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var serverAddress = _connectionClient.GetServerAddress();
|
|
|
|
|
if (serverAddress == null)
|
|
|
|
|
return;
|
2025-10-28 22:21:16 -05:00
|
|
|
|
2025-12-23 21:12:06 -06:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_connection = new HubConnectionBuilder()
|
|
|
|
|
.WithUrl(serverAddress.Join("hubs/chat"), options =>
|
|
|
|
|
{
|
|
|
|
|
options.AccessTokenProvider = () => Task.FromResult(_tokenProvider.GetToken().AccessToken);
|
|
|
|
|
})
|
|
|
|
|
.WithAutomaticReconnect()
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
_hub = _connection.ServerProxy<IChatHub>();
|
|
|
|
|
_ = _connection.ClientRegistration<HubChatClient>(this);
|
|
|
|
|
|
|
|
|
|
await _connection.StartAsync();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Failed to connect to chat hub at {ServerAddress}", serverAddress);
|
|
|
|
|
}
|
2025-10-25 22:00:50 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 21:12:06 -06:00
|
|
|
private async Task DisconnectAsync()
|
|
|
|
|
{
|
|
|
|
|
if (_connection != null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _connection.StopAsync();
|
|
|
|
|
await _connection.DisposeAsync();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Failed to disconnect from chat hub");
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_connection = null;
|
|
|
|
|
_hub = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-25 22:00:50 -05:00
|
|
|
|
2025-12-23 21:12:06 -06:00
|
|
|
public async Task<ChatThread> GetThreadAsync(Guid threadId)
|
|
|
|
|
{
|
2025-12-31 20:01:37 -06:00
|
|
|
return _threads[threadId];
|
2025-12-23 21:12:06 -06:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 19:34:14 -05:00
|
|
|
public async Task<Guid> StartThreadAsync(IEnumerable<string> userIdentifiers)
|
2025-08-19 03:08:30 -05:00
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
|
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
|
|
|
|
|
|
|
|
|
var threadId = await _hub.StartThreadAsync(userIdentifiers.ToArray());
|
2025-08-20 20:53:48 -05:00
|
|
|
|
|
|
|
|
if (threadId != Guid.Empty)
|
2025-11-03 23:27:05 -06:00
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
_threads[threadId] = await _hub.GetThreadAsync(threadId);
|
2025-11-03 23:27:05 -06:00
|
|
|
}
|
2025-08-24 19:34:14 -05:00
|
|
|
|
|
|
|
|
return threadId;
|
2025-08-19 03:08:30 -05:00
|
|
|
}
|
|
|
|
|
|
2025-08-21 17:47:30 -05:00
|
|
|
public async Task AddedToThreadAsync(ChatThread thread)
|
|
|
|
|
{
|
|
|
|
|
_threads[thread.Id] = thread;
|
|
|
|
|
}
|
2025-12-31 20:01:37 -06:00
|
|
|
|
2025-08-21 17:47:30 -05:00
|
|
|
public async Task<IEnumerable<ChatThread>> GetThreadsAsync()
|
|
|
|
|
{
|
2025-10-28 22:21:16 -05:00
|
|
|
if (_threads.Count == 0)
|
|
|
|
|
return await LoadThreadsAsync();
|
|
|
|
|
|
|
|
|
|
return _threads.Values.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<IEnumerable<ChatThread>> LoadThreadsAsync()
|
|
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
|
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
|
|
|
|
|
|
|
|
|
var threads = await _hub.GetThreadsAsync();
|
2025-10-25 22:00:50 -05:00
|
|
|
|
2025-08-21 17:47:30 -05:00
|
|
|
_threads.Clear();
|
2025-10-25 22:00:50 -05:00
|
|
|
|
2025-08-21 17:47:30 -05:00
|
|
|
foreach (var thread in threads)
|
|
|
|
|
_threads[thread.Id] = thread;
|
|
|
|
|
|
|
|
|
|
return threads;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-11 01:10:29 -06:00
|
|
|
public async Task<IEnumerable<User>> GetUsersAsync()
|
|
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
|
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
|
|
|
|
|
|
|
|
|
return await _hub.GetUsersAsync();
|
2025-11-11 01:10:29 -06:00
|
|
|
}
|
2025-12-31 20:01:37 -06:00
|
|
|
|
2025-08-20 20:53:48 -05:00
|
|
|
public async Task ReceiveMessagesAsync(Guid threadId, IEnumerable<ChatMessage> messages)
|
2025-08-19 03:08:30 -05:00
|
|
|
{
|
2025-08-20 20:53:48 -05:00
|
|
|
if (_threads.TryGetValue(threadId, out var thread))
|
2025-10-28 22:21:16 -05:00
|
|
|
await thread.MessagesReceivedAsync(messages);
|
2025-08-19 03:08:30 -05:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 20:53:48 -05:00
|
|
|
public async Task ReceiveMessageAsync(Guid threadId, ChatMessage message)
|
2025-08-19 03:08:30 -05:00
|
|
|
{
|
2025-08-20 20:53:48 -05:00
|
|
|
if (_threads.TryGetValue(threadId, out var thread))
|
2025-10-28 22:21:16 -05:00
|
|
|
await thread.MessageReceivedAsync(message);
|
2025-08-20 20:53:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StartTypingAsync(Guid threadId, string userId)
|
|
|
|
|
{
|
|
|
|
|
if (_threads.TryGetValue(threadId, out var thread))
|
2025-10-28 22:21:16 -05:00
|
|
|
await thread.StartTypingAsync(userId);
|
2025-08-20 20:53:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StopTypingAsync(Guid threadId, string userId)
|
|
|
|
|
{
|
|
|
|
|
if (_threads.TryGetValue(threadId, out var thread))
|
2025-10-28 22:21:16 -05:00
|
|
|
await thread.StopTypingAsync(userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task SendMessageAsync(Guid threadId, string message)
|
|
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
|
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
|
|
|
|
|
2025-10-28 22:21:16 -05:00
|
|
|
if (_threads.TryGetValue(threadId, out var thread))
|
2025-12-23 21:12:06 -06:00
|
|
|
await _hub.SendMessageAsync(thread.Id, message);
|
2025-11-12 18:26:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task UpdatedReadStatus(Guid threadId)
|
|
|
|
|
{
|
2025-12-23 21:12:06 -06:00
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
|
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
|
|
|
|
|
2025-11-12 18:26:25 -06:00
|
|
|
if (_threads.TryGetValue(threadId, out var thread))
|
2025-12-23 21:12:06 -06:00
|
|
|
await _hub.UpdateReadStatusAsync(thread.Id);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 14:38:24 -06:00
|
|
|
public async Task<InfiniteResponse<ChatMessage>> GetMessagesAsync(Guid threadId, Guid? cursor, int count)
|
2025-12-23 21:12:06 -06:00
|
|
|
{
|
|
|
|
|
await EnsureConnectedAsync();
|
2026-01-03 23:22:25 -06:00
|
|
|
|
2025-12-23 21:12:06 -06:00
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
2026-01-03 23:22:25 -06:00
|
|
|
|
|
|
|
|
return await _hub.GetMessagesAsync(threadId, cursor, count);
|
2025-12-23 21:12:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<int> GetUnreadMessageCountAsync(Guid threadId)
|
|
|
|
|
{
|
|
|
|
|
await EnsureConnectedAsync();
|
|
|
|
|
|
|
|
|
|
if (_hub == null)
|
|
|
|
|
throw new InvalidOperationException("Chat hub is not connected");
|
|
|
|
|
|
|
|
|
|
return await _hub.GetUnreadMessageCountAsync(threadId);
|
2025-08-20 20:53:48 -05:00
|
|
|
}
|
2025-08-19 03:08:30 -05:00
|
|
|
}
|