@d0v3riz/baileys 7.0.0-rc.9 → 7.0.0-rc13

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 (213) hide show
  1. package/README.md +21 -15
  2. package/WAProto/fix-imports.js +74 -18
  3. package/WAProto/index.js +201 -160
  4. package/lib/Defaults/index.d.ts +19 -6
  5. package/lib/Defaults/index.d.ts.map +1 -1
  6. package/lib/Defaults/index.js +24 -10
  7. package/lib/Defaults/index.js.map +1 -1
  8. package/lib/Signal/libsignal.d.ts.map +1 -1
  9. package/lib/Signal/libsignal.js +105 -16
  10. package/lib/Signal/libsignal.js.map +1 -1
  11. package/lib/Signal/lid-mapping.d.ts +8 -8
  12. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  13. package/lib/Signal/lid-mapping.js +176 -70
  14. package/lib/Signal/lid-mapping.js.map +1 -1
  15. package/lib/Socket/Client/websocket.d.ts +1 -1
  16. package/lib/Socket/Client/websocket.d.ts.map +1 -1
  17. package/lib/Socket/Client/websocket.js +5 -1
  18. package/lib/Socket/Client/websocket.js.map +1 -1
  19. package/lib/Socket/business.d.ts +36 -8
  20. package/lib/Socket/business.d.ts.map +1 -1
  21. package/lib/Socket/business.js +11 -8
  22. package/lib/Socket/business.js.map +1 -1
  23. package/lib/Socket/chats.d.ts +29 -5
  24. package/lib/Socket/chats.d.ts.map +1 -1
  25. package/lib/Socket/chats.js +292 -63
  26. package/lib/Socket/chats.js.map +1 -1
  27. package/lib/Socket/communities.d.ts +36 -8
  28. package/lib/Socket/communities.d.ts.map +1 -1
  29. package/lib/Socket/groups.d.ts +27 -5
  30. package/lib/Socket/groups.d.ts.map +1 -1
  31. package/lib/Socket/groups.js +20 -0
  32. package/lib/Socket/groups.js.map +1 -1
  33. package/lib/Socket/index.d.ts +36 -8
  34. package/lib/Socket/index.d.ts.map +1 -1
  35. package/lib/Socket/index.js +0 -6
  36. package/lib/Socket/index.js.map +1 -1
  37. package/lib/Socket/messages-recv.d.ts +40 -11
  38. package/lib/Socket/messages-recv.d.ts.map +1 -1
  39. package/lib/Socket/messages-recv.js +841 -342
  40. package/lib/Socket/messages-recv.js.map +1 -1
  41. package/lib/Socket/messages-send.d.ts +35 -6
  42. package/lib/Socket/messages-send.d.ts.map +1 -1
  43. package/lib/Socket/messages-send.js +263 -92
  44. package/lib/Socket/messages-send.js.map +1 -1
  45. package/lib/Socket/mex.d.ts.map +1 -1
  46. package/lib/Socket/mex.js +2 -0
  47. package/lib/Socket/mex.js.map +1 -1
  48. package/lib/Socket/newsletter.d.ts +27 -5
  49. package/lib/Socket/newsletter.d.ts.map +1 -1
  50. package/lib/Socket/newsletter.js +35 -3
  51. package/lib/Socket/newsletter.js.map +1 -1
  52. package/lib/Socket/socket.d.ts +9 -3
  53. package/lib/Socket/socket.d.ts.map +1 -1
  54. package/lib/Socket/socket.js +124 -39
  55. package/lib/Socket/socket.js.map +1 -1
  56. package/lib/Types/Auth.d.ts +2 -0
  57. package/lib/Types/Auth.d.ts.map +1 -1
  58. package/lib/Types/Call.d.ts +2 -1
  59. package/lib/Types/Call.d.ts.map +1 -1
  60. package/lib/Types/Chat.d.ts +1 -0
  61. package/lib/Types/Chat.d.ts.map +1 -1
  62. package/lib/Types/Contact.d.ts +2 -0
  63. package/lib/Types/Contact.d.ts.map +1 -1
  64. package/lib/Types/Events.d.ts +60 -6
  65. package/lib/Types/Events.d.ts.map +1 -1
  66. package/lib/Types/GroupMetadata.d.ts +4 -0
  67. package/lib/Types/GroupMetadata.d.ts.map +1 -1
  68. package/lib/Types/Message.d.ts +20 -5
  69. package/lib/Types/Message.d.ts.map +1 -1
  70. package/lib/Types/Message.js.map +1 -1
  71. package/lib/Types/{Newsletter.d.ts → Mex.d.ts} +13 -5
  72. package/lib/Types/Mex.d.ts.map +1 -0
  73. package/lib/Types/{Newsletter.js → Mex.js} +11 -3
  74. package/lib/Types/Mex.js.map +1 -0
  75. package/lib/Types/Signal.d.ts +12 -1
  76. package/lib/Types/Signal.d.ts.map +1 -1
  77. package/lib/Types/Socket.d.ts +3 -0
  78. package/lib/Types/Socket.d.ts.map +1 -1
  79. package/lib/Types/State.d.ts +58 -0
  80. package/lib/Types/State.d.ts.map +1 -1
  81. package/lib/Types/State.js +43 -0
  82. package/lib/Types/State.js.map +1 -1
  83. package/lib/Types/index.d.ts +1 -1
  84. package/lib/Types/index.d.ts.map +1 -1
  85. package/lib/Types/index.js +1 -1
  86. package/lib/Types/index.js.map +1 -1
  87. package/lib/Utils/auth-utils.d.ts +5 -0
  88. package/lib/Utils/auth-utils.d.ts.map +1 -1
  89. package/lib/Utils/auth-utils.js +65 -20
  90. package/lib/Utils/auth-utils.js.map +1 -1
  91. package/lib/Utils/chat-utils.d.ts +31 -1
  92. package/lib/Utils/chat-utils.d.ts.map +1 -1
  93. package/lib/Utils/chat-utils.js +180 -71
  94. package/lib/Utils/chat-utils.js.map +1 -1
  95. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  96. package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
  97. package/lib/Utils/companion-reg-client-utils.js +35 -0
  98. package/lib/Utils/companion-reg-client-utils.js.map +1 -0
  99. package/lib/Utils/crypto.d.ts +4 -8
  100. package/lib/Utils/crypto.d.ts.map +1 -1
  101. package/lib/Utils/crypto.js +2 -26
  102. package/lib/Utils/crypto.js.map +1 -1
  103. package/lib/Utils/decode-wa-message.d.ts +18 -0
  104. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  105. package/lib/Utils/decode-wa-message.js +34 -0
  106. package/lib/Utils/decode-wa-message.js.map +1 -1
  107. package/lib/Utils/event-buffer.d.ts +2 -0
  108. package/lib/Utils/event-buffer.d.ts.map +1 -1
  109. package/lib/Utils/event-buffer.js +82 -8
  110. package/lib/Utils/event-buffer.js.map +1 -1
  111. package/lib/Utils/generics.d.ts +2 -0
  112. package/lib/Utils/generics.d.ts.map +1 -1
  113. package/lib/Utils/generics.js +13 -1
  114. package/lib/Utils/generics.js.map +1 -1
  115. package/lib/Utils/history.d.ts +8 -3
  116. package/lib/Utils/history.d.ts.map +1 -1
  117. package/lib/Utils/history.js +58 -14
  118. package/lib/Utils/history.js.map +1 -1
  119. package/lib/Utils/identity-change-handler.d.ts +44 -0
  120. package/lib/Utils/identity-change-handler.d.ts.map +1 -0
  121. package/lib/Utils/identity-change-handler.js +50 -0
  122. package/lib/Utils/identity-change-handler.js.map +1 -0
  123. package/lib/Utils/index.d.ts +3 -0
  124. package/lib/Utils/index.d.ts.map +1 -1
  125. package/lib/Utils/index.js +3 -0
  126. package/lib/Utils/index.js.map +1 -1
  127. package/lib/Utils/link-preview.js +2 -2
  128. package/lib/Utils/link-preview.js.map +1 -1
  129. package/lib/Utils/lt-hash.d.ts +7 -12
  130. package/lib/Utils/lt-hash.d.ts.map +1 -1
  131. package/lib/Utils/lt-hash.js +2 -42
  132. package/lib/Utils/lt-hash.js.map +1 -1
  133. package/lib/Utils/make-mutex.d.ts +1 -0
  134. package/lib/Utils/make-mutex.d.ts.map +1 -1
  135. package/lib/Utils/make-mutex.js +20 -27
  136. package/lib/Utils/make-mutex.js.map +1 -1
  137. package/lib/Utils/message-retry-manager.d.ts +35 -2
  138. package/lib/Utils/message-retry-manager.d.ts.map +1 -1
  139. package/lib/Utils/message-retry-manager.js +98 -5
  140. package/lib/Utils/message-retry-manager.js.map +1 -1
  141. package/lib/Utils/messages-media.d.ts +22 -3
  142. package/lib/Utils/messages-media.d.ts.map +1 -1
  143. package/lib/Utils/messages-media.js +169 -44
  144. package/lib/Utils/messages-media.js.map +1 -1
  145. package/lib/Utils/messages.d.ts +2 -0
  146. package/lib/Utils/messages.d.ts.map +1 -1
  147. package/lib/Utils/messages.js +70 -27
  148. package/lib/Utils/messages.js.map +1 -1
  149. package/lib/Utils/noise-handler.d.ts +4 -4
  150. package/lib/Utils/noise-handler.d.ts.map +1 -1
  151. package/lib/Utils/noise-handler.js +139 -85
  152. package/lib/Utils/noise-handler.js.map +1 -1
  153. package/lib/Utils/offline-node-processor.d.ts +17 -0
  154. package/lib/Utils/offline-node-processor.d.ts.map +1 -0
  155. package/lib/Utils/offline-node-processor.js +40 -0
  156. package/lib/Utils/offline-node-processor.js.map +1 -0
  157. package/lib/Utils/process-message.d.ts.map +1 -1
  158. package/lib/Utils/process-message.js +165 -15
  159. package/lib/Utils/process-message.js.map +1 -1
  160. package/lib/Utils/reporting-utils.d.ts +11 -0
  161. package/lib/Utils/reporting-utils.d.ts.map +1 -0
  162. package/lib/Utils/reporting-utils.js +258 -0
  163. package/lib/Utils/reporting-utils.js.map +1 -0
  164. package/lib/Utils/signal.d.ts +13 -0
  165. package/lib/Utils/signal.d.ts.map +1 -1
  166. package/lib/Utils/signal.js +42 -0
  167. package/lib/Utils/signal.js.map +1 -1
  168. package/lib/Utils/stanza-ack.d.ts +11 -0
  169. package/lib/Utils/stanza-ack.d.ts.map +1 -0
  170. package/lib/Utils/stanza-ack.js +38 -0
  171. package/lib/Utils/stanza-ack.js.map +1 -0
  172. package/lib/Utils/sync-action-utils.d.ts +19 -0
  173. package/lib/Utils/sync-action-utils.d.ts.map +1 -0
  174. package/lib/Utils/sync-action-utils.js +49 -0
  175. package/lib/Utils/sync-action-utils.js.map +1 -0
  176. package/lib/Utils/tc-token-utils.d.ts +37 -0
  177. package/lib/Utils/tc-token-utils.d.ts.map +1 -0
  178. package/lib/Utils/tc-token-utils.js +163 -0
  179. package/lib/Utils/tc-token-utils.js.map +1 -0
  180. package/lib/Utils/validate-connection.d.ts.map +1 -1
  181. package/lib/Utils/validate-connection.js +4 -1
  182. package/lib/Utils/validate-connection.js.map +1 -1
  183. package/lib/WABinary/decode.d.ts.map +1 -1
  184. package/lib/WABinary/decode.js +24 -0
  185. package/lib/WABinary/decode.js.map +1 -1
  186. package/lib/WABinary/encode.js +5 -1
  187. package/lib/WABinary/encode.js.map +1 -1
  188. package/lib/WABinary/generic-utils.d.ts +1 -1
  189. package/lib/WABinary/generic-utils.d.ts.map +1 -1
  190. package/lib/WABinary/generic-utils.js +19 -8
  191. package/lib/WABinary/generic-utils.js.map +1 -1
  192. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -1
  193. package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
  194. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -1
  195. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  196. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
  197. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  198. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
  199. package/lib/WAUSync/Protocols/index.d.ts +1 -0
  200. package/lib/WAUSync/Protocols/index.d.ts.map +1 -1
  201. package/lib/WAUSync/Protocols/index.js +1 -0
  202. package/lib/WAUSync/Protocols/index.js.map +1 -1
  203. package/lib/WAUSync/USyncQuery.d.ts +1 -0
  204. package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
  205. package/lib/WAUSync/USyncQuery.js +6 -2
  206. package/lib/WAUSync/USyncQuery.js.map +1 -1
  207. package/lib/WAUSync/USyncUser.d.ts +4 -0
  208. package/lib/WAUSync/USyncUser.d.ts.map +1 -1
  209. package/lib/WAUSync/USyncUser.js +8 -0
  210. package/lib/WAUSync/USyncUser.js.map +1 -1
  211. package/package.json +39 -8
  212. package/lib/Types/Newsletter.d.ts.map +0 -1
  213. package/lib/Types/Newsletter.js.map +0 -1
@@ -2,30 +2,39 @@ import NodeCache from '@cacheable/node-cache';
2
2
  import { Boom } from '@hapi/boom';
3
3
  import { proto } from '../../WAProto/index.js';
4
4
  import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
- import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
5
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
6
  import { getUrlInfo } from '../Utils/link-preview.js';
7
- import { makeKeyedMutex } from '../Utils/make-mutex.js';
8
- import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidGroup, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
7
+ import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
8
+ import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
9
+ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
10
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
9
11
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
10
12
  import { makeNewsletterSocket } from './newsletter.js';
11
13
  export const makeMessagesSocket = (config) => {
12
14
  const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
13
15
  const sock = makeNewsletterSocket(config);
14
- const { ev, authState, processingMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral } = sock;
16
+ const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
17
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
18
+ /**
19
+ * Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
20
+ * Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
21
+ * Entries are always removed in `.finally()`, so the set is bounded by concurrency.
22
+ */
23
+ const inFlightTcTokenIssuance = new Set();
15
24
  const userDevicesCache = config.userDevicesCache ||
16
25
  new NodeCache({
17
26
  stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
18
27
  useClones: false
19
28
  });
20
- const peerSessionsCache = new NodeCache({
21
- stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
22
- useClones: false
23
- });
29
+ /** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
30
+ const devicesMutex = makeMutex();
24
31
  // Initialize message retry manager if enabled
25
32
  const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
26
33
  // Prevent race conditions in Signal session encryption by user
27
34
  const encryptionMutex = makeKeyedMutex();
28
35
  let mediaConn;
36
+ /** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
37
+ let mediaHost = DEF_MEDIA_HOST;
29
38
  const refreshMediaConn = async (forceGet = false) => {
30
39
  const media = await mediaConn;
31
40
  if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
@@ -51,6 +60,9 @@ export const makeMessagesSocket = (config) => {
51
60
  fetchDate: new Date()
52
61
  };
53
62
  logger.debug('fetched media conn');
63
+ if (node.hosts[0]) {
64
+ mediaHost = node.hosts[0].hostname;
65
+ }
54
66
  return node;
55
67
  })();
56
68
  }
@@ -226,16 +238,18 @@ export const makeMessagesSocket = (config) => {
226
238
  }, 'Processed device with LID priority');
227
239
  }
228
240
  }
229
- if (userDevicesCache.mset) {
230
- // if the cache supports mset, we can set all devices in one go
231
- await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
232
- }
233
- else {
234
- for (const key in deviceMap) {
235
- if (deviceMap[key])
236
- await userDevicesCache.set(key, deviceMap[key]);
241
+ await devicesMutex.mutex(async () => {
242
+ if (userDevicesCache.mset) {
243
+ // if the cache supports mset, we can set all devices in one go
244
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
237
245
  }
238
- }
246
+ else {
247
+ for (const key in deviceMap) {
248
+ if (deviceMap[key])
249
+ await userDevicesCache.set(key, deviceMap[key]);
250
+ }
251
+ }
252
+ });
239
253
  const userDeviceUpdates = {};
240
254
  for (const [userId, devices] of Object.entries(deviceMap)) {
241
255
  if (devices && devices.length > 0) {
@@ -254,25 +268,40 @@ export const makeMessagesSocket = (config) => {
254
268
  }
255
269
  return deviceResults;
256
270
  };
271
+ /**
272
+ * Update Member Label
273
+ */
274
+ const updateMemberLabel = (jid, memberLabel) => {
275
+ return relayMessage(jid, {
276
+ protocolMessage: {
277
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
278
+ memberLabel: {
279
+ label: memberLabel?.slice(0, 30),
280
+ labelTimestamp: unixTimestampSeconds()
281
+ }
282
+ }
283
+ }, {
284
+ additionalNodes: [
285
+ {
286
+ tag: 'meta',
287
+ attrs: {
288
+ tag_reason: 'user_update',
289
+ appdata: 'member_tag'
290
+ },
291
+ content: undefined
292
+ }
293
+ ]
294
+ });
295
+ };
257
296
  const assertSessions = async (jids, force) => {
258
297
  let didFetchNewSession = false;
259
- const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
298
+ const uniqueJids = [...new Set(jids)];
260
299
  const jidsRequiringFetch = [];
261
300
  logger.debug({ jids }, 'assertSessions call with jids');
262
- // Check peerSessionsCache and validate sessions using libsignal loadSession
263
301
  for (const jid of uniqueJids) {
264
- const signalId = signalRepository.jidToSignalProtocolAddress(jid);
265
- const cachedSession = peerSessionsCache.get(signalId);
266
- if (cachedSession !== undefined) {
267
- if (cachedSession && !force) {
268
- continue; // Session exists in cache
269
- }
270
- }
271
- else {
302
+ if (!force) {
272
303
  const sessionValidation = await signalRepository.validateSession(jid);
273
- const hasSession = sessionValidation.exists;
274
- peerSessionsCache.set(signalId, hasSession);
275
- if (hasSession && !force) {
304
+ if (sessionValidation.exists) {
276
305
  continue;
277
306
  }
278
307
  }
@@ -307,11 +336,6 @@ export const makeMessagesSocket = (config) => {
307
336
  });
308
337
  await parseAndInjectE2ESessions(result, signalRepository);
309
338
  didFetchNewSession = true;
310
- // Cache fetched sessions using wire JIDs
311
- for (const wireJid of wireJids) {
312
- const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
313
- peerSessionsCache.set(signalId, true);
314
- }
315
339
  }
316
340
  return didFetchNewSession;
317
341
  };
@@ -354,53 +378,55 @@ export const makeMessagesSocket = (config) => {
354
378
  const meLid = authState.creds.me?.lid;
355
379
  const meLidUser = meLid ? jidDecode(meLid)?.user : null;
356
380
  const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
357
- if (!jid)
358
- return null;
359
- let msgToEncrypt = patchedMessage;
360
- if (dsmMessage) {
361
- const { user: targetUser } = jidDecode(jid);
362
- const { user: ownPnUser } = jidDecode(meId);
363
- const ownLidUser = meLidUser;
364
- const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
365
- const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
366
- if (isOwnUser && !isExactSenderDevice) {
367
- msgToEncrypt = dsmMessage;
368
- logger.debug({ jid, targetUser }, 'Using DSM for own device');
369
- }
370
- }
371
- const bytes = encodeWAMessage(msgToEncrypt);
372
- const mutexKey = jid;
373
- const node = await encryptionMutex.mutex(mutexKey, async () => {
374
- const { type, ciphertext } = await signalRepository.encryptMessage({
375
- jid,
376
- data: bytes
381
+ try {
382
+ if (!jid)
383
+ return null;
384
+ let msgToEncrypt = patchedMessage;
385
+ if (dsmMessage) {
386
+ const { user: targetUser } = jidDecode(jid);
387
+ const { user: ownPnUser } = jidDecode(meId);
388
+ const ownLidUser = meLidUser;
389
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
390
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
391
+ if (isOwnUser && !isExactSenderDevice) {
392
+ msgToEncrypt = dsmMessage;
393
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
394
+ }
395
+ }
396
+ const bytes = encodeWAMessage(msgToEncrypt);
397
+ const mutexKey = jid;
398
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
399
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
400
+ if (type === 'pkmsg') {
401
+ shouldIncludeDeviceIdentity = true;
402
+ }
403
+ return {
404
+ tag: 'to',
405
+ attrs: { jid },
406
+ content: [
407
+ {
408
+ tag: 'enc',
409
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
410
+ content: ciphertext
411
+ }
412
+ ]
413
+ };
377
414
  });
378
- if (type === 'pkmsg') {
379
- shouldIncludeDeviceIdentity = true;
380
- }
381
- return {
382
- tag: 'to',
383
- attrs: { jid },
384
- content: [
385
- {
386
- tag: 'enc',
387
- attrs: {
388
- v: '2',
389
- type,
390
- ...(extraAttrs || {})
391
- },
392
- content: ciphertext
393
- }
394
- ]
395
- };
396
- });
397
- return node;
415
+ return node;
416
+ }
417
+ catch (err) {
418
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
419
+ return null;
420
+ }
398
421
  });
399
422
  const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
423
+ if (recipientJids.length > 0 && nodes.length === 0) {
424
+ throw new Boom('All encryptions failed', { statusCode: 500 });
425
+ }
400
426
  return { nodes, shouldIncludeDeviceIdentity };
401
427
  };
402
428
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
403
- const meId = authState.creds.me.id;
429
+ const meId = assertMeId(authState.creds);
404
430
  const meLid = authState.creds.me?.lid;
405
431
  const isRetryResend = Boolean(participant?.jid);
406
432
  let shouldIncludeDeviceIdentity = isRetryResend;
@@ -419,6 +445,7 @@ export const makeMessagesSocket = (config) => {
419
445
  const destinationJid = !isStatus ? finalJid : statusJid;
420
446
  const binaryNodeContent = [];
421
447
  const devices = [];
448
+ let reportingMessage;
422
449
  const meMsg = {
423
450
  deviceSentMessage: {
424
451
  destinationJid,
@@ -465,7 +492,7 @@ export const makeMessagesSocket = (config) => {
465
492
  await sendNode(stanza);
466
493
  return;
467
494
  }
468
- if (normalizeMessageContent(message)?.pinInChatMessage) {
495
+ if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
469
496
  extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
470
497
  }
471
498
  if (isGroupOrStatus && !isRetryResend) {
@@ -513,6 +540,7 @@ export const makeMessagesSocket = (config) => {
513
540
  throw new Boom('Per-jid patching is not supported in groups');
514
541
  }
515
542
  const bytes = encodeWAMessage(patched);
543
+ reportingMessage = patched;
516
544
  const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
517
545
  const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
518
546
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
@@ -567,6 +595,12 @@ export const makeMessagesSocket = (config) => {
567
595
  logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
568
596
  }
569
597
  const { user: ownUser } = jidDecode(ownId);
598
+ if (!participant) {
599
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
600
+ reportingMessage = Array.isArray(patchedForReporting)
601
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
602
+ : patchedForReporting;
603
+ }
570
604
  if (!isRetryResend) {
571
605
  const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
572
606
  devices.push({
@@ -636,14 +670,42 @@ export const makeMessagesSocket = (config) => {
636
670
  if (isRetryResend) {
637
671
  const isParticipantLid = isLidUser(participant.jid);
638
672
  const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
673
+ let messageToSend = message;
674
+ if (isGroupOrStatus) {
675
+ let groupSenderIdentity;
676
+ if (meLid && (await signalRepository.hasSenderKey({ group: destinationJid, meId: meLid }))) {
677
+ groupSenderIdentity = meLid;
678
+ }
679
+ else if (await signalRepository.hasSenderKey({ group: destinationJid, meId })) {
680
+ groupSenderIdentity = meId;
681
+ }
682
+ if (groupSenderIdentity) {
683
+ try {
684
+ const skdm = await signalRepository.getSenderKeyDistributionMessage({
685
+ group: destinationJid,
686
+ meId: groupSenderIdentity
687
+ });
688
+ messageToSend = {
689
+ ...message,
690
+ senderKeyDistributionMessage: {
691
+ groupId: destinationJid,
692
+ axolotlSenderKeyDistributionMessage: skdm
693
+ }
694
+ };
695
+ }
696
+ catch (err) {
697
+ logger.warn({ err, jid: destinationJid }, 'failed to build SKDM for retry, sending without it');
698
+ }
699
+ }
700
+ }
639
701
  const encodedMessageToSend = isMe
640
702
  ? encodeWAMessage({
641
703
  deviceSentMessage: {
642
704
  destinationJid,
643
- message
705
+ message: messageToSend
644
706
  }
645
707
  })
646
- : encodeWAMessage(message);
708
+ : encodeWAMessage(messageToSend);
647
709
  const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
648
710
  data: encodedMessageToSend,
649
711
  jid: participant.jid
@@ -711,9 +773,53 @@ export const makeMessagesSocket = (config) => {
711
773
  });
712
774
  logger.debug({ jid }, 'adding device identity');
713
775
  }
714
- const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {};
715
- const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
716
- if (tcTokenBuffer) {
776
+ if (!isNewsletter &&
777
+ !isRetryResend &&
778
+ reportingMessage?.messageContextInfo?.messageSecret &&
779
+ shouldIncludeReportingToken(reportingMessage)) {
780
+ try {
781
+ const encoded = encodeWAMessage(reportingMessage);
782
+ const reportingKey = {
783
+ id: msgId,
784
+ fromMe: true,
785
+ remoteJid: destinationJid,
786
+ participant: participant?.jid
787
+ };
788
+ const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
789
+ if (reportingNode) {
790
+ ;
791
+ stanza.content.push(reportingNode);
792
+ logger.trace({ jid }, 'added reporting token to message');
793
+ }
794
+ }
795
+ catch (error) {
796
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
797
+ }
798
+ }
799
+ // WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
800
+ const isPeerMessage = additionalAttributes?.['category'] === 'peer';
801
+ const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
802
+ // Resolve destination to LID for tctoken storage — matches Signal session key pattern
803
+ const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
804
+ const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
805
+ const existingTokenEntry = contactTcTokenData[tcTokenJid];
806
+ let tcTokenBuffer = existingTokenEntry?.token;
807
+ // Treat expired tokens the same as missing — clear from cache
808
+ if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
809
+ logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
810
+ tcTokenBuffer = undefined;
811
+ // Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
812
+ const cleared = existingTokenEntry?.senderTimestamp !== undefined
813
+ ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
814
+ : null;
815
+ try {
816
+ await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
817
+ }
818
+ catch (err) {
819
+ logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
820
+ }
821
+ }
822
+ if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
717
823
  ;
718
824
  stanza.content.push({
719
825
  tag: 'tctoken',
@@ -727,6 +833,48 @@ export const makeMessagesSocket = (config) => {
727
833
  }
728
834
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
729
835
  await sendNode(stanza);
836
+ // Fire-and-forget: issue our token to the contact AFTER message send.
837
+ // WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
838
+ const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
839
+ const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
840
+ if (is1on1Send &&
841
+ !isProtocolMsg &&
842
+ !isBotOrPSA &&
843
+ shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
844
+ !inFlightTcTokenIssuance.has(tcTokenJid)) {
845
+ inFlightTcTokenIssuance.add(tcTokenJid);
846
+ const issueTimestamp = unixTimestampSeconds();
847
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
848
+ resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
849
+ .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
850
+ .then(async (result) => {
851
+ await storeTcTokensFromIqResult({
852
+ result,
853
+ fallbackJid: tcTokenJid,
854
+ keys: authState.keys,
855
+ getLIDForPN
856
+ });
857
+ const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
858
+ const currentEntry = currentData[tcTokenJid];
859
+ const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
860
+ await authState.keys.set({
861
+ tctoken: {
862
+ [tcTokenJid]: {
863
+ token: Buffer.alloc(0),
864
+ ...currentEntry,
865
+ senderTimestamp: issueTimestamp
866
+ },
867
+ ...indexWrite
868
+ }
869
+ });
870
+ })
871
+ .catch(err => {
872
+ logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
873
+ })
874
+ .finally(() => {
875
+ inFlightTcTokenIssuance.delete(tcTokenJid);
876
+ });
877
+ }
730
878
  // Add message to retry cache if enabled
731
879
  if (messageRetryManager && !participant) {
732
880
  messageRetryManager.addRecentMessage(destinationJid, msgId, message);
@@ -735,13 +883,22 @@ export const makeMessagesSocket = (config) => {
735
883
  return msgId;
736
884
  };
737
885
  const getMessageType = (message) => {
738
- if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
886
+ const normalizedMessage = normalizeMessageContent(message);
887
+ if (!normalizedMessage)
888
+ return 'text';
889
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
890
+ return 'reaction';
891
+ }
892
+ if (normalizedMessage.pollCreationMessage ||
893
+ normalizedMessage.pollCreationMessageV2 ||
894
+ normalizedMessage.pollCreationMessageV3 ||
895
+ normalizedMessage.pollUpdateMessage) {
739
896
  return 'poll';
740
897
  }
741
- if (message.eventMessage) {
898
+ if (normalizedMessage.eventMessage) {
742
899
  return 'event';
743
900
  }
744
- if (getMediaType(message) !== '') {
901
+ if (getMediaType(normalizedMessage) !== '') {
745
902
  return 'media';
746
903
  }
747
904
  return 'text';
@@ -794,8 +951,8 @@ export const makeMessagesSocket = (config) => {
794
951
  }
795
952
  return '';
796
953
  };
797
- const getPrivacyTokens = async (jids) => {
798
- const t = unixTimestampSeconds().toString();
954
+ const issuePrivacyTokens = async (jids, timestamp) => {
955
+ const t = (timestamp ?? unixTimestampSeconds()).toString();
799
956
  const result = await query({
800
957
  tag: 'iq',
801
958
  attrs: {
@@ -822,26 +979,40 @@ export const makeMessagesSocket = (config) => {
822
979
  };
823
980
  const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
824
981
  const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
982
+ registerSocketEndHandler(() => {
983
+ if (!config.userDevicesCache && userDevicesCache.close) {
984
+ userDevicesCache.close();
985
+ }
986
+ mediaConn = undefined;
987
+ if (messageRetryManager) {
988
+ messageRetryManager.clear();
989
+ }
990
+ });
825
991
  return {
826
992
  ...sock,
827
- getPrivacyTokens,
993
+ userDevicesCache,
994
+ devicesMutex,
995
+ issuePrivacyTokens,
828
996
  assertSessions,
829
997
  relayMessage,
830
998
  sendReceipt,
831
999
  sendReceipts,
832
1000
  readMessages,
833
1001
  refreshMediaConn,
1002
+ // Function (not getter) so the spread in chats.ts preserves the live closure binding.
1003
+ getMediaHost: () => mediaHost,
834
1004
  waUploadToServer,
835
1005
  fetchPrivacySettings,
836
1006
  sendPeerDataOperationMessage,
837
1007
  createParticipantNodes,
838
1008
  getUSyncDevices,
839
1009
  messageRetryManager,
1010
+ updateMemberLabel,
840
1011
  updateMediaMessage: async (message) => {
841
1012
  const content = assertMediaContent(message.message);
842
1013
  const mediaKey = content.mediaKey;
843
1014
  const meId = authState.creds.me.id;
844
- const node = await encryptMediaRetryRequest(message.key, mediaKey, meId);
1015
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
845
1016
  let error = undefined;
846
1017
  await Promise.all([
847
1018
  sendNode(node),
@@ -853,7 +1024,7 @@ export const makeMessagesSocket = (config) => {
853
1024
  }
854
1025
  else {
855
1026
  try {
856
- const media = await decryptMediaRetryData(result.media, mediaKey, result.key.id);
1027
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
857
1028
  if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
858
1029
  const resultStr = proto.MediaRetryNotification.ResultType[media.result];
859
1030
  throw new Boom(`Media re-upload failed by device (${resultStr})`, {
@@ -862,7 +1033,7 @@ export const makeMessagesSocket = (config) => {
862
1033
  });
863
1034
  }
864
1035
  content.directPath = media.directPath;
865
- content.url = getUrlFromDirectPath(content.directPath);
1036
+ content.url = getUrlFromDirectPath(content.directPath, mediaHost);
866
1037
  logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
867
1038
  }
868
1039
  catch (err) {
@@ -963,7 +1134,7 @@ export const makeMessagesSocket = (config) => {
963
1134
  });
964
1135
  if (config.emitOwnEvents) {
965
1136
  process.nextTick(async () => {
966
- await processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1137
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
967
1138
  });
968
1139
  }
969
1140
  return fullMsg;