feat: add identity change handler for improved connection stability

Cherry-pick from upstream PR #2264 (commit 5cbad317) with enhancements:

## Changes

### New Files
- `src/Utils/identity-change-handler.ts`: Centralized identity change handling
  - Enterprise-grade JSDoc documentation
  - Clear result types for all 9 possible outcomes
  - Debouncing to prevent duplicate session refreshes
  - Companion device filtering
  - Offline notification handling

- `src/__tests__/Socket/identity-change-handling.test.ts`: Comprehensive tests
  - Core functionality tests
  - Debounce behavior tests
  - Error handling tests
  - Edge case coverage

### Modified Files
- `src/Socket/messages-recv.ts`: Updated error handling
  - MISSING_KEYS_ERROR now returns NACK with ParsingError
  - Maintains CTWA recovery logic intact
  - Preserves all Prometheus metrics integration

- `src/Utils/generics.ts`: Added `isStringNullOrEmpty()` helper
- `src/Utils/index.ts`: Added identity-change-handler export

## Benefits
- Fixes "Online" status shown when actually disconnected (#2132)
- Better session management after contact identity changes
- Clear separation of concerns with dedicated handler module

Co-authored-by: João Lucas de Oliveira Lopes <55464917+jlucaso1@users.noreply.github.com>
This commit is contained in:
Claude
2026-01-22 18:34:19 +00:00
parent 9e1e67df6a
commit c4da46321f
5 changed files with 408 additions and 16 deletions
+13 -16
View File
@@ -36,6 +36,7 @@ import {
getHistoryMsg,
getNextPreKeys,
getStatusFromReceiptType,
handleIdentityChange,
hkdf,
MISSING_KEYS_ERROR_TEXT,
NACK_REASONS,
@@ -564,21 +565,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
await uploadPreKeys()
}
} else {
const identityNode = getBinaryNodeChild(node, 'identity')
if (identityNode) {
logger.info({ jid: from }, 'identity changed')
if (identityAssertDebounce.get(from!)) {
logger.debug({ jid: from }, 'skipping identity assert (debounced)')
return
}
const result = await handleIdentityChange(node, {
meId: authState.creds.me?.id,
meLid: authState.creds.me?.lid,
validateSession: signalRepository.validateSession,
assertSessions,
debounceCache: identityAssertDebounce,
logger
})
identityAssertDebounce.set(from!, true)
try {
await assertSessions([from!], true)
} catch (error) {
logger.warn({ error, jid: from }, 'failed to assert sessions after identity change')
}
} else {
if (result.action === 'no_identity_node') {
logger.info({ node }, 'unknown encrypt notification')
}
}
@@ -1239,9 +1235,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
await decrypt()
// message failed to decrypt
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
// Handle "Missing keys" - standard decryption failure, just ACK
// Handle "Missing keys" - standard decryption failure
// Return NACK with parsing error to signal the issue
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
return sendMessageAck(node)
return sendMessageAck(node, NACK_REASONS.ParsingError)
}
// Handle "Message absent from node" - likely a CTWA (Click-to-WhatsApp) ads message