Files
EchoHub/flows/connection.html
T
2026-02-24 21:51:45 +00:00

349 lines
12 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Connection | EchoHub Documentation </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Connection | EchoHub Documentation ">
<link rel="icon" href="../images/hue_icon.svg">
<link rel="stylesheet" href="../public/docfx.min.css">
<link rel="stylesheet" href="../public/main.css">
<meta name="docfx:navrel" content="../toc.html">
<meta name="docfx:tocrel" content="../articles/toc.html">
<meta name="docfx:rel" content="../">
<meta name="docfx:docurl" content="https://github.com/HueByte/EchoHub/blob/master/docs/#L1">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../index.html">
<img id="logo" class="svg" src="../images/hue_icon.svg" alt="EchoHub">
EchoHub
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="">
<h1 id="connection">Connection</h1>
<h2 id="signalr-client-connection">SignalR Client Connection</h2>
<p>After authentication, the TUI client establishes a SignalR WebSocket, registers
event handlers, joins the default channel, and loads history.</p>
<pre><code class="lang-mermaid">sequenceDiagram
participant CM as ConnectionManager
participant EHC as EchoHubConnection
participant Hub as ChatHub
participant CS as ChatService
participant PT as PresenceTracker
participant DB as SQLite
CM-&gt;&gt;CM: Fetch encryption key (GET /api/server/encryption-key)
CM-&gt;&gt;EHC: new EchoHubConnection(apiClient, encryption)
EHC-&gt;&gt;EHC: Build HubConnection (URL + JWT token provider + auto-reconnect)
EHC-&gt;&gt;EHC: RegisterHandlers() — wire ReceiveMessage, UserJoined, etc.
EHC-&gt;&gt;Hub: ConnectAsync() → WebSocket handshake
Hub-&gt;&gt;CS: UserConnectedAsync(connectionId, userId, username)
CS-&gt;&gt;PT: UserConnected(connectionId, userId, username)
CS-&gt;&gt;DB: Update user: Status=Online, LastSeenAt=now
CM-&gt;&gt;CM: Fetch channel list (GET /api/channels)
CM-&gt;&gt;EHC: JoinChannelAsync(&quot;general&quot;)
EHC-&gt;&gt;Hub: InvokeAsync(&quot;JoinChannel&quot;, &quot;general&quot;)
Hub-&gt;&gt;CS: JoinChannelAsync(connectionId, userId, username, &quot;general&quot;)
CS-&gt;&gt;DB: EnsureChannelMembership
CS-&gt;&gt;PT: JoinChannel(username, &quot;general&quot;)
CS-&gt;&gt;CS: BroadcastToAllAsync → UserJoined
CS-&gt;&gt;DB: Fetch message history
Hub--&gt;&gt;EHC: List&lt;MessageDto&gt; (encrypted)
EHC--&gt;&gt;CM: Decrypted history
</code></pre>
<p><strong>Code references:</strong></p>
<table>
<thead>
<tr>
<th>Step</th>
<th>File</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr>
<td>Connection orchestration</td>
<td><code>src/EchoHub.Client/Services/ConnectionManager.cs</code></td>
<td>Lines 58-140 (<code>ConnectAsync</code>)</td>
</tr>
<tr>
<td>EchoHubConnection setup</td>
<td><code>src/EchoHub.Client/Services/EchoHubConnection.cs</code></td>
<td>Lines 29-62 (constructor)</td>
</tr>
<tr>
<td>Handler registration</td>
<td><code>src/EchoHub.Client/Services/EchoHubConnection.cs</code></td>
<td>Lines 64-122 (<code>RegisterHandlers</code>)</td>
</tr>
<tr>
<td>Hub OnConnected</td>
<td><code>src/EchoHub.Server/Hubs/ChatHub.cs</code></td>
<td>Lines 31-43</td>
</tr>
<tr>
<td>ChatService connected</td>
<td><code>src/EchoHub.Server/Services/ChatService.cs</code></td>
<td>Lines 41-57</td>
</tr>
<tr>
<td>PresenceTracker connect</td>
<td><code>src/EchoHub.Server/Services/PresenceTracker.cs</code></td>
<td>Lines 13-29</td>
</tr>
<tr>
<td>Join channel (hub)</td>
<td><code>src/EchoHub.Server/Hubs/ChatHub.cs</code></td>
<td>Lines 59-81</td>
</tr>
<tr>
<td>Join channel (service)</td>
<td><code>src/EchoHub.Server/Services/ChatService.cs</code></td>
<td>Lines 96-135</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="irc-client-connection">IRC Client Connection</h2>
<p>IRC clients connect via TCP, authenticate with PASS/NICK/USER or SASL PLAIN,
and auto-join channels. New usernames are auto-registered.</p>
<pre><code class="lang-mermaid">sequenceDiagram
participant IRC as IRC Client
participant GW as IrcGatewayService
participant CH as IrcCommandHandler
participant US as UserService
participant CS as ChatService
participant PT as PresenceTracker
IRC-&gt;&gt;GW: TCP connect (:6667 or :6697 TLS)
GW-&gt;&gt;GW: Accept + create IrcClientConnection
GW-&gt;&gt;CH: new IrcCommandHandler(connection, services)
GW-&gt;&gt;CH: RunAsync() — start read loop
alt SASL PLAIN
IRC-&gt;&gt;CH: CAP REQ :sasl
CH--&gt;&gt;IRC: CAP ACK :sasl
IRC-&gt;&gt;CH: AUTHENTICATE PLAIN
CH--&gt;&gt;IRC: AUTHENTICATE +
IRC-&gt;&gt;CH: AUTHENTICATE &lt;base64(\0user\0pass)&gt;
CH-&gt;&gt;US: AuthenticateUserAsync(user, pass)
alt Auth fails → auto-register
CH-&gt;&gt;US: RegisterUserAsync(user, pass)
end
CH--&gt;&gt;IRC: 903 :SASL authentication successful
else PASS/NICK/USER
IRC-&gt;&gt;CH: PASS &lt;password&gt;
IRC-&gt;&gt;CH: NICK &lt;nickname&gt;
IRC-&gt;&gt;CH: USER &lt;username&gt; 0 * :&lt;realname&gt;
CH-&gt;&gt;US: AuthenticateUserAsync(nick, pass)
alt Auth fails → auto-register
CH-&gt;&gt;US: RegisterUserAsync(nick, pass)
end
end
CH-&gt;&gt;CS: UserConnectedAsync(irc-{guid}, userId, username)
CS-&gt;&gt;PT: UserConnected(irc-{guid}, userId, username)
CH--&gt;&gt;IRC: 001-004 RPL_WELCOME burst + MOTD
Note over IRC,CH: Client is now ready for JOIN/PART/PRIVMSG
</code></pre>
<p><strong>Code references:</strong></p>
<table>
<thead>
<tr>
<th>Step</th>
<th>File</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr>
<td>TCP listener</td>
<td><code>src/EchoHub.Server.Irc/IrcGatewayService.cs</code></td>
<td>Lines 45-90 (<code>ExecuteAsync</code>)</td>
</tr>
<tr>
<td>Client handler</td>
<td><code>src/EchoHub.Server.Irc/IrcGatewayService.cs</code></td>
<td>Lines 92-154 (<code>HandleClientAsync</code>)</td>
</tr>
<tr>
<td>Command read loop</td>
<td><code>src/EchoHub.Server.Irc/IrcCommandHandler.cs</code></td>
<td>Lines 40-98 (<code>RunAsync</code>)</td>
</tr>
<tr>
<td>SASL auth</td>
<td><code>src/EchoHub.Server.Irc/IrcCommandHandler.cs</code></td>
<td>Lines 136-207 (<code>HandleAuthenticateAsync</code>)</td>
</tr>
<tr>
<td>PASS/NICK/USER</td>
<td><code>src/EchoHub.Server.Irc/IrcCommandHandler.cs</code></td>
<td>Lines 209-267</td>
</tr>
<tr>
<td>Registration completion</td>
<td><code>src/EchoHub.Server.Irc/IrcCommandHandler.cs</code></td>
<td>Lines 268-315 (<code>TryCompleteRegistrationAsync</code>)</td>
</tr>
<tr>
<td>Cleanup on disconnect</td>
<td><code>src/EchoHub.Server.Irc/IrcGatewayService.cs</code></td>
<td>Lines 136-153</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="user-disconnect--presence">User Disconnect &amp; Presence</h2>
<p>When a client disconnects, the presence tracker determines if the user has any
remaining connections. If not, status is set to Invisible and all channels are
notified.</p>
<pre><code class="lang-mermaid">sequenceDiagram
participant Client as Client
participant Entry as ChatHub / IrcGatewayService
participant CS as ChatService
participant PT as PresenceTracker
participant DB as SQLite
participant SRB as SignalRBroadcaster
Client-&gt;&gt;Entry: Disconnect / TCP close
Entry-&gt;&gt;CS: UserDisconnectedAsync(connectionId)
CS-&gt;&gt;PT: Get username + channels (before removal)
CS-&gt;&gt;PT: UserDisconnected(connectionId)
PT-&gt;&gt;PT: Remove connection from tracking
alt No remaining connections for user
CS-&gt;&gt;DB: Update user: Status=Invisible, LastSeenAt=now
loop For each channel user was in
CS-&gt;&gt;CS: BroadcastToAllAsync(SendUserStatusChangedAsync)
CS-&gt;&gt;SRB: Notify channel members
end
end
</code></pre>
<p><strong>Code references:</strong></p>
<table>
<thead>
<tr>
<th>Step</th>
<th>File</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr>
<td>SignalR disconnect</td>
<td><code>src/EchoHub.Server/Hubs/ChatHub.cs</code></td>
<td>Lines 45-57</td>
</tr>
<tr>
<td>IRC cleanup</td>
<td><code>src/EchoHub.Server.Irc/IrcGatewayService.cs</code></td>
<td>Lines 136-153</td>
</tr>
<tr>
<td>ChatService disconnect</td>
<td><code>src/EchoHub.Server/Services/ChatService.cs</code></td>
<td>Lines 59-94</td>
</tr>
<tr>
<td>Presence disconnect</td>
<td><code>src/EchoHub.Server/Services/PresenceTracker.cs</code></td>
<td>Lines 31-53</td>
</tr>
</tbody>
</table>
</article>
<div class="contribution d-print-none">
<a href="https://github.com/HueByte/EchoHub/blob/master/docs/#L1" class="edit-link">Edit this page</a>
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<div class='footer-custom'><div class='footer-inner'><span class='footer-brand'>EchoHub</span><span class='footer-sep'>&middot;</span><a href='https://github.com/HueByte/EchoHub'>GitHub</a><span class='footer-sep'>&middot;</span><a href='https://echohub.voidcube.cloud'>Website</a><span class='footer-sep'>&middot;</span><span class='footer-credit'>Built with <a href='https://dotnet.github.io/docfx'>DocFX</a></span></div></div>
</div>
</div>
</footer>
</body>
</html>