Files
securebit-chat/docs/api/index.html
T
lockbitchat 9e63cf65a4 v6.8.0: light theme
The palette lived as ~620 hex literals in inline styles plus a few hundred more in
the stylesheets, so there was no single thing to change. It is now 113 custom
properties in src/styles/theme.css, in two blocks.

src/scripts/theme-boot.js decides the theme before first paint — blocking, in <head>,
above the stylesheet, because a deferred script paints dark first and corrects itself.
It stores the mode ('system' | 'light' | 'dark'), never the colour it resolved to, and
stamps data-theme so an explicit choice can beat the media query. The switcher in the
header is a view onto it.

A filled accent stays the brand colour in both themes — the ink on it is near-black
either way — while an accent used as text darkens to clear 4.5:1 on white. A colour
reaches a fill by four routes (a style property, a constant, a helper argument, an SVG
source string), and tests/theme-switching.test.mjs covers all four.

The dark theme is unchanged: every colour declaration the previous build produced comes
out of this one identically once the properties are resolved.

Also: the roadmap drops its status chips on mobile, and Roadmap.jsx no longer splits a
colour with parseInt at runtime, which a var() reference cannot survive.
2026-09-04 17:38:41 -04:00

236 lines
13 KiB
HTML

<!DOCTYPE html>
<!-- Generated by scripts/build-docs.js from doc/*.md. Edits here are overwritten;
change the Markdown instead. -->
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; upgrade-insecure-requests;">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
<title>API Notes - SecureBit.chat</title>
<meta name="description" content="API Notes — SecureBit.chat technical documentation.">
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1">
<link rel="canonical" href="https://securebit.chat/docs/api/">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico">
<meta property="og:site_name" content="SecureBit.chat">
<meta property="og:title" content="API Notes - SecureBit.chat">
<meta property="og:description" content="API Notes — SecureBit.chat technical documentation.">
<meta property="og:url" content="https://securebit.chat/docs/api/">
<meta property="og:type" content="article">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="https://securebit.chat/assets/social-card.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="API Notes - SecureBit.chat">
<meta name="twitter:description" content="API Notes — SecureBit.chat technical documentation.">
<meta name="twitter:image" content="https://securebit.chat/assets/social-card.png">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "API Notes",
"description": "API Notes — SecureBit.chat technical documentation.",
"url": "https://securebit.chat/docs/api/",
"inLanguage": "en",
"isPartOf": {
"@type": "WebSite",
"@id": "https://securebit.chat/#website"
},
"publisher": {
"@id": "https://securebit.chat/#organization"
}
}
</script>
<style>
/* These pages carry no script — that is the point of them — so the theme can only
come from the media query. There is no toggle here and no stored preference: a
reference page follows the reader's system and nothing else. The values are the
same two palettes as src/styles/theme.css, restated because these pages do not
load the app's stylesheet and are not going to start for eleven declarations. */
:root {
color-scheme: dark;
--d-ink: 255, 255, 255;
--d-bg: #0f0f11;
--d-bg-deep: #0b0b0e;
--d-code-bg: #17171c;
--d-text: #d6d6dc;
--d-heading: #f4f4f6;
--d-strong: #e8e8eb;
--d-body: #a9a9b3;
--d-pre: #c9c9d1;
--d-muted: #8a8a92;
--d-faint: #6b6b73;
--d-accent: #f0892a;
--d-accent-rgb: 240, 137, 42;
}
@media (prefers-color-scheme: light) {
:root {
color-scheme: light;
--d-ink: 0, 0, 0;
--d-bg: #fbfbfc;
--d-bg-deep: #f2f3f5;
--d-code-bg: #f2f3f5;
--d-text: #26262c;
--d-heading: #0e0e12;
--d-strong: #1b1b20;
--d-body: #43434c;
--d-pre: #2f3340;
--d-muted: #63636c;
--d-faint: #7c7c85;
--d-accent: #b05c08;
--d-accent-rgb: 176, 92, 8;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--d-bg);
color: var(--d-text);
font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
font-size: 16px;
line-height: 1.68;
}
.wrap { max-width: 46rem; margin: 0 auto; padding: 28px 24px 90px; }
.top {
display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: baseline;
padding-bottom: 16px; margin-bottom: 40px;
border-bottom: 1px solid rgba(var(--d-ink),.08);
font-size: 13.5px;
}
.top a { color: var(--d-muted); text-decoration: none; }
.top a:hover, .top a:focus-visible { color: var(--d-accent); }
.top .brand { color: var(--d-accent); font-weight: 700; letter-spacing: .04em; }
.top .here { color: var(--d-text); margin-inline-start: auto; }
h1 { font-size: clamp(28px, 5vw, 36px); font-weight: 800; letter-spacing: -1px; line-height: 1.14; color: var(--d-heading); margin: 0 0 24px; }
h2 { font-size: 22px; font-weight: 700; letter-spacing: -.4px; color: var(--d-heading); margin: 46px 0 12px; padding-top: 14px; border-top: 1px solid rgba(var(--d-ink),.07); }
h3 { font-size: 17.5px; font-weight: 700; color: var(--d-strong); margin: 30px 0 8px; }
h4 { font-size: 15.5px; font-weight: 700; color: var(--d-strong); margin: 22px 0 6px; }
p, li { color: var(--d-body); }
p { margin: 0 0 16px; }
ul, ol { padding-inline-start: 22px; margin: 0 0 16px; }
li { margin: 5px 0; }
a { color: var(--d-accent); text-underline-offset: 2px; }
strong { color: var(--d-strong); }
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; background: var(--d-code-bg); border: 1px solid rgba(var(--d-ink),.07); border-radius: 4px; padding: 1px 5px; color: var(--d-strong); }
pre { background: var(--d-bg-deep); border: 1px solid rgba(var(--d-ink),.08); border-radius: 8px; padding: 14px 16px; overflow-x: auto; margin: 0 0 18px; }
pre code { background: none; border: 0; padding: 0; font-size: 13px; line-height: 1.62; color: var(--d-pre); }
.tablewrap { overflow-x: auto; margin: 0 0 20px; }
table { border-collapse: collapse; width: 100%; font-size: 14.5px; min-width: 30rem; }
th { text-align: start; color: var(--d-muted); font-weight: 600; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; padding: 0 14px 8px 0; border-bottom: 1px solid rgba(var(--d-ink),.12); }
td { padding: 9px 14px 9px 0; border-bottom: 1px solid rgba(var(--d-ink),.06); vertical-align: top; color: var(--d-body); }
td:first-child, th:first-child { padding-inline-start: 0; }
blockquote { margin: 0 0 18px; padding: 2px 0 2px 16px; border-inline-start: 3px solid rgba(var(--d-accent-rgb),.4); color: var(--d-muted); }
hr { border: 0; border-top: 1px solid rgba(var(--d-ink),.08); margin: 34px 0; }
img { max-width: 100%; height: auto; }
a:focus-visible { outline: 2px solid var(--d-accent); outline-offset: 2px; border-radius: 2px; }
.more { margin-top: 64px; padding-top: 22px; border-top: 1px solid rgba(var(--d-ink),.08); }
.more h2 { font-size: 13px; letter-spacing: .12em; text-transform: uppercase; color: var(--d-faint); border: 0; margin: 0 0 12px; padding: 0; font-weight: 700; }
.more ul { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 4px 24px; }
.more li { margin: 0; padding: 7px 0; border-bottom: 1px solid rgba(var(--d-ink),.05); font-size: 14.5px; }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
</style>
</head>
<body>
<div class="wrap">
<nav class="top">
<a class="brand" href="/">SecureBit.chat</a>
<a href="/docs/">Documentation</a>
<a href="https://github.com/SecureBitChat/securebit-chat" rel="noopener">GitHub</a>
<span class="here">API Notes</span>
</nav>
<h1>API Notes</h1>
<h2 id="enhancedsecurewebrtcmanager">EnhancedSecureWebRTCManager</h2>
<h3 id="verification">Verification</h3>
<ul>
<li><code>confirmVerification(userCode)</code> validates a manually entered SAS code.</li>
<li>Verification succeeds only after both local and remote confirmations are present.</li>
<li><code>isVerified</code> is assigned in one place (<code>_setVerifiedStatus</code>), which refuses any
SAS-based transition without a recorded local confirmation.</li>
<li>Control frames listed in <code>POST_VERIFICATION_CONTROL_TYPES</code> (reconnection
signalling, call setup, message deletion, delivery receipts) are only acted on
after verification. The set is an allowlist; unrecognised frame types are
rejected by the chat channel&#39;s default-deny branch.</li>
<li>Protocol version <code>4.1</code> is enforced during offer/answer processing.</li>
</ul>
<h3 id="forward-secrecy">Forward secrecy</h3>
<ul>
<li><code>isRatchetActive()</code> reports whether the Double Ratchet is running on this
connection. It is negotiated: both peers advertise <code>RATCHET_VERSION</code> in the
offer and answer, and a peer that does not falls back to per-session keys.</li>
<li><code>_ratchet.canEncrypt</code> is false on the joining peer until the inviting peer&#39;s
first message arrives, because the sending chain does not exist until then. Callers
must check it rather than assume; the send path falls back to session keys for
those first frames.</li>
<li><code>_ratchet.getState()</code> returns counters and the number of retained keys for
diagnostics. It exposes no key material.</li>
<li>Ratcheted chat arrives as <code>MESSAGE_TYPES.RATCHET_MESSAGE</code> with <code>h</code> (the header
string, used verbatim as AES-GCM additional data) and <code>c</code> (base64 body). The
header must be passed back to <code>decrypt()</code> exactly as received; re-serialising
it can change a byte and fail authentication.</li>
</ul>
<h3 id="privacy-mode">Privacy mode</h3>
<ul>
<li>relay-only configuration sets WebRTC <code>iceTransportPolicy</code> to <code>&quot;relay&quot;</code>.</li>
<li>TURN availability is checked before claiming IP protection.</li>
</ul>
<h3 id="file-transfer-callbacks">File transfer callbacks</h3>
<ul>
<li><code>setFileTransferCallbacks(onProgress, onReceived, onError, onIncomingRequest)</code> updates manager fields and any live <code>EnhancedSecureFileTransfer</code> instance.</li>
<li>Passing <code>null</code> values detaches callbacks from the active transfer system.</li>
</ul>
<h3 id="voice-messages">Voice messages</h3>
<ul>
<li><code>sendFile(file, options)</code> accepts an optional <code>options</code> object. <code>options.voice</code>
(<code>{ dur, bars }</code>) marks the transfer as a voice note and rides along as unsigned
metadata; <code>options.uiId</code> correlates progress events to a UI bubble before the
<code>fileId</code> resolves.</li>
<li><code>onProgress</code> receives <code>{ fileId, uiId, direction, progress, isVoice, voice }</code>.
<code>onIncomingFileRequest</code> and <code>onReceived</code> include <code>isVoice</code> and <code>voice</code> so the UI
can auto-accept and render a voice bubble instead of a file card.</li>
<li>The <code>isVoice</code> a callback receives is the <strong>receiver&#39;s</strong> verdict, not the
sender&#39;s claim: <code>validateIncomingMetadata</code> clears it unless the transfer
declares a recognised audio MIME type and fits the per-note and per-session
size budgets. A transfer that fails those checks is not rejected; it simply
loses the consent-free shortcut and is offered as a normal file.</li>
</ul>
<h2 id="enhancedsecurefiletransfer">EnhancedSecureFileTransfer</h2>
<h3 id="incoming-transfers">Incoming transfers</h3>
<ul>
<li>metadata is validated before prompting</li>
<li>acceptance is explicit</li>
<li>receive buffers are allocated only after consent</li>
<li>file type acceptance is allowlist-based</li>
</ul>
<h3 id="cleanup">Cleanup</h3>
<ul>
<li>pending sender consent promises are rejected on cleanup</li>
<li>consent timeouts are cleared immediately</li>
<li>retained received buffers are bounded</li>
<li>evicted download handles fail with a user-facing availability message</li>
</ul>
<h2 id="securepersistentkeystorage">SecurePersistentKeyStorage</h2>
<ul>
<li>metadata is encrypted before storage</li>
<li>legacy plaintext records migrate lazily</li>
<li>corrupted encrypted metadata is ignored safely</li>
</ul>
<nav class="more">
<h2>More documentation</h2>
<ul>
<li><a href="/docs/">Documentation</a></li>
<li><a href="/docs/faq/">Frequently asked questions</a></li>
<li><a href="/docs/architecture/">Architecture</a></li>
<li><a href="/docs/cryptography/">Cryptography</a></li>
<li><a href="/docs/descriptor-sbq2/">SBQ2 — connection descriptor v2</a></li>
<li><a href="/docs/configuration/">Configuration</a></li>
<li><a href="/docs/calls/">Voice and video calls</a></li>
<li><a href="/docs/contributing/">Contributing</a></li>
<li><a href="/docs/use-policy/">Use policy and limitations</a></li>
</ul>
</nav>
</div>
</body>
</html>