@queenanya/baileys 9.2.1 → 9.5.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 (149) hide show
  1. package/README.md +531 -445
  2. package/WAProto/fix-imports.js +70 -18
  3. package/WAProto/index.js +197 -160
  4. package/engine-requirements.js +7 -7
  5. package/lib/Defaults/index.d.ts +12 -0
  6. package/lib/Defaults/index.d.ts.map +1 -1
  7. package/lib/Defaults/index.js +17 -5
  8. package/lib/Defaults/index.js.map +1 -1
  9. package/lib/Signal/libsignal.d.ts.map +1 -1
  10. package/lib/Signal/libsignal.js +63 -2
  11. package/lib/Signal/libsignal.js.map +1 -1
  12. package/lib/Signal/lid-mapping.d.ts +5 -9
  13. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  14. package/lib/Signal/lid-mapping.js +170 -70
  15. package/lib/Signal/lid-mapping.js.map +1 -1
  16. package/lib/Socket/Client/websocket.d.ts +1 -1
  17. package/lib/Socket/Client/websocket.d.ts.map +1 -1
  18. package/lib/Socket/Client/websocket.js +5 -1
  19. package/lib/Socket/Client/websocket.js.map +1 -1
  20. package/lib/Socket/business.d.ts +16 -4
  21. package/lib/Socket/business.d.ts.map +1 -1
  22. package/lib/Socket/business.js +11 -8
  23. package/lib/Socket/business.js.map +1 -1
  24. package/lib/Socket/chats.d.ts +14 -3
  25. package/lib/Socket/chats.d.ts.map +1 -1
  26. package/lib/Socket/chats.js +55 -28
  27. package/lib/Socket/chats.js.map +1 -1
  28. package/lib/Socket/communities.d.ts +16 -4
  29. package/lib/Socket/communities.d.ts.map +1 -1
  30. package/lib/Socket/groups.d.ts +14 -3
  31. package/lib/Socket/groups.d.ts.map +1 -1
  32. package/lib/Socket/groups.js +1 -1
  33. package/lib/Socket/groups.js.map +1 -1
  34. package/lib/Socket/index.d.ts +16 -4
  35. package/lib/Socket/index.d.ts.map +1 -1
  36. package/lib/Socket/index.js +0 -6
  37. package/lib/Socket/index.js.map +1 -1
  38. package/lib/Socket/messages-recv.d.ts +16 -4
  39. package/lib/Socket/messages-recv.d.ts.map +1 -1
  40. package/lib/Socket/messages-recv.js +152 -87
  41. package/lib/Socket/messages-recv.js.map +1 -1
  42. package/lib/Socket/messages-send.d.ts +15 -3
  43. package/lib/Socket/messages-send.d.ts.map +1 -1
  44. package/lib/Socket/messages-send.js +117 -48
  45. package/lib/Socket/messages-send.js.map +1 -1
  46. package/lib/Socket/newsletter.d.ts +15 -5
  47. package/lib/Socket/newsletter.d.ts.map +1 -1
  48. package/lib/Socket/newsletter.js +2 -47
  49. package/lib/Socket/newsletter.js.map +1 -1
  50. package/lib/Socket/socket.d.ts +3 -1
  51. package/lib/Socket/socket.d.ts.map +1 -1
  52. package/lib/Socket/socket.js +71 -16
  53. package/lib/Socket/socket.js.map +1 -1
  54. package/lib/Types/Auth.d.ts +1 -0
  55. package/lib/Types/Auth.d.ts.map +1 -1
  56. package/lib/Types/Call.d.ts +1 -0
  57. package/lib/Types/Call.d.ts.map +1 -1
  58. package/lib/Types/Events.d.ts +40 -5
  59. package/lib/Types/Events.d.ts.map +1 -1
  60. package/lib/Types/Message.d.ts +3 -3
  61. package/lib/Types/Message.d.ts.map +1 -1
  62. package/lib/Types/Newsletter.d.ts +0 -17
  63. package/lib/Types/Newsletter.d.ts.map +1 -1
  64. package/lib/Utils/auth-utils.d.ts.map +1 -1
  65. package/lib/Utils/auth-utils.js +53 -20
  66. package/lib/Utils/auth-utils.js.map +1 -1
  67. package/lib/Utils/chat-utils.d.ts.map +1 -1
  68. package/lib/Utils/chat-utils.js +100 -51
  69. package/lib/Utils/chat-utils.js.map +1 -1
  70. package/lib/Utils/crypto.d.ts +4 -8
  71. package/lib/Utils/crypto.d.ts.map +1 -1
  72. package/lib/Utils/crypto.js +2 -26
  73. package/lib/Utils/crypto.js.map +1 -1
  74. package/lib/Utils/event-buffer.d.ts.map +1 -1
  75. package/lib/Utils/event-buffer.js +33 -7
  76. package/lib/Utils/event-buffer.js.map +1 -1
  77. package/lib/Utils/generics.d.ts +1 -0
  78. package/lib/Utils/generics.d.ts.map +1 -1
  79. package/lib/Utils/generics.js +5 -2
  80. package/lib/Utils/generics.js.map +1 -1
  81. package/lib/Utils/history.d.ts +6 -3
  82. package/lib/Utils/history.d.ts.map +1 -1
  83. package/lib/Utils/history.js +46 -5
  84. package/lib/Utils/history.js.map +1 -1
  85. package/lib/Utils/identity-change-handler.d.ts +37 -0
  86. package/lib/Utils/identity-change-handler.d.ts.map +1 -0
  87. package/lib/Utils/identity-change-handler.js +49 -0
  88. package/lib/Utils/identity-change-handler.js.map +1 -0
  89. package/lib/Utils/index.d.ts +1 -0
  90. package/lib/Utils/index.d.ts.map +1 -1
  91. package/lib/Utils/index.js +1 -0
  92. package/lib/Utils/index.js.map +1 -1
  93. package/lib/Utils/lt-hash.d.ts +7 -12
  94. package/lib/Utils/lt-hash.d.ts.map +1 -1
  95. package/lib/Utils/lt-hash.js +2 -42
  96. package/lib/Utils/lt-hash.js.map +1 -1
  97. package/lib/Utils/make-mutex.d.ts +1 -0
  98. package/lib/Utils/make-mutex.d.ts.map +1 -1
  99. package/lib/Utils/make-mutex.js +20 -27
  100. package/lib/Utils/make-mutex.js.map +1 -1
  101. package/lib/Utils/message-retry-manager.d.ts +30 -2
  102. package/lib/Utils/message-retry-manager.d.ts.map +1 -1
  103. package/lib/Utils/message-retry-manager.js +58 -5
  104. package/lib/Utils/message-retry-manager.js.map +1 -1
  105. package/lib/Utils/messages-media.d.ts +18 -2
  106. package/lib/Utils/messages-media.d.ts.map +1 -1
  107. package/lib/Utils/messages-media.js +150 -39
  108. package/lib/Utils/messages-media.js.map +1 -1
  109. package/lib/Utils/messages.d.ts +2 -0
  110. package/lib/Utils/messages.d.ts.map +1 -1
  111. package/lib/Utils/messages.js +41 -23
  112. package/lib/Utils/messages.js.map +1 -1
  113. package/lib/Utils/noise-handler.d.ts +4 -4
  114. package/lib/Utils/noise-handler.d.ts.map +1 -1
  115. package/lib/Utils/noise-handler.js +139 -85
  116. package/lib/Utils/noise-handler.js.map +1 -1
  117. package/lib/Utils/process-message.d.ts.map +1 -1
  118. package/lib/Utils/process-message.js +57 -14
  119. package/lib/Utils/process-message.js.map +1 -1
  120. package/lib/Utils/reporting-utils.d.ts +11 -0
  121. package/lib/Utils/reporting-utils.d.ts.map +1 -0
  122. package/lib/Utils/reporting-utils.js +258 -0
  123. package/lib/Utils/reporting-utils.js.map +1 -0
  124. package/lib/Utils/sync-action-utils.d.ts +19 -0
  125. package/lib/Utils/sync-action-utils.d.ts.map +1 -0
  126. package/lib/Utils/sync-action-utils.js +48 -0
  127. package/lib/Utils/sync-action-utils.js.map +1 -0
  128. package/lib/Utils/tc-token-utils.d.ts +12 -0
  129. package/lib/Utils/tc-token-utils.d.ts.map +1 -0
  130. package/lib/Utils/tc-token-utils.js +18 -0
  131. package/lib/Utils/tc-token-utils.js.map +1 -0
  132. package/lib/Utils/use-multi-file-auth-state.js +1 -1
  133. package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
  134. package/lib/Utils/use-single-file-auth-state.d.ts.map +1 -1
  135. package/lib/Utils/use-single-file-auth-state.js.map +1 -1
  136. package/lib/WABinary/decode.d.ts.map +1 -1
  137. package/lib/WABinary/decode.js +24 -0
  138. package/lib/WABinary/decode.js.map +1 -1
  139. package/lib/WABinary/encode.js +5 -1
  140. package/lib/WABinary/encode.js.map +1 -1
  141. package/lib/WABinary/generic-utils.d.ts +1 -1
  142. package/lib/WABinary/generic-utils.d.ts.map +1 -1
  143. package/lib/WABinary/generic-utils.js +19 -8
  144. package/lib/WABinary/generic-utils.js.map +1 -1
  145. package/lib/WABinary/jid-utils.d.ts +0 -3
  146. package/lib/WABinary/jid-utils.d.ts.map +1 -1
  147. package/lib/WABinary/jid-utils.js +0 -4
  148. package/lib/WABinary/jid-utils.js.map +1 -1
  149. package/package.json +4 -2
@@ -8,7 +8,7 @@ export declare const makeMessagesRecvSocket: (config: SocketConfig) => {
8
8
  sendRetryRequest: (node: BinaryNode, forceIncludeKeys?: boolean) => Promise<void>;
9
9
  rejectCall: (callId: string, callFrom: string) => Promise<void>;
10
10
  fetchMessageHistory: (count: number, oldestMsgKey: WAMessageKey, oldestMsgTimestamp: number | Long) => Promise<string>;
11
- requestPlaceholderResend: (messageKey: WAMessageKey) => Promise<string | undefined>;
11
+ requestPlaceholderResend: (messageKey: WAMessageKey, msgData?: Partial<WAMessage>) => Promise<string | undefined>;
12
12
  messageRetryManager: import("../Utils/index.js").MessageRetryManager | null;
13
13
  getPrivacyTokens: (jids: string[]) => Promise<any>;
14
14
  assertSessions: (jids: string[], force?: boolean) => Promise<boolean>;
@@ -29,6 +29,7 @@ export declare const makeMessagesRecvSocket: (config: SocketConfig) => {
29
29
  getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<(import("../WABinary/index.js").JidWithDevice & {
30
30
  jid: string;
31
31
  })[]>;
32
+ updateMemberLabel: (jid: string, memberLabel: string) => Promise<string>;
32
33
  updateMediaMessage: (message: WAMessage) => Promise<WAMessage>;
33
34
  sendMessage: (jid: string, content: import("../Types/index.js").AnyMessageContent, options?: import("../Types/index.js").MiscMessageGenerationOptions) => Promise<WAMessage | undefined>;
34
35
  newsletterCreate: (name: string, description?: string) => Promise<import("../Types/index.js").NewsletterMetadata>;
@@ -89,13 +90,22 @@ export declare const makeMessagesRecvSocket: (config: SocketConfig) => {
89
90
  startTime: number;
90
91
  }, timeoutMs?: number) => Promise<string | undefined>;
91
92
  getBotListV2: () => Promise<import("../Types/index.js").BotListInfo[]>;
92
- processingMutex: {
93
+ messageMutex: {
94
+ mutex<T>(code: () => Promise<T> | T): Promise<T>;
95
+ };
96
+ receiptMutex: {
97
+ mutex<T>(code: () => Promise<T> | T): Promise<T>;
98
+ };
99
+ appStatePatchMutex: {
100
+ mutex<T>(code: () => Promise<T> | T): Promise<T>;
101
+ };
102
+ notificationMutex: {
93
103
  mutex<T>(code: () => Promise<T> | T): Promise<T>;
94
104
  };
95
105
  upsertMessage: (msg: WAMessage, type: import("../Types/index.js").MessageUpsertType) => Promise<void>;
96
106
  appPatch: (patchCreate: import("../Types/index.js").WAPatchCreate) => Promise<void>;
97
107
  sendPresenceUpdate: (type: import("../Types/index.js").WAPresence, toJid?: string) => Promise<void>;
98
- presenceSubscribe: (toJid: string, tcToken?: Buffer) => Promise<void>;
108
+ presenceSubscribe: (toJid: string) => Promise<void>;
99
109
  profilePictureUrl: (jid: string, type?: "preview" | "image", timeoutMs?: number) => Promise<string | undefined>;
100
110
  fetchBlocklist: () => Promise<(string | undefined)[]>;
101
111
  fetchStatus: (...jids: string[]) => Promise<import("../index.js").USyncQueryResultList[] | undefined>;
@@ -157,13 +167,15 @@ export declare const makeMessagesRecvSocket: (config: SocketConfig) => {
157
167
  sendRawMessage: (data: Uint8Array | Buffer) => Promise<void>;
158
168
  sendNode: (frame: BinaryNode) => Promise<void>;
159
169
  logout: (msg?: string) => Promise<void>;
160
- end: (error: Error | undefined) => void;
170
+ end: (error: Error | undefined) => Promise<void>;
161
171
  onUnexpectedError: (err: Error | Boom, msg: string) => void;
162
172
  uploadPreKeys: (count?: number, retryCount?: number) => Promise<void>;
163
173
  uploadPreKeysToServerIfRequired: () => Promise<void>;
164
174
  digestKeyBundle: () => Promise<void>;
165
175
  rotateSignedPreKey: () => Promise<void>;
166
176
  requestPairingCode: (phoneNumber: string, customPairingCode?: string) => Promise<string>;
177
+ updateServerTimeOffset: ({ attrs }: BinaryNode) => void;
178
+ sendUnifiedSession: () => Promise<void>;
167
179
  wamBuffer: import("../index.js").BinaryInfo;
168
180
  waitForConnectionUpdate: (check: (u: Partial<import("../Types/index.js").ConnectionState>) => Promise<boolean | undefined>, timeoutMs?: number) => Promise<void>;
169
181
  sendWAMBuffer: (wamBuffer: Buffer) => Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"messages-recv.d.ts","sourceRoot":"","sources":["../../src/Socket/messages-recv.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEjC,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAE9C,OAAO,KAAK,EAEX,kBAAkB,EAClB,mBAAmB,EAEnB,YAAY,EAEZ,SAAS,EACT,YAAY,EAEZ,MAAM,UAAU,CAAA;AA2BjB,OAAO,EAEN,KAAK,UAAU,EAef,MAAM,aAAa,CAAA;AAIpB,eAAO,MAAM,sBAAsB,GAAI,QAAQ,YAAY;8CAwQH,UAAU,cAAc,MAAM;6BA2D/C,UAAU;yBAtBd,MAAM,YAAY,MAAM;iCAzPlD,MAAM,gBACC,YAAY,sBACN,MAAM,GAAG,IAAI,KAC/B,OAAO,CAAC,MAAM,CAAC;2CAmBkC,YAAY,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAynBnE,CAAC;;;;;;;;;0DAjsB3B,GAAC,0BACiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAk+CtB,CAAA"}
1
+ {"version":3,"file":"messages-recv.d.ts","sourceRoot":"","sources":["../../src/Socket/messages-recv.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEjC,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAQ9C,OAAO,KAAK,EAEX,kBAAkB,EAClB,mBAAmB,EAEnB,YAAY,EAEZ,SAAS,EACT,YAAY,EAEZ,MAAM,UAAU,CAAA;AA8BjB,OAAO,EAEN,KAAK,UAAU,EAef,MAAM,aAAa,CAAA;AAIpB,eAAO,MAAM,sBAAsB,GAAI,QAAQ,YAAY;8CA+QH,UAAU,cAAc,MAAM;6BA2D/C,UAAU;yBAtBd,MAAM,YAAY,MAAM;iCA9PlD,MAAM,gBACC,YAAY,sBACN,MAAM,GAAG,IAAI,KAC/B,OAAO,CAAC,MAAM,CAAC;2CAoBL,YAAY,YACd,OAAO,CAAC,SAAS,CAAC,KAC1B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAipBzB,CAAC;;;;;;;;;0DAhuBuD,GAAG,0BACxC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyjD3B,CAAA"}
@@ -3,9 +3,9 @@ import { Boom } from '@hapi/boom';
3
3
  import { randomBytes } from 'crypto';
4
4
  import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
- import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
6
+ import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
7
7
  import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
10
  import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
11
11
  import { extractGroupMetadata } from './groups.js';
@@ -13,7 +13,7 @@ import { makeMessagesSocket } from './messages-send.js';
13
13
  export const makeMessagesRecvSocket = (config) => {
14
14
  const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
15
15
  const sock = makeMessagesSocket(config);
16
- const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
16
+ const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
17
17
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
18
18
  const retryMutex = makeMutex();
19
19
  const msgRetryCache = config.msgRetryCounterCache ||
@@ -50,19 +50,21 @@ export const makeMessagesRecvSocket = (config) => {
50
50
  };
51
51
  return sendPeerDataOperationMessage(pdoMessage);
52
52
  };
53
- const requestPlaceholderResend = async (messageKey) => {
53
+ const requestPlaceholderResend = async (messageKey, msgData) => {
54
54
  if (!authState.creds.me?.id) {
55
55
  throw new Boom('Not authenticated');
56
56
  }
57
- if (placeholderResendCache.get(messageKey?.id)) {
57
+ if (await placeholderResendCache.get(messageKey?.id)) {
58
58
  logger.debug({ messageKey }, 'already requested resend');
59
59
  return;
60
60
  }
61
61
  else {
62
- await placeholderResendCache.set(messageKey?.id, true);
62
+ // Store original message data so PDO response handler can preserve
63
+ // metadata (LID details, timestamps, etc.) that the phone may omit
64
+ await placeholderResendCache.set(messageKey?.id, msgData || true);
63
65
  }
64
- await delay(5000);
65
- if (!placeholderResendCache.get(messageKey?.id)) {
66
+ await delay(2000);
67
+ if (!(await placeholderResendCache.get(messageKey?.id))) {
66
68
  logger.debug({ messageKey }, 'message received while resend requested');
67
69
  return 'RESOLVED';
68
70
  }
@@ -75,11 +77,11 @@ export const makeMessagesRecvSocket = (config) => {
75
77
  peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
76
78
  };
77
79
  setTimeout(async () => {
78
- if (placeholderResendCache.get(messageKey?.id)) {
79
- logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
80
+ if (await placeholderResendCache.get(messageKey?.id)) {
81
+ logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
80
82
  await placeholderResendCache.del(messageKey?.id);
81
83
  }
82
- }, 15000);
84
+ }, 8000);
83
85
  return sendPeerDataOperationMessage(pdoMessage);
84
86
  };
85
87
  // Handles mex newsletter notifications
@@ -300,12 +302,12 @@ export const makeMessagesRecvSocket = (config) => {
300
302
  // Check if we should recreate the session
301
303
  let shouldRecreateSession = false;
302
304
  let recreateReason = '';
303
- if (enableAutoSessionRecreation && messageRetryManager) {
305
+ if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
304
306
  try {
305
307
  // Check if we have a session with this JID
306
308
  const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
307
309
  const hasSession = await signalRepository.validateSession(fromJid);
308
- const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists);
310
+ const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
309
311
  shouldRecreateSession = result.recreate;
310
312
  recreateReason = result.reason;
311
313
  if (shouldRecreateSession) {
@@ -407,22 +409,15 @@ export const makeMessagesRecvSocket = (config) => {
407
409
  }
408
410
  }
409
411
  else {
410
- const identityNode = getBinaryNodeChild(node, 'identity');
411
- if (identityNode) {
412
- logger.info({ jid: from }, 'identity changed');
413
- if (identityAssertDebounce.get(from)) {
414
- logger.debug({ jid: from }, 'skipping identity assert (debounced)');
415
- return;
416
- }
417
- identityAssertDebounce.set(from, true);
418
- try {
419
- await assertSessions([from], true);
420
- }
421
- catch (error) {
422
- logger.warn({ error, jid: from }, 'failed to assert sessions after identity change');
423
- }
424
- }
425
- else {
412
+ const result = await handleIdentityChange(node, {
413
+ meId: authState.creds.me?.id,
414
+ meLid: authState.creds.me?.lid,
415
+ validateSession: signalRepository.validateSession,
416
+ assertSessions,
417
+ debounceCache: identityAssertDebounce,
418
+ logger
419
+ });
420
+ if (result.action === 'no_identity_node') {
426
421
  logger.info({ node }, 'unknown encrypt notification');
427
422
  }
428
423
  }
@@ -556,19 +551,6 @@ export const makeMessagesRecvSocket = (config) => {
556
551
  const nodeType = node.attrs.type;
557
552
  const from = jidNormalizedUser(node.attrs.from);
558
553
  switch (nodeType) {
559
- case 'privacy_token':
560
- const tokenList = getBinaryNodeChildren(child, 'token');
561
- for (const { attrs, content } of tokenList) {
562
- const jid = attrs.jid;
563
- ev.emit('chats.update', [
564
- {
565
- id: jid,
566
- tcToken: content
567
- }
568
- ]);
569
- logger.debug({ jid }, 'got privacy token update');
570
- }
571
- break;
572
554
  case 'newsletter':
573
555
  await handleNewsletterNotification(node);
574
556
  break;
@@ -605,6 +587,7 @@ export const makeMessagesRecvSocket = (config) => {
605
587
  case 'picture':
606
588
  const setPicture = getBinaryNodeChild(node, 'set');
607
589
  const delPicture = getBinaryNodeChild(node, 'delete');
590
+ // TODO: WAJIDHASH stuff proper support inhouse
608
591
  ev.emit('contacts.update', [
609
592
  {
610
593
  id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
@@ -657,7 +640,7 @@ export const makeMessagesRecvSocket = (config) => {
657
640
  const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
658
641
  const random = randomBytes(32);
659
642
  const linkCodeSalt = randomBytes(32);
660
- const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, {
643
+ const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
661
644
  salt: linkCodeSalt,
662
645
  info: 'link_code_pairing_key_bundle_encryption_key'
663
646
  });
@@ -671,7 +654,7 @@ export const makeMessagesRecvSocket = (config) => {
671
654
  const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
672
655
  const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
673
656
  const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
674
- authState.creds.advSecretKey = (await hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
657
+ authState.creds.advSecretKey = Buffer.from(hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
675
658
  await query({
676
659
  tag: 'iq',
677
660
  attrs: {
@@ -802,11 +785,11 @@ export const makeMessagesRecvSocket = (config) => {
802
785
  // Check if we should recreate session for this retry
803
786
  let shouldRecreateSession = false;
804
787
  let recreateReason = '';
805
- if (enableAutoSessionRecreation && messageRetryManager) {
788
+ if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
806
789
  try {
807
790
  const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
808
791
  const hasSession = await signalRepository.validateSession(participant);
809
- const result = messageRetryManager.shouldRecreateSession(participant, retryCount, hasSession.exists);
792
+ const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
810
793
  shouldRecreateSession = result.recreate;
811
794
  recreateReason = result.reason;
812
795
  if (shouldRecreateSession) {
@@ -869,7 +852,7 @@ export const makeMessagesRecvSocket = (config) => {
869
852
  }
870
853
  try {
871
854
  await Promise.all([
872
- processingMutex.mutex(async () => {
855
+ receiptMutex.mutex(async () => {
873
856
  const status = getStatusFromReceiptType(attrs.type);
874
857
  if (typeof status !== 'undefined' &&
875
858
  // basically, we only want to know when a message from us has been delivered to/read by the other person
@@ -890,7 +873,7 @@ export const makeMessagesRecvSocket = (config) => {
890
873
  else {
891
874
  ev.emit('messages.update', ids.map(id => ({
892
875
  key: { ...key, id },
893
- update: { status }
876
+ update: { status, messageTimestamp: toNumber(+(attrs.t ?? 0)) }
894
877
  })));
895
878
  }
896
879
  }
@@ -933,7 +916,7 @@ export const makeMessagesRecvSocket = (config) => {
933
916
  }
934
917
  try {
935
918
  await Promise.all([
936
- processingMutex.mutex(async () => {
919
+ notificationMutex.mutex(async () => {
937
920
  const msg = await processNotification(node);
938
921
  if (msg) {
939
922
  const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
@@ -967,7 +950,7 @@ export const makeMessagesRecvSocket = (config) => {
967
950
  }
968
951
  const encNode = getBinaryNodeChild(node, 'enc');
969
952
  // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
970
- if (encNode && encNode.attrs.type === 'msmsg') {
953
+ if (encNode?.attrs.type === 'msmsg') {
971
954
  logger.debug({ key: node.attrs.key }, 'ignored msmsg');
972
955
  await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
973
956
  return;
@@ -997,58 +980,123 @@ export const makeMessagesRecvSocket = (config) => {
997
980
  }, 'Added message to recent cache for retry receipts');
998
981
  }
999
982
  try {
1000
- await processingMutex.mutex(async () => {
983
+ await messageMutex.mutex(async () => {
1001
984
  await decrypt();
1002
985
  // message failed to decrypt
1003
986
  if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
1004
987
  if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
1005
988
  return sendMessageAck(node, NACK_REASONS.ParsingError);
1006
989
  }
1007
- const errorMessage = msg?.messageStubParameters?.[0] || '';
1008
- const isPreKeyError = errorMessage.includes('PreKey');
1009
- logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
1010
- // Handle both pre-key and normal retries in single mutex
1011
- await retryMutex.mutex(async () => {
1012
- try {
1013
- if (!ws.isOpen) {
1014
- logger.debug({ node }, 'Connection closed, skipping retry');
1015
- return;
1016
- }
1017
- // Handle pre-key errors with upload and delay
1018
- if (isPreKeyError) {
1019
- logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
1020
- try {
1021
- logger.debug('Uploading pre-keys for error recovery');
1022
- await uploadPreKeys(5);
1023
- logger.debug('Waiting for server to process new pre-keys');
1024
- await delay(1000);
1025
- }
1026
- catch (uploadErr) {
1027
- logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
1028
- }
990
+ if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
991
+ // Message arrived without encryption (e.g. CTWA ads messages).
992
+ // Check if this is eligible for placeholder resend (matching WA Web filters).
993
+ const unavailableNode = getBinaryNodeChild(node, 'unavailable');
994
+ const unavailableType = unavailableNode?.attrs?.type;
995
+ if (unavailableType === 'bot_unavailable_fanout' ||
996
+ unavailableType === 'hosted_unavailable_fanout' ||
997
+ unavailableType === 'view_once_unavailable_fanout') {
998
+ logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
999
+ return sendMessageAck(node);
1000
+ }
1001
+ const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1002
+ if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
1003
+ logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
1004
+ return sendMessageAck(node);
1005
+ }
1006
+ // Request the real content from the phone via placeholder resend PDO.
1007
+ // Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
1008
+ // and store the requestId in stubParameters[1] so users can correlate
1009
+ // with the incoming PDO response event.
1010
+ const cleanKey = {
1011
+ remoteJid: msg.key.remoteJid,
1012
+ fromMe: msg.key.fromMe,
1013
+ id: msg.key.id,
1014
+ participant: msg.key.participant
1015
+ };
1016
+ // Cache the original message metadata so the PDO response handler
1017
+ // can preserve key fields (LID details etc.) that the phone may omit
1018
+ const msgData = {
1019
+ key: msg.key,
1020
+ messageTimestamp: msg.messageTimestamp,
1021
+ pushName: msg.pushName,
1022
+ participant: msg.participant,
1023
+ verifiedBizName: msg.verifiedBizName
1024
+ };
1025
+ requestPlaceholderResend(cleanKey, msgData)
1026
+ .then(requestId => {
1027
+ if (requestId && requestId !== 'RESOLVED') {
1028
+ logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
1029
+ ev.emit('messages.update', [
1030
+ {
1031
+ key: msg.key,
1032
+ update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
1033
+ }
1034
+ ]);
1029
1035
  }
1030
- const encNode = getBinaryNodeChild(node, 'enc');
1031
- await sendRetryRequest(node, !encNode);
1032
- if (retryRequestDelayMs) {
1033
- await delay(retryRequestDelayMs);
1036
+ })
1037
+ .catch(err => {
1038
+ logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
1039
+ });
1040
+ await sendMessageAck(node);
1041
+ // Don't return — fall through to upsertMessage so the stub is emitted
1042
+ }
1043
+ else {
1044
+ // Skip retry for expired status messages (>24h old)
1045
+ if (isJidStatusBroadcast(msg.key.remoteJid)) {
1046
+ const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1047
+ if (messageAge > STATUS_EXPIRY_SECONDS) {
1048
+ logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
1049
+ return sendMessageAck(node);
1034
1050
  }
1035
1051
  }
1036
- catch (err) {
1037
- logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
1038
- // Still attempt retry even if pre-key upload failed
1052
+ const errorMessage = msg?.messageStubParameters?.[0] || '';
1053
+ const isPreKeyError = errorMessage.includes('PreKey');
1054
+ logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
1055
+ // Handle both pre-key and normal retries in single mutex
1056
+ await retryMutex.mutex(async () => {
1039
1057
  try {
1058
+ if (!ws.isOpen) {
1059
+ logger.debug({ node }, 'Connection closed, skipping retry');
1060
+ return;
1061
+ }
1062
+ // Handle pre-key errors with upload and delay
1063
+ if (isPreKeyError) {
1064
+ logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
1065
+ try {
1066
+ logger.debug('Uploading pre-keys for error recovery');
1067
+ await uploadPreKeys(5);
1068
+ logger.debug('Waiting for server to process new pre-keys');
1069
+ await delay(1000);
1070
+ }
1071
+ catch (uploadErr) {
1072
+ logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
1073
+ }
1074
+ }
1040
1075
  const encNode = getBinaryNodeChild(node, 'enc');
1041
1076
  await sendRetryRequest(node, !encNode);
1077
+ if (retryRequestDelayMs) {
1078
+ await delay(retryRequestDelayMs);
1079
+ }
1042
1080
  }
1043
- catch (retryErr) {
1044
- logger.error({ retryErr }, 'Failed to send retry after error handling');
1081
+ catch (err) {
1082
+ logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
1083
+ // Still attempt retry even if pre-key upload failed
1084
+ try {
1085
+ const encNode = getBinaryNodeChild(node, 'enc');
1086
+ await sendRetryRequest(node, !encNode);
1087
+ }
1088
+ catch (retryErr) {
1089
+ logger.error({ retryErr }, 'Failed to send retry after error handling');
1090
+ }
1045
1091
  }
1046
- }
1047
- await sendMessageAck(node, NACK_REASONS.UnhandledError);
1048
- });
1092
+ await sendMessageAck(node, NACK_REASONS.UnhandledError);
1093
+ });
1094
+ }
1049
1095
  }
1050
1096
  else {
1051
- await sendMessageAck(node);
1097
+ if (messageRetryManager && msg.key.id) {
1098
+ messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
1099
+ }
1052
1100
  const isNewsletter = isJidNewsletter(msg.key.remoteJid);
1053
1101
  if (!isNewsletter) {
1054
1102
  // no type in the receipt => message delivered
@@ -1074,10 +1122,11 @@ export const makeMessagesRecvSocket = (config) => {
1074
1122
  const isAnyHistoryMsg = getHistoryMsg(msg.message);
1075
1123
  if (isAnyHistoryMsg) {
1076
1124
  const jid = jidNormalizedUser(msg.key.remoteJid);
1077
- await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
1125
+ await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
1078
1126
  }
1079
1127
  }
1080
1128
  else {
1129
+ await sendMessageAck(node);
1081
1130
  logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
1082
1131
  }
1083
1132
  }
@@ -1101,6 +1150,7 @@ export const makeMessagesRecvSocket = (config) => {
1101
1150
  const call = {
1102
1151
  chatId: attrs.from,
1103
1152
  from,
1153
+ callerPn: infoChild.attrs['caller_pn'],
1104
1154
  id: callId,
1105
1155
  date: new Date(+attrs.t * 1000),
1106
1156
  offline: !!attrs.offline,
@@ -1117,6 +1167,7 @@ export const makeMessagesRecvSocket = (config) => {
1117
1167
  if (existingCall) {
1118
1168
  call.isVideo = existingCall.isVideo;
1119
1169
  call.isGroup = existingCall.isGroup;
1170
+ call.callerPn = call.callerPn || existingCall.callerPn;
1120
1171
  }
1121
1172
  // delete data once call has ended
1122
1173
  if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
@@ -1178,6 +1229,10 @@ export const makeMessagesRecvSocket = (config) => {
1178
1229
  return exec(node, false).catch(err => onUnexpectedError(err, identifier));
1179
1230
  }
1180
1231
  };
1232
+ /** Yields control to the event loop to prevent blocking */
1233
+ const yieldToEventLoop = () => {
1234
+ return new Promise(resolve => setImmediate(resolve));
1235
+ };
1181
1236
  const makeOfflineNodeProcessor = () => {
1182
1237
  const nodeProcessorMap = new Map([
1183
1238
  ['message', handleMessage],
@@ -1187,6 +1242,8 @@ export const makeMessagesRecvSocket = (config) => {
1187
1242
  ]);
1188
1243
  const nodes = [];
1189
1244
  let isProcessing = false;
1245
+ // Number of nodes to process before yielding to event loop
1246
+ const BATCH_SIZE = 10;
1190
1247
  const enqueue = (type, node) => {
1191
1248
  nodes.push({ type, node });
1192
1249
  if (isProcessing) {
@@ -1194,6 +1251,7 @@ export const makeMessagesRecvSocket = (config) => {
1194
1251
  }
1195
1252
  isProcessing = true;
1196
1253
  const promise = async () => {
1254
+ let processedInBatch = 0;
1197
1255
  while (nodes.length && ws.isOpen) {
1198
1256
  const { type, node } = nodes.shift();
1199
1257
  const nodeProcessor = nodeProcessorMap.get(type);
@@ -1202,6 +1260,13 @@ export const makeMessagesRecvSocket = (config) => {
1202
1260
  continue;
1203
1261
  }
1204
1262
  await nodeProcessor(node);
1263
+ processedInBatch++;
1264
+ // Yield to event loop after processing a batch
1265
+ // This prevents blocking the event loop for too long when there are many offline nodes
1266
+ if (processedInBatch >= BATCH_SIZE) {
1267
+ processedInBatch = 0;
1268
+ await yieldToEventLoop();
1269
+ }
1205
1270
  }
1206
1271
  isProcessing = false;
1207
1272
  };