Files
securebit-chat/tests/timer-lifecycle.test.mjs
lockbitchat f2a4276b31
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
fix: remove untracked disconnect timer
2026-05-17 23:16:14 -04:00

181 lines
7.0 KiB
JavaScript

import assert from 'node:assert/strict';
globalThis.window = {
EnhancedSecureCryptoUtils: {
secureLog: { log() {} }
}
};
globalThis.CustomEvent = class CustomEvent {
constructor(type, init) {
this.type = type;
this.detail = init?.detail;
}
};
globalThis.document = { dispatchEvent() {} };
const { EnhancedSecureWebRTCManager } = await import('../src/network/EnhancedSecureWebRTCManager.js');
const realSetTimeout = globalThis.setTimeout;
const realClearTimeout = globalThis.clearTimeout;
const realSetInterval = globalThis.setInterval;
const realClearInterval = globalThis.clearInterval;
const timers = [];
globalThis.setTimeout = (callback, delay) => {
const timer = { kind: 'timeout', callback, delay, cleared: false };
timers.push(timer);
return timer;
};
globalThis.clearTimeout = (timer) => {
if (timer) timer.cleared = true;
};
globalThis.setInterval = (callback, delay) => {
const timer = { kind: 'interval', callback, delay, cleared: false };
timers.push(timer);
return timer;
};
globalThis.clearInterval = (timer) => {
if (timer) timer.cleared = true;
};
try {
// Periodic log cleanup is tracked and cleared with the existing timer system.
{
const manager = {
_activeTimers: new Set(),
_startKeySecurityMonitoring() {},
_verifyAPIIntegrity() { return true; },
_startSecurityMonitoring() {},
_cleanupLogs() {},
_secureLog() {}
};
manager._trackActiveTimer = EnhancedSecureWebRTCManager.prototype._trackActiveTimer;
EnhancedSecureWebRTCManager.prototype._finalizeSecureInitialization.call(manager);
assert.equal(manager._activeTimers.size, 1);
const logTimer = manager._logCleanupInterval;
EnhancedSecureWebRTCManager.prototype._stopAllTimers.call(manager);
assert.equal(logTimer.cleared, true);
}
// Deferred file-transfer retries are tracked, cleared, and cannot re-run after session shutdown.
{
let initCalls = 0;
const manager = {
_sessionAlive: true,
_activeTimers: new Set(),
_fileTransferInitRetryTimers: new Set(),
fileTransferSystem: null,
dataChannel: { readyState: 'open' },
isVerified: false,
_secureLog() {},
_trackActiveTimer: EnhancedSecureWebRTCManager.prototype._trackActiveTimer,
_untrackActiveTimer: EnhancedSecureWebRTCManager.prototype._untrackActiveTimer,
_scheduleFileTransferInitRetry: EnhancedSecureWebRTCManager.prototype._scheduleFileTransferInitRetry,
initializeFileTransfer() {
initCalls += 1;
}
};
EnhancedSecureWebRTCManager.prototype.initializeFileTransfer.call(manager);
const retryTimer = [...manager._fileTransferInitRetryTimers][0];
manager._sessionAlive = false;
EnhancedSecureWebRTCManager.prototype._stopAllTimers.call(manager);
if (!retryTimer.cleared) retryTimer.callback();
assert.equal(retryTimer.cleared, true);
assert.equal(manager._fileTransferInitRetryTimers.size, 0);
assert.equal(initCalls, 0);
}
// Repeated peer-disconnect notifications schedule only one delayed cleanup, and cleanup is cancelled on disconnect.
{
let disconnectCalls = 0;
const manager = {
_sessionAlive: true,
_activeTimers: new Set(),
peerDisconnectNotificationSent: false,
_peerDisconnectCleanupTimer: null,
deliverMessageToUI() {},
onStatusChange() {},
stopHeartbeat() {},
onKeyExchange() {},
onVerificationRequired() {},
disconnect() { disconnectCalls += 1; },
_secureLog() {},
_trackActiveTimer: EnhancedSecureWebRTCManager.prototype._trackActiveTimer,
_untrackActiveTimer: EnhancedSecureWebRTCManager.prototype._untrackActiveTimer
};
EnhancedSecureWebRTCManager.prototype.handlePeerDisconnectNotification.call(manager, { reason: 'connection_lost' });
EnhancedSecureWebRTCManager.prototype.handlePeerDisconnectNotification.call(manager, { reason: 'connection_lost' });
const scheduled = timers.filter(timer => timer.kind === 'timeout' && timer.delay === 2000);
assert.equal(scheduled.length, 1);
manager._sessionAlive = false;
EnhancedSecureWebRTCManager.prototype._stopAllTimers.call(manager);
if (!scheduled[0].cleared) scheduled[0].callback();
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;
globalThis.setInterval = realSetInterval;
globalThis.clearInterval = realClearInterval;
}
console.log('Timer lifecycle tests passed');