@periskope/baileys 6.7.19 → 7.0.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 (139) hide show
  1. package/README.md +6 -0
  2. package/WAProto/GenerateStatics.sh +2 -2
  3. package/WAProto/index.d.ts +2793 -45578
  4. package/WAProto/index.js +19344 -147065
  5. package/lib/Defaults/index.d.ts +7 -1
  6. package/lib/Defaults/index.d.ts.map +1 -1
  7. package/lib/Defaults/index.js +11 -4
  8. package/lib/Defaults/index.js.map +1 -1
  9. package/lib/Signal/Group/group_cipher.d.ts +0 -1
  10. package/lib/Signal/Group/group_cipher.d.ts.map +1 -1
  11. package/lib/Signal/Group/group_cipher.js +28 -36
  12. package/lib/Signal/Group/group_cipher.js.map +1 -1
  13. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -1
  14. package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
  15. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -1
  16. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -1
  17. package/lib/Signal/Group/sender-key-message.js +1 -1
  18. package/lib/Signal/Group/sender-key-message.js.map +1 -1
  19. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -1
  20. package/lib/Signal/Group/sender-key-state.js +6 -1
  21. package/lib/Signal/Group/sender-key-state.js.map +1 -1
  22. package/lib/Signal/libsignal.d.ts +5 -1
  23. package/lib/Signal/libsignal.d.ts.map +1 -1
  24. package/lib/Signal/libsignal.js +211 -38
  25. package/lib/Signal/libsignal.js.map +1 -1
  26. package/lib/Signal/lid-mapping.d.ts +14 -3
  27. package/lib/Signal/lid-mapping.d.ts.map +1 -0
  28. package/lib/Signal/lid-mapping.js +53 -65
  29. package/lib/Signal/lid-mapping.js.map +1 -0
  30. package/lib/Socket/business.d.ts +25 -13
  31. package/lib/Socket/business.d.ts.map +1 -1
  32. package/lib/Socket/business.js +122 -1
  33. package/lib/Socket/business.js.map +1 -1
  34. package/lib/Socket/chats.d.ts +14 -8
  35. package/lib/Socket/chats.d.ts.map +1 -1
  36. package/lib/Socket/chats.js +54 -21
  37. package/lib/Socket/chats.js.map +1 -1
  38. package/lib/Socket/communities.d.ts +24 -10
  39. package/lib/Socket/communities.d.ts.map +1 -1
  40. package/lib/Socket/communities.js +45 -1
  41. package/lib/Socket/communities.js.map +1 -1
  42. package/lib/Socket/groups.d.ts +13 -8
  43. package/lib/Socket/groups.d.ts.map +1 -1
  44. package/lib/Socket/groups.js +12 -11
  45. package/lib/Socket/groups.js.map +1 -1
  46. package/lib/Socket/index.d.ts +24 -10
  47. package/lib/Socket/index.d.ts.map +1 -1
  48. package/lib/Socket/messages-recv.d.ts +18 -10
  49. package/lib/Socket/messages-recv.d.ts.map +1 -1
  50. package/lib/Socket/messages-recv.js +383 -220
  51. package/lib/Socket/messages-recv.js.map +1 -1
  52. package/lib/Socket/messages-send.d.ts +19 -10
  53. package/lib/Socket/messages-send.d.ts.map +1 -1
  54. package/lib/Socket/messages-send.js +454 -62
  55. package/lib/Socket/messages-send.js.map +1 -1
  56. package/lib/Socket/newsletter.d.ts +15 -11
  57. package/lib/Socket/newsletter.d.ts.map +1 -1
  58. package/lib/Socket/newsletter.js +3 -1
  59. package/lib/Socket/newsletter.js.map +1 -1
  60. package/lib/Socket/socket.d.ts +9 -2
  61. package/lib/Socket/socket.d.ts.map +1 -1
  62. package/lib/Socket/socket.js +266 -70
  63. package/lib/Socket/socket.js.map +1 -1
  64. package/lib/Types/Auth.d.ts +2 -1
  65. package/lib/Types/Auth.d.ts.map +1 -1
  66. package/lib/Types/Bussines.d.ts +25 -0
  67. package/lib/Types/Bussines.d.ts.map +1 -0
  68. package/lib/Types/Bussines.js +2 -0
  69. package/lib/Types/Bussines.js.map +1 -0
  70. package/lib/Types/Chat.d.ts +5 -0
  71. package/lib/Types/Chat.d.ts.map +1 -1
  72. package/lib/Types/Chat.js.map +1 -1
  73. package/lib/Types/Contact.d.ts +4 -4
  74. package/lib/Types/Contact.d.ts.map +1 -1
  75. package/lib/Types/Events.d.ts +2 -2
  76. package/lib/Types/Events.d.ts.map +1 -1
  77. package/lib/Types/GroupMetadata.d.ts +6 -4
  78. package/lib/Types/GroupMetadata.d.ts.map +1 -1
  79. package/lib/Types/Message.d.ts +28 -7
  80. package/lib/Types/Message.d.ts.map +1 -1
  81. package/lib/Types/Message.js +5 -1
  82. package/lib/Types/Message.js.map +1 -1
  83. package/lib/Types/Signal.d.ts +24 -0
  84. package/lib/Types/Signal.d.ts.map +1 -1
  85. package/lib/Types/Socket.d.ts +9 -1
  86. package/lib/Types/Socket.d.ts.map +1 -1
  87. package/lib/Utils/auth-utils.d.ts.map +1 -1
  88. package/lib/Utils/auth-utils.js +362 -78
  89. package/lib/Utils/auth-utils.js.map +1 -1
  90. package/lib/Utils/chat-utils.d.ts +2 -2
  91. package/lib/Utils/chat-utils.d.ts.map +1 -1
  92. package/lib/Utils/chat-utils.js +20 -3
  93. package/lib/Utils/chat-utils.js.map +1 -1
  94. package/lib/Utils/decode-wa-message.d.ts +10 -0
  95. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  96. package/lib/Utils/decode-wa-message.js +84 -13
  97. package/lib/Utils/decode-wa-message.js.map +1 -1
  98. package/lib/Utils/event-buffer.d.ts +0 -1
  99. package/lib/Utils/event-buffer.d.ts.map +1 -1
  100. package/lib/Utils/event-buffer.js +48 -4
  101. package/lib/Utils/event-buffer.js.map +1 -1
  102. package/lib/Utils/generics.d.ts.map +1 -1
  103. package/lib/Utils/generics.js +17 -8
  104. package/lib/Utils/generics.js.map +1 -1
  105. package/lib/Utils/history.d.ts.map +1 -1
  106. package/lib/Utils/history.js +1 -2
  107. package/lib/Utils/history.js.map +1 -1
  108. package/lib/Utils/index.d.ts +1 -0
  109. package/lib/Utils/index.d.ts.map +1 -1
  110. package/lib/Utils/index.js +1 -0
  111. package/lib/Utils/index.js.map +1 -1
  112. package/lib/Utils/message-retry-manager.d.ts +82 -0
  113. package/lib/Utils/message-retry-manager.d.ts.map +1 -0
  114. package/lib/Utils/message-retry-manager.js +149 -0
  115. package/lib/Utils/message-retry-manager.js.map +1 -0
  116. package/lib/Utils/messages-media.d.ts +3 -2
  117. package/lib/Utils/messages-media.d.ts.map +1 -1
  118. package/lib/Utils/messages-media.js +4 -1
  119. package/lib/Utils/messages-media.js.map +1 -1
  120. package/lib/Utils/messages.d.ts.map +1 -1
  121. package/lib/Utils/messages.js +35 -15
  122. package/lib/Utils/messages.js.map +1 -1
  123. package/lib/Utils/process-message.d.ts +3 -2
  124. package/lib/Utils/process-message.d.ts.map +1 -1
  125. package/lib/Utils/process-message.js +13 -2
  126. package/lib/Utils/process-message.js.map +1 -1
  127. package/lib/Utils/use-multi-file-auth-state.js +1 -1
  128. package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
  129. package/lib/Utils/validate-connection.d.ts.map +1 -1
  130. package/lib/Utils/validate-connection.js +5 -4
  131. package/lib/Utils/validate-connection.js.map +1 -1
  132. package/lib/WABinary/jid-utils.d.ts +6 -5
  133. package/lib/WABinary/jid-utils.d.ts.map +1 -1
  134. package/lib/WABinary/jid-utils.js +11 -5
  135. package/lib/WABinary/jid-utils.js.map +1 -1
  136. package/lib/WAM/encode.d.ts.map +1 -1
  137. package/lib/WAM/encode.js +0 -1
  138. package/lib/WAM/encode.js.map +1 -1
  139. package/package.json +12 -8
@@ -2,21 +2,26 @@ 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, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
5
+ import { WAMessageAddressingMode } from '../Types/index.js';
6
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
7
  import { getUrlInfo } from '../Utils/link-preview.js';
7
- import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
8
+ import { makeKeyedMutex } from '../Utils/make-mutex.js';
9
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET, transferDevice } from '../WABinary/index.js';
8
10
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
9
- import { makeGroupsSocket } from './groups.js';
10
11
  import { makeNewsletterSocket } from './newsletter.js';
11
12
  export const makeMessagesSocket = (config) => {
12
- const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: axiosOptions, patchMessageBeforeSending, cachedGroupMetadata } = config;
13
- const sock = makeNewsletterSocket(makeGroupsSocket(config));
13
+ const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: axiosOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
14
+ const sock = makeNewsletterSocket(config);
14
15
  const { ev, authState, processingMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral } = sock;
15
16
  const userDevicesCache = config.userDevicesCache ||
16
17
  new NodeCache({
17
18
  stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
18
19
  useClones: false
19
20
  });
21
+ // Initialize message retry manager if enabled
22
+ const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
23
+ // Prevent race conditions in Signal session encryption by user
24
+ const encryptionMutex = makeKeyedMutex();
20
25
  let mediaConn;
21
26
  const refreshMediaConn = async (forceGet = false) => {
22
27
  const media = await mediaConn;
@@ -65,7 +70,7 @@ export const makeMessagesSocket = (config) => {
65
70
  if (isReadReceipt) {
66
71
  node.attrs.t = unixTimestampSeconds().toString();
67
72
  }
68
- if (type === 'sender' && isJidUser(jid)) {
73
+ if (type === 'sender' && isPnUser(jid)) {
69
74
  node.attrs.recipient = jid;
70
75
  node.attrs.to = participant;
71
76
  }
@@ -108,6 +113,34 @@ export const makeMessagesSocket = (config) => {
108
113
  const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
109
114
  await sendReceipts(keys, readType);
110
115
  };
116
+ /**
117
+ * Deduplicate JIDs when both LID and PN versions exist for same user
118
+ * Prefers LID over PN to maintain single encryption layer
119
+ */
120
+ const deduplicateLidPnJids = (jids) => {
121
+ const lidUsers = new Set();
122
+ const filteredJids = [];
123
+ // Collect all LID users
124
+ for (const jid of jids) {
125
+ if (jid.includes('@lid')) {
126
+ const user = jidDecode(jid)?.user;
127
+ if (user)
128
+ lidUsers.add(user);
129
+ }
130
+ }
131
+ // Filter out PN versions when LID exists
132
+ for (const jid of jids) {
133
+ if (jid.includes('@s.whatsapp.net')) {
134
+ const user = jidDecode(jid)?.user;
135
+ if (user && lidUsers.has(user)) {
136
+ logger.debug({ jid }, 'Skipping PN - LID version exists');
137
+ continue;
138
+ }
139
+ }
140
+ filteredJids.push(jid);
141
+ }
142
+ return filteredJids;
143
+ };
111
144
  /** Fetch all the devices we've to send a message to */
112
145
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
113
146
  const deviceResults = [];
@@ -115,14 +148,33 @@ export const makeMessagesSocket = (config) => {
115
148
  logger.debug('not using cache for devices');
116
149
  }
117
150
  const toFetch = [];
118
- jids = Array.from(new Set(jids));
151
+ // Deduplicate and normalize JIDs
152
+ jids = deduplicateLidPnJids(Array.from(new Set(jids)));
119
153
  for (let jid of jids) {
120
- const user = jidDecode(jid)?.user;
154
+ const decoded = jidDecode(jid);
155
+ const user = decoded?.user;
156
+ const device = decoded?.device;
157
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
158
+ // Handle explicit device JIDs directly
159
+ if (isExplicitDevice && user) {
160
+ deviceResults.push({
161
+ user,
162
+ device,
163
+ wireJid: jid // Preserve exact JID format for wire protocol
164
+ });
165
+ continue;
166
+ }
167
+ // For user JIDs, normalize and prepare for device enumeration
121
168
  jid = jidNormalizedUser(jid);
122
169
  if (useCache) {
123
170
  const devices = userDevicesCache.get(user);
124
171
  if (devices) {
125
- deviceResults.push(...devices);
172
+ const isLidJid = jid.includes('@lid');
173
+ const devicesWithWire = devices.map(d => ({
174
+ ...d,
175
+ wireJid: isLidJid ? jidEncode(d.user, 'lid', d.device) : jidEncode(d.user, 's.whatsapp.net', d.device)
176
+ }));
177
+ deviceResults.push(...devicesWithWire);
126
178
  logger.trace({ user }, 'using cache for devices');
127
179
  }
128
180
  else {
@@ -136,6 +188,14 @@ export const makeMessagesSocket = (config) => {
136
188
  if (!toFetch.length) {
137
189
  return deviceResults;
138
190
  }
191
+ const requestedLidUsers = new Set();
192
+ for (const jid of toFetch) {
193
+ if (jid.includes('@lid')) {
194
+ const user = jidDecode(jid)?.user;
195
+ if (user)
196
+ requestedLidUsers.add(user);
197
+ }
198
+ }
139
199
  const query = new USyncQuery().withContext('message').withDeviceProtocol();
140
200
  for (const jid of toFetch) {
141
201
  query.withUser(new USyncUser().withId(jid));
@@ -147,7 +207,26 @@ export const makeMessagesSocket = (config) => {
147
207
  for (const item of extracted) {
148
208
  deviceMap[item.user] = deviceMap[item.user] || [];
149
209
  deviceMap[item.user]?.push(item);
150
- deviceResults.push(item);
210
+ }
211
+ // Process each user's devices as a group for bulk LID migration
212
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
213
+ const isLidUser = requestedLidUsers.has(user);
214
+ // Process all devices for this user
215
+ for (const item of userDevices) {
216
+ const finalWireJid = isLidUser
217
+ ? jidEncode(user, 'lid', item.device)
218
+ : jidEncode(item.user, 's.whatsapp.net', item.device);
219
+ deviceResults.push({
220
+ ...item,
221
+ wireJid: finalWireJid
222
+ });
223
+ logger.debug({
224
+ user: item.user,
225
+ device: item.device,
226
+ finalWireJid,
227
+ usedLid: isLidUser
228
+ }, 'Processed device with LID priority');
229
+ }
151
230
  }
152
231
  for (const key in deviceMap) {
153
232
  userDevicesCache.set(key, deviceMap[key]);
@@ -155,24 +234,176 @@ export const makeMessagesSocket = (config) => {
155
234
  }
156
235
  return deviceResults;
157
236
  };
237
+ // Helper to check if JID has migrated LID session
238
+ const checkForMigratedLidSession = async (jid) => {
239
+ if (!jid.includes('@s.whatsapp.net'))
240
+ return false;
241
+ const lidMapping = signalRepository.getLIDMappingStore();
242
+ const lidForPN = await lidMapping.getLIDForPN(jid);
243
+ if (!lidForPN?.includes('@lid'))
244
+ return false;
245
+ const lidSignalId = signalRepository.jidToSignalProtocolAddress(lidForPN);
246
+ const lidSessions = await authState.keys.get('session', [lidSignalId]);
247
+ return !!lidSessions[lidSignalId];
248
+ };
158
249
  const assertSessions = async (jids, force) => {
159
250
  let didFetchNewSession = false;
160
- let jidsRequiringFetch = [];
251
+ const jidsRequiringFetch = [];
252
+ // Apply same deduplication as in getUSyncDevices
253
+ jids = deduplicateLidPnJids(jids);
161
254
  if (force) {
162
- jidsRequiringFetch = jids;
255
+ // Check which sessions are missing (with LID migration check)
256
+ const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
257
+ const sessions = await authState.keys.get('session', addrs);
258
+ // Helper to check session for a JID
259
+ const checkJidSession = async (jid) => {
260
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
261
+ let hasSession = !!sessions[signalId];
262
+ // Check for migrated LID session if PN session missing
263
+ if (!hasSession) {
264
+ hasSession = await checkForMigratedLidSession(jid);
265
+ if (hasSession) {
266
+ logger.debug({ jid }, 'Found migrated LID session during force assert, skipping PN fetch');
267
+ }
268
+ }
269
+ // Add to fetch list if no session exists
270
+ if (!hasSession) {
271
+ if (jid.includes('@lid')) {
272
+ logger.debug({ jid }, 'No LID session found, will create new LID session');
273
+ }
274
+ jidsRequiringFetch.push(jid);
275
+ }
276
+ };
277
+ // Process all JIDs
278
+ for (const jid of jids) {
279
+ await checkJidSession(jid);
280
+ }
163
281
  }
164
282
  else {
283
+ const lidMapping = signalRepository.getLIDMappingStore();
165
284
  const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
166
285
  const sessions = await authState.keys.get('session', addrs);
286
+ // Group JIDs by user for bulk migration
287
+ const userGroups = new Map();
167
288
  for (const jid of jids) {
168
- const signalId = signalRepository.jidToSignalProtocolAddress(jid);
169
- if (!sessions[signalId]) {
170
- jidsRequiringFetch.push(jid);
289
+ const user = jidNormalizedUser(jid);
290
+ if (!userGroups.has(user)) {
291
+ userGroups.set(user, []);
292
+ }
293
+ userGroups.get(user).push(jid);
294
+ }
295
+ // Helper to check LID mapping for a user
296
+ const checkUserLidMapping = async (user, userJids) => {
297
+ if (!userJids.some(jid => jid.includes('@s.whatsapp.net'))) {
298
+ return { shouldMigrate: false, lidForPN: undefined };
299
+ }
300
+ try {
301
+ const mapping = await lidMapping.getLIDForPN(user);
302
+ if (mapping?.includes('@lid')) {
303
+ logger.debug({ user, lidForPN: mapping, deviceCount: userJids.length }, 'User has LID mapping - preparing bulk migration');
304
+ return { shouldMigrate: true, lidForPN: mapping };
305
+ }
306
+ }
307
+ catch (error) {
308
+ logger.debug({ user, error }, 'Failed to check LID mapping for user');
309
+ }
310
+ return { shouldMigrate: false, lidForPN: undefined };
311
+ };
312
+ // Helper to migrate a single device
313
+ const migrateDeviceToLid = async (jid, lidForPN) => {
314
+ if (!jid.includes('@s.whatsapp.net'))
315
+ return;
316
+ try {
317
+ const lidWithDevice = transferDevice(jid, lidForPN);
318
+ await signalRepository.migrateSession(jid, lidWithDevice);
319
+ logger.debug({ fromJid: jid, toJid: lidWithDevice }, 'Migrated device session to LID');
320
+ // Delete PN session after successful migration
321
+ try {
322
+ await signalRepository.deleteSession(jid);
323
+ logger.debug({ deletedPNSession: jid }, 'Deleted PN session after migration');
324
+ }
325
+ catch (deleteError) {
326
+ logger.warn({ jid, error: deleteError }, 'Failed to delete PN session');
327
+ }
328
+ }
329
+ catch (migrationError) {
330
+ logger.warn({ jid, error: migrationError }, 'Failed to migrate device session');
331
+ }
332
+ };
333
+ // Process each user group for potential bulk LID migration
334
+ for (const [user, userJids] of userGroups) {
335
+ const mappingResult = await checkUserLidMapping(user, userJids);
336
+ const shouldMigrateUser = mappingResult.shouldMigrate;
337
+ const lidForPN = mappingResult.lidForPN;
338
+ // Migrate all devices for this user if LID mapping exists
339
+ if (shouldMigrateUser && lidForPN) {
340
+ // Migrate each device individually
341
+ for (const jid of userJids) {
342
+ await migrateDeviceToLid(jid, lidForPN);
343
+ }
344
+ logger.info({
345
+ user,
346
+ lidMapping: lidForPN,
347
+ deviceCount: userJids.length
348
+ }, 'Completed migration attempt for user devices');
349
+ }
350
+ // Helper to check session for migrated user
351
+ const checkMigratedSession = async (jid) => {
352
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
353
+ let hasSession = !!sessions[signalId];
354
+ let jidToFetch = jid;
355
+ // Check if we should use migrated LID session instead
356
+ if (shouldMigrateUser && lidForPN && jid.includes('@s.whatsapp.net')) {
357
+ const originalDecoded = jidDecode(jid);
358
+ const deviceId = originalDecoded?.device || 0;
359
+ const lidDecoded = jidDecode(lidForPN);
360
+ const lidWithDevice = jidEncode(lidDecoded?.user, 'lid', deviceId);
361
+ // Check if LID session exists
362
+ const lidSignalId = signalRepository.jidToSignalProtocolAddress(lidWithDevice);
363
+ const lidSessions = await authState.keys.get('session', [lidSignalId]);
364
+ hasSession = !!lidSessions[lidSignalId];
365
+ jidToFetch = lidWithDevice;
366
+ if (hasSession) {
367
+ logger.debug({ originalJid: jid, lidJid: lidWithDevice }, '✅ Found bulk-migrated LID session');
368
+ }
369
+ }
370
+ // Add to fetch list if no session exists
371
+ if (!hasSession) {
372
+ jidsRequiringFetch.push(jidToFetch);
373
+ logger.debug({ jid: jidToFetch, originalJid: jid !== jidToFetch ? jid : undefined }, 'Adding to session fetch list');
374
+ }
375
+ };
376
+ // Now check which sessions need to be fetched for this user
377
+ for (const jid of userJids) {
378
+ await checkMigratedSession(jid);
171
379
  }
172
380
  }
173
381
  }
174
382
  if (jidsRequiringFetch.length) {
175
383
  logger.debug({ jidsRequiringFetch }, 'fetching sessions');
384
+ // DEBUG: Check if there are PN versions of LID users being fetched
385
+ const lidUsersBeingFetched = new Set();
386
+ const pnUsersBeingFetched = new Set();
387
+ for (const jid of jidsRequiringFetch) {
388
+ const user = jidDecode(jid)?.user;
389
+ if (user) {
390
+ if (jid.includes('@lid')) {
391
+ lidUsersBeingFetched.add(user);
392
+ }
393
+ else if (jid.includes('@s.whatsapp.net')) {
394
+ pnUsersBeingFetched.add(user);
395
+ }
396
+ }
397
+ }
398
+ // Find overlaps
399
+ const overlapping = Array.from(pnUsersBeingFetched).filter(user => lidUsersBeingFetched.has(user));
400
+ if (overlapping.length > 0) {
401
+ logger.warn({
402
+ overlapping,
403
+ lidUsersBeingFetched: Array.from(lidUsersBeingFetched),
404
+ pnUsersBeingFetched: Array.from(pnUsersBeingFetched)
405
+ }, 'Fetching both LID and PN sessions for same users');
406
+ }
176
407
  const result = await query({
177
408
  tag: 'iq',
178
409
  attrs: {
@@ -216,43 +447,132 @@ export const makeMessagesSocket = (config) => {
216
447
  });
217
448
  return msgId;
218
449
  };
219
- const createParticipantNodes = async (jids, message, extraAttrs) => {
450
+ const createParticipantNodes = async (jids, message, extraAttrs, dsmMessage) => {
220
451
  let patched = await patchMessageBeforeSending(message, jids);
221
452
  if (!Array.isArray(patched)) {
222
453
  patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
223
454
  }
224
455
  let shouldIncludeDeviceIdentity = false;
225
- const nodes = await Promise.all(patched.map(async (patchedMessageWithJid) => {
226
- const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid;
227
- if (!jid) {
228
- return {};
229
- }
230
- const bytes = encodeWAMessage(patchedMessage);
231
- const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
232
- if (type === 'pkmsg') {
233
- shouldIncludeDeviceIdentity = true;
234
- }
235
- const node = {
236
- tag: 'to',
237
- attrs: { jid },
238
- content: [
239
- {
240
- tag: 'enc',
241
- attrs: {
242
- v: '2',
243
- type,
244
- ...(extraAttrs || {})
245
- },
246
- content: ciphertext
456
+ const meId = authState.creds.me.id;
457
+ const meLid = authState.creds.me?.lid;
458
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
459
+ const devicesByUser = new Map();
460
+ for (const patchedMessageWithJid of patched) {
461
+ const { recipientJid: wireJid, ...patchedMessage } = patchedMessageWithJid;
462
+ if (!wireJid)
463
+ continue;
464
+ // Extract user from JID for grouping
465
+ const decoded = jidDecode(wireJid);
466
+ const user = decoded?.user;
467
+ if (!user)
468
+ continue;
469
+ if (!devicesByUser.has(user)) {
470
+ devicesByUser.set(user, []);
471
+ }
472
+ devicesByUser.get(user).push({ recipientJid: wireJid, patchedMessage });
473
+ }
474
+ // Process each user's devices sequentially, but different users in parallel
475
+ const userEncryptionPromises = Array.from(devicesByUser.entries()).map(([user, userDevices]) => encryptionMutex.mutex(user, async () => {
476
+ logger.debug({ user, deviceCount: userDevices.length }, 'Acquiring encryption lock for user devices');
477
+ const userNodes = [];
478
+ // Helper to get encryption JID with LID migration
479
+ const getEncryptionJid = async (wireJid) => {
480
+ if (!wireJid.includes('@s.whatsapp.net'))
481
+ return wireJid;
482
+ try {
483
+ const lidMapping = signalRepository.getLIDMappingStore();
484
+ const lidForPN = await lidMapping.getLIDForPN(wireJid);
485
+ if (!lidForPN?.includes('@lid'))
486
+ return wireJid;
487
+ // Preserve device ID from original wire JID
488
+ const wireDecoded = jidDecode(wireJid);
489
+ const deviceId = wireDecoded?.device || 0;
490
+ const lidDecoded = jidDecode(lidForPN);
491
+ const lidWithDevice = jidEncode(lidDecoded?.user, 'lid', deviceId);
492
+ // Migrate session to LID for unified encryption layer
493
+ try {
494
+ await signalRepository.migrateSession(wireJid, lidWithDevice);
495
+ const recipientUser = jidNormalizedUser(wireJid);
496
+ const ownPnUser = jidNormalizedUser(meId);
497
+ const isOwnDevice = recipientUser === ownPnUser;
498
+ logger.info({ wireJid, lidWithDevice, isOwnDevice }, 'Migrated to LID encryption');
499
+ // Delete PN session after successful migration
500
+ try {
501
+ await signalRepository.deleteSession(wireJid);
502
+ logger.debug({ deletedPNSession: wireJid }, 'Deleted PN session');
503
+ }
504
+ catch (deleteError) {
505
+ logger.warn({ wireJid, error: deleteError }, 'Failed to delete PN session');
506
+ }
507
+ return lidWithDevice;
247
508
  }
248
- ]
509
+ catch (migrationError) {
510
+ logger.warn({ wireJid, error: migrationError }, 'Failed to migrate session');
511
+ return wireJid;
512
+ }
513
+ }
514
+ catch (error) {
515
+ logger.debug({ wireJid, error }, 'Failed to check LID mapping');
516
+ return wireJid;
517
+ }
249
518
  };
250
- return node;
519
+ // Encrypt to this user's devices sequentially to prevent session corruption
520
+ for (const { recipientJid: wireJid, patchedMessage } of userDevices) {
521
+ // DSM logic: Use DSM for own other devices (following whatsmeow implementation)
522
+ let messageToEncrypt = patchedMessage;
523
+ if (dsmMessage) {
524
+ const { user: targetUser } = jidDecode(wireJid);
525
+ const { user: ownPnUser } = jidDecode(meId);
526
+ const ownLidUser = meLidUser;
527
+ // Check if this is our device (same user, different device)
528
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
529
+ // Exclude exact sender device (whatsmeow: if jid == ownJID || jid == ownLID { continue })
530
+ const isExactSenderDevice = wireJid === meId || (authState.creds.me?.lid && wireJid === authState.creds.me.lid);
531
+ if (isOwnUser && !isExactSenderDevice) {
532
+ messageToEncrypt = dsmMessage;
533
+ logger.debug({ wireJid, targetUser }, 'Using DSM for own device');
534
+ }
535
+ }
536
+ const bytes = encodeWAMessage(messageToEncrypt);
537
+ // Get encryption JID with LID migration
538
+ const encryptionJid = await getEncryptionJid(wireJid);
539
+ // ENCRYPT: Use the determined encryption identity (prefers migrated LID)
540
+ const { type, ciphertext } = await signalRepository.encryptMessage({
541
+ jid: encryptionJid, // Unified encryption layer (LID when available)
542
+ data: bytes
543
+ });
544
+ if (type === 'pkmsg') {
545
+ shouldIncludeDeviceIdentity = true;
546
+ }
547
+ const node = {
548
+ tag: 'to',
549
+ attrs: { jid: wireJid }, // Always use original wire identity in envelope
550
+ content: [
551
+ {
552
+ tag: 'enc',
553
+ attrs: {
554
+ v: '2',
555
+ type,
556
+ ...(extraAttrs || {})
557
+ },
558
+ content: ciphertext
559
+ }
560
+ ]
561
+ };
562
+ userNodes.push(node);
563
+ }
564
+ logger.debug({ user, nodesCreated: userNodes.length }, 'Releasing encryption lock for user devices');
565
+ return userNodes;
251
566
  }));
567
+ // Wait for all users to complete (users are processed in parallel)
568
+ const userNodesArrays = await Promise.all(userEncryptionPromises);
569
+ const nodes = userNodesArrays.flat();
252
570
  return { nodes, shouldIncludeDeviceIdentity };
253
571
  };
254
572
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
255
573
  const meId = authState.creds.me.id;
574
+ const meLid = authState.creds.me?.lid;
575
+ // ADDRESSING CONSISTENCY: Keep envelope addressing as user provided, handle LID migration in encryption
256
576
  let shouldIncludeDeviceIdentity = false;
257
577
  const { user, server } = jidDecode(jid);
258
578
  const statusJid = 'status@broadcast';
@@ -260,11 +580,22 @@ export const makeMessagesSocket = (config) => {
260
580
  const isStatus = jid === statusJid;
261
581
  const isLid = server === 'lid';
262
582
  const isNewsletter = server === 'newsletter';
583
+ // Keep user's original JID choice for envelope addressing
584
+ const finalJid = jid;
585
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
586
+ let ownId = meId;
587
+ if (isLid && meLid) {
588
+ ownId = meLid;
589
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
590
+ }
591
+ else {
592
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
593
+ }
263
594
  msgId = msgId || generateMessageIDV2(sock.user?.id);
264
595
  useUserDevicesCache = useUserDevicesCache !== false;
265
596
  useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
266
597
  const participants = [];
267
- const destinationJid = !isStatus ? jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid;
598
+ const destinationJid = !isStatus ? finalJid : statusJid;
268
599
  const binaryNodeContent = [];
269
600
  const devices = [];
270
601
  const meMsg = {
@@ -283,7 +614,11 @@ export const makeMessagesSocket = (config) => {
283
614
  additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
284
615
  }
285
616
  const { user, device } = jidDecode(participant.jid);
286
- devices.push({ user, device });
617
+ devices.push({
618
+ user,
619
+ device,
620
+ wireJid: participant.jid // Use the participant JID as wire JID
621
+ });
287
622
  }
288
623
  await authState.keys.transaction(async () => {
289
624
  const mediaType = getMediaType(message);
@@ -342,9 +677,10 @@ export const makeMessagesSocket = (config) => {
342
677
  participantsList.push(...statusJidList);
343
678
  }
344
679
  if (!isStatus) {
680
+ const groupAddressingMode = groupData?.addressingMode || (isLid ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN);
345
681
  additionalAttributes = {
346
682
  ...additionalAttributes,
347
- addressing_mode: groupData?.addressingMode || 'pn'
683
+ addressing_mode: groupAddressingMode
348
684
  };
349
685
  }
350
686
  const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
@@ -355,19 +691,24 @@ export const makeMessagesSocket = (config) => {
355
691
  throw new Boom('Per-jid patching is not supported in groups');
356
692
  }
357
693
  const bytes = encodeWAMessage(patched);
694
+ // This should match the group's addressing mode and conversation context
695
+ const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn');
696
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
358
697
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
359
698
  group: destinationJid,
360
699
  data: bytes,
361
- meId
700
+ meId: groupSenderIdentity
362
701
  });
363
702
  const senderKeyJids = [];
364
703
  // ensure a connection is established with every device
365
- for (const { user, device } of devices) {
366
- const jid = jidEncode(user, groupData?.addressingMode === 'lid' ? 'lid' : 's.whatsapp.net', device);
367
- if (!senderKeyMap[jid] || !!participant) {
368
- senderKeyJids.push(jid);
704
+ for (const device of devices) {
705
+ // This preserves the LID migration results from getUSyncDevices
706
+ const deviceJid = device.wireJid;
707
+ const hasKey = !!senderKeyMap[deviceJid];
708
+ if (!hasKey || !!participant) {
709
+ senderKeyJids.push(deviceJid);
369
710
  // store that this person has had the sender keys sent to them
370
- senderKeyMap[jid] = true;
711
+ senderKeyMap[deviceJid] = true;
371
712
  }
372
713
  }
373
714
  // if there are some participants with whom the session has not been established
@@ -393,23 +734,54 @@ export const makeMessagesSocket = (config) => {
393
734
  await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
394
735
  }
395
736
  else {
396
- const { user: meUser } = jidDecode(meId);
737
+ const { user: ownUser } = jidDecode(ownId);
397
738
  if (!participant) {
398
- devices.push({ user });
399
- if (user !== meUser) {
400
- devices.push({ user: meUser });
739
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
740
+ devices.push({
741
+ user,
742
+ device: 0,
743
+ wireJid: jidEncode(user, targetUserServer, 0)
744
+ });
745
+ // Own user matches conversation addressing mode
746
+ if (user !== ownUser) {
747
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
748
+ const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
749
+ devices.push({
750
+ user: ownUserForAddressing,
751
+ device: 0,
752
+ wireJid: jidEncode(ownUserForAddressing, ownUserServer, 0)
753
+ });
401
754
  }
402
755
  if (additionalAttributes?.['category'] !== 'peer') {
403
- const additionalDevices = await getUSyncDevices([meId, jid], !!useUserDevicesCache, true);
404
- devices.push(...additionalDevices);
756
+ // Clear placeholders and enumerate actual devices
757
+ devices.length = 0;
758
+ // Use conversation-appropriate sender identity
759
+ const senderIdentity = isLid && meLid
760
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
761
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
762
+ // Enumerate devices for sender and target with consistent addressing
763
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], false, false);
764
+ devices.push(...sessionDevices);
765
+ logger.debug({
766
+ deviceCount: devices.length,
767
+ devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.wireJid)?.server}`)
768
+ }, 'Device enumeration complete with unified addressing');
405
769
  }
406
770
  }
407
771
  const allJids = [];
408
772
  const meJids = [];
409
773
  const otherJids = [];
410
- for (const { user, device } of devices) {
411
- const isMe = user === meUser;
412
- const jid = jidEncode(isMe && isLid ? authState.creds?.me?.lid.split(':')[0] || user : user, isLid ? 'lid' : 's.whatsapp.net', device);
774
+ const { user: mePnUser } = jidDecode(meId);
775
+ const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
776
+ for (const { user, wireJid } of devices) {
777
+ const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid);
778
+ if (isExactSenderDevice) {
779
+ logger.debug({ wireJid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
780
+ continue;
781
+ }
782
+ // Check if this is our device (could match either PN or LID user)
783
+ const isMe = user === mePnUser || (meLidUser && user === meLidUser);
784
+ const jid = wireJid;
413
785
  if (isMe) {
414
786
  meJids.push(jid);
415
787
  }
@@ -418,10 +790,11 @@ export const makeMessagesSocket = (config) => {
418
790
  }
419
791
  allJids.push(jid);
420
792
  }
421
- await assertSessions(allJids, false);
793
+ await assertSessions([...otherJids, ...meJids], false);
422
794
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
423
- createParticipantNodes(meJids, meMsg, extraAttrs),
424
- createParticipantNodes(otherJids, message, extraAttrs)
795
+ // For own devices: use DSM if available (1:1 chats only)
796
+ createParticipantNodes(meJids, meMsg || message, extraAttrs),
797
+ createParticipantNodes(otherJids, message, extraAttrs, meMsg)
425
798
  ]);
426
799
  participants.push(...meNodes);
427
800
  participants.push(...otherNodes);
@@ -446,6 +819,7 @@ export const makeMessagesSocket = (config) => {
446
819
  tag: 'message',
447
820
  attrs: {
448
821
  id: msgId,
822
+ to: destinationJid,
449
823
  type: getMessageType(message),
450
824
  ...(additionalAttributes || {})
451
825
  },
@@ -485,13 +859,20 @@ export const makeMessagesSocket = (config) => {
485
859
  }
486
860
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
487
861
  await sendNode(stanza);
488
- });
862
+ // Add message to retry cache if enabled
863
+ if (messageRetryManager && !participant) {
864
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message);
865
+ }
866
+ }, meId);
489
867
  return msgId;
490
868
  };
491
869
  const getMessageType = (message) => {
492
870
  if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
493
871
  return 'poll';
494
872
  }
873
+ if (message.eventMessage) {
874
+ return 'event';
875
+ }
495
876
  return 'text';
496
877
  };
497
878
  const getMediaType = (message) => {
@@ -583,6 +964,7 @@ export const makeMessagesSocket = (config) => {
583
964
  sendPeerDataOperationMessage,
584
965
  createParticipantNodes,
585
966
  getUSyncDevices,
967
+ messageRetryManager,
586
968
  updateMediaMessage: async (message) => {
587
969
  const content = assertMediaContent(message.message);
588
970
  const mediaKey = content.mediaKey;
@@ -654,12 +1036,14 @@ export const makeMessagesSocket = (config) => {
654
1036
  }),
655
1037
  //TODO: CACHE
656
1038
  getProfilePicUrl: sock.profilePictureUrl,
1039
+ getCallLink: sock.createCallLink,
657
1040
  upload: waUploadToServer,
658
1041
  mediaCache: config.mediaCache,
659
1042
  options: config.options,
660
1043
  messageId: generateMessageIDV2(sock.user?.id),
661
1044
  ...options
662
1045
  });
1046
+ const isEventMsg = 'event' in content && !!content.event;
663
1047
  const isDeleteMsg = 'delete' in content && !!content.delete;
664
1048
  const isEditMsg = 'edit' in content && !!content.edit;
665
1049
  const isPinMsg = 'pin' in content && !!content.pin;
@@ -690,6 +1074,14 @@ export const makeMessagesSocket = (config) => {
690
1074
  }
691
1075
  });
692
1076
  }
1077
+ else if (isEventMsg) {
1078
+ additionalNodes.push({
1079
+ tag: 'meta',
1080
+ attrs: {
1081
+ event_type: 'creation'
1082
+ }
1083
+ });
1084
+ }
693
1085
  if ('cachedGroupMetadata' in options) {
694
1086
  console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
695
1087
  }