Messaging
Sending a Message (SignalR)
A message typed in the TUI travels through encryption, the SignalR hub,
ChatService validation, database storage, and fan-out to both SignalR and IRC
clients.
sequenceDiagram
participant UI as MainWindow (TUI)
participant AO as AppOrchestrator
participant EHC as EchoHubConnection
participant Hub as ChatHub
participant CS as ChatService
participant LE as LinkEmbedService
participant DB as SQLite
participant SRB as SignalRBroadcaster
participant IRCB as IrcBroadcaster
participant Clients as Other Clients
UI->>AO: OnMessageSubmitted(channel, text)
AO->>AO: IsCommand(text)? → No
AO->>EHC: SendMessageAsync(channel, text)
EHC->>EHC: Encrypt(text) → ciphertext
EHC->>Hub: InvokeAsync("SendMessage", channel, ciphertext)
Hub->>CS: SendMessageAsync(userId, username, channel, ciphertext)
CS->>CS: Decrypt(ciphertext) → plaintext
CS->>CS: Validate (length, newlines, channel exists)
CS->>DB: Check mute status
CS->>LE: TryGetEmbedsAsync(plaintext)
LE->>LE: Extract URLs, fetch OG tags
LE-->>CS: List<EmbedDto> (or null)
CS->>DB: INSERT Message (encrypted at rest)
CS->>CS: Re-encrypt plaintext for transport
CS->>CS: Build MessageDto with embeds
par Fan-out to all broadcasters
CS->>SRB: SendMessageToChannelAsync(channel, dto)
SRB->>Clients: HubContext.Group(channel).ReceiveMessage(dto)
and
CS->>IRCB: SendMessageToChannelAsync(channel, dto)
IRCB->>IRCB: Decrypt → format as PRIVMSG lines
IRCB->>Clients: Send to each IRC conn (skip sender)
end
Sending a Message (IRC)
Messages from IRC clients follow the same ChatService path but enter as
plaintext (no app-layer encryption).
sequenceDiagram
participant IRC as IRC Client
participant CH as IrcCommandHandler
participant CS as ChatService
participant DB as SQLite
participant SRB as SignalRBroadcaster
participant IRCB as IrcBroadcaster
IRC->>CH: PRIVMSG #channel :Hello world
CH->>CH: Parse target + content
CH->>CH: IrcToEchoHubChannel("#channel") → "channel"
CH->>CS: SendMessageAsync(userId, username, "channel", "Hello world")
CS->>CS: Decrypt("Hello world") → passthrough (no $ENC$ prefix)
CS->>CS: Validate, check mute, fetch embeds
CS->>DB: INSERT Message
CS->>CS: Encrypt plaintext for SignalR transport
par Fan-out
CS->>SRB: SendMessageToChannelAsync (encrypted for SignalR)
and
CS->>IRCB: SendMessageToChannelAsync (decrypt → PRIVMSG)
IRCB->>IRCB: Skip sender (IRC echo suppression)
end
Receiving a Message (TUI Client)
When a message arrives via SignalR, the client decrypts it, adds it to the chat view, and optionally plays a notification sound for @mentions.
sequenceDiagram
participant SRB as SignalRBroadcaster
participant EHC as EchoHubConnection
participant AO as AppOrchestrator
participant MM as ChatMessageManager
participant UI as MainWindow
SRB->>EHC: ReceiveMessage(MessageDto)
EHC->>EHC: Decrypt(message.Content)
EHC-->>AO: OnMessageReceived(decrypted dto)
AO->>AO: InvokeUI (thread-safe)
AO->>MM: AddMessage(message)
MM->>UI: Render in chat ListView
alt Message contains @username
AO->>AO: PlayAsync() notification sound
end
Command Execution
Slash commands (/status, /nick, /kick, etc.) are parsed client-side and
dispatched to appropriate handlers, which call REST APIs or SignalR methods.
sequenceDiagram
participant UI as MainWindow
participant AO as AppOrchestrator
participant CMD as CommandHandler
participant API as ApiClient
participant EHC as EchoHubConnection
participant Server as Server (API/Hub)
UI->>AO: OnMessageSubmitted(channel, "/kick baduser")
AO->>CMD: IsCommand("/kick baduser")? → true
AO->>CMD: HandleAsync("/kick baduser")
CMD->>CMD: Parse → command="kick", args="baduser"
alt API command (kick, ban, mute, nick, etc.)
CMD-->>AO: Fire OnKickRequested("baduser")
AO->>API: KickUserAsync("baduser")
API->>Server: POST /api/moderation/kick/baduser
else Hub command (status, join, leave, etc.)
CMD-->>AO: Fire OnSetStatus / OnJoinChannel / etc.
AO->>EHC: UpdateStatusAsync() / JoinChannelAsync() / etc.
EHC->>Server: SignalR Invoke
else Local command (theme, help, quit)
CMD-->>AO: Fire OnThemeChanged / etc.
AO->>UI: Apply locally (no server call)
end
AO->>UI: AddSystemMessage(result)
Available commands:
| Command | Type | Handler |
|---|---|---|
/status <status> [message] |
Hub | UpdateStatusAsync |
/nick <name> |
API | UpdateProfileAsync |
/color <hex> |
API | UpdateProfileAsync |
/join <channel> |
Hub | JoinChannelAsync |
/leave |
Hub | LeaveChannelAsync |
/topic <text> |
API | UpdateChannelTopicAsync |
/kick <user> |
API | POST /api/moderation/kick/{user} |
/ban <user> |
API | POST /api/moderation/ban/{user} |
/unban <user> |
API | POST /api/moderation/unban/{user} |
/mute <user> [mins] |
API | POST /api/moderation/mute/{user} |
/unmute <user> |
API | POST /api/moderation/unmute/{user} |
/role <user> <role> |
API | PUT /api/moderation/role/{user} |
/nuke |
API | DELETE /api/channels/{channel}/messages |
/send <file> |
API | POST /api/channels/{channel}/upload |
/profile [user] |
Local | Show profile dialog |
/avatar |
API | POST /api/users/avatar |
/theme <name> |
Local | ThemeManager.SetTheme() |
/servers |
API | GET /api/serverdir/servers |
/users |
Local | Show userlist |
/help |
Local | Show help text |
/quit |
Local | Exit application |