fix: chat not opening after SAS in v4.8.14 (nowTick scope) — v4.8.15
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled

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.
This commit is contained in:
lockbitchat
2026-06-18 21:15:43 -04:00
parent 15173a9278
commit cb72b9cb1c
14 changed files with 111 additions and 101 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## v4.8.15 — Fix: chat would not open after SAS in v4.8.14
### Fixed
- The secure chat failed to open after both peers confirmed the SAS code: the message list and composer (in `EnhancedChatInterface`) referenced `nowTick`, `onUnsendMessage` and the new composer props, but those were threaded into the sibling `EnhancedConnectionSetup` component by mistake. At runtime this threw `ReferenceError: Can't find variable: nowTick` during the verified-state re-render, so the chat never rendered. The new props are now destructured and passed on `EnhancedChatInterface`, where the chat UI actually lives. No behavioural change to the v4.8.14 features otherwise.
## v4.8.14 — Secure chat tools: code blocks, view-once, disappearing, unsend, panic
Adds privacy-focused messaging controls. Per-message metadata (id, view-once, timer) travels **inside the encrypted message envelope**, never in the sanitized text, so message content cannot spoof or corrupt these controls. The unsend/delete signal travels over the authenticated DTLS control channel like other system messages.
+6 -2
View File
@@ -1,4 +1,4 @@
# SecureBit.chat v4.8.14
# SecureBit.chat v4.8.15
SecureBit.chat is a browser-based peer-to-peer chat application built on WebRTC and Web Crypto APIs. It is designed for direct encrypted communication, explicit peer verification, and a small operational footprint without account registration or server-side message storage.
@@ -15,7 +15,11 @@ SecureBit.chat uses:
A session is not treated as verified until both peers complete the interactive SAS flow. Each user must compare the displayed code with the peer through an out-of-band channel and enter the matching code manually. Three failed SAS attempts terminate the session.
## Highlights in v4.8.14
## Highlights in v4.8.15
- Fix: the secure chat failed to open after SAS confirmation in v4.8.14 (a `nowTick` reference was scoped to the wrong component). The new messaging controls are now wired into the chat component correctly.
The v4.8.14 messaging features:
- Code blocks: a composer button wraps a message in a monospace code window with a one-click copy button (clipboard auto-clears after 30s).
- View-once messages: the recipient sees a blurred bubble that reveals on tap and is then deleted. Cooperative, like WhatsApp view-once — not screenshot-proof.
+1 -1
View File
@@ -22,6 +22,6 @@ SecureBit.chat is intended for legitimate private communication, journalism, res
## Current release
- Product release: `v4.8.14`
- Product release: `v4.8.15`
- Protocol version: `4.1`
- Last updated: May 17, 2026
+1 -1
View File
@@ -17487,7 +17487,7 @@ Right-click or Ctrl+click to disconnect`,
React.createElement("p", {
key: "subtitle",
className: "text-xs sm:text-sm text-muted hidden sm:block"
}, "End-to-end freedom v4.8.14")
}, "End-to-end freedom v4.8.15")
])
]),
// Status and Controls - Responsive
+1 -1
View File
File diff suppressed because one or more lines are too long
Vendored
+49 -49
View File
@@ -266,12 +266,12 @@ var MessageBody = ({ text }) => {
)
);
};
var ChatToolbar = ({ codeMode: codeMode2, setCodeMode: setCodeMode2, viewOnceMode: viewOnceMode2, setViewOnceMode: setViewOnceMode2, disappearTtl: disappearTtl2, setDisappearTtl: setDisappearTtl2, onPanicWipe: onPanicWipe2 }) => {
var ChatToolbar = ({ codeMode, setCodeMode, viewOnceMode, setViewOnceMode, disappearTtl, setDisappearTtl, onPanicWipe }) => {
const ttlCycle = [0, 30, 300, 3600];
const ttlLabel = (s) => s === 0 ? "Off" : s >= 3600 ? `${Math.round(s / 3600)}h` : s >= 60 ? `${Math.round(s / 60)}m` : `${s}s`;
const cycleTtl = () => {
const i = ttlCycle.indexOf(disappearTtl2);
setDisappearTtl2(ttlCycle[(i + 1) % ttlCycle.length] || 0);
const i = ttlCycle.indexOf(disappearTtl);
setDisappearTtl(ttlCycle[(i + 1) % ttlCycle.length] || 0);
};
const pill = (key, { active, activeClass, icon, label, title, onClick }) => React.createElement("button", {
key,
@@ -287,26 +287,26 @@ var ChatToolbar = ({ codeMode: codeMode2, setCodeMode: setCodeMode2, viewOnceMod
className: "flex items-center flex-wrap gap-2 pb-3"
}, [
pill("code", {
active: codeMode2,
active: codeMode,
activeClass: "text-green-400 border-green-500/40 bg-green-500/10",
icon: "fas fa-code",
label: "Code",
title: "Send as a code block (with copy button)",
onClick: () => setCodeMode2((v) => !v)
onClick: () => setCodeMode((v) => !v)
}),
pill("once", {
active: viewOnceMode2,
active: viewOnceMode,
activeClass: "text-orange-400 border-orange-500/40 bg-orange-500/10",
icon: "fas fa-eye-slash",
label: "View once",
title: "Recipient can read it once, then it is deleted (cooperative \u2014 not screenshot-proof)",
onClick: () => setViewOnceMode2((v) => !v)
onClick: () => setViewOnceMode((v) => !v)
}),
pill("ttl", {
active: disappearTtl2 > 0,
active: disappearTtl > 0,
activeClass: "text-blue-400 border-blue-500/40 bg-blue-500/10",
icon: "fas fa-stopwatch",
label: `Timer: ${ttlLabel(disappearTtl2)}`,
label: `Timer: ${ttlLabel(disappearTtl)}`,
title: "Disappearing messages \u2014 auto-delete on both sides",
onClick: cycleTtl
}),
@@ -319,7 +319,7 @@ var ChatToolbar = ({ codeMode: codeMode2, setCodeMode: setCodeMode2, viewOnceMod
title: "Wipe this conversation and keys, and disconnect",
onClick: () => {
const ok = typeof window !== "undefined" && window.confirm ? window.confirm("Panic wipe: delete all messages, wipe keys and disconnect now?") : true;
if (ok && typeof onPanicWipe2 === "function") onPanicWipe2();
if (ok && typeof onPanicWipe === "function") onPanicWipe();
}
})
]);
@@ -536,7 +536,7 @@ var VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed,
])
]);
};
var EnhancedChatMessage = ({ message, type, timestamp, mid, viewOnce, expiresAt, nowTick: nowTick2, canUnsend, onUnsend, onExpire }) => {
var EnhancedChatMessage = ({ message, type, timestamp, mid, viewOnce, expiresAt, nowTick, canUnsend, onUnsend, onExpire }) => {
const [revealed, setRevealed] = React.useState(false);
const revealTimerRef = React.useRef(null);
const formatTime = (ts) => {
@@ -578,7 +578,7 @@ var EnhancedChatMessage = ({ message, type, timestamp, mid, viewOnce, expiresAt,
}
};
const style = getMessageStyle();
const remaining = typeof expiresAt === "number" ? Math.max(0, Math.ceil((expiresAt - (nowTick2 || Date.now())) / 1e3)) : null;
const remaining = typeof expiresAt === "number" ? Math.max(0, Math.ceil((expiresAt - (nowTick || Date.now())) / 1e3)) : null;
const isViewOnce = type === "received" && viewOnce === true;
const handleReveal = () => {
if (revealed) return;
@@ -700,18 +700,7 @@ var EnhancedConnectionSetup = ({
handleCreateOffer,
relayOnlyMode,
setRelayOnlyMode,
webrtcManagerRef,
// Secure chat extras
codeMode: codeMode2,
setCodeMode: setCodeMode2,
viewOnceMode: viewOnceMode2,
setViewOnceMode: setViewOnceMode2,
disappearTtl: disappearTtl2,
setDisappearTtl: setDisappearTtl2,
nowTick: nowTick2,
onUnsendMessage: onUnsendMessage2,
onMessageExpire: onMessageExpire2,
onPanicWipe: onPanicWipe2
webrtcManagerRef
}) => {
const [mode, setMode] = React.useState("select");
const [notificationPermissionRequested, setNotificationPermissionRequested] = React.useState(false);
@@ -1536,7 +1525,18 @@ var EnhancedChatInterface = ({
scrollToBottom,
webrtcManager,
pendingIncomingFiles = [],
onIncomingDecision
onIncomingDecision,
// Secure chat extras
codeMode,
setCodeMode,
viewOnceMode,
setViewOnceMode,
disappearTtl,
setDisappearTtl,
nowTick,
onUnsendMessage,
onMessageExpire,
onPanicWipe
}) => {
const [showScrollButton, setShowScrollButton] = React.useState(false);
const [showFileTransfer, setShowFileTransfer] = React.useState(false);
@@ -1840,10 +1840,10 @@ var EnhancedChatInterface = ({
};
var EnhancedSecureP2PChat = () => {
const [messages, setMessages] = React.useState([]);
const [codeMode2, setCodeMode2] = React.useState(false);
const [viewOnceMode2, setViewOnceMode2] = React.useState(false);
const [disappearTtl2, setDisappearTtl2] = React.useState(0);
const [nowTick2, setNowTick] = React.useState(() => Date.now());
const [codeMode, setCodeMode] = React.useState(false);
const [viewOnceMode, setViewOnceMode] = React.useState(false);
const [disappearTtl, setDisappearTtl] = React.useState(0);
const [nowTick, setNowTick] = React.useState(() => Date.now());
const [connectionStatus, setConnectionStatus] = React.useState("disconnected");
const [relayOnlyMode, setRelayOnlyMode] = React.useState(() => {
try {
@@ -2270,7 +2270,7 @@ var EnhancedSecureP2PChat = () => {
} catch (error) {
}
}
handleMessage(" SecureBit.chat Enhanced Security Edition v4.8.14 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.", "system");
handleMessage(" SecureBit.chat Enhanced Security Edition v4.8.15 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.", "system");
const handleBeforeUnload = (event) => {
if (event.type === "beforeunload" && !isTabSwitching) {
if (webrtcManagerRef.current && webrtcManagerRef.current.isConnected()) {
@@ -3467,18 +3467,18 @@ var EnhancedSecureP2PChat = () => {
}
try {
const baseText = messageInput.trim();
const outText = codeMode2 ? "```\n" + baseText + "\n```" : baseText;
const outText = codeMode ? "```\n" + baseText + "\n```" : baseText;
const mid = `m_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
const meta = { mid };
if (viewOnceMode2) meta.once = true;
if (disappearTtl2 > 0) meta.ttl = disappearTtl2;
if (viewOnceMode) meta.once = true;
if (disappearTtl > 0) meta.ttl = disappearTtl;
const localOpts = { mid };
if (disappearTtl2 > 0) localOpts.expiresAt = Date.now() + disappearTtl2 * 1e3;
if (disappearTtl > 0) localOpts.expiresAt = Date.now() + disappearTtl * 1e3;
addMessageWithAutoScroll(outText, "sent", localOpts);
await webrtcManagerRef.current.sendMessage(outText, meta);
setMessageInput("");
if (codeMode2) setCodeMode2(false);
if (viewOnceMode2) setViewOnceMode2(false);
if (codeMode) setCodeMode(false);
if (viewOnceMode) setViewOnceMode(false);
} catch (error) {
const msg = String(error?.message || error);
if (!/queued for sending|Data channel not ready/i.test(msg)) {
@@ -3782,7 +3782,18 @@ var EnhancedSecureP2PChat = () => {
scrollToBottom,
webrtcManager: webrtcManagerRef.current,
pendingIncomingFiles,
onIncomingDecision: handleIncomingDecision
onIncomingDecision: handleIncomingDecision,
// Secure chat extras
codeMode,
setCodeMode,
viewOnceMode,
setViewOnceMode,
disappearTtl,
setDisappearTtl,
nowTick,
onUnsendMessage: handleUnsendMessage,
onMessageExpire: handleMessageExpire,
onPanicWipe: handlePanicWipe
});
})() : React.createElement(EnhancedConnectionSetup, {
onCreateOffer: handleCreateOffer,
@@ -3826,18 +3837,7 @@ var EnhancedSecureP2PChat = () => {
handleCreateOffer,
relayOnlyMode,
setRelayOnlyMode,
webrtcManagerRef,
// Secure chat extras
codeMode: codeMode2,
setCodeMode: setCodeMode2,
viewOnceMode: viewOnceMode2,
setViewOnceMode: setViewOnceMode2,
disappearTtl: disappearTtl2,
setDisappearTtl: setDisappearTtl2,
nowTick: nowTick2,
onUnsendMessage: handleUnsendMessage,
onMessageExpire: handleMessageExpire,
onPanicWipe: handlePanicWipe
webrtcManagerRef
})
),
// QR Scanner Modal
+3 -3
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -113,7 +113,7 @@
<!-- GitHub Pages SEO -->
<meta name="description" content="SecureBit.chat v4.8.14 — P2P messenger with ECDH + DTLS + SAS security and 18-layer military-grade cryptography">
<meta name="description" content="SecureBit.chat v4.8.15 — P2P messenger with ECDH + DTLS + SAS security and 18-layer military-grade cryptography">
<meta name="keywords" content="P2P messenger, ECDH, DTLS, SAS, encryption, WebRTC, privacy, ASN.1 validation, military-grade security, 18-layer defense, MITM protection, PFS">
<meta name="author" content="Volodymyr">
<link rel="canonical" href="https://github.com/SecureBitChat/securebit-chat/">
@@ -148,13 +148,13 @@
<!-- Update Manager - система принудительного обновления -->
<script src="src/utils/updateManager.js"></script>
<script type="module" src="src/components/UpdateChecker.jsx"></script>
<script type="module" src="dist/qr-local.js?v=1781829306093"></script>
<script type="module" src="src/components/QRScanner.js?v=1781829306093"></script>
<script type="module" src="dist/qr-local.js?v=1781831678894"></script>
<script type="module" src="src/components/QRScanner.js?v=1781831678894"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="dist/app-boot.js?v=1781829306093"></script>
<script type="module" src="dist/app.js?v=1781829306093"></script>
<script type="module" src="dist/app-boot.js?v=1781831678894"></script>
<script type="module" src="dist/app.js?v=1781831678894"></script>
<script src="src/scripts/pwa-register.js"></script>
<script src="./src/pwa/install-prompt.js" type="module"></script>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "SecureBit.chat v4.8.14 - ECDH + DTLS + SAS",
"name": "SecureBit.chat v4.8.15 - ECDH + DTLS + SAS",
"short_name": "SecureBit",
"description": "P2P messenger with ECDH + DTLS + SAS security, military-grade cryptography and Lightning Network payments",
"start_url": "./",
+7 -7
View File
@@ -1,10 +1,10 @@
{
"version": "1781829306093",
"buildVersion": "1781829306093",
"appVersion": "4.8.14",
"buildTime": "2026-06-19T00:35:06.138Z",
"buildId": "1781829306093-cf36656",
"gitHash": "cf36656",
"version": "1781831678894",
"buildVersion": "1781831678894",
"appVersion": "4.8.15",
"buildTime": "2026-06-19T01:14:38.934Z",
"buildId": "1781831678894-15173a9",
"gitHash": "15173a9",
"generated": true,
"generatedAt": "2026-06-19T00:35:06.139Z"
"generatedAt": "2026-06-19T01:14:38.935Z"
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "securebit-chat",
"version": "4.8.14",
"version": "4.8.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "securebit-chat",
"version": "4.8.14",
"version": "4.8.15",
"license": "MIT",
"dependencies": {
"base64-js": "1.5.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "securebit-chat",
"version": "4.8.14",
"version": "4.8.15",
"description": "Secure P2P Communication Application with End-to-End Encryption",
"main": "index.html",
"scripts": {
+27 -27
View File
@@ -594,18 +594,7 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
handleCreateOffer,
relayOnlyMode,
setRelayOnlyMode,
webrtcManagerRef,
// Secure chat extras
codeMode,
setCodeMode,
viewOnceMode,
setViewOnceMode,
disappearTtl,
setDisappearTtl,
nowTick,
onUnsendMessage,
onMessageExpire,
onPanicWipe
webrtcManagerRef
}) => {
const [mode, setMode] = React.useState('select');
const [notificationPermissionRequested, setNotificationPermissionRequested] = React.useState(false);
@@ -1503,7 +1492,18 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
scrollToBottom,
webrtcManager,
pendingIncomingFiles = [],
onIncomingDecision
onIncomingDecision,
// Secure chat extras
codeMode,
setCodeMode,
viewOnceMode,
setViewOnceMode,
disappearTtl,
setDisappearTtl,
nowTick,
onUnsendMessage,
onMessageExpire,
onPanicWipe
}) => {
const [showScrollButton, setShowScrollButton] = React.useState(false);
const [showFileTransfer, setShowFileTransfer] = React.useState(false);
@@ -2388,7 +2388,7 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
}
}
handleMessage(' SecureBit.chat Enhanced Security Edition v4.8.14 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
handleMessage(' SecureBit.chat Enhanced Security Edition v4.8.15 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
const handleBeforeUnload = (event) => {
if (event.type === 'beforeunload' && !isTabSwitching) {
@@ -4171,7 +4171,18 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
scrollToBottom: scrollToBottom,
webrtcManager: webrtcManagerRef.current,
pendingIncomingFiles: pendingIncomingFiles,
onIncomingDecision: handleIncomingDecision
onIncomingDecision: handleIncomingDecision,
// Secure chat extras
codeMode: codeMode,
setCodeMode: setCodeMode,
viewOnceMode: viewOnceMode,
setViewOnceMode: setViewOnceMode,
disappearTtl: disappearTtl,
setDisappearTtl: setDisappearTtl,
nowTick: nowTick,
onUnsendMessage: handleUnsendMessage,
onMessageExpire: handleMessageExpire,
onPanicWipe: handlePanicWipe
});
})()
: React.createElement(EnhancedConnectionSetup, {
@@ -4216,18 +4227,7 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
handleCreateOffer: handleCreateOffer,
relayOnlyMode: relayOnlyMode,
setRelayOnlyMode: setRelayOnlyMode,
webrtcManagerRef: webrtcManagerRef,
// Secure chat extras
codeMode: codeMode,
setCodeMode: setCodeMode,
viewOnceMode: viewOnceMode,
setViewOnceMode: setViewOnceMode,
disappearTtl: disappearTtl,
setDisappearTtl: setDisappearTtl,
nowTick: nowTick,
onUnsendMessage: handleUnsendMessage,
onMessageExpire: handleMessageExpire,
onPanicWipe: handlePanicWipe
webrtcManagerRef: webrtcManagerRef
})
),
+1 -1
View File
@@ -539,7 +539,7 @@ const EnhancedMinimalHeader = ({
React.createElement('p', {
key: 'subtitle',
className: 'text-xs sm:text-sm text-muted hidden sm:block'
}, 'End-to-end freedom v4.8.14')
}, 'End-to-end freedom v4.8.15')
])
]),