From f2a4276b3135ff7772fbfa547c235370841220a7 Mon Sep 17 00:00:00 2001 From: lockbitchat Date: Sun, 17 May 2026 23:16:14 -0400 Subject: [PATCH] fix: remove untracked disconnect timer --- src/network/EnhancedSecureWebRTCManager.js | 3 -- tests/timer-lifecycle.test.mjs | 56 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/network/EnhancedSecureWebRTCManager.js b/src/network/EnhancedSecureWebRTCManager.js index 2fd9f7a..7b49fa7 100644 --- a/src/network/EnhancedSecureWebRTCManager.js +++ b/src/network/EnhancedSecureWebRTCManager.js @@ -11735,9 +11735,6 @@ async processMessage(data) { this.intentionalDisconnect = true; window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Starting intentional disconnect'); this.sendDisconnectNotification(); - setTimeout(() => { - this.sendDisconnectNotification(); - }, 100); // Stop every timer-backed subsystem first. this._stopAllTimers(); diff --git a/tests/timer-lifecycle.test.mjs b/tests/timer-lifecycle.test.mjs index 3d8f9ed..06cca6b 100644 --- a/tests/timer-lifecycle.test.mjs +++ b/tests/timer-lifecycle.test.mjs @@ -114,6 +114,62 @@ try { assert.equal(scheduled[0].cleared, true); assert.equal(disconnectCalls, 0); } + + // Intentional disconnect performs notification before teardown without leaving a delayed retry timer behind. + { + let notifications = 0; + const manager = { + _sessionAlive: true, + _activeTimers: new Set(), + intentionalDisconnect: false, + fileTransferSystem: null, + dataChannel: null, + heartbeatChannel: null, + peerConnection: null, + decoyTimers: new Map(), + decoyChannels: new Map(), + packetBuffer: new Map(), + chunkQueue: [], + processedMessageIds: new Set(), + messageCounter: 0, + keyVersions: new Map(), + oldKeys: new Map(), + currentKeyVersion: 0, + lastKeyRotation: 0, + sequenceNumber: 0, + expectedSequenceNumber: 0, + replayWindow: new Set(), + messageQueue: [], + _heartbeatConfig: {}, + _secureLog() {}, + _stopAllTimers: EnhancedSecureWebRTCManager.prototype._stopAllTimers, + stopHeartbeat() {}, + stopFakeTrafficGeneration() {}, + _wipeEphemeralKeys() {}, + _hardWipeOldKeys() {}, + _secureCleanupCryptographicMaterials() {}, + _clearVerificationStates() {}, + _secureWipeMemory() {}, + _forceGarbageCollection() { return Promise.resolve(); }, + sendDisconnectNotification() { notifications += 1; }, + onStatusChange() {}, + onKeyExchange() {}, + onVerificationRequired() {} + }; + + const timersBeforeDisconnect = timers.length; + EnhancedSecureWebRTCManager.prototype.disconnect.call(manager); + assert.equal(notifications, 1); + assert.equal(manager._activeTimers.size, 0); + assert.equal(timers.length, timersBeforeDisconnect); + + // A second disconnect/reconnect-style cycle still does not accumulate deferred timers. + manager._sessionAlive = true; + EnhancedSecureWebRTCManager.prototype.disconnect.call(manager); + assert.equal(notifications, 2); + assert.equal(manager._activeTimers.size, 0); + assert.equal(timers.length, timersBeforeDisconnect); + } } finally { globalThis.setTimeout = realSetTimeout; globalThis.clearTimeout = realClearTimeout;