From a9519a52cc89811b57c469ffce6196f4c63ce3c2 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:46:08 +0200 Subject: [PATCH] feat: add support for broadcasting and handling channel deletions across clients --- src/EchoHub.Client/AppOrchestrator.cs | 9 +++++++++ src/EchoHub.Client/Services/ConnectionManager.cs | 2 ++ src/EchoHub.Client/Services/EchoHubConnection.cs | 6 ++++++ src/EchoHub.Core/Contracts/IChatBroadcaster.cs | 1 + src/EchoHub.Core/Contracts/IChatService.cs | 1 + src/EchoHub.Core/Contracts/IEchoHubClient.cs | 1 + src/EchoHub.Server.Irc/IrcBroadcaster.cs | 8 ++++++++ src/EchoHub.Server/Controllers/ChannelsController.cs | 2 ++ src/EchoHub.Server/Services/ChatService.cs | 3 +++ src/EchoHub.Server/Services/SignalRBroadcaster.cs | 3 +++ src/EchoHub.Tests/Irc/TestHelpers.cs | 3 +++ 11 files changed, 39 insertions(+) diff --git a/src/EchoHub.Client/AppOrchestrator.cs b/src/EchoHub.Client/AppOrchestrator.cs index 8c73134..460a975 100644 --- a/src/EchoHub.Client/AppOrchestrator.cs +++ b/src/EchoHub.Client/AppOrchestrator.cs @@ -1071,6 +1071,15 @@ public sealed class AppOrchestrator : IDisposable _conn.MessageDeleted += (channelName, messageId) => InvokeUI(() => _messageManager.RemoveMessage(channelName, messageId)); + _conn.ChannelDeleted += channelName => + { + InvokeUI(() => + { + _conn.UntrackChannel(channelName); + _mainWindow.RemoveChannel(channelName); + }); + }; + _conn.ChannelNuked += channelName => { InvokeUI(() => diff --git a/src/EchoHub.Client/Services/ConnectionManager.cs b/src/EchoHub.Client/Services/ConnectionManager.cs index ac3301a..bedd98d 100644 --- a/src/EchoHub.Client/Services/ConnectionManager.cs +++ b/src/EchoHub.Client/Services/ConnectionManager.cs @@ -46,6 +46,7 @@ internal sealed class ConnectionManager : IAsyncDisposable public event Action? UserBanned; public event Action? ForceDisconnected; public event Action? MessageDeleted; + public event Action? ChannelDeleted; public event Action? ChannelNuked; public event Action? ChannelUpdated; public event Action? Error; @@ -300,6 +301,7 @@ internal sealed class ConnectionManager : IAsyncDisposable connection.OnUserBanned += (user, reason) => UserBanned?.Invoke(user, reason); connection.OnForceDisconnect += reason => ForceDisconnected?.Invoke(reason); connection.OnMessageDeleted += (ch, id) => MessageDeleted?.Invoke(ch, id); + connection.OnChannelDeleted += ch => ChannelDeleted?.Invoke(ch); connection.OnChannelNuked += ch => ChannelNuked?.Invoke(ch); connection.OnChannelUpdated += ch => { diff --git a/src/EchoHub.Client/Services/EchoHubConnection.cs b/src/EchoHub.Client/Services/EchoHubConnection.cs index 70a5e67..f90b8ba 100644 --- a/src/EchoHub.Client/Services/EchoHubConnection.cs +++ b/src/EchoHub.Client/Services/EchoHubConnection.cs @@ -58,6 +58,7 @@ public sealed class EchoHubConnection : IAsyncDisposable public event Action? OnUserKicked; public event Action? OnUserBanned; public event Action? OnMessageDeleted; + public event Action? OnChannelDeleted; public event Action? OnChannelNuked; public event Action? OnForceDisconnect; public event Action? OnError; @@ -144,6 +145,11 @@ public sealed class EchoHubConnection : IAsyncDisposable OnMessageDeleted?.Invoke(channelName, messageId); }); + _connection.On(nameof(Core.Contracts.IEchoHubClient.ChannelDeleted), channelName => + { + OnChannelDeleted?.Invoke(channelName); + }); + _connection.On(nameof(Core.Contracts.IEchoHubClient.ChannelNuked), channelName => { OnChannelNuked?.Invoke(channelName); diff --git a/src/EchoHub.Core/Contracts/IChatBroadcaster.cs b/src/EchoHub.Core/Contracts/IChatBroadcaster.cs index 4e7585d..884b587 100644 --- a/src/EchoHub.Core/Contracts/IChatBroadcaster.cs +++ b/src/EchoHub.Core/Contracts/IChatBroadcaster.cs @@ -12,6 +12,7 @@ public interface IChatBroadcaster Task SendUserKickedAsync(string channelName, string username, string? reason); Task SendUserBannedAsync(string username, string? reason); Task SendMessageDeletedAsync(string channelName, Guid messageId); + Task SendChannelDeletedAsync(string channelName); Task SendChannelNukedAsync(string channelName); Task SendErrorAsync(string connectionId, string message); Task ForceDisconnectUserAsync(List connectionIds, string reason); diff --git a/src/EchoHub.Core/Contracts/IChatService.cs b/src/EchoHub.Core/Contracts/IChatService.cs index 230b7b4..e3735af 100644 --- a/src/EchoHub.Core/Contracts/IChatService.cs +++ b/src/EchoHub.Core/Contracts/IChatService.cs @@ -24,6 +24,7 @@ public interface IChatService // Broadcasting (used by controllers and IRC gateway) Task BroadcastMessageAsync(string channelName, MessageDto message); Task BroadcastChannelUpdatedAsync(ChannelDto channel, string? channelName = null); + Task BroadcastChannelDeletedAsync(string channelName); // Query operations (used by IRC gateway for WHOIS) Task> GetChannelsForUserAsync(string username); diff --git a/src/EchoHub.Core/Contracts/IEchoHubClient.cs b/src/EchoHub.Core/Contracts/IEchoHubClient.cs index 87e6cf0..3a677ac 100644 --- a/src/EchoHub.Core/Contracts/IEchoHubClient.cs +++ b/src/EchoHub.Core/Contracts/IEchoHubClient.cs @@ -15,6 +15,7 @@ public interface IEchoHubClient Task UserKicked(string channelName, string username, string? reason); Task UserBanned(string username, string? reason); Task MessageDeleted(string channelName, Guid messageId); + Task ChannelDeleted(string channelName); Task ChannelNuked(string channelName); Task ForceDisconnect(string reason); Task Error(string message); diff --git a/src/EchoHub.Server.Irc/IrcBroadcaster.cs b/src/EchoHub.Server.Irc/IrcBroadcaster.cs index 1d8d496..074e8ba 100644 --- a/src/EchoHub.Server.Irc/IrcBroadcaster.cs +++ b/src/EchoHub.Server.Irc/IrcBroadcaster.cs @@ -101,6 +101,14 @@ public class IrcBroadcaster : IChatBroadcaster } } + public async Task SendChannelDeletedAsync(string channelName) + { + foreach (var conn in _gateway.GetConnectionsInChannel(channelName)) + { + await conn.SendAsync($":{_gateway.Options.ServerName} NOTICE {conn.Nickname ?? "*"} :Channel #{channelName} has been deleted"); + } + } + public async Task SendChannelNukedAsync(string channelName) { foreach (var conn in _gateway.GetConnectionsInChannel(channelName)) diff --git a/src/EchoHub.Server/Controllers/ChannelsController.cs b/src/EchoHub.Server/Controllers/ChannelsController.cs index 1889e61..a66f17b 100644 --- a/src/EchoHub.Server/Controllers/ChannelsController.cs +++ b/src/EchoHub.Server/Controllers/ChannelsController.cs @@ -162,6 +162,8 @@ public class ChannelsController : ControllerBase if (!result.IsSuccess) return MapChannelError(result); + await _chatService.BroadcastChannelDeletedAsync(channel.ToLowerInvariant().Trim()); + return NoContent(); } diff --git a/src/EchoHub.Server/Services/ChatService.cs b/src/EchoHub.Server/Services/ChatService.cs index 889b345..11004ef 100644 --- a/src/EchoHub.Server/Services/ChatService.cs +++ b/src/EchoHub.Server/Services/ChatService.cs @@ -320,6 +320,9 @@ public class ChatService : IChatService public Task BroadcastChannelUpdatedAsync(ChannelDto channel, string? channelName = null) => BroadcastToAllAsync(b => b.SendChannelUpdatedAsync(channel, channelName)); + public Task BroadcastChannelDeletedAsync(string channelName) + => BroadcastToAllAsync(b => b.SendChannelDeletedAsync(channelName)); + private async Task BroadcastToAllAsync(Func action) { foreach (var broadcaster in _broadcasters) diff --git a/src/EchoHub.Server/Services/SignalRBroadcaster.cs b/src/EchoHub.Server/Services/SignalRBroadcaster.cs index 4c8b660..7e4165d 100644 --- a/src/EchoHub.Server/Services/SignalRBroadcaster.cs +++ b/src/EchoHub.Server/Services/SignalRBroadcaster.cs @@ -63,6 +63,9 @@ public class SignalRBroadcaster : IChatBroadcaster public Task SendMessageDeletedAsync(string channelName, Guid messageId) => HubContext.Clients.Group(channelName).MessageDeleted(channelName, messageId); + public Task SendChannelDeletedAsync(string channelName) + => HubContext.Clients.All.ChannelDeleted(channelName); + public Task SendChannelNukedAsync(string channelName) => HubContext.Clients.Group(channelName).ChannelNuked(channelName); diff --git a/src/EchoHub.Tests/Irc/TestHelpers.cs b/src/EchoHub.Tests/Irc/TestHelpers.cs index ad61996..760c47e 100644 --- a/src/EchoHub.Tests/Irc/TestHelpers.cs +++ b/src/EchoHub.Tests/Irc/TestHelpers.cs @@ -212,6 +212,9 @@ internal sealed class FakeChatService : IChatService public Task BroadcastChannelUpdatedAsync(ChannelDto channel, string? channelName = null) => Task.CompletedTask; + public Task BroadcastChannelDeletedAsync(string channelName) => + Task.CompletedTask; + public Task> GetChannelsForUserAsync(string username) => Task.FromResult(ChannelsForUserToReturn); }