release: v4.8.12 chat notification & file-transfer UI fixes
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
This commit is contained in:
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v4.8.12 — Chat notification & file-transfer UI fixes
|
||||||
|
|
||||||
|
Fixes duplicated chat output and a layout overflow in the message list.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A received file was announced many times in the chat instead of once. The per-transfer lock used a single `if` check, so when 3+ chunk operations queued on the same file they ran concurrently and broke assembly atomicity. The lock now serializes correctly, and file assembly is idempotent, so `File received` is shown exactly once per file.
|
||||||
|
- System messages were duplicated during connection setup (e.g. "Both parties confirmed!" and "Secure connection successfully established"). `handleVerificationBothConfirmed` now bails out if both confirmations were already recorded, so the message and the verified transition fire only once.
|
||||||
|
- The DTLS fingerprint (a long unbroken string) overflowed the chat bubble. The message text container now uses `min-w-0` so the fingerprint wraps within the bubble.
|
||||||
|
- Site header, init banner, and manifest now report the current version.
|
||||||
|
|
||||||
## v4.8.11 — File transfer reliability fix
|
## v4.8.11 — File transfer reliability fix
|
||||||
|
|
||||||
Fixes file transfers that silently failed to reach the peer, and relaxes the overly strict file-type check that rejected legitimate files.
|
Fixes file transfers that silently failed to reach the peer, and relaxes the overly strict file-type check that rejected legitimate files.
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+19
-10
@@ -4340,18 +4340,20 @@ var AtomicOperations = class {
|
|||||||
this.locks = /* @__PURE__ */ new Map();
|
this.locks = /* @__PURE__ */ new Map();
|
||||||
}
|
}
|
||||||
async withLock(key, operation) {
|
async withLock(key, operation) {
|
||||||
if (this.locks.has(key)) {
|
while (this.locks.has(key)) {
|
||||||
await this.locks.get(key);
|
await this.locks.get(key);
|
||||||
}
|
}
|
||||||
const lockPromise = (async () => {
|
let releaseLock;
|
||||||
try {
|
const lockPromise = new Promise((resolve) => {
|
||||||
return await operation();
|
releaseLock = resolve;
|
||||||
} finally {
|
});
|
||||||
this.locks.delete(key);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
this.locks.set(key, lockPromise);
|
this.locks.set(key, lockPromise);
|
||||||
return lockPromise;
|
try {
|
||||||
|
return await operation();
|
||||||
|
} finally {
|
||||||
|
this.locks.delete(key);
|
||||||
|
releaseLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var RateLimiter = class {
|
var RateLimiter = class {
|
||||||
@@ -5297,6 +5299,10 @@ var EnhancedSecureFileTransfer = class {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async assembleFile(receivingState) {
|
async assembleFile(receivingState) {
|
||||||
|
if (receivingState._assembled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
receivingState._assembled = true;
|
||||||
try {
|
try {
|
||||||
receivingState.status = "assembling";
|
receivingState.status = "assembling";
|
||||||
for (let i = 0; i < receivingState.totalChunks; i++) {
|
for (let i = 0; i < receivingState.totalChunks; i++) {
|
||||||
@@ -14873,6 +14879,9 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
|||||||
this._checkBothVerificationsConfirmed();
|
this._checkBothVerificationsConfirmed();
|
||||||
}
|
}
|
||||||
handleVerificationBothConfirmed(data) {
|
handleVerificationBothConfirmed(data) {
|
||||||
|
if (this.bothVerificationsConfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.bothVerificationsConfirmed = true;
|
this.bothVerificationsConfirmed = true;
|
||||||
if (this.onVerificationStateChange) {
|
if (this.onVerificationStateChange) {
|
||||||
this.onVerificationStateChange({
|
this.onVerificationStateChange({
|
||||||
@@ -17493,7 +17502,7 @@ Right-click or Ctrl+click to disconnect`,
|
|||||||
React.createElement("p", {
|
React.createElement("p", {
|
||||||
key: "subtitle",
|
key: "subtitle",
|
||||||
className: "text-xs sm:text-sm text-muted hidden sm:block"
|
className: "text-xs sm:text-sm text-muted hidden sm:block"
|
||||||
}, "End-to-end freedom v4.8.10")
|
}, "End-to-end freedom v4.8.12")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
// Status and Controls - Responsive
|
// Status and Controls - Responsive
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -414,7 +414,7 @@ var EnhancedChatMessage = ({ message, type, timestamp }) => {
|
|||||||
}),
|
}),
|
||||||
React.createElement("div", {
|
React.createElement("div", {
|
||||||
key: "text",
|
key: "text",
|
||||||
className: "flex-1"
|
className: "flex-1 min-w-0"
|
||||||
}, [
|
}, [
|
||||||
React.createElement("div", {
|
React.createElement("div", {
|
||||||
key: "message",
|
key: "message",
|
||||||
@@ -1987,7 +1987,7 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleMessage(" SecureBit.chat Enhanced Security Edition v4.8.10 - 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.12 - 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) => {
|
const handleBeforeUnload = (event) => {
|
||||||
if (event.type === "beforeunload" && !isTabSwitching) {
|
if (event.type === "beforeunload" && !isTabSwitching) {
|
||||||
if (webrtcManagerRef.current && webrtcManagerRef.current.isConnected()) {
|
if (webrtcManagerRef.current && webrtcManagerRef.current.isConnected()) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -113,7 +113,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- GitHub Pages SEO -->
|
<!-- GitHub Pages SEO -->
|
||||||
<meta name="description" content="SecureBit.chat v4.8.11 — P2P messenger with ECDH + DTLS + SAS security and 18-layer military-grade cryptography">
|
<meta name="description" content="SecureBit.chat v4.8.12 — 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="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">
|
<meta name="author" content="Volodymyr">
|
||||||
<link rel="canonical" href="https://github.com/SecureBitChat/securebit-chat/">
|
<link rel="canonical" href="https://github.com/SecureBitChat/securebit-chat/">
|
||||||
@@ -148,13 +148,13 @@
|
|||||||
<!-- Update Manager - система принудительного обновления -->
|
<!-- Update Manager - система принудительного обновления -->
|
||||||
<script src="src/utils/updateManager.js"></script>
|
<script src="src/utils/updateManager.js"></script>
|
||||||
<script type="module" src="src/components/UpdateChecker.jsx"></script>
|
<script type="module" src="src/components/UpdateChecker.jsx"></script>
|
||||||
<script type="module" src="dist/qr-local.js?v=1781648539643"></script>
|
<script type="module" src="dist/qr-local.js?v=1781732923420"></script>
|
||||||
<script type="module" src="src/components/QRScanner.js?v=1781648539643"></script>
|
<script type="module" src="src/components/QRScanner.js?v=1781732923420"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="dist/app-boot.js?v=1781648539643"></script>
|
<script type="module" src="dist/app-boot.js?v=1781732923420"></script>
|
||||||
<script type="module" src="dist/app.js?v=1781648539643"></script>
|
<script type="module" src="dist/app.js?v=1781732923420"></script>
|
||||||
|
|
||||||
<script src="src/scripts/pwa-register.js"></script>
|
<script src="src/scripts/pwa-register.js"></script>
|
||||||
<script src="./src/pwa/install-prompt.js" type="module"></script>
|
<script src="./src/pwa/install-prompt.js" type="module"></script>
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "SecureBit.chat v4.8.11 - ECDH + DTLS + SAS",
|
"name": "SecureBit.chat v4.8.12 - ECDH + DTLS + SAS",
|
||||||
"short_name": "SecureBit",
|
"short_name": "SecureBit",
|
||||||
"description": "P2P messenger with ECDH + DTLS + SAS security, military-grade cryptography and Lightning Network payments",
|
"description": "P2P messenger with ECDH + DTLS + SAS security, military-grade cryptography and Lightning Network payments",
|
||||||
"start_url": "./",
|
"start_url": "./",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"version": "1781648539643",
|
"version": "1781732923420",
|
||||||
"buildVersion": "1781648539643",
|
"buildVersion": "1781732923420",
|
||||||
"appVersion": "4.8.11",
|
"appVersion": "4.8.12",
|
||||||
"buildTime": "2026-06-16T22:22:19.692Z",
|
"buildTime": "2026-06-17T21:48:43.476Z",
|
||||||
"buildId": "1781648539643-9244250",
|
"buildId": "1781732923420-be1d02f",
|
||||||
"gitHash": "9244250",
|
"gitHash": "be1d02f",
|
||||||
"generated": true,
|
"generated": true,
|
||||||
"generatedAt": "2026-06-16T22:22:19.693Z"
|
"generatedAt": "2026-06-17T21:48:43.478Z"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "securebit-chat",
|
"name": "securebit-chat",
|
||||||
"version": "4.8.11",
|
"version": "4.8.12",
|
||||||
"description": "Secure P2P Communication Application with End-to-End Encryption",
|
"description": "Secure P2P Communication Application with End-to-End Encryption",
|
||||||
"main": "index.html",
|
"main": "index.html",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+2
-2
@@ -276,7 +276,7 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
|
|||||||
}),
|
}),
|
||||||
React.createElement('div', {
|
React.createElement('div', {
|
||||||
key: 'text',
|
key: 'text',
|
||||||
className: "flex-1"
|
className: "flex-1 min-w-0"
|
||||||
}, [
|
}, [
|
||||||
React.createElement('div', {
|
React.createElement('div', {
|
||||||
key: 'message',
|
key: 'message',
|
||||||
@@ -2071,7 +2071,7 @@ import { loadIceSettings, saveIceSettings, clearIceSettings } from './network/ic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(' SecureBit.chat Enhanced Security Edition v4.8.10 - 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.12 - 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) => {
|
const handleBeforeUnload = (event) => {
|
||||||
if (event.type === 'beforeunload' && !isTabSwitching) {
|
if (event.type === 'beforeunload' && !isTabSwitching) {
|
||||||
|
|||||||
@@ -539,7 +539,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
React.createElement('p', {
|
React.createElement('p', {
|
||||||
key: 'subtitle',
|
key: 'subtitle',
|
||||||
className: 'text-xs sm:text-sm text-muted hidden sm:block'
|
className: 'text-xs sm:text-sm text-muted hidden sm:block'
|
||||||
}, 'End-to-end freedom v4.8.10')
|
}, 'End-to-end freedom v4.8.12')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|||||||
@@ -11460,6 +11460,14 @@ async processMessage(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleVerificationBothConfirmed(data) {
|
handleVerificationBothConfirmed(data) {
|
||||||
|
// Both parties can reach the "both confirmed" state independently: this
|
||||||
|
// party may have already detected it locally (_checkBothVerificationsConfirmed)
|
||||||
|
// and now also receives the peer's notification. Without this guard the
|
||||||
|
// "Both parties confirmed" message and the verified transition fire twice.
|
||||||
|
if (this.bothVerificationsConfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle notification that both parties have confirmed
|
// Handle notification that both parties have confirmed
|
||||||
this.bothVerificationsConfirmed = true;
|
this.bothVerificationsConfirmed = true;
|
||||||
|
|
||||||
|
|||||||
@@ -185,20 +185,24 @@ class AtomicOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async withLock(key, operation) {
|
async withLock(key, operation) {
|
||||||
if (this.locks.has(key)) {
|
// Serialize all operations sharing the same key. Must loop (not a single
|
||||||
|
// `if`): when 3+ callers queue on one key they all await the same in-flight
|
||||||
|
// lock, so after it resolves we have to re-check before claiming the slot —
|
||||||
|
// otherwise multiple operations run concurrently and break atomicity.
|
||||||
|
while (this.locks.has(key)) {
|
||||||
await this.locks.get(key);
|
await this.locks.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lockPromise = (async () => {
|
let releaseLock;
|
||||||
try {
|
const lockPromise = new Promise(resolve => { releaseLock = resolve; });
|
||||||
return await operation();
|
|
||||||
} finally {
|
|
||||||
this.locks.delete(key);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
this.locks.set(key, lockPromise);
|
this.locks.set(key, lockPromise);
|
||||||
return lockPromise;
|
|
||||||
|
try {
|
||||||
|
return await operation();
|
||||||
|
} finally {
|
||||||
|
this.locks.delete(key);
|
||||||
|
releaseLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1384,6 +1388,14 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async assembleFile(receivingState) {
|
async assembleFile(receivingState) {
|
||||||
|
// Idempotency guard: assembly must run (and onFileReceived must fire)
|
||||||
|
// exactly once per transfer. Guards against any duplicate/concurrent
|
||||||
|
// completion trigger so the receiver never sees the same file repeated.
|
||||||
|
if (receivingState._assembled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
receivingState._assembled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
receivingState.status = 'assembling';
|
receivingState.status = 'assembling';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user