@periskope/baileys 6.7.19-4 → 6.8.0-alpha-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/WAProto/GenerateStatics.sh +2 -2
  2. package/WAProto/index.d.ts +2793 -45578
  3. package/WAProto/index.js +19344 -147065
  4. package/lib/Defaults/index.d.ts +5 -0
  5. package/lib/Defaults/index.d.ts.map +1 -1
  6. package/lib/Defaults/index.js +8 -2
  7. package/lib/Defaults/index.js.map +1 -1
  8. package/lib/Signal/Group/queue-job.d.ts +1 -0
  9. package/lib/Signal/Group/queue-job.d.ts.map +1 -1
  10. package/lib/Signal/Group/queue-job.js +3 -0
  11. package/lib/Signal/Group/queue-job.js.map +1 -1
  12. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -1
  13. package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
  14. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -1
  15. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -1
  16. package/lib/Signal/Group/sender-key-message.js +1 -1
  17. package/lib/Signal/Group/sender-key-message.js.map +1 -1
  18. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -1
  19. package/lib/Signal/Group/sender-key-state.js +6 -1
  20. package/lib/Signal/Group/sender-key-state.js.map +1 -1
  21. package/lib/Signal/libsignal.d.ts.map +1 -1
  22. package/lib/Signal/libsignal.js +5 -53
  23. package/lib/Signal/libsignal.js.map +1 -1
  24. package/lib/Signal/lid-mapping.d.ts +1 -3
  25. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  26. package/lib/Signal/lid-mapping.js +3 -29
  27. package/lib/Signal/lid-mapping.js.map +1 -1
  28. package/lib/Socket/business.d.ts +14 -5
  29. package/lib/Socket/business.d.ts.map +1 -1
  30. package/lib/Socket/business.js +122 -1
  31. package/lib/Socket/business.js.map +1 -1
  32. package/lib/Socket/chats.d.ts +8 -2
  33. package/lib/Socket/chats.d.ts.map +1 -1
  34. package/lib/Socket/chats.js +39 -2
  35. package/lib/Socket/chats.js.map +1 -1
  36. package/lib/Socket/communities.d.ts +13 -2
  37. package/lib/Socket/communities.d.ts.map +1 -1
  38. package/lib/Socket/communities.js +45 -1
  39. package/lib/Socket/communities.js.map +1 -1
  40. package/lib/Socket/groups.d.ts +7 -2
  41. package/lib/Socket/groups.d.ts.map +1 -1
  42. package/lib/Socket/groups.js +1 -1
  43. package/lib/Socket/groups.js.map +1 -1
  44. package/lib/Socket/index.d.ts +13 -2
  45. package/lib/Socket/index.d.ts.map +1 -1
  46. package/lib/Socket/messages-recv.d.ts +7 -2
  47. package/lib/Socket/messages-recv.d.ts.map +1 -1
  48. package/lib/Socket/messages-recv.js +97 -303
  49. package/lib/Socket/messages-recv.js.map +1 -1
  50. package/lib/Socket/messages-send.d.ts +7 -2
  51. package/lib/Socket/messages-send.d.ts.map +1 -1
  52. package/lib/Socket/messages-send.js +34 -10
  53. package/lib/Socket/messages-send.js.map +1 -1
  54. package/lib/Socket/newsletter.d.ts +7 -2
  55. package/lib/Socket/newsletter.d.ts.map +1 -1
  56. package/lib/Socket/socket.d.ts +2 -2
  57. package/lib/Socket/socket.d.ts.map +1 -1
  58. package/lib/Socket/socket.js +131 -25
  59. package/lib/Socket/socket.js.map +1 -1
  60. package/lib/Socket/usync.d.ts +2 -2
  61. package/lib/Types/Auth.d.ts +0 -2
  62. package/lib/Types/Auth.d.ts.map +1 -1
  63. package/lib/Types/Bussines.d.ts +25 -0
  64. package/lib/Types/Bussines.d.ts.map +1 -0
  65. package/lib/Types/Bussines.js +2 -0
  66. package/lib/Types/Bussines.js.map +1 -0
  67. package/lib/Types/Chat.d.ts +3 -2
  68. package/lib/Types/Chat.d.ts.map +1 -1
  69. package/lib/Types/Chat.js.map +1 -1
  70. package/lib/Types/Message.d.ts +21 -1
  71. package/lib/Types/Message.d.ts.map +1 -1
  72. package/lib/Types/Signal.d.ts +0 -3
  73. package/lib/Types/Signal.d.ts.map +1 -1
  74. package/lib/Utils/chat-utils.d.ts.map +1 -1
  75. package/lib/Utils/chat-utils.js +18 -1
  76. package/lib/Utils/chat-utils.js.map +1 -1
  77. package/lib/Utils/decode-wa-message.d.ts +2 -57
  78. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  79. package/lib/Utils/decode-wa-message.js +28 -406
  80. package/lib/Utils/decode-wa-message.js.map +1 -1
  81. package/lib/Utils/event-buffer.d.ts +1 -1
  82. package/lib/Utils/event-buffer.d.ts.map +1 -1
  83. package/lib/Utils/event-buffer.js +5 -76
  84. package/lib/Utils/event-buffer.js.map +1 -1
  85. package/lib/Utils/message-retry-manager.d.ts +82 -0
  86. package/lib/Utils/message-retry-manager.d.ts.map +1 -0
  87. package/lib/Utils/message-retry-manager.js +149 -0
  88. package/lib/Utils/message-retry-manager.js.map +1 -0
  89. package/lib/Utils/messages-media.d.ts.map +1 -1
  90. package/lib/Utils/messages-media.js +4 -1
  91. package/lib/Utils/messages-media.js.map +1 -1
  92. package/lib/Utils/messages.d.ts.map +1 -1
  93. package/lib/Utils/messages.js +35 -15
  94. package/lib/Utils/messages.js.map +1 -1
  95. package/lib/Utils/signal.d.ts +0 -13
  96. package/lib/Utils/signal.d.ts.map +1 -1
  97. package/lib/Utils/signal.js +0 -47
  98. package/lib/Utils/signal.js.map +1 -1
  99. package/lib/Utils/use-multi-file-auth-state.js +1 -1
  100. package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
  101. package/lib/Utils/validate-connection.js +2 -2
  102. package/lib/Utils/validate-connection.js.map +1 -1
  103. package/package.json +3 -2
@@ -5,7 +5,7 @@ import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
7
7
  import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { addRecentMessage, aesDecryptCTR, aesEncryptGCM, cleanMessage, cleanupOldRetryStates, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getMessageForRetry, getNextPreKeys, getRecentMessage, getStatusFromReceiptType, hkdf, incrementIncomingRetryCounter, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, shouldDropRetryRequest, shouldRecreateSession, executeSessionRecreation, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
10
  import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
11
11
  import { extractGroupMetadata } from './groups.js';
@@ -14,9 +14,6 @@ export const makeMessagesRecvSocket = (config) => {
14
14
  const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
15
15
  const sock = makeMessagesSocket(config);
16
16
  const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage } = sock;
17
- const PENDING_MESSAGE_DECRYPTIONS = new Map();
18
- const MAX_DECRYPT_RETRY_COUNT = 3;
19
- const DECRYPT_RETRY_DELAY = 2000;
20
17
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
21
18
  const retryMutex = makeMutex();
22
19
  const msgRetryCache = config.msgRetryCounterCache ||
@@ -110,55 +107,20 @@ export const makeMessagesRecvSocket = (config) => {
110
107
  const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
111
108
  const { key: msgKey } = fullMessage;
112
109
  const msgId = msgKey.id;
113
- // const senderJid = node.attrs.from || ''
114
- // // Check internal retry counter (whatsmeow pattern - max 10 per sender/message)
115
- // const internalRetryCount = incrementIncomingRetryCounter(senderJid, msgId)
116
- // if (shouldDropRetryRequest(senderJid, msgId)) {
117
- // logger.warn(
118
- // {
119
- // senderJid,
120
- // msgId,
121
- // internalRetryCount
122
- // },
123
- // 'Dropping retry request: internal retry counter exceeded limit (10)'
124
- // )
125
- // return
126
- // }
127
110
  const key = `${msgId}:${msgKey?.participant}`;
128
111
  let retryCount = msgRetryCache.get(key) || 0;
129
- // Enhanced retry limit check (whatsmeow uses 5 max retries)
130
112
  if (retryCount >= maxMsgRetryCount) {
131
- logger.warn({ retryCount, msgId }, 'reached maximum retry limit (5), not sending more retry receipts');
113
+ logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
132
114
  msgRetryCache.del(key);
133
115
  return;
134
116
  }
135
117
  retryCount += 1;
136
118
  msgRetryCache.set(key, retryCount);
137
119
  const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
138
- // Enhanced retry logic inspired by whatsmeow
139
120
  if (retryCount === 1) {
140
- // First retry - request resend via phone (whatsmeow pattern)
141
- try {
142
- const msgId = await requestPlaceholderResend(msgKey);
143
- logger.debug({
144
- retryCount,
145
- msgId
146
- // internalRetryCount
147
- }, 'sendRetryRequest: requested placeholder resend for message (first retry)');
148
- }
149
- catch (error) {
150
- logger.warn({
151
- msgId,
152
- error: error.message
153
- }, 'Failed to request placeholder resend');
154
- }
155
- }
156
- else {
157
- logger.debug({
158
- retryCount,
159
- msgId
160
- // internalRetryCount
161
- }, 'sendRetryRequest: retry count > 1, skipping placeholder resend');
121
+ //request a resend via phone
122
+ const msgId = await requestPlaceholderResend(msgKey);
123
+ logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
162
124
  }
163
125
  const deviceIdentity = encodeSignedDeviceIdentity(account, true);
164
126
  await authState.keys.transaction(async () => {
@@ -192,12 +154,7 @@ export const makeMessagesRecvSocket = (config) => {
192
154
  if (node.attrs.participant) {
193
155
  receipt.attrs.participant = node.attrs.participant;
194
156
  }
195
- // Include keys based on whatsmeow logic:
196
- // - Always include on first retry (retryCount === 1)
197
- // - Include when forced (MAC errors, session errors)
198
- // - Include when retry count > 1 (session recreation scenarios)
199
- const shouldIncludeKeys = retryCount === 1 || forceIncludeKeys || retryCount > 1;
200
- if (shouldIncludeKeys) {
157
+ if (retryCount > 1 || forceIncludeKeys) {
201
158
  const { update, preKeys } = await getNextPreKeys(authState, 1);
202
159
  const [keyId] = Object.keys(preKeys);
203
160
  const key = preKeys[+keyId];
@@ -214,20 +171,6 @@ export const makeMessagesRecvSocket = (config) => {
214
171
  ]
215
172
  });
216
173
  ev.emit('creds.update', update);
217
- logger.debug({
218
- retryCount,
219
- forceIncludeKeys,
220
- shouldIncludeKeys
221
- // internalRetryCount
222
- }, 'Including keys and device identity in retry receipt');
223
- }
224
- else {
225
- logger.debug({
226
- retryCount,
227
- forceIncludeKeys,
228
- shouldIncludeKeys
229
- // internalRetryCount
230
- }, 'Not including keys in retry receipt');
231
174
  }
232
175
  await sendNode(receipt);
233
176
  logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
@@ -535,7 +478,7 @@ export const makeMessagesRecvSocket = (config) => {
535
478
  const willSendMessageAgain = (id, participant) => {
536
479
  const key = `${id}:${participant}`;
537
480
  const retryCount = msgRetryCache.get(key) || 0;
538
- return retryCount < maxMsgRetryCount;
481
+ return retryCount <= maxMsgRetryCount;
539
482
  };
540
483
  const updateSendMessageAgainCount = (id, participant) => {
541
484
  const key = `${id}:${participant}`;
@@ -543,53 +486,23 @@ export const makeMessagesRecvSocket = (config) => {
543
486
  msgRetryCache.set(key, newValue);
544
487
  };
545
488
  const sendMessagesAgain = async (key, ids, retryNode) => {
546
- // Use WhatsmeOW-compatible message retrieval (cache first, then callback)
547
- const msgs = await Promise.all(ids.map(id => getMessageForRetry(key.remoteJid, id, getMessage)));
489
+ // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
490
+ const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
548
491
  const remoteJid = key.remoteJid;
549
492
  const participant = key.participant || remoteJid;
550
493
  // if it's the primary jid sending the request
551
494
  // just re-send the message to everyone
552
495
  // prevents the first message decryption failure
553
496
  const sendToAll = !jidDecode(participant)?.device;
554
- const retryCount = +(retryNode.attrs.count || '1');
555
- // Create session recreation context (same approach as decode-wa-message.ts)
556
- const sessionContext = {
557
- authState,
558
- logger,
559
- signalRepository,
560
- query
561
- };
562
- // Use the sophisticated session recreation logic from decode-wa-message.ts
563
- const sessionResult = await shouldRecreateSession(participant, retryCount, sessionContext);
564
- if (sessionResult.recreate && sessionResult.shouldFetchPreKeys) {
565
- logger.warn({
566
- participant,
567
- retryCount,
568
- reason: sessionResult.reason
569
- }, 'Session recreation recommended for retry');
570
- // Execute session recreation with prekey fetching (includes LID support)
571
- const success = await executeSessionRecreation(participant, sessionContext);
572
- if (!success) {
573
- logger.warn({ participant }, 'Session recreation failed, proceeding with existing session');
574
- }
575
- }
576
- // Ensure we have sessions (use force=false to allow LID migration logic)
577
- await assertSessions([participant], false);
578
- // Only clear sender key memory if session recreation was recommended and it's a group
579
- // This prevents unnecessary decryption failures for other group members
580
- if (isJidGroup(remoteJid) && sessionResult.recreate) {
497
+ await assertSessions([participant], true);
498
+ if (isJidGroup(remoteJid)) {
581
499
  await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
582
- logger.debug({ participant, retryCount, reason: sessionResult.reason }, 'cleared sender key memory due to session recreation');
583
500
  }
584
- logger.debug({
585
- participant,
586
- sendToAll,
587
- retryCount,
588
- sessionRecreated: sessionResult.recreate,
589
- reason: sessionResult.reason
590
- }, 'handling retry receipt');
501
+ logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
591
502
  for (const [i, msg] of msgs.entries()) {
592
- if (msg) {
503
+ if (!ids[i])
504
+ continue;
505
+ if (msg && willSendMessageAgain(ids[i], participant)) {
593
506
  updateSendMessageAgainCount(ids[i], participant);
594
507
  const msgRelayOpts = { messageId: ids[i] };
595
508
  if (sendToAll) {
@@ -637,7 +550,7 @@ export const makeMessagesRecvSocket = (config) => {
637
550
  if (typeof status !== 'undefined' &&
638
551
  // basically, we only want to know when a message from us has been delivered to/read by the other person
639
552
  // or another device of ours has read some messages
640
- status >= proto.WebMessageInfo.Status.SERVER_ACK) {
553
+ (status >= proto.WebMessageInfo.Status.SERVER_ACK)) {
641
554
  if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
642
555
  const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
643
556
  if (attrs.participant) {
@@ -679,60 +592,24 @@ export const makeMessagesRecvSocket = (config) => {
679
592
  // correctly set who is asking for the retry
680
593
  key.participant = key.participant || attrs.from;
681
594
  const retryNode = getBinaryNodeChild(node, 'retry');
682
- // // Check internal retry counter (whatsmeow pattern)
683
- // const senderJid = attrs.from || ''
684
- const messageId = ids[0] || '';
685
- // if (shouldDropRetryRequest(senderJid, messageId)) {
686
- // logger.warn(
687
- // {
688
- // senderJid,
689
- // messageId,
690
- // attrs
691
- // },
692
- // 'Dropping retry receipt: internal retry counter exceeded limit (10)'
693
- // )
694
- // return
695
- // }
696
- // // Increment internal retry counter
697
- // const internalRetryCount = incrementIncomingRetryCounter(senderJid, messageId)
698
- if (willSendMessageAgain(ids[0], key.participant)) {
595
+ if (ids[0] && key.participant && willSendMessageAgain(ids[0], key.participant)) {
699
596
  if (key.fromMe) {
700
597
  try {
701
- logger.debug({
702
- attrs,
703
- key
704
- // internalRetryCount
705
- }, 'recv retry request');
706
- // Check if we have the message in recent cache (whatsmeow pattern)
707
- const recentMessage = getRecentMessage(key.remoteJid || '', messageId);
708
- //eslint-disable-next-line max-depth
709
- if (recentMessage) {
710
- logger.debug({
711
- jid: key.remoteJid,
712
- id: messageId
713
- }, 'Found message in recent cache for retry');
714
- }
598
+ updateSendMessageAgainCount(ids[0], key.participant);
599
+ logger.debug({ attrs, key }, 'recv retry request');
715
600
  await sendMessagesAgain(key, ids, retryNode);
716
601
  ev.emit('messages.retry', { key, ids, retryNode });
717
602
  }
718
603
  catch (error) {
719
- logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
604
+ logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
720
605
  }
721
606
  }
722
607
  else {
723
- logger.info({
724
- attrs,
725
- key
726
- // internalRetryCount
727
- }, 'recv retry for not fromMe message');
608
+ logger.info({ attrs, key }, 'recv retry for not fromMe message');
728
609
  }
729
610
  }
730
611
  else {
731
- logger.info({
732
- attrs,
733
- key
734
- // internalRetryCount
735
- }, 'will not send message again, as sent too many times');
612
+ logger.info({ attrs, key }, 'will not send message again, as sent too many times');
736
613
  }
737
614
  }
738
615
  })
@@ -756,15 +633,15 @@ export const makeMessagesRecvSocket = (config) => {
756
633
  if (msg) {
757
634
  const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
758
635
  msg.key = {
759
- ...(msg.key || {}),
760
636
  remoteJid,
761
637
  fromMe,
762
638
  participant: node.attrs.participant,
763
- id: node.attrs.id
639
+ id: node.attrs.id,
640
+ ...(msg.key || {})
764
641
  };
765
642
  msg.participant ?? (msg.participant = node.attrs.participant);
766
643
  msg.messageTimestamp = +node.attrs.t;
767
- const fullMsg = proto.WebMessageInfo.fromObject(msg);
644
+ const fullMsg = proto.WebMessageInfo.create(msg);
768
645
  await upsertMessage(fullMsg, 'append');
769
646
  }
770
647
  })
@@ -802,15 +679,7 @@ export const makeMessagesRecvSocket = (config) => {
802
679
  placeholderResendCache.del(node.attrs.id);
803
680
  }
804
681
  }
805
- const messageKey = `${node.attrs.from}_${node.attrs.id}_${node.attrs.participant || ''}`;
806
- // Create session recreation context (whatsmeow-inspired)
807
- const sessionContext = {
808
- authState,
809
- logger,
810
- signalRepository,
811
- query
812
- };
813
- const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger, sendRetryRequest, sessionContext);
682
+ const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
814
683
  if (response && msg?.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
815
684
  msg.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT, response];
816
685
  }
@@ -851,155 +720,95 @@ export const makeMessagesRecvSocket = (config) => {
851
720
  try {
852
721
  await Promise.all([
853
722
  processingMutex.mutex(async () => {
854
- try {
855
- await decrypt();
856
- // Remove from retry queue if the decrypt was succeed
857
- if (PENDING_MESSAGE_DECRYPTIONS.has(messageKey)) {
858
- PENDING_MESSAGE_DECRYPTIONS.delete(messageKey);
859
- console.warn({ messageKey }, 'Message decrypted successfully after retry');
723
+ await decrypt();
724
+ // message failed to decrypt
725
+ if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
726
+ if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
727
+ return sendMessageAck(node, NACK_REASONS.ParsingError);
860
728
  }
861
- if (msg.key?.remoteJid && msg.key?.id) {
862
- addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
863
- logger.debug({
864
- jid: msg.key.remoteJid,
865
- id: msg.key.id
866
- }, 'Added message to recent cache for retry receipts');
867
- }
868
- // message failed to decrypt
869
- if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
870
- if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
871
- return sendMessageAck(node, NACK_REASONS.ParsingError);
872
- }
873
- retryMutex.mutex(async () => {
874
- if (ws.isOpen) {
875
- if (getBinaryNodeChild(node, 'unavailable')) {
876
- return;
729
+ const errorMessage = msg?.messageStubParameters?.[0] || '';
730
+ const isPreKeyError = errorMessage.includes('PreKey');
731
+ console.debug(`[handleMessage] Attempting retry request for failed decryption`);
732
+ // Handle both pre-key and normal retries in single mutex
733
+ retryMutex.mutex(async () => {
734
+ try {
735
+ if (!ws.isOpen) {
736
+ logger.debug({ node }, 'Connection closed, skipping retry');
737
+ return;
738
+ }
739
+ if (getBinaryNodeChild(node, 'unavailable')) {
740
+ logger.debug('Message unavailable, skipping retry');
741
+ return;
742
+ }
743
+ // Handle pre-key errors with upload and delay
744
+ if (isPreKeyError) {
745
+ logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
746
+ try {
747
+ logger.debug('Uploading pre-keys for error recovery');
748
+ await uploadPreKeys(5);
749
+ logger.debug('Waiting for server to process new pre-keys');
750
+ await delay(1000);
877
751
  }
878
- const encNode = getBinaryNodeChild(node, 'enc');
879
- console.warn({ messageKey, node }, 'Message decryption failed, sending retry request');
880
- await sendRetryRequest(node, !encNode);
881
- if (retryRequestDelayMs) {
882
- await delay(retryRequestDelayMs);
752
+ catch (uploadErr) {
753
+ logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
883
754
  }
884
755
  }
885
- else {
886
- logger.debug({ node }, 'connection closed, ignoring retry req');
756
+ const encNode = getBinaryNodeChild(node, 'enc');
757
+ await sendRetryRequest(node, !encNode);
758
+ if (retryRequestDelayMs) {
759
+ await delay(retryRequestDelayMs);
887
760
  }
888
- });
889
- }
890
- else {
891
- let type = undefined;
892
- let participant = msg.key.participant;
893
- if (category === 'peer') {
894
- type = 'peer_msg';
895
761
  }
896
- else if (msg.key.fromMe) {
897
- type = 'sender';
898
- if (isJidUser(msg.key.remoteJid)) {
899
- participant = author;
762
+ catch (err) {
763
+ logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
764
+ // Still attempt retry even if pre-key upload failed
765
+ try {
766
+ const encNode = getBinaryNodeChild(node, 'enc');
767
+ await sendRetryRequest(node, !encNode);
768
+ }
769
+ catch (retryErr) {
770
+ logger.error({ retryErr }, 'Failed to send retry after error handling');
900
771
  }
901
772
  }
902
- else if (!sendActiveReceipts) {
903
- type = 'inactive';
904
- }
905
- await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
906
- const isAnyHistoryMsg = getHistoryMsg(msg.message);
907
- if (isAnyHistoryMsg) {
908
- const jid = jidNormalizedUser(msg.key.remoteJid);
909
- await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
910
- }
911
- }
912
- cleanMessage(msg, authState.creds.me.id);
913
- await sendMessageAck(node);
914
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
773
+ });
915
774
  }
916
- catch (decryptError) {
917
- console.error({ error: decryptError, messageKey }, 'Decryption failed');
918
- if (decryptError.message.includes('No session') ||
919
- decryptError.message.includes('Bad MAC') ||
920
- decryptError.message.includes('MAC verification') ||
921
- decryptError.message.includes('No matching sessions')) {
922
- console.warn('trying to handle session error in message decryption');
923
- const pendingDecryption = PENDING_MESSAGE_DECRYPTIONS.get(messageKey);
924
- if (!pendingDecryption) {
925
- // Primeira falha - adiciona à queue
926
- PENDING_MESSAGE_DECRYPTIONS.set(messageKey, {
927
- node,
928
- retryCount: 1,
929
- timestamp: Date.now()
930
- });
931
- console.warn({ messageKey, error: decryptError.message }, 'Session not found, scheduling retry');
932
- // Agenda retry
933
- setTimeout(() => {
934
- retryMessageDecryption(messageKey).catch(err => logger.error({ err, messageKey }, 'Error in retry decryption'));
935
- }, DECRYPT_RETRY_DELAY);
936
- // try to process the message as a ciphertext
937
- msg.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
938
- msg.messageStubParameters = ['Session not ready, retrying...'];
939
- cleanMessage(msg, authState.creds.me.id);
940
- await sendMessageAck(node);
941
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
942
- }
943
- else if (pendingDecryption.retryCount < MAX_DECRYPT_RETRY_COUNT) {
944
- // Retry
945
- pendingDecryption.retryCount++;
946
- console.warn({ messageKey, retryCount: pendingDecryption.retryCount }, 'Retrying message decryption');
947
- setTimeout(() => {
948
- retryMessageDecryption(messageKey).catch(err => console.error({ err, messageKey }, 'Error in retry decryption'));
949
- }, DECRYPT_RETRY_DELAY * pendingDecryption.retryCount);
950
- }
951
- else {
952
- // max retry count reached
953
- PENDING_MESSAGE_DECRYPTIONS.delete(messageKey);
954
- console.error({ messageKey }, 'Max retry count reached for message decryption');
955
- msg.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
956
- msg.messageStubParameters = ['Decryption failed after retries'];
957
- cleanMessage(msg, authState.creds.me.id);
958
- await sendMessageAck(node);
959
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
775
+ else {
776
+ // no type in the receipt => message delivered
777
+ let type = undefined;
778
+ let participant = msg.key.participant;
779
+ if (category === 'peer') {
780
+ // special peer message
781
+ type = 'peer_msg';
782
+ }
783
+ else if (msg.key.fromMe) {
784
+ // message was sent by us from a different device
785
+ type = 'sender';
786
+ // need to specially handle this case
787
+ if (isJidUser(msg.key.remoteJid)) {
788
+ participant = author;
960
789
  }
961
790
  }
962
- else {
963
- // other error, log and throw
964
- logger.error({ error: decryptError, messageKey }, 'Decryption failed with non-session error');
965
- throw decryptError;
791
+ else if (!sendActiveReceipts) {
792
+ type = 'inactive';
793
+ }
794
+ await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
795
+ // send ack for history message
796
+ const isAnyHistoryMsg = getHistoryMsg(msg.message);
797
+ if (isAnyHistoryMsg) {
798
+ const jid = jidNormalizedUser(msg.key.remoteJid);
799
+ await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
966
800
  }
967
801
  }
802
+ cleanMessage(msg, authState.creds.me.id);
803
+ await sendMessageAck(node);
804
+ await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
968
805
  })
969
806
  ]);
970
807
  }
971
808
  catch (error) {
972
809
  logger.error({ error, node }, 'error in handling message');
973
- await sendMessageAck(node); // Ainda assim envia ack para evitar loops
974
- }
975
- };
976
- const retryMessageDecryption = async (messageKey) => {
977
- const pendingDecryption = PENDING_MESSAGE_DECRYPTIONS.get(messageKey);
978
- if (!pendingDecryption) {
979
- return;
980
- }
981
- const { node } = pendingDecryption;
982
- try {
983
- // try to decrypt the message again
984
- await handleMessage(node);
985
- }
986
- catch (error) {
987
- logger.error({ error, messageKey }, 'Retry decryption failed');
988
- if (pendingDecryption.retryCount >= MAX_DECRYPT_RETRY_COUNT) {
989
- PENDING_MESSAGE_DECRYPTIONS.delete(messageKey);
990
- }
991
810
  }
992
811
  };
993
- setInterval(() => {
994
- const now = Date.now();
995
- const maxAge = 5 * 60 * 1000; // 5 minutes
996
- for (const [key, pending] of PENDING_MESSAGE_DECRYPTIONS.entries()) {
997
- if (now - pending.timestamp > maxAge) {
998
- PENDING_MESSAGE_DECRYPTIONS.delete(key);
999
- logger.debug({ key }, 'Removed old pending decryption from queue');
1000
- }
1001
- }
1002
- }, 60000);
1003
812
  const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
1004
813
  if (!authState.creds.me?.id) {
1005
814
  throw new Boom('Not authenticated');
@@ -1241,7 +1050,7 @@ export const makeMessagesRecvSocket = (config) => {
1241
1050
  ? Buffer.from(plaintextNode.content, 'binary')
1242
1051
  : Buffer.from(plaintextNode.content);
1243
1052
  const messageProto = proto.Message.decode(contentBuf);
1244
- const fullMessage = proto.WebMessageInfo.fromObject({
1053
+ const fullMessage = proto.WebMessageInfo.create({
1245
1054
  key: {
1246
1055
  remoteJid: from,
1247
1056
  id: child.attrs.message_id || child.attrs.server_id,
@@ -1357,7 +1166,7 @@ export const makeMessagesRecvSocket = (config) => {
1357
1166
  else {
1358
1167
  msg.message = { call: { callKey: Buffer.from(call.id) } };
1359
1168
  }
1360
- const protoMsg = proto.WebMessageInfo.fromObject(msg);
1169
+ const protoMsg = proto.WebMessageInfo.create(msg);
1361
1170
  upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1362
1171
  }
1363
1172
  });
@@ -1367,21 +1176,6 @@ export const makeMessagesRecvSocket = (config) => {
1367
1176
  logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
1368
1177
  }
1369
1178
  });
1370
- // Setup automatic cleanup of old retry states (inspired by whatsmeow)
1371
- const cleanupInterval = setInterval(() => {
1372
- try {
1373
- cleanupOldRetryStates();
1374
- }
1375
- catch (error) {
1376
- logger.warn({ error: error.message }, 'Failed to cleanup old retry states');
1377
- }
1378
- }, 60 * 60 * 1000); // Run every hour
1379
- // Cleanup on socket close
1380
- sock.ev.on('connection.update', ({ connection }) => {
1381
- if (connection === 'close') {
1382
- clearInterval(cleanupInterval);
1383
- }
1384
- });
1385
1179
  return {
1386
1180
  ...sock,
1387
1181
  sendMessageAck,