Files
securebit-chat/docs/api/index.html
T
lockbitchat 414c27fda6 v6.7.3: faster loading, and pages search engines can read
The bundles carried all thirteen translations at once and a page fetched them a
third time as raw source; each page now loads only its own language. Alongside
that: JavaScript is minified, the eight stylesheets are served as one file, the
QR scanner is fetched after the app is up instead of on every visit, Inter ships
once rather than five copies of the same file, and Font Awesome is subset to the
82 icons this app draws instead of all 2468.

1.85 MB across 43 requests becomes under 700 KB across 33. On mobile the page
starts drawing in 1.6 s instead of 6.3 s and is usable in 4.5 s instead of 11 s.

Pages also carry their text in the HTML now. Everything was drawn by JavaScript
into an empty div, so crawlers saw correct metadata around nothing, and twelve of
the thirteen language pages had never been shown to anyone. The documentation is
published under /docs/ with a new FAQ, and unknown addresses return a real 404.

Separately: the localized shells were served with the year-long immutable cache
header meant for static assets, which pinned anyone who opened /de/ or /ru/ to
that build. The header is fixed and the service worker refreshes what it cached.

Claude-Session: https://claude.ai/code/session_014KjzTXxrhzYoDDWChYQ4u2
2026-09-04 00:41:46 -04:00

197 lines
12 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>
:root { color-scheme: dark; }
* { box-sizing: border-box; }
body {
margin: 0;
background: #0f0f11;
color: #d6d6dc;
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(255,255,255,.08);
font-size: 13.5px;
}
.top a { color: #8a8a92; text-decoration: none; }
.top a:hover, .top a:focus-visible { color: #f0892a; }
.top .brand { color: #f0892a; font-weight: 700; letter-spacing: .04em; }
.top .here { color: #d6d6dc; margin-inline-start: auto; }
h1 { font-size: clamp(28px, 5vw, 36px); font-weight: 800; letter-spacing: -1px; line-height: 1.14; color: #f4f4f6; margin: 0 0 24px; }
h2 { font-size: 22px; font-weight: 700; letter-spacing: -.4px; color: #f4f4f6; margin: 46px 0 12px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,.07); }
h3 { font-size: 17.5px; font-weight: 700; color: #e8e8eb; margin: 30px 0 8px; }
h4 { font-size: 15.5px; font-weight: 700; color: #e8e8eb; margin: 22px 0 6px; }
p, li { color: #a9a9b3; }
p { margin: 0 0 16px; }
ul, ol { padding-inline-start: 22px; margin: 0 0 16px; }
li { margin: 5px 0; }
a { color: #f0892a; text-underline-offset: 2px; }
strong { color: #e8e8eb; }
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; background: #17171c; border: 1px solid rgba(255,255,255,.07); border-radius: 4px; padding: 1px 5px; color: #e8e8eb; }
pre { background: #0b0b0e; border: 1px solid rgba(255,255,255,.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: #c9c9d1; }
.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: #8a8a92; font-weight: 600; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; padding: 0 14px 8px 0; border-bottom: 1px solid rgba(255,255,255,.12); }
td { padding: 9px 14px 9px 0; border-bottom: 1px solid rgba(255,255,255,.06); vertical-align: top; color: #a9a9b3; }
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(240,137,42,.4); color: #8a8a92; }
hr { border: 0; border-top: 1px solid rgba(255,255,255,.08); margin: 34px 0; }
img { max-width: 100%; height: auto; }
a:focus-visible { outline: 2px solid #f0892a; outline-offset: 2px; border-radius: 2px; }
.more { margin-top: 64px; padding-top: 22px; border-top: 1px solid rgba(255,255,255,.08); }
.more h2 { font-size: 13px; letter-spacing: .12em; text-transform: uppercase; color: #6b6b73; 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(255,255,255,.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>