diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index 1ff3f7d3..2ef0d008 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -425,10 +425,18 @@ export const decryptMessageNode = ( // Automatic cleanup of corrupted session (if enabled) if (autoCleanCorrupted) { try { - await cleanupCorruptedSession(decryptionJid, repository, logger) + const deletedCount = await cleanupCorruptedSession(decryptionJid, repository, logger) + + // Mask only user portion of JID for privacy (preserve domain info) + const { user, server } = jidDecode(decryptionJid) || {} + const maskedUser = user && user.length > 8 + ? `${user.substring(0, 4)}****${user.substring(user.length - 4)}` + : user + const maskedJid = maskedUser && server ? `${maskedUser}@${server}` : decryptionJid + logger.info( - { decryptionJid, author }, - '✅ Corrupted session cleaned up. New session will be created on next message.' + { jid: decryptionJid, maskedJid, targetedDevices: deletedCount }, + `🔄 Session Reset | JID: ${maskedJid} | Targeted: ${deletedCount} devices | Will recreate on next message` ) } catch (cleanupErr) { logger.error( @@ -500,11 +508,11 @@ async function cleanupCorruptedSession( jid: string, repository: SignalRepositoryWithLIDStore, logger: ILogger -): Promise { +): Promise { const { user, device } = jidDecode(jid) || {} if (!user) { logger.warn({ jid }, 'Cannot cleanup session - invalid JID') - return + return 0 } // Build list of JIDs to delete (primary + secondary devices) @@ -541,8 +549,5 @@ async function cleanupCorruptedSession( await repository.deleteSession(jidsToDelete) - logger.debug( - { jid, user, device, deletedSessions: jidsToDelete }, - 'Deleted corrupted sessions for user' - ) + return jidsToDelete.length } diff --git a/src/index.ts b/src/index.ts index 48ee4303..7449b7ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,73 +2,24 @@ // Format session error logs from libsignal BEFORE any imports // This MUST be at the very top to intercept console before libsignal loads // ============================================ -const _origConsoleLog = console.log const _origConsoleError = console.error -// Track last error to avoid duplicates -let _lastErrorMsg = '' -let _lastErrorTime = 0 +// Track errors by type + JID to avoid duplicates (using Map for better performance) +const _errorTimestamps = new Map() +const DEDUP_WINDOW_MS = 150 -console.log = function(...args: unknown[]) { +console.error = function(...args: unknown[]) { if (args.length > 0 && typeof args[0] === 'string') { const msg = args[0] const stack = new Error().stack || '' const isFromLibsignal = stack.includes('libsignal') || stack.includes('session_cipher') if (isFromLibsignal) { - // Suppress session lifecycle dumps + // Suppress session lifecycle logs if (msg.startsWith('Closing session')) { return } - // Format session errors cleanly - if ( - msg.includes('Session error') || - msg.includes('Bad MAC') || - msg.includes('MessageCounterError') || - msg.includes('Key used already') || - msg.includes('Failed to decrypt') - ) { - // Extract error type - let errorType = '⚠️ Session Error' - if (msg.includes('Bad MAC')) errorType = '🔐 Bad MAC Error' - else if (msg.includes('MessageCounterError') || msg.includes('Key used already')) errorType = '🔢 Counter Error' - else if (msg.includes('Failed to decrypt')) errorType = '🔌 Decryption Failed' - - // Extract JID if present (format: 46802258641027_1.0 or similar) - const jidMatch = msg.match(/(\d{10,}(?:_\d+\.\d+)?)/); - const jid = jidMatch ? jidMatch[1] : null - const maskedJid = jid && jid.length > 8 ? `${jid.substring(0, 4)}****${jid.substring(jid.length - 4)}` : jid - - // Format clean message - const cleanMsg = maskedJid - ? `${errorType} | JID: ${maskedJid}` - : errorType - - // Avoid duplicate logs (within 100ms) - const now = Date.now() - if (cleanMsg === _lastErrorMsg && now - _lastErrorTime < 100) { - return - } - _lastErrorMsg = cleanMsg - _lastErrorTime = now - - _origConsoleError(cleanMsg) - return - } - } - } - - _origConsoleLog.apply(console, args) -} - -console.error = function(...args: unknown[]) { - if (args.length > 0 && typeof args[0] === 'string') { - const msg = args[0] - const stack = new Error().stack || '' - const isFromLibsignal = stack.includes('libsignal') || stack.includes('session_cipher') - - if (isFromLibsignal) { // Format session errors cleanly if ( msg.includes('Session error') || @@ -84,7 +35,7 @@ console.error = function(...args: unknown[]) { else if (msg.includes('Failed to decrypt')) errorType = '🔌 Decryption Failed' // Extract JID from stack trace or message - const jidMatch = (msg + String(args[1] ?? '')).match(/(\d{10,}(?:_\d+\.\d+)?)/); + const jidMatch = (msg + String(args[1] ?? '')).match(/(\d{10,}(?:_\d+\.\d+)?)/) const jid = jidMatch ? jidMatch[1] : null const maskedJid = jid && jid.length > 8 ? `${jid.substring(0, 4)}****${jid.substring(jid.length - 4)}` : jid @@ -93,13 +44,22 @@ console.error = function(...args: unknown[]) { ? `${errorType} | JID: ${maskedJid}` : errorType - // Avoid duplicate logs (within 100ms) + // Deduplication key: type + ORIGINAL JID (use unmasked to prevent collisions) + const dedupeKey = `${errorType}:${jid || 'unknown'}` const now = Date.now() - if (cleanMsg === _lastErrorMsg && now - _lastErrorTime < 100) { - return + const lastTime = _errorTimestamps.get(dedupeKey) + + if (lastTime && now - lastTime < DEDUP_WINDOW_MS) { + return // Skip duplicate within 150ms window + } + + _errorTimestamps.set(dedupeKey, now) + + // Cleanup old entries (keep only last 50 to prevent memory leak) + if (_errorTimestamps.size > 50) { + const oldestKey = _errorTimestamps.keys().next().value + if (oldestKey) _errorTimestamps.delete(oldestKey) } - _lastErrorMsg = cleanMsg - _lastErrorTime = now _origConsoleError(cleanMsg) return