mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: mutex processing in a chat to preserve order of events
This commit is contained in:
@@ -2,7 +2,7 @@ import { Boom } from '@hapi/boom'
|
||||
import { proto } from '../../WAProto'
|
||||
import { AppStateChunk, Chat, ChatModification, ChatMutation, Contact, LTHashState, PresenceData, SocketConfig, WABusinessHoursConfig, WABusinessProfile, WAMediaUpload, WAPatchCreate, WAPatchName, WAPresence } from '../Types'
|
||||
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, extractSyncdPatches, generateProfilePicture, newLTHashState, toNumber } from '../Utils'
|
||||
import makeMutex from '../Utils/make-mutex'
|
||||
import { makeMutex } from '../Utils/make-mutex'
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { makeMessagesSocket } from './messages-send'
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { proto } from '../../WAProto'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { Chat, GroupMetadata, MessageUserReceipt, ParticipantAction, SocketConfig, WAMessageStubType } from '../Types'
|
||||
import { decodeMessageStanza, downloadAndProcessHistorySyncNotification, encodeBigEndian, generateSignalPubKey, toNumber, xmppPreKey, xmppSignedPreKey } from '../Utils'
|
||||
import { makeKeyedMutex } from '../Utils/make-mutex'
|
||||
import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getAllBinaryNodeChildren, getBinaryNodeChildren, isJidGroup, jidDecode, jidEncode, jidNormalizedUser } from '../WABinary'
|
||||
import { makeChatsSocket } from './chats'
|
||||
import { extractGroupMetadata } from './groups'
|
||||
@@ -37,6 +38,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
resyncMainAppState,
|
||||
} = sock
|
||||
|
||||
/** the mutex ensures that the notifications (receipts, messages etc.) are processed in order */
|
||||
const processingMutex = makeKeyedMutex()
|
||||
|
||||
const msgRetryMap = config.msgRetryCounterMap || { }
|
||||
|
||||
const historyCache = new Set<string>()
|
||||
@@ -338,8 +342,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
}
|
||||
|
||||
// recv a message
|
||||
ws.on('CB:message', async(stanza: BinaryNode) => {
|
||||
const msg = await decodeMessageStanza(stanza, authState)
|
||||
ws.on('CB:message', (stanza: BinaryNode) => {
|
||||
const { fullMessage: msg, decryptionTask } = decodeMessageStanza(stanza, authState)
|
||||
processingMutex.mutex(
|
||||
msg.key.remoteJid!,
|
||||
async() => {
|
||||
await decryptionTask
|
||||
// message failed to decrypt
|
||||
if(msg.messageStubType === proto.WebMessageInfo.WebMessageInfoStubType.CIPHERTEXT) {
|
||||
logger.error(
|
||||
@@ -356,6 +364,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
|
||||
msg.key.remoteJid = jidNormalizedUser(msg.key.remoteJid!)
|
||||
ev.emit('messages.upsert', { messages: [msg], type: stanza.attrs.offline ? 'append' : 'notify' })
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
ws.on('CB:ack,class:message', async(node: BinaryNode) => {
|
||||
@@ -428,6 +438,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
participant: attrs.participant
|
||||
}
|
||||
|
||||
await processingMutex.mutex(
|
||||
remoteJid,
|
||||
async() => {
|
||||
const status = getStatusFromReceiptType(attrs.type)
|
||||
if(
|
||||
typeof status !== 'undefined' &&
|
||||
@@ -481,14 +494,17 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
if(shouldAck) {
|
||||
await sendMessageAck(node, { class: 'receipt', type: attrs.type })
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ws.on('CB:receipt', handleReceipt)
|
||||
|
||||
ws.on('CB:notification', async(node: BinaryNode) => {
|
||||
await sendMessageAck(node, { class: 'notification', type: node.attrs.type })
|
||||
|
||||
const remoteJid = node.attrs.from
|
||||
processingMutex.mutex(
|
||||
remoteJid,
|
||||
() => {
|
||||
const msg = processNotification(node)
|
||||
if(msg) {
|
||||
const fromMe = areJidsSameUser(node.attrs.participant || node.attrs.from, authState.creds.me!.id)
|
||||
@@ -504,6 +520,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const fullMsg = proto.WebMessageInfo.fromObject(msg)
|
||||
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
await sendMessageAck(node, { class: 'notification', type: node.attrs.type })
|
||||
})
|
||||
|
||||
ev.on('messages.upsert', async({ messages, type }) => {
|
||||
@@ -520,7 +540,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
}
|
||||
}
|
||||
|
||||
await processMessage(msg, chat)
|
||||
await processingMutex.mutex(
|
||||
'p-' + chat.id!,
|
||||
() => processMessage(msg, chat)
|
||||
)
|
||||
|
||||
if(!!msg.message && !msg.message!.protocolMessage) {
|
||||
chat.conversationTimestamp = toNumber(msg.messageTimestamp)
|
||||
if(!msg.key.fromMe) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { decryptGroupSignalProto, decryptSignalProto, processSenderKeyMessage }
|
||||
|
||||
type MessageType = 'chat' | 'peer_broadcast' | 'other_broadcast' | 'group' | 'direct_peer_status' | 'other_status'
|
||||
|
||||
export const decodeMessageStanza = async(stanza: BinaryNode, auth: AuthenticationState) => {
|
||||
export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationState) => {
|
||||
//const deviceIdentity = (stanza.content as BinaryNodeM[])?.find(m => m.tag === 'device-identity')
|
||||
//const deviceIdentityBytes = deviceIdentity ? deviceIdentity.content as Buffer : undefined
|
||||
|
||||
@@ -81,6 +81,9 @@ export const decodeMessageStanza = async(stanza: BinaryNode, auth: Authenticatio
|
||||
fullMessage.status = proto.WebMessageInfo.WebMessageInfoStatus.SERVER_ACK
|
||||
}
|
||||
|
||||
return {
|
||||
fullMessage,
|
||||
decryptionTask: (async() => {
|
||||
if(Array.isArray(stanza.content)) {
|
||||
for(const { tag, attrs, content } of stanza.content) {
|
||||
if(tag !== 'enc') {
|
||||
@@ -123,6 +126,6 @@ export const decodeMessageStanza = async(stanza: BinaryNode, auth: Authenticatio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fullMessage
|
||||
})()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
export default () => {
|
||||
export const makeMutex = () => {
|
||||
let task = Promise.resolve() as Promise<any>
|
||||
return {
|
||||
mutex<T>(code: () => Promise<T>):Promise<T> {
|
||||
mutex<T>(code: () => Promise<T> | T): Promise<T> {
|
||||
task = (async() => {
|
||||
// wait for the previous task to complete
|
||||
// if there is an error, we swallow so as to not block the queue
|
||||
@@ -20,3 +19,18 @@ export default () => {
|
||||
}
|
||||
}
|
||||
|
||||
export type Mutex = ReturnType<typeof makeMutex>
|
||||
|
||||
export const makeKeyedMutex = () => {
|
||||
const map: { [id: string]: Mutex } = {}
|
||||
|
||||
return {
|
||||
mutex<T>(key: string, task: () => Promise<T> | T): Promise<T> {
|
||||
if(!map[key]) {
|
||||
map[key] = makeMutex()
|
||||
}
|
||||
|
||||
return map[key].mutex(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user