Completes the messaging controls from v4.8.14 and fixes the bug that made them
appear broken for recipients.
Fixed:
- Per-message metadata was silently dropped for recipients. NotificationIntegration
wrapped onMessage and deliverMessageToUI with 2-arg shims that called the
originals without the 3rd argument (meta); with notifications enabled, view-once,
disappearing timers and unsend all failed on the receiving side. Both wrappers
now forward all arguments. Added tests/notification-meta-forwarding.test.mjs.
- Chat would not open after SAS: composer props were threaded into the wrong
component (EnhancedConnectionSetup vs EnhancedChatInterface) -> ReferenceError
nowTick on the verified re-render. Props moved to the chat component.
Changed:
- Code blocks: lightweight dependency-free syntax highlighting via React nodes
(no innerHTML/remote scripts); code mode expands the input; copy auto-clears
the clipboard after ~30s.
- View-once: configurable visible-after-open time (5s/15s/30s/1m) via meta.onceTtl.
- Disappearing timer: duration picker (Off/30s/5m/1h) instead of click-cycling.
- Composer toolbar moved next to "Send files"; borderless buttons, brand-orange
active state; pickers open upward and are mobile-friendly.
- Sender bubble background lightened to rgba(249,115,22,0.05).
Removed:
- Panic wipe button (disconnect already wipes keys and clears session state).
Transport unchanged: per-message metadata travels inside the encrypted envelope,
whitelisted/bounded by _sanitizeMessageMeta. Full suite: 19 files, all passing.
Docs (README, CHANGELOG) updated; version bumped to 4.8.20.
The new composer props (nowTick, codeMode, view-once/timer setters, unsend/expire
handlers) were threaded into EnhancedConnectionSetup, but the message list and
composer live in the sibling EnhancedChatInterface. After SAS confirmation the
verified-state re-render referenced an out-of-scope `nowTick`, throwing
"ReferenceError: Can't find variable: nowTick" so the chat never rendered.
Move the prop destructuring and pass-through onto EnhancedChatInterface (where the
chat UI actually is) and revert the mistaken additions on EnhancedConnectionSetup.
No behavioural change to the v4.8.14 features otherwise. Bumps to 4.8.15.
New privacy-focused messaging controls in the composer:
- Code blocks: button wraps the message in a fenced block; both peers render a
monospace code window with a copy button (clipboard auto-clears after ~30s).
Window is built from sanitized text via React nodes — no new XSS surface.
- View-once: recipient sees a blurred bubble, reveals on tap, then it is wiped.
Honestly cooperative (not screenshot-proof).
- Disappearing messages: optional 30s/5m/1h timer auto-deletes on both sides
with a live countdown; incoming TTL clamped to [5s, 24h].
- Unsend (delete for everyone) via new MESSAGE_TYPES.message_delete control.
- Panic wipe: clears chat, wipes keys and disconnects (behind a confirm).
Transport:
- Per-message metadata (id / view-once / timer) travels inside the encrypted
envelope, not in the sanitized text, so content cannot spoof these controls.
- _sanitizeMessageMeta whitelists + bounds metadata on send and receive.
- AAD/replay protection, SAS gate and receive-side DOMPurify are unchanged.
Adds tests/secure-chat-features.test.mjs (full suite: 17 files, all passing).
Bumps version to 4.8.14 across package.json, package-lock.json, manifest.json,
index.html, meta.json, README, SECURITY_DISCLAIMER, header and init banner.
Bumps version to 4.8.13 across package.json, package-lock.json, manifest.json,
index.html, meta.json, README, SECURITY_DISCLAIMER, the site header and the
in-app init banner (previously desynced at 4.8.10/4.8.11/4.8.12).
Ships the security-review fixes already on main:
- removed the over-broad send-path keyword blocklist that silently rejected
legitimate messages (real XSS defense remains receive-side DOMPurify)
- preserve newlines/tabs/indentation in outgoing message sanitization
- stop logging raw AAD (sessionId + keyFingerprint) on validation failure
- add Strict-Transport-Security and Permissions-Policy headers
- add outgoing-message-integrity regression tests
fix(file-transfer): announce received file once, not many times
The per-transfer lock used a single `if` check, so when 3+ chunk
operations queued on the same fileId they awaited the same in-flight
lock and then ran concurrently, breaking assembly atomicity. The lock
now loops until the slot is free (true serialization) and file assembly
is idempotent, so `File received` shows exactly once per file.
fix(verification): stop duplicate connection-setup system messages
handleVerificationBothConfirmed had no guard, so when both peers sent
verification_both_confirmed symmetrically one side ran both the local
detection path and the peer-notification path, emitting "Both parties
confirmed!" and the verified transition (and "Secure connection
established") twice. It now bails out if both confirmations are already
recorded.
fix(ui): wrap long DTLS fingerprint inside the chat bubble
The message text column is a flex child with default min-width:auto, so
the long unbroken fingerprint overflowed. Added min-w-0 so break-words
can wrap it.
chore(release): bump version to 4.8.12 in header, init banner, manifest
fix(file-transfer): size chunks under the 64KB SCTP message limit
Each 64KB chunk became a ~87KB AES-GCM+Base64 file_chunk message,
exceeding WebRTC's 64KB SCTP message-size floor. The consent handshake
(small messages) succeeded, but no chunk was ever delivered on Safari
and cross-browser connections whose SDP omits a=max-message-size, so
files never transferred. Send chunk size is now 16KB (~22KB on the
wire); inbound chunks up to 64KB stay accepted for backward compat.
fix(file-transfer): make MIME advisory, drive validation by extension
The client-supplied MIME type is easily spoofed and varies across
browsers/OSes, yet was a hard gate: files with an empty MIME or a
cross-OS variant (application/x-zip-compressed, image/jpg) were wrongly
rejected. Extension allow-list plus BLOCKED_EXTENSIONS is now the
boundary; a blatantly foreign MIME on a safe extension is still rejected
and per-type size limits still apply.
- add header gear + connection-screen entry points to Advanced network settings
- render the ICE settings modal at the app root (reachable from any screen via event)
- remove the standalone relay-only toggle/description from the start screen
(relay-only now lives in the advanced settings panel)
- fix crash from referencing main-component state inside EnhancedConnectionSetup
- bump version to 4.8.10 across header, manifest, README, init message, disclaimer
- document the feature in CHANGELOG and README
Complete the mandatory receiver-consent gate that was wired in the
backend but never connected to the UI callback chain:
- Add the missing onIncomingFileRequest (4th) callback to
setFileTransferCallbacks in app.jsx — its absence caused
handleFileTransferStart to auto-reject every incoming file.
- Remove independent callback registration from FileTransferComponent;
the component was overwriting app-level callbacks on mount and
nulling all four on unmount, silently breaking progress/received/
error handlers whenever the panel was hidden.
- Lift pendingIncomingFiles state to the root component so consent
prompts are shown regardless of panel visibility; auto-open the
panel on incoming request.
- Add getReceivedFileObjectURL / revokeReceivedFileObjectURL on
EnhancedSecureWebRTCManager for download buttons in the panel.
- Update file-transfer-ui-cleanup regression test to match the new
single-owner callback architecture.
- All 14 tests pass; clean production build.