This commit is contained in:
HueByte
2026-07-16 18:34:55 +00:00
parent c7f47c5104
commit dfbe5be317
34 changed files with 3098 additions and 86 deletions
@@ -148,6 +148,42 @@ Class ConfigManager <a class="header-action link-secondary" title="View source"
<h2 class="section" id="properties">Properties
</h2>
<a id="EchoHub_Client_Config_ConfigManager_ConfigDirectory_" data-uid="EchoHub.Client.Config.ConfigManager.ConfigDirectory*"></a>
<h3 id="EchoHub_Client_Config_ConfigManager_ConfigDirectory" data-uid="EchoHub.Client.Config.ConfigManager.ConfigDirectory">
ConfigDirectory
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Config/ConfigManager.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Directory holding the client config and local key material.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static string ConfigDirectory { get; }</code></pre>
</div>
<h4 class="section">Property Value</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
<h2 class="section" id="methods">Methods
</h2>
@@ -190,7 +190,8 @@ Class SavedServer <a class="header-action link-secondary" title="View source" h
</h3>
<div class="markdown level1 summary"><p>Cached room content keys for end-to-end encrypted channels on this server,
keyed by channel name (base64). Like RefreshToken, these live only on the
keyed by channel name and encrypted at rest (see RoomKeyProtector; legacy
entries were plain base64). Like RefreshToken, these live only on the
user's machine — the server never sees them.</p>
</div>
<div class="markdown level1 conceptual"></div>
@@ -0,0 +1,258 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Class ClipboardImage | EchoHub Documentation </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Class ClipboardImage | EchoHub Documentation ">
<meta name="description" content="Reads raw image data from the OS clipboard (e.g. an image copied from a browser, or a Win+Shift+S screenshot), which terminals cannot paste as text. Always returns PNG bytes: clipboard PNG data is passed through, clipboard bitmaps (CF_DIB) are re-encoded.">
<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="../toc.html">
<meta name="docfx:rel" content="../../">
<meta name="docfx:docurl" content="https://github.com/HueByte/EchoHub/new/master/apiSpec/new?filename=EchoHub_Client_Services_ClipboardImage.md&amp;value=---%0Auid%3A%20EchoHub.Client.Services.ClipboardImage%0Asummary%3A%20&#39;*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax&#39;%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">
<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="ManagedReference">
<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="EchoHub.Client.Services.ClipboardImage">
<h1 id="EchoHub_Client_Services_ClipboardImage" data-uid="EchoHub.Client.Services.ClipboardImage" class="text-break">
Class ClipboardImage <a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/ClipboardImage.cs"><i class="bi bi-code-slash"></i></a>
</h1>
<div class="facts text-secondary">
<dl><dt>Namespace</dt><dd><a class="xref" href="EchoHub.html">EchoHub</a>.<a class="xref" href="EchoHub.Client.html">Client</a>.<a class="xref" href="EchoHub.Client.Services.html">Services</a></dd></dl>
<dl><dt>Assembly</dt><dd>EchoHub.Client.dll</dd></dl>
</div>
<div class="markdown summary"><p>Reads raw image data from the OS clipboard (e.g. an image copied from a browser, or a
Win+Shift+S screenshot), which terminals cannot paste as text. Always returns PNG bytes:
clipboard PNG data is passed through, clipboard bitmaps (CF_DIB) are re-encoded.</p>
</div>
<div class="markdown conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static class ClipboardImage</code></pre>
</div>
<dl class="typelist inheritance">
<dt>Inheritance</dt>
<dd>
<div><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object">object</a></div>
<div><span class="xref">ClipboardImage</span></div>
</dd>
</dl>
<dl class="typelist inheritedMembers">
<dt>Inherited Members</dt>
<dd>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.gettype">object.GetType()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone">object.MemberwiseClone()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.tostring">object.ToString()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object, object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object, object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a>
</div>
</dd></dl>
<h2 class="section" id="methods">Methods
</h2>
<a id="EchoHub_Client_Services_ClipboardImage_DibToPng_" data-uid="EchoHub.Client.Services.ClipboardImage.DibToPng*"></a>
<h3 id="EchoHub_Client_Services_ClipboardImage_DibToPng_System_Byte___" data-uid="EchoHub.Client.Services.ClipboardImage.DibToPng(System.Byte[])">
DibToPng(byte[])
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/ClipboardImage.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Converts clipboard DIB bytes (a BITMAPINFOHEADER/V4/V5 + optional palette/masks + pixel
data, i.e. a .bmp file without its 14-byte file header) to PNG. Returns null when the
data is malformed or not decodable as a bitmap.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static byte[]? DibToPng(byte[] dib)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>dib</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]</dt>
<dd></dd>
</dl>
<h4 class="section">Returns</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]</dt>
<dd></dd>
</dl>
<a id="EchoHub_Client_Services_ClipboardImage_TryGetPng_" data-uid="EchoHub.Client.Services.ClipboardImage.TryGetPng*"></a>
<h3 id="EchoHub_Client_Services_ClipboardImage_TryGetPng_System_Byte____" data-uid="EchoHub.Client.Services.ClipboardImage.TryGetPng(System.Byte[]@)">
TryGetPng(out byte[])
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/ClipboardImage.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static bool TryGetPng(out byte[] png)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>png</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]</dt>
<dd></dd>
</dl>
<h4 class="section">Returns</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a></dt>
<dd></dd>
</dl>
</article>
<div class="contribution d-print-none">
<a href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/ClipboardImage.cs" class="edit-link">Edit this page</a>
</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></div></div>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,370 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Class RoomKeyProtector | EchoHub Documentation </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Class RoomKeyProtector | EchoHub Documentation ">
<meta name="description" content="Encrypts cached room content keys at rest so the client config never holds them as plain base64. Windows uses DPAPI (current-user scope, format prefix &amp;quot;dp1:&amp;quot;). On other platforms the keys are AES-GCM encrypted with a per-user master key file stored next to the config with 0600 permissions (prefix &amp;quot;k1:&amp;quot;) — without an OS keychain that is file-permission-level protection, not zero-knowledge: anyone who can read both the config and the key file can recover the room keys. Values with no recognized prefix are legacy plain-base64 keys from older clients; they load once and are re-encrypted. The room passphrase itself is never stored in any form.">
<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="../toc.html">
<meta name="docfx:rel" content="../../">
<meta name="docfx:docurl" content="https://github.com/HueByte/EchoHub/new/master/apiSpec/new?filename=EchoHub_Client_Services_RoomKeyProtector.md&amp;value=---%0Auid%3A%20EchoHub.Client.Services.RoomKeyProtector%0Asummary%3A%20&#39;*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax&#39;%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">
<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="ManagedReference">
<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="EchoHub.Client.Services.RoomKeyProtector">
<h1 id="EchoHub_Client_Services_RoomKeyProtector" data-uid="EchoHub.Client.Services.RoomKeyProtector" class="text-break">
Class RoomKeyProtector <a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs"><i class="bi bi-code-slash"></i></a>
</h1>
<div class="facts text-secondary">
<dl><dt>Namespace</dt><dd><a class="xref" href="EchoHub.html">EchoHub</a>.<a class="xref" href="EchoHub.Client.html">Client</a>.<a class="xref" href="EchoHub.Client.Services.html">Services</a></dd></dl>
<dl><dt>Assembly</dt><dd>EchoHub.Client.dll</dd></dl>
</div>
<div class="markdown summary"><p>Encrypts cached room content keys at rest so the client config never holds them as
plain base64. Windows uses DPAPI (current-user scope, format prefix &quot;dp1:&quot;). On other
platforms the keys are AES-GCM encrypted with a per-user master key file stored next
to the config with 0600 permissions (prefix &quot;k1:&quot;) — without an OS keychain that is
file-permission-level protection, not zero-knowledge: anyone who can read both the
config and the key file can recover the room keys. Values with no recognized prefix
are legacy plain-base64 keys from older clients; they load once and are re-encrypted.
The room passphrase itself is never stored in any form.</p>
</div>
<div class="markdown conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public sealed class RoomKeyProtector</code></pre>
</div>
<dl class="typelist inheritance">
<dt>Inheritance</dt>
<dd>
<div><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object">object</a></div>
<div><span class="xref">RoomKeyProtector</span></div>
</dd>
</dl>
<dl class="typelist inheritedMembers">
<dt>Inherited Members</dt>
<dd>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.gettype">object.GetType()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.tostring">object.ToString()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object, object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object, object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a>
</div>
</dd></dl>
<h2 class="section" id="constructors">Constructors
</h2>
<a id="EchoHub_Client_Services_RoomKeyProtector__ctor_" data-uid="EchoHub.Client.Services.RoomKeyProtector.#ctor*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyProtector__ctor_System_String_System_Nullable_System_Boolean__" data-uid="EchoHub.Client.Services.RoomKeyProtector.#ctor(System.String,System.Nullable{System.Boolean})">
RoomKeyProtector(string, bool?)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public RoomKeyProtector(string keyDirectory, bool? useDpapi = null)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>keyDirectory</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd><p>Directory holding the master key file (the client config dir).</p>
</dd>
<dt><code>useDpapi</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a>?</dt>
<dd><p>Overrides the platform default (DPAPI on Windows) — for tests.</p>
</dd>
</dl>
<h2 class="section" id="fields">Fields
</h2>
<h3 id="EchoHub_Client_Services_RoomKeyProtector_DpapiPrefix" data-uid="EchoHub.Client.Services.RoomKeyProtector.DpapiPrefix">
DpapiPrefix
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public const string DpapiPrefix = &quot;dp1:&quot;</code></pre>
</div>
<h4 class="section">Field Value</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
<h3 id="EchoHub_Client_Services_RoomKeyProtector_KeyFilePrefix" data-uid="EchoHub.Client.Services.RoomKeyProtector.KeyFilePrefix">
KeyFilePrefix
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public const string KeyFilePrefix = &quot;k1:&quot;</code></pre>
</div>
<h4 class="section">Field Value</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
<h2 class="section" id="methods">Methods
</h2>
<a id="EchoHub_Client_Services_RoomKeyProtector_Protect_" data-uid="EchoHub.Client.Services.RoomKeyProtector.Protect*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyProtector_Protect_System_Byte___" data-uid="EchoHub.Client.Services.RoomKeyProtector.Protect(System.Byte[])">
Protect(byte[])
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Encrypts a room key for storage in the config file.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public string Protect(byte[] roomKey)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>roomKey</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]</dt>
<dd></dd>
</dl>
<h4 class="section">Returns</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
<a id="EchoHub_Client_Services_RoomKeyProtector_TryUnprotect_" data-uid="EchoHub.Client.Services.RoomKeyProtector.TryUnprotect*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyProtector_TryUnprotect_System_String_System_Byte____System_Boolean__" data-uid="EchoHub.Client.Services.RoomKeyProtector.TryUnprotect(System.String,System.Byte[]@,System.Boolean@)">
TryUnprotect(string, out byte[], out bool)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Decrypts a stored value back into a room key. <code class="paramref">wasLegacy</code> is true when
the value was an unencrypted legacy entry that should be re-persisted via
<a class="xref" href="EchoHub.Client.Services.RoomKeyProtector.html#EchoHub_Client_Services_RoomKeyProtector_Protect_System_Byte___">Protect(byte[])</a>. Returns false for unreadable values (wrong user/machine, missing
or regenerated key file, malformed data) — the caller drops the entry and the user can
recover it by re-entering the passphrase.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public bool TryUnprotect(string stored, out byte[] roomKey, out bool wasLegacy)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>stored</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
<dt><code>roomKey</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]</dt>
<dd></dd>
<dt><code>wasLegacy</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a></dt>
<dd></dd>
</dl>
<h4 class="section">Returns</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a></dt>
<dd></dd>
</dl>
</article>
<div class="contribution d-print-none">
<a href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyProtector.cs" class="edit-link">Edit this page</a>
</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></div></div>
</div>
</div>
</footer>
</body>
</html>
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Class RoomKeyStore | EchoHub Documentation ">
<meta name="description" content="Holds room content keys for end-to-end encrypted channels: in-memory for the active session, persisted per-server in the client config (like saved sessions) so users don&#39;t retype the passphrase every launch. Keys never leave this machine.">
<meta name="description" content="Holds room content keys for end-to-end encrypted channels: in-memory for the active session, persisted per-server in the client config (like saved sessions) so users don&#39;t retype the passphrase every launch. Keys never leave this machine and are encrypted at rest by . Also tracks which channels are known to be end-to-end encrypted, so senders can refuse to emit plaintext into a room whose key isn&#39;t cached yet.">
<link rel="icon" href="../../images/hue_icon.svg">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
@@ -99,7 +99,10 @@ Class RoomKeyStore <a class="header-action link-secondary" title="View source"
<div class="markdown summary"><p>Holds room content keys for end-to-end encrypted channels: in-memory for the
active session, persisted per-server in the client config (like saved sessions)
so users don't retype the passphrase every launch. Keys never leave this machine.</p>
so users don't retype the passphrase every launch. Keys never leave this machine
and are encrypted at rest by <a class="xref" href="EchoHub.Client.Services.RoomKeyProtector.html">RoomKeyProtector</a>. Also tracks which
channels are known to be end-to-end encrypted, so senders can refuse to emit
plaintext into a room whose key isn't cached yet.</p>
</div>
<div class="markdown conceptual"></div>
@@ -178,6 +181,37 @@ so users don't retype the passphrase every launch. Keys never leave this machine
<a id="EchoHub_Client_Services_RoomKeyStore__ctor_" data-uid="EchoHub.Client.Services.RoomKeyStore.#ctor*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyStore__ctor_EchoHub_Client_Services_RoomKeyProtector_" data-uid="EchoHub.Client.Services.RoomKeyStore.#ctor(EchoHub.Client.Services.RoomKeyProtector)">
RoomKeyStore(RoomKeyProtector)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyStore.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public RoomKeyStore(RoomKeyProtector protector)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>protector</code> <a class="xref" href="EchoHub.Client.Services.RoomKeyProtector.html">RoomKeyProtector</a></dt>
<dd></dd>
</dl>
<h2 class="section" id="methods">Methods
</h2>
@@ -244,6 +278,42 @@ so users don't retype the passphrase every launch. Keys never leave this machine
<a id="EchoHub_Client_Services_RoomKeyStore_IsChannelEncrypted_" data-uid="EchoHub.Client.Services.RoomKeyStore.IsChannelEncrypted*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyStore_IsChannelEncrypted_System_String_" data-uid="EchoHub.Client.Services.RoomKeyStore.IsChannelEncrypted(System.String)">
IsChannelEncrypted(string)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyStore.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public bool IsChannelEncrypted(string channelName)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>channelName</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
<h4 class="section">Returns</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a></dt>
<dd></dd>
</dl>
<a id="EchoHub_Client_Services_RoomKeyStore_LoadForServer_" data-uid="EchoHub.Client.Services.RoomKeyStore.LoadForServer*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyStore_LoadForServer_System_String_" data-uid="EchoHub.Client.Services.RoomKeyStore.LoadForServer(System.String)">
@@ -276,6 +346,42 @@ so users don't retype the passphrase every launch. Keys never leave this machine
<a id="EchoHub_Client_Services_RoomKeyStore_MarkChannelEncrypted_" data-uid="EchoHub.Client.Services.RoomKeyStore.MarkChannelEncrypted*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyStore_MarkChannelEncrypted_System_String_System_Boolean_" data-uid="EchoHub.Client.Services.RoomKeyStore.MarkChannelEncrypted(System.String,System.Boolean)">
MarkChannelEncrypted(string, bool)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyStore.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Records whether a channel is end-to-end encrypted (from channel listings, crypto
metadata, or join outcomes). Senders consult this to block plaintext into rooms
whose key isn't cached.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public void MarkChannelEncrypted(string channelName, bool isEncrypted)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>channelName</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
<dt><code>isEncrypted</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a></dt>
<dd></dd>
</dl>
<a id="EchoHub_Client_Services_RoomKeyStore_RemoveKey_" data-uid="EchoHub.Client.Services.RoomKeyStore.RemoveKey*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyStore_RemoveKey_System_String_" data-uid="EchoHub.Client.Services.RoomKeyStore.RemoveKey(System.String)">
@@ -379,6 +485,50 @@ so users don't retype the passphrase every launch. Keys never leave this machine
<a id="EchoHub_Client_Services_RoomKeyStore_TryStoreFromEnvelope_" data-uid="EchoHub.Client.Services.RoomKeyStore.TryStoreFromEnvelope*"></a>
<h3 id="EchoHub_Client_Services_RoomKeyStore_TryStoreFromEnvelope_System_String_System_String_System_Byte___" data-uid="EchoHub.Client.Services.RoomKeyStore.TryStoreFromEnvelope(System.String,System.String,System.Byte[])">
TryStoreFromEnvelope(string, string, byte[])
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/RoomKeyStore.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Unwraps a fresh key envelope and caches the key, overwriting any stale cached key
(e.g. the channel was deleted and recreated under the same name, so the old key
would encrypt messages nobody else can read). Returns false when the KEK doesn't
open the envelope — the cache is left untouched.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public bool TryStoreFromEnvelope(string channelName, string wrappedRoomKey, byte[] kek)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>channelName</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
<dt><code>wrappedRoomKey</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
<dt><code>kek</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]</dt>
<dd></dd>
</dl>
<h4 class="section">Returns</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.boolean">bool</a></dt>
<dd></dd>
</dl>
</article>
@@ -0,0 +1,273 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Class RoomLockedException | EchoHub Documentation </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Class RoomLockedException | EchoHub Documentation ">
<meta name="description" content="Thrown when sending into an end-to-end encrypted channel whose room key isn&#39;t cached: without the key the message would leave the client as plaintext, which must never happen.">
<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="../toc.html">
<meta name="docfx:rel" content="../../">
<meta name="docfx:docurl" content="https://github.com/HueByte/EchoHub/new/master/apiSpec/new?filename=EchoHub_Client_Services_RoomLockedException.md&amp;value=---%0Auid%3A%20EchoHub.Client.Services.RoomLockedException%0Asummary%3A%20&#39;*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax&#39;%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">
<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="ManagedReference">
<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="EchoHub.Client.Services.RoomLockedException">
<h1 id="EchoHub_Client_Services_RoomLockedException" data-uid="EchoHub.Client.Services.RoomLockedException" class="text-break">
Class RoomLockedException <a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/EchoHubConnection.cs"><i class="bi bi-code-slash"></i></a>
</h1>
<div class="facts text-secondary">
<dl><dt>Namespace</dt><dd><a class="xref" href="EchoHub.html">EchoHub</a>.<a class="xref" href="EchoHub.Client.html">Client</a>.<a class="xref" href="EchoHub.Client.Services.html">Services</a></dd></dl>
<dl><dt>Assembly</dt><dd>EchoHub.Client.dll</dd></dl>
</div>
<div class="markdown summary"><p>Thrown when sending into an end-to-end encrypted channel whose room key isn't cached:
without the key the message would leave the client as plaintext, which must never happen.</p>
</div>
<div class="markdown conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public sealed class RoomLockedException : Exception</code></pre>
</div>
<dl class="typelist inheritance">
<dt>Inheritance</dt>
<dd>
<div><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object">object</a></div>
<div><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception">Exception</a></div>
<div><span class="xref">RoomLockedException</span></div>
</dd>
</dl>
<dl class="typelist inheritedMembers">
<dt>Inherited Members</dt>
<dd>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.getbaseexception">Exception.GetBaseException()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.tostring">Exception.ToString()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.gettype">Exception.GetType()</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.targetsite">Exception.TargetSite</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.message">Exception.Message</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.data">Exception.Data</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.innerexception">Exception.InnerException</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.helplink">Exception.HelpLink</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.source">Exception.Source</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.hresult">Exception.HResult</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.exception.stacktrace">Exception.StackTrace</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object, object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object, object)</a>
</div>
<div>
<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a>
</div>
</dd></dl>
<h2 class="section" id="constructors">Constructors
</h2>
<a id="EchoHub_Client_Services_RoomLockedException__ctor_" data-uid="EchoHub.Client.Services.RoomLockedException.#ctor*"></a>
<h3 id="EchoHub_Client_Services_RoomLockedException__ctor_System_String_" data-uid="EchoHub.Client.Services.RoomLockedException.#ctor(System.String)">
RoomLockedException(string)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/EchoHubConnection.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public RoomLockedException(string channelName)</code></pre>
</div>
<h4 class="section">Parameters</h4>
<dl class="parameters">
<dt><code>channelName</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
<h2 class="section" id="properties">Properties
</h2>
<a id="EchoHub_Client_Services_RoomLockedException_ChannelName_" data-uid="EchoHub.Client.Services.RoomLockedException.ChannelName*"></a>
<h3 id="EchoHub_Client_Services_RoomLockedException_ChannelName" data-uid="EchoHub.Client.Services.RoomLockedException.ChannelName">
ChannelName
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/EchoHubConnection.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public string ChannelName { get; }</code></pre>
</div>
<h4 class="section">Property Value</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
<dd></dd>
</dl>
</article>
<div class="contribution d-print-none">
<a href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/Services/EchoHubConnection.cs" class="edit-link">Edit this page</a>
</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></div></div>
</div>
</div>
</footer>
</body>
</html>
+29 -1
View File
@@ -129,6 +129,13 @@ so messages are encrypted end-to-end between client and server.</p>
<dd><p>Reads file paths that live on the OS clipboard as a <em>file list</em> (e.g. after copying a file in
Explorer/Finder/Nautilus), which terminals do not paste as text. Lets Ctrl+V attach a copied
file directly instead of requiring the user to paste a raw path.</p>
</dd>
</dl>
<dl class="jumplist">
<dt><a class="xref" href="EchoHub.Client.Services.ClipboardImage.html">ClipboardImage</a></dt>
<dd><p>Reads raw image data from the OS clipboard (e.g. an image copied from a browser, or a
Win+Shift+S screenshot), which terminals cannot paste as text. Always returns PNG bytes:
clipboard PNG data is passed through, clipboard bitmaps (CF_DIB) are re-encoded.</p>
</dd>
</dl>
<dl class="jumplist">
@@ -168,13 +175,34 @@ For normal channels only <a class="xref" href="EchoHub.Client.Services.OutgoingA
<dt><a class="xref" href="EchoHub.Client.Services.PathSetup.html">PathSetup</a></dt>
<dd><p>Ensures the application's directory is on the system PATH so users
can run 'echohub' from any terminal session.</p>
</dd>
</dl>
<dl class="jumplist">
<dt><a class="xref" href="EchoHub.Client.Services.RoomKeyProtector.html">RoomKeyProtector</a></dt>
<dd><p>Encrypts cached room content keys at rest so the client config never holds them as
plain base64. Windows uses DPAPI (current-user scope, format prefix &quot;dp1:&quot;). On other
platforms the keys are AES-GCM encrypted with a per-user master key file stored next
to the config with 0600 permissions (prefix &quot;k1:&quot;) — without an OS keychain that is
file-permission-level protection, not zero-knowledge: anyone who can read both the
config and the key file can recover the room keys. Values with no recognized prefix
are legacy plain-base64 keys from older clients; they load once and are re-encrypted.
The room passphrase itself is never stored in any form.</p>
</dd>
</dl>
<dl class="jumplist">
<dt><a class="xref" href="EchoHub.Client.Services.RoomKeyStore.html">RoomKeyStore</a></dt>
<dd><p>Holds room content keys for end-to-end encrypted channels: in-memory for the
active session, persisted per-server in the client config (like saved sessions)
so users don't retype the passphrase every launch. Keys never leave this machine.</p>
so users don't retype the passphrase every launch. Keys never leave this machine
and are encrypted at rest by <a class="xref" href="EchoHub.Client.Services.RoomKeyProtector.html">RoomKeyProtector</a>. Also tracks which
channels are known to be end-to-end encrypted, so senders can refuse to emit
plaintext into a room whose key isn't cached yet.</p>
</dd>
</dl>
<dl class="jumplist">
<dt><a class="xref" href="EchoHub.Client.Services.RoomLockedException.html">RoomLockedException</a></dt>
<dd><p>Thrown when sending into an end-to-end encrypted channel whose room key isn't cached:
without the key the message would leave the client as plaintext, which must never happen.</p>
</dd>
</dl>
<dl class="jumplist">
@@ -459,8 +459,8 @@ Active channel gets a &gt; indicator, unread channels are bright with a count ba
<a id="EchoHub_Client_UI_ListSources_ChannelListSource_Update_" data-uid="EchoHub.Client.UI.ListSources.ChannelListSource.Update*"></a>
<h3 id="EchoHub_Client_UI_ListSources_ChannelListSource_Update_System_Collections_Generic_List_System_String__System_Collections_Generic_Dictionary_System_String_System_Int32__System_String_System_Collections_Generic_IReadOnlySet_System_String__System_Collections_Generic_IReadOnlySet_System_String__" data-uid="EchoHub.Client.UI.ListSources.ChannelListSource.Update(System.Collections.Generic.List{System.String},System.Collections.Generic.Dictionary{System.String,System.Int32},System.String,System.Collections.Generic.IReadOnlySet{System.String},System.Collections.Generic.IReadOnlySet{System.String})">
Update(List&lt;string&gt;, Dictionary&lt;string, int&gt;, string, IReadOnlySet&lt;string&gt;?, IReadOnlySet&lt;string&gt;?)
<h3 id="EchoHub_Client_UI_ListSources_ChannelListSource_Update_System_Collections_Generic_List_System_String__System_Collections_Generic_Dictionary_System_String_System_Int32__System_String_System_Collections_Generic_IReadOnlySet_System_String__System_Collections_Generic_IReadOnlySet_System_String__System_Collections_Generic_IReadOnlySet_System_String__" data-uid="EchoHub.Client.UI.ListSources.ChannelListSource.Update(System.Collections.Generic.List{System.String},System.Collections.Generic.Dictionary{System.String,System.Int32},System.String,System.Collections.Generic.IReadOnlySet{System.String},System.Collections.Generic.IReadOnlySet{System.String},System.Collections.Generic.IReadOnlySet{System.String})">
Update(List&lt;string&gt;, Dictionary&lt;string, int&gt;, string, IReadOnlySet&lt;string&gt;?, IReadOnlySet&lt;string&gt;?, IReadOnlySet&lt;string&gt;?)
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/UI/ListSources/ChannelListSource.cs"><i class="bi bi-code-slash"></i></a>
</h3>
@@ -468,7 +468,7 @@ Active channel gets a &gt; indicator, unread channels are bright with a count ba
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public void Update(List&lt;string&gt; channels, Dictionary&lt;string, int&gt; unread, string activeChannel, IReadOnlySet&lt;string&gt;? protectedChannels = null, IReadOnlySet&lt;string&gt;? mentionChannels = null)</code></pre>
<pre><code class="lang-csharp hljs">public void Update(List&lt;string&gt; channels, Dictionary&lt;string, int&gt; unread, string activeChannel, IReadOnlySet&lt;string&gt;? protectedChannels = null, IReadOnlySet&lt;string&gt;? mentionChannels = null, IReadOnlySet&lt;string&gt;? privateChannels = null)</code></pre>
</div>
<h4 class="section">Parameters</h4>
@@ -483,6 +483,8 @@ Active channel gets a &gt; indicator, unread channels are bright with a count ba
<dd></dd>
<dt><code>mentionChannels</code> <span class="xref">IReadOnlySet</span>&lt;<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a>&gt;</dt>
<dd></dd>
<dt><code>privateChannels</code> <span class="xref">IReadOnlySet</span>&lt;<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a>&gt;</dt>
<dd></dd>
</dl>
@@ -1698,6 +1698,70 @@ current ASCII-art size for images. Passing an empty list restores the default hi
<h3 id="EchoHub_Client_UI_MainWindow_OnFilesStaged" data-uid="EchoHub.Client.UI.MainWindow.OnFilesStaged">
OnFilesStaged
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/UI/MainWindow.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Fired when local files arrive via paste or drag-and-drop to be staged as attachments.
Parameters: channel name, absolute paths of existing files.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public event Action&lt;string, IReadOnlyList&lt;string&gt;&gt;? OnFilesStaged</code></pre>
</div>
<h4 class="section">Event Type</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.action-2">Action</a>&lt;<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a>, <span class="xref">IReadOnlyList</span>&lt;<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a>&gt;&gt;</dt>
<dd></dd>
</dl>
<h3 id="EchoHub_Client_UI_MainWindow_OnImagePasted" data-uid="EchoHub.Client.UI.MainWindow.OnImagePasted">
OnImagePasted
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/UI/MainWindow.cs"><i class="bi bi-code-slash"></i></a>
</h3>
<div class="markdown level1 summary"><p>Fired when raw image data is pasted from the clipboard (e.g. copied from a browser or a
screenshot tool). Parameters: channel name, PNG-encoded image bytes.</p>
</div>
<div class="markdown level1 conceptual"></div>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public event Action&lt;string, byte[]&gt;? OnImagePasted</code></pre>
</div>
<h4 class="section">Event Type</h4>
<dl class="parameters">
<dt><a class="xref" href="https://learn.microsoft.com/dotnet/api/system.action-2">Action</a>&lt;<a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a>, <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.byte">byte</a>[]&gt;</dt>
<dd></dd>
</dl>
<h3 id="EchoHub_Client_UI_MainWindow_OnImageSaveRequested" data-uid="EchoHub.Client.UI.MainWindow.OnImageSaveRequested">
OnImageSaveRequested
<a class="header-action link-secondary" title="View source" href="https://github.com/HueByte/EchoHub/blob/master/src/EchoHub.Client/UI/MainWindow.cs"><i class="bi bi-code-slash"></i></a>