refactor(retry): improve message encryption and retry logic (#2055)

This commit is contained in:
vini
2025-11-19 10:13:51 -03:00
committed by GitHub
parent 9b012baa5c
commit b4863b624e
4 changed files with 66 additions and 50 deletions
+1 -1
View File
@@ -1200,7 +1200,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
await processingMutex.mutex(async () => { await processingMutex.mutex(async () => {
await decrypt() await decrypt()
// message failed to 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) { if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
return sendMessageAck(node, NACK_REASONS.ParsingError) return sendMessageAck(node, NACK_REASONS.ParsingError)
} }
+53 -47
View File
@@ -572,6 +572,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const isStatus = jid === statusJid const isStatus = jid === statusJid
const isLid = server === 'lid' const isLid = server === 'lid'
const isNewsletter = server === 'newsletter' const isNewsletter = server === 'newsletter'
const isGroupOrStatus = isGroup || isStatus
const finalJid = jid const finalJid = jid
msgId = msgId || generateMessageIDV2(meId) msgId = msgId || generateMessageIDV2(meId)
@@ -639,7 +640,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types
} }
if (isGroup || isStatus) { if (isGroupOrStatus && !isRetryResend) {
const [groupData, senderKeyMap] = await Promise.all([ const [groupData, senderKeyMap] = await Promise.all([
(async () => { (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? 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 = groupData ? groupData.participants.map(p => p.id) : []
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)
}
if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) { if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
additionalAttributes = { 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) const patched = await patchMessageBeforeSending(message)
if (Array.isArray(patched)) { if (Array.isArray(patched)) {
throw new Boom('Per-jid patching is not supported in groups') throw new Boom('Per-jid patching is not supported in groups')
@@ -744,30 +738,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
participants.push(...result.nodes) participants.push(...result.nodes)
} }
if (isRetryResend) { binaryNodeContent.push({
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({ tag: 'enc',
data: bytes, attrs: { v: '2', type: 'skmsg', ...extraAttrs },
jid: participant?.jid! content: ciphertext
}) })
binaryNodeContent.push({ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
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 } })
}
} else { } else {
// ADDRESSING CONSISTENCY: Match own identity to conversation context // ADDRESSING CONSISTENCY: Match own identity to conversation context
// TODO: investigate if this is true // TODO: investigate if this is true
@@ -781,7 +758,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const { user: ownUser } = jidDecode(ownId)! const { user: ownUser } = jidDecode(ownId)!
if (!participant) { if (!isRetryResend) {
const targetUserServer = isLid ? 'lid' : 's.whatsapp.net' const targetUserServer = isLid ? 'lid' : 's.whatsapp.net'
devices.push({ devices.push({
user, user,
@@ -869,6 +846,35 @@ export const makeMessagesSocket = (config: SocketConfig) => {
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 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 (participants.length) {
if (additionalAttributes?.['category'] === 'peer') { if (additionalAttributes?.['category'] === 'peer') {
const peerNode = participants[0]?.content?.[0] as BinaryNode const peerNode = participants[0]?.content?.[0] as BinaryNode
+6 -1
View File
@@ -8,7 +8,12 @@ import type { CacheStore } from './Socket'
// export the WAMessage Prototypes // export the WAMessage Prototypes
export { proto as WAProto } 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 WAMessageContent = proto.IMessage
export type WAContactMessage = proto.Message.IContactMessage export type WAContactMessage = proto.Message.IContactMessage
export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage
+6 -1
View File
@@ -206,6 +206,7 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin
const fullMessage: WAMessage = { const fullMessage: WAMessage = {
key, key,
category: stanza.attrs.category,
messageTimestamp: +stanza.attrs.t!, messageTimestamp: +stanza.attrs.t!,
pushName: pushname, pushName: pushname,
broadcast: isJidBroadcast(from) broadcast: isJidBroadcast(from)
@@ -248,6 +249,10 @@ export const decryptMessageNode = (
fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE 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') { if (tag !== 'enc' && tag !== 'plaintext') {
continue continue
} }
@@ -333,7 +338,7 @@ export const decryptMessageNode = (
} }
// if nothing was found to decrypt // if nothing was found to decrypt
if (!decryptables) { if (!decryptables && !fullMessage.key?.isViewOnce) {
fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT] fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT]
} }