From 5c456e514e74b64ac281594685dd7b4c137ebe01 Mon Sep 17 00:00:00 2001 From: Cassio Santos Date: Wed, 19 Nov 2025 10:18:19 -0300 Subject: [PATCH] fix: cannot send message on new Whatsapp Business limited accounts (#2080) * fix: cannot send message on new Whatsapp Business limited accounts * chore: add woraround to resend the message on 475 errors * fix: lint * fix: add handler * fix: lint * Update src/Socket/messages-send.ts --------- Co-authored-by: Rajeh Taher --- src/Defaults/index.ts | 2 +- src/Socket/chats.ts | 3 ++- src/Socket/messages-recv.ts | 48 +++++++++++++++++++++++++++++++++++++ src/Socket/messages-send.ts | 13 ++++++++++ src/Types/Auth.ts | 1 + src/Utils/auth-utils.ts | 2 +- src/Utils/history.ts | 1 + 7 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index 3f966b3b..4efe8ed9 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -41,7 +41,7 @@ export const PROCESSABLE_HISTORY_TYPES = [ proto.HistorySync.HistorySyncType.FULL, proto.HistorySync.HistorySyncType.ON_DEMAND, proto.HistorySync.HistorySyncType.NON_BLOCKING_DATA, - proto.HistorySync.HistorySyncType.INITIAL_STATUS_V3, + proto.HistorySync.HistorySyncType.INITIAL_STATUS_V3 ] export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index c938c645..3ae52791 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -1037,7 +1037,8 @@ export const makeChatsSocket = (config: SocketConfig) => { const historyMsg = getHistoryMsg(msg.message!) const shouldProcessHistoryMsg = historyMsg - ? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType! as proto.HistorySync.HistorySyncType) + ? shouldSyncHistoryMessage(historyMsg) && + PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType! as proto.HistorySync.HistorySyncType) : false // State machine: decide on sync and flush diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index 009497d6..91a265d7 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -872,6 +872,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { }) authState.creds.registered = true ev.emit('creds.update', authState.creds) + break + case 'privacy_token': + await handlePrivacyTokenNotification(node) + break } if (Object.keys(result).length) { @@ -879,6 +883,36 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } } + const handlePrivacyTokenNotification = async (node: BinaryNode) => { + const tokensNode = getBinaryNodeChild(node, 'tokens') + const from = jidNormalizedUser(node.attrs.from) + + if (!tokensNode) return + + const tokenNodes = getBinaryNodeChildren(tokensNode, 'token') + + for (const tokenNode of tokenNodes) { + const { attrs, content } = tokenNode + const type = attrs.type + const timestamp = attrs.t + + if (type === 'trusted_contact' && content instanceof Buffer) { + logger.debug( + { + from, + timestamp, + tcToken: content + }, + 'received trusted contact token' + ) + + await authState.keys.set({ + 'contacts-tc-token': { [from]: { token: content, timestamp } } + }) + } + } + } + async function decipherLinkPublicKey(data: Uint8Array | Buffer) { const buffer = toRequiredBuffer(data) const salt = buffer.slice(0, 32) @@ -1369,6 +1403,20 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } } ]) + + // resend the message with device_fanout=false, use at your own risk + // if (attrs.error === '475') { + // const msg = await getMessage(key) + // if (msg) { + // await relayMessage(key.remoteJid!, msg, { + // messageId: key.id!, + // useUserDevicesCache: false, + // additionalAttributes: { + // device_fanout: 'false' + // } + // }) + // } + // } } } diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index de7c7a05..8388297a 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -929,6 +929,19 @@ export const makeMessagesSocket = (config: SocketConfig) => { logger.debug({ jid }, 'adding device identity') } + const contactTcTokenData = + !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('contacts-tc-token', [destinationJid]) : {} + + const tcTokenBuffer = contactTcTokenData[destinationJid]?.token + + if (tcTokenBuffer) { + (stanza.content as BinaryNode[]).push({ + tag: 'tctoken', + attrs: {}, + content: tcTokenBuffer + }) + } + if (additionalNodes && additionalNodes.length > 0) { ;(stanza.content as BinaryNode[]).push(...additionalNodes) } diff --git a/src/Types/Auth.ts b/src/Types/Auth.ts index f1e61625..d6d449da 100644 --- a/src/Types/Auth.ts +++ b/src/Types/Auth.ts @@ -80,6 +80,7 @@ export type SignalDataTypeMap = { 'app-state-sync-version': LTHashState 'lid-mapping': string 'device-list': string[] + 'contacts-tc-token': { token: Buffer; timestamp?: string } } export type SignalDataSet = { [T in keyof SignalDataTypeMap]?: { [id: string]: SignalDataTypeMap[T] | null } } diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index 9661d0c6..a7296f5d 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -75,7 +75,7 @@ export function makeCacheableSignalKeyStore( const item = fetched[id] if (item) { data[id] = item - cache.set(getUniqueId(type, id), item) + cache.set(getUniqueId(type, id), item as SignalDataTypeMap[keyof SignalDataTypeMap]) } } } diff --git a/src/Utils/history.ts b/src/Utils/history.ts index 57f47614..7adbe09c 100644 --- a/src/Utils/history.ts +++ b/src/Utils/history.ts @@ -102,6 +102,7 @@ export const downloadAndProcessHistorySyncNotification = async ( } else { historyMsg = await downloadHistory(msg, options) } + return processHistoryMessage(historyMsg) }