From b4863b624e6f3d87382ce654f05f08ffc6e0cee2 Mon Sep 17 00:00:00 2001 From: vini Date: Wed, 19 Nov 2025 10:13:51 -0300 Subject: [PATCH] refactor(retry): improve message encryption and retry logic (#2055) --- src/Socket/messages-recv.ts | 2 +- src/Socket/messages-send.ts | 100 +++++++++++++++++---------------- src/Types/Message.ts | 7 ++- src/Utils/decode-wa-message.ts | 7 ++- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index b242d276..009497d6 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -1200,7 +1200,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { await processingMutex.mutex(async () => { await decrypt() // message failed to decrypt - if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) { + if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') { if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) { return sendMessageAck(node, NACK_REASONS.ParsingError) } diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index a2c989df..de7c7a05 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -572,6 +572,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const isStatus = jid === statusJid const isLid = server === 'lid' const isNewsletter = server === 'newsletter' + const isGroupOrStatus = isGroup || isStatus const finalJid = jid msgId = msgId || generateMessageIDV2(meId) @@ -639,7 +640,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types } - if (isGroup || isStatus) { + if (isGroupOrStatus && !isRetryResend) { const [groupData, senderKeyMap] = await Promise.all([ (async () => { let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields? @@ -663,28 +664,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { })() ]) - if (!participant) { - const participantsList = [] - if (isStatus) { - if (statusJidList?.length) participantsList.push(...statusJidList) - } else { - // default to LID based groups - let groupAddressingMode = 'lid' - if (groupData) { - participantsList.push(...groupData.participants.map(p => p.id)) - groupAddressingMode = groupData?.addressingMode || groupAddressingMode - } - - // default to lid addressing mode in a group - additionalAttributes = { - ...additionalAttributes, - addressing_mode: groupAddressingMode - } - } - - const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false) - devices.push(...additionalDevices) - } + const participantsList = groupData ? groupData.participants.map(p => p.id) : [] if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) { additionalAttributes = { @@ -693,6 +673,20 @@ export const makeMessagesSocket = (config: SocketConfig) => { } } + if (isStatus && statusJidList) { + participantsList.push(...statusJidList) + } + + const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false) + devices.push(...additionalDevices) + + if (isGroup) { + additionalAttributes = { + ...additionalAttributes, + addressing_mode: groupData?.addressingMode || 'lid' + } + } + const patched = await patchMessageBeforeSending(message) if (Array.isArray(patched)) { throw new Boom('Per-jid patching is not supported in groups') @@ -744,30 +738,13 @@ export const makeMessagesSocket = (config: SocketConfig) => { participants.push(...result.nodes) } - if (isRetryResend) { - const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({ - data: bytes, - jid: participant?.jid! - }) + binaryNodeContent.push({ + tag: 'enc', + attrs: { v: '2', type: 'skmsg', ...extraAttrs }, + content: ciphertext + }) - binaryNodeContent.push({ - tag: 'enc', - attrs: { - v: '2', - type, - count: participant!.count.toString() - }, - content: encryptedContent - }) - } else { - binaryNodeContent.push({ - tag: 'enc', - attrs: { v: '2', type: 'skmsg', ...extraAttrs }, - content: ciphertext - }) - - await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) - } + await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) } else { // ADDRESSING CONSISTENCY: Match own identity to conversation context // TODO: investigate if this is true @@ -781,7 +758,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const { user: ownUser } = jidDecode(ownId)! - if (!participant) { + if (!isRetryResend) { const targetUserServer = isLid ? 'lid' : 's.whatsapp.net' devices.push({ user, @@ -869,6 +846,35 @@ export const makeMessagesSocket = (config: SocketConfig) => { shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 } + if (isRetryResend) { + const isParticipantLid = isLidUser(participant!.jid) + const isMe = areJidsSameUser(participant!.jid, isParticipantLid ? meLid : meId) + + const encodedMessageToSend = isMe + ? encodeWAMessage({ + deviceSentMessage: { + destinationJid, + message + } + }) + : encodeWAMessage(message) + + const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({ + data: encodedMessageToSend, + jid: participant!.jid + }) + + binaryNodeContent.push({ + tag: 'enc', + attrs: { + v: '2', + type, + count: participant!.count.toString() + }, + content: encryptedContent + }) + } + if (participants.length) { if (additionalAttributes?.['category'] === 'peer') { const peerNode = participants[0]?.content?.[0] as BinaryNode diff --git a/src/Types/Message.ts b/src/Types/Message.ts index 452876c4..dc8d7dfd 100644 --- a/src/Types/Message.ts +++ b/src/Types/Message.ts @@ -8,7 +8,12 @@ import type { CacheStore } from './Socket' // export the WAMessage Prototypes export { proto as WAProto } -export type WAMessage = proto.IWebMessageInfo & { key: WAMessageKey; messageStubParameters?: any } +export type WAMessage = proto.IWebMessageInfo & { + key: WAMessageKey + messageStubParameters?: any + category?: string + retryCount?: number +} export type WAMessageContent = proto.IMessage export type WAContactMessage = proto.Message.IContactMessage export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index 99d7dc7c..dc13fa4e 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -206,6 +206,7 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin const fullMessage: WAMessage = { key, + category: stanza.attrs.category, messageTimestamp: +stanza.attrs.t!, pushName: pushname, broadcast: isJidBroadcast(from) @@ -248,6 +249,10 @@ export const decryptMessageNode = ( fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE } + if (attrs.count && tag === 'enc') { + fullMessage.retryCount = Number(attrs.count) + } + if (tag !== 'enc' && tag !== 'plaintext') { continue } @@ -333,7 +338,7 @@ export const decryptMessageNode = ( } // if nothing was found to decrypt - if (!decryptables) { + if (!decryptables && !fullMessage.key?.isViewOnce) { fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT] }