@periskope/baileys 7.0.0-alpha-8 → 7.0.0-alpha-10

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 (113) hide show
  1. package/README.md +3 -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.js +1 -1
  6. package/lib/Defaults/index.js.map +1 -1
  7. package/lib/Signal/Group/group_cipher.d.ts +1 -1
  8. package/lib/Signal/Group/group_cipher.d.ts.map +1 -1
  9. package/lib/Signal/Group/group_cipher.js +1 -4
  10. package/lib/Signal/Group/group_cipher.js.map +1 -1
  11. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -1
  12. package/lib/Signal/Group/sender-chain-key.js +1 -13
  13. package/lib/Signal/Group/sender-chain-key.js.map +1 -1
  14. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -1
  15. package/lib/Signal/Group/sender-key-distribution-message.js +6 -11
  16. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -1
  17. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -1
  18. package/lib/Signal/Group/sender-key-message.js +3 -5
  19. package/lib/Signal/Group/sender-key-message.js.map +1 -1
  20. package/lib/Signal/Group/sender-key-record.d.ts +1 -1
  21. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -1
  22. package/lib/Signal/Group/sender-key-record.js +2 -11
  23. package/lib/Signal/Group/sender-key-record.js.map +1 -1
  24. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -1
  25. package/lib/Signal/Group/sender-key-state.js +9 -52
  26. package/lib/Signal/Group/sender-key-state.js.map +1 -1
  27. package/lib/Signal/libsignal.d.ts +2 -5
  28. package/lib/Signal/libsignal.d.ts.map +1 -1
  29. package/lib/Signal/libsignal.js +109 -88
  30. package/lib/Signal/libsignal.js.map +1 -1
  31. package/lib/Signal/lid-mapping.d.ts +3 -6
  32. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  33. package/lib/Signal/lid-mapping.js +19 -33
  34. package/lib/Signal/lid-mapping.js.map +1 -1
  35. package/lib/Socket/business.d.ts +2 -2
  36. package/lib/Socket/communities.d.ts +2 -2
  37. package/lib/Socket/communities.d.ts.map +1 -1
  38. package/lib/Socket/communities.js +1 -1
  39. package/lib/Socket/communities.js.map +1 -1
  40. package/lib/Socket/groups.d.ts.map +1 -1
  41. package/lib/Socket/groups.js +1 -1
  42. package/lib/Socket/groups.js.map +1 -1
  43. package/lib/Socket/index.d.ts +2 -2
  44. package/lib/Socket/messages-recv.d.ts +2 -2
  45. package/lib/Socket/messages-recv.d.ts.map +1 -1
  46. package/lib/Socket/messages-recv.js +12 -33
  47. package/lib/Socket/messages-recv.js.map +1 -1
  48. package/lib/Socket/messages-send.d.ts +2 -2
  49. package/lib/Socket/messages-send.d.ts.map +1 -1
  50. package/lib/Socket/messages-send.js +157 -288
  51. package/lib/Socket/messages-send.js.map +1 -1
  52. package/lib/Socket/socket.d.ts.map +1 -1
  53. package/lib/Socket/socket.js +34 -12
  54. package/lib/Socket/socket.js.map +1 -1
  55. package/lib/Types/Auth.d.ts +1 -0
  56. package/lib/Types/Auth.d.ts.map +1 -1
  57. package/lib/Types/Message.d.ts +1 -0
  58. package/lib/Types/Message.d.ts.map +1 -1
  59. package/lib/Types/Message.js.map +1 -1
  60. package/lib/Types/Signal.d.ts +1 -11
  61. package/lib/Types/Signal.d.ts.map +1 -1
  62. package/lib/Types/Socket.d.ts +1 -1
  63. package/lib/Types/Socket.d.ts.map +1 -1
  64. package/lib/Utils/auth-utils.js +1 -1
  65. package/lib/Utils/auth-utils.js.map +1 -1
  66. package/lib/Utils/browser-utils.d.ts +4 -0
  67. package/lib/Utils/browser-utils.d.ts.map +1 -0
  68. package/lib/Utils/browser-utils.js +28 -0
  69. package/lib/Utils/browser-utils.js.map +1 -0
  70. package/lib/Utils/chat-utils.d.ts.map +1 -1
  71. package/lib/Utils/chat-utils.js +7 -6
  72. package/lib/Utils/chat-utils.js.map +1 -1
  73. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  74. package/lib/Utils/decode-wa-message.js +20 -6
  75. package/lib/Utils/decode-wa-message.js.map +1 -1
  76. package/lib/Utils/generics.d.ts +1 -3
  77. package/lib/Utils/generics.d.ts.map +1 -1
  78. package/lib/Utils/generics.js +11 -29
  79. package/lib/Utils/generics.js.map +1 -1
  80. package/lib/Utils/history.d.ts.map +1 -1
  81. package/lib/Utils/history.js +2 -1
  82. package/lib/Utils/history.js.map +1 -1
  83. package/lib/Utils/index.d.ts +2 -0
  84. package/lib/Utils/index.d.ts.map +1 -1
  85. package/lib/Utils/index.js +2 -0
  86. package/lib/Utils/index.js.map +1 -1
  87. package/lib/Utils/messages-media.d.ts.map +1 -1
  88. package/lib/Utils/messages-media.js +2 -1
  89. package/lib/Utils/messages-media.js.map +1 -1
  90. package/lib/Utils/messages.d.ts.map +1 -1
  91. package/lib/Utils/messages.js +20 -16
  92. package/lib/Utils/messages.js.map +1 -1
  93. package/lib/Utils/noise-handler.d.ts.map +1 -1
  94. package/lib/Utils/noise-handler.js +2 -1
  95. package/lib/Utils/noise-handler.js.map +1 -1
  96. package/lib/Utils/process-message.d.ts.map +1 -1
  97. package/lib/Utils/process-message.js +8 -2
  98. package/lib/Utils/process-message.js.map +1 -1
  99. package/lib/Utils/proto-utils.d.ts +7 -0
  100. package/lib/Utils/proto-utils.d.ts.map +1 -0
  101. package/lib/Utils/proto-utils.js +7 -0
  102. package/lib/Utils/proto-utils.js.map +1 -0
  103. package/lib/Utils/signal.d.ts.map +1 -1
  104. package/lib/Utils/signal.js.map +1 -1
  105. package/lib/Utils/use-multi-file-auth-state.js +1 -1
  106. package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
  107. package/lib/Utils/validate-connection.d.ts.map +1 -1
  108. package/lib/Utils/validate-connection.js +7 -6
  109. package/lib/Utils/validate-connection.js.map +1 -1
  110. package/lib/WABinary/generic-utils.d.ts.map +1 -1
  111. package/lib/WABinary/generic-utils.js +2 -1
  112. package/lib/WABinary/generic-utils.js.map +1 -1
  113. package/package.json +1 -1
@@ -18,6 +18,10 @@ export const makeMessagesSocket = (config) => {
18
18
  stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
19
19
  useClones: false
20
20
  });
21
+ const peerSessionsCache = new NodeCache({
22
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
23
+ useClones: false
24
+ });
21
25
  // Initialize message retry manager if enabled
22
26
  const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
23
27
  // Prevent race conditions in Signal session encryption by user
@@ -113,33 +117,28 @@ export const makeMessagesSocket = (config) => {
113
117
  const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
114
118
  await sendReceipts(keys, readType);
115
119
  };
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
- }
120
+ const resolveSessionJids = async (jids) => {
121
+ const uniquePnJids = Array.from(new Set(jids.filter(isPnUser)));
122
+ if (!uniquePnJids.length) {
123
+ return new Map();
130
124
  }
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
- }
125
+ const lookups = await Promise.all(uniquePnJids.map(async (pnJid) => {
126
+ try {
127
+ const resolved = await signalRepository.lidMapping.getLIDForPN(pnJid);
128
+ return resolved ? [pnJid, resolved] : [pnJid, pnJid];
129
+ }
130
+ catch (error) {
131
+ logger.warn({ pnJid, error }, 'Failed to resolve LID mapping for PN JID');
132
+ return [pnJid, pnJid];
133
+ }
134
+ }));
135
+ const sessionMap = new Map();
136
+ for (const entry of lookups) {
137
+ if (entry) {
138
+ sessionMap.set(entry[0], entry[1]);
139
139
  }
140
- filteredJids.push(jid);
141
140
  }
142
- return filteredJids;
141
+ return sessionMap;
143
142
  };
144
143
  /** Fetch all the devices we've to send a message to */
145
144
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
@@ -148,7 +147,6 @@ export const makeMessagesSocket = (config) => {
148
147
  logger.debug('not using cache for devices');
149
148
  }
150
149
  const toFetch = [];
151
- jids = deduplicateLidPnJids(Array.from(new Set(jids)));
152
150
  const jidsWithUser = jids
153
151
  .map(jid => {
154
152
  const decoded = jidDecode(jid);
@@ -246,139 +244,49 @@ export const makeMessagesSocket = (config) => {
246
244
  await userDevicesCache.set(key, deviceMap[key]);
247
245
  }
248
246
  }
247
+ const userDeviceUpdates = {};
248
+ for (const [userId, devices] of Object.entries(deviceMap)) {
249
+ if (devices && devices.length > 0) {
250
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
251
+ }
252
+ }
253
+ if (Object.keys(userDeviceUpdates).length > 0) {
254
+ try {
255
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
256
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
257
+ }
258
+ catch (error) {
259
+ logger.warn({ error }, 'failed to store user device lists');
260
+ }
261
+ }
249
262
  }
250
263
  return deviceResults;
251
264
  };
252
- const assertSessions = async (jids, force) => {
265
+ const assertSessions = async (jids) => {
253
266
  let didFetchNewSession = false;
267
+ const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
254
268
  const jidsRequiringFetch = [];
255
- // Apply same deduplication as in getUSyncDevices
256
- jids = deduplicateLidPnJids(jids);
257
- if (force) {
258
- // Check which sessions are missing (with LID migration check)
259
- const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
260
- const sessions = await authState.keys.get('session', addrs);
261
- // Simplified: Check session existence directly
262
- const checkJidSession = (jid) => {
263
- const signalId = signalRepository.jidToSignalProtocolAddress(jid);
264
- const hasSession = !!sessions[signalId];
265
- // Add to fetch list if no session exists
266
- // Session type selection (LID vs PN) is handled in encryptMessage
267
- if (!hasSession) {
268
- if (jid.includes('@lid')) {
269
- logger.debug({ jid }, 'No LID session found, will create new LID session');
270
- }
271
- jidsRequiringFetch.push(jid);
272
- }
273
- };
274
- // Process all JIDs
275
- for (const jid of jids) {
276
- checkJidSession(jid);
277
- }
278
- }
279
- else {
280
- const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid));
281
- const sessions = await authState.keys.get('session', addrs);
282
- // Group JIDs by user for bulk migration
283
- const userGroups = new Map();
284
- for (const jid of jids) {
285
- const user = jidNormalizedUser(jid);
286
- if (!userGroups.has(user)) {
287
- userGroups.set(user, []);
269
+ // Check peerSessionsCache and authState.keys
270
+ for (const jid of uniqueJids) {
271
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
272
+ const cachedSession = peerSessionsCache.get(signalId);
273
+ if (cachedSession !== undefined) {
274
+ if (cachedSession) {
275
+ continue; // Session exists in cache
288
276
  }
289
- userGroups.get(user).push(jid);
290
277
  }
291
- // Helper to check LID mapping for a user
292
- const checkUserLidMapping = async (user, userJids) => {
293
- if (!userJids.some(jid => jid.includes('@s.whatsapp.net'))) {
294
- return { shouldMigrate: false, lidForPN: undefined };
295
- }
296
- try {
297
- // Convert user to proper PN JID format for getLIDForPN
298
- const pnJid = `${user}@s.whatsapp.net`;
299
- const mapping = await signalRepository.lidMapping.getLIDForPN(pnJid);
300
- if (mapping?.includes('@lid')) {
301
- logger.debug({ user, lidForPN: mapping, deviceCount: userJids.length }, 'User has LID mapping - preparing bulk migration');
302
- return { shouldMigrate: true, lidForPN: mapping };
303
- }
304
- }
305
- catch (error) {
306
- logger.debug({ user, error }, 'Failed to check LID mapping for user');
307
- }
308
- return { shouldMigrate: false, lidForPN: undefined };
309
- };
310
- // Process each user group for potential bulk LID migration
311
- for (const [user, userJids] of userGroups) {
312
- const mappingResult = await checkUserLidMapping(user, userJids);
313
- const shouldMigrateUser = mappingResult.shouldMigrate;
314
- const lidForPN = mappingResult.lidForPN;
315
- // Migrate all devices for this user if LID mapping exists
316
- if (shouldMigrateUser && lidForPN) {
317
- // Bulk migrate all user devices in single transaction
318
- const migrationResult = await signalRepository.migrateSession(userJids, lidForPN);
319
- if (migrationResult.migrated > 0) {
320
- logger.info({
321
- user,
322
- lidMapping: lidForPN,
323
- migrated: migrationResult.migrated,
324
- skipped: migrationResult.skipped,
325
- total: migrationResult.total
326
- }, 'Completed bulk migration for user devices');
327
- }
328
- else {
329
- logger.debug({
330
- user,
331
- lidMapping: lidForPN,
332
- skipped: migrationResult.skipped,
333
- total: migrationResult.total
334
- }, 'All user device sessions already migrated');
335
- }
278
+ else {
279
+ const sessions = await authState.keys.get('session', [signalId]);
280
+ const hasSession = !!sessions[signalId];
281
+ peerSessionsCache.set(signalId, hasSession);
282
+ if (hasSession) {
283
+ continue;
336
284
  }
337
- // Direct bulk session check with LID single source of truth
338
- const addMissingSessionsToFetchList = (jid) => {
339
- const signalId = signalRepository.jidToSignalProtocolAddress(jid);
340
- if (sessions[signalId])
341
- return;
342
- // Determine correct JID to fetch (LID if mapping exists, otherwise original)
343
- if (jid.includes('@s.whatsapp.net') && shouldMigrateUser && lidForPN) {
344
- const decoded = jidDecode(jid);
345
- const lidDeviceJid = decoded.device !== undefined ? `${jidDecode(lidForPN).user}:${decoded.device}@lid` : lidForPN;
346
- jidsRequiringFetch.push(lidDeviceJid);
347
- logger.debug({ pnJid: jid, lidJid: lidDeviceJid }, 'Adding LID JID to fetch list (conversion)');
348
- }
349
- else {
350
- jidsRequiringFetch.push(jid);
351
- logger.debug({ jid }, 'Adding JID to fetch list');
352
- }
353
- };
354
- userJids.forEach(addMissingSessionsToFetchList);
355
285
  }
286
+ jidsRequiringFetch.push(jid);
356
287
  }
357
288
  if (jidsRequiringFetch.length) {
358
289
  logger.debug({ jidsRequiringFetch }, 'fetching sessions');
359
- // DEBUG: Check if there are PN versions of LID users being fetched
360
- const lidUsersBeingFetched = new Set();
361
- const pnUsersBeingFetched = new Set();
362
- for (const jid of jidsRequiringFetch) {
363
- const user = jidDecode(jid)?.user;
364
- if (user) {
365
- if (jid.includes('@lid')) {
366
- lidUsersBeingFetched.add(user);
367
- }
368
- else if (jid.includes('@s.whatsapp.net')) {
369
- pnUsersBeingFetched.add(user);
370
- }
371
- }
372
- }
373
- // Find overlaps
374
- const overlapping = Array.from(pnUsersBeingFetched).filter(user => lidUsersBeingFetched.has(user));
375
- if (overlapping.length > 0) {
376
- logger.warn({
377
- overlapping,
378
- lidUsersBeingFetched: Array.from(lidUsersBeingFetched),
379
- pnUsersBeingFetched: Array.from(pnUsersBeingFetched)
380
- }, 'Fetching both LID and PN sessions for same users');
381
- }
382
290
  const result = await query({
383
291
  tag: 'iq',
384
292
  attrs: {
@@ -399,6 +307,11 @@ export const makeMessagesSocket = (config) => {
399
307
  });
400
308
  await parseAndInjectE2ESessions(result, signalRepository);
401
309
  didFetchNewSession = true;
310
+ // Cache fetched sessions
311
+ for (const jid of jidsRequiringFetch) {
312
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
313
+ peerSessionsCache.set(signalId, true);
314
+ }
402
315
  }
403
316
  return didFetchNewSession;
404
317
  };
@@ -422,107 +335,47 @@ export const makeMessagesSocket = (config) => {
422
335
  });
423
336
  return msgId;
424
337
  };
425
- const createParticipantNodes = async (jids, message, extraAttrs, dsmMessage) => {
426
- let patched = await patchMessageBeforeSending(message, jids);
427
- if (!Array.isArray(patched)) {
428
- patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
429
- }
338
+ const createParticipantNodesWithSessionMap = async (recipientWireJids, sessionMap, message, extraAttrs, dsmMessage) => {
339
+ if (!recipientWireJids.length) {
340
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
341
+ }
342
+ const patched = await patchMessageBeforeSending(message, recipientWireJids);
343
+ const patchedMessages = Array.isArray(patched)
344
+ ? patched
345
+ : recipientWireJids.map(jid => ({ recipientJid: jid, message: patched }));
430
346
  let shouldIncludeDeviceIdentity = false;
431
347
  const meId = authState.creds.me.id;
432
348
  const meLid = authState.creds.me?.lid;
433
349
  const meLidUser = meLid ? jidDecode(meLid)?.user : null;
434
- const devicesByUser = new Map();
435
- for (const patchedMessageWithJid of patched) {
436
- const { recipientJid: wireJid, ...patchedMessage } = patchedMessageWithJid;
350
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: wireJid, message: patchedMessage }) => {
437
351
  if (!wireJid)
438
- continue;
439
- // Extract user from JID for grouping
440
- const decoded = jidDecode(wireJid);
441
- const user = decoded?.user;
442
- if (!user)
443
- continue;
444
- if (!devicesByUser.has(user)) {
445
- devicesByUser.set(user, []);
446
- }
447
- devicesByUser.get(user).push({ recipientJid: wireJid, patchedMessage });
448
- }
449
- // Process each user's devices sequentially, but different users in parallel
450
- const userEncryptionPromises = Array.from(devicesByUser.entries()).map(([user, userDevices]) => encryptionMutex.mutex(user, async () => {
451
- logger.debug({ user, deviceCount: userDevices.length }, 'Acquiring encryption lock for user devices');
452
- const userNodes = [];
453
- // Helper to get encryption JID with LID migration
454
- const getEncryptionJid = async (wireJid) => {
455
- if (!wireJid.includes('@s.whatsapp.net'))
456
- return wireJid;
457
- try {
458
- const lidForPN = await signalRepository.lidMapping.getLIDForPN(wireJid);
459
- if (!lidForPN?.includes('@lid'))
460
- return wireJid;
461
- // Preserve device ID from original wire JID
462
- const wireDecoded = jidDecode(wireJid);
463
- const deviceId = wireDecoded?.device || 0;
464
- const lidDecoded = jidDecode(lidForPN);
465
- const lidWithDevice = jidEncode(lidDecoded?.user, 'lid', deviceId);
466
- // Migrate session to LID for unified encryption layer
467
- try {
468
- const migrationResult = await signalRepository.migrateSession([wireJid], lidWithDevice);
469
- const recipientUser = jidNormalizedUser(wireJid);
470
- const ownPnUser = jidNormalizedUser(meId);
471
- const isOwnDevice = recipientUser === ownPnUser;
472
- logger.info({ wireJid, lidWithDevice, isOwnDevice }, 'Migrated to LID encryption');
473
- // Delete PN session after successful migration
474
- try {
475
- if (migrationResult.migrated) {
476
- await signalRepository.deleteSession([wireJid]);
477
- logger.debug({ deletedPNSession: wireJid }, 'Deleted PN session');
478
- }
479
- }
480
- catch (deleteError) {
481
- logger.warn({ wireJid, error: deleteError }, 'Failed to delete PN session');
482
- }
483
- return lidWithDevice;
484
- }
485
- catch (migrationError) {
486
- logger.warn({ wireJid, error: migrationError }, 'Failed to migrate session');
487
- return wireJid;
488
- }
489
- }
490
- catch (error) {
491
- logger.debug({ wireJid, error }, 'Failed to check LID mapping');
492
- return wireJid;
493
- }
494
- };
495
- // Encrypt to this user's devices sequentially to prevent session corruption
496
- for (const { recipientJid: wireJid, patchedMessage } of userDevices) {
497
- // DSM logic: Use DSM for own other devices (following whatsmeow implementation)
498
- let messageToEncrypt = patchedMessage;
499
- if (dsmMessage) {
500
- const { user: targetUser } = jidDecode(wireJid);
501
- const { user: ownPnUser } = jidDecode(meId);
502
- const ownLidUser = meLidUser;
503
- // Check if this is our device (same user, different device)
504
- const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
505
- // Exclude exact sender device (whatsmeow: if jid == ownJID || jid == ownLID { continue })
506
- const isExactSenderDevice = wireJid === meId || (authState.creds.me?.lid && wireJid === authState.creds.me.lid);
507
- if (isOwnUser && !isExactSenderDevice) {
508
- messageToEncrypt = dsmMessage;
509
- logger.debug({ wireJid, targetUser }, 'Using DSM for own device');
510
- }
511
- }
512
- const bytes = encodeWAMessage(messageToEncrypt);
513
- // Get encryption JID with LID migration
514
- const encryptionJid = await getEncryptionJid(wireJid);
515
- // ENCRYPT: Use the determined encryption identity (prefers migrated LID)
352
+ return null;
353
+ wireJid = sessionMap.get(wireJid) ?? wireJid;
354
+ let msgToEncrypt = patchedMessage;
355
+ if (dsmMessage) {
356
+ const { user: targetUser } = jidDecode(wireJid);
357
+ const { user: ownPnUser } = jidDecode(meId);
358
+ const ownLidUser = meLidUser;
359
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
360
+ const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid);
361
+ if (isOwnUser && !isExactSenderDevice) {
362
+ msgToEncrypt = dsmMessage;
363
+ logger.debug({ wireJid, targetUser }, 'Using DSM for own device');
364
+ }
365
+ }
366
+ const bytes = encodeWAMessage(msgToEncrypt);
367
+ const mutexKey = wireJid;
368
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
516
369
  const { type, ciphertext } = await signalRepository.encryptMessage({
517
- jid: encryptionJid, // Unified encryption layer (LID when available)
370
+ jid: wireJid,
518
371
  data: bytes
519
372
  });
520
373
  if (type === 'pkmsg') {
521
374
  shouldIncludeDeviceIdentity = true;
522
375
  }
523
- const node = {
376
+ return {
524
377
  tag: 'to',
525
- attrs: { jid: wireJid }, // Always use original wire identity in envelope
378
+ attrs: { jid: wireJid },
526
379
  content: [
527
380
  {
528
381
  tag: 'enc',
@@ -535,28 +388,27 @@ export const makeMessagesSocket = (config) => {
535
388
  }
536
389
  ]
537
390
  };
538
- userNodes.push(node);
539
- }
540
- logger.debug({ user, nodesCreated: userNodes.length }, 'Releasing encryption lock for user devices');
541
- return userNodes;
542
- }));
543
- // Wait for all users to complete (users are processed in parallel)
544
- const userNodesArrays = await Promise.all(userEncryptionPromises);
545
- const nodes = userNodesArrays.flat();
391
+ });
392
+ return node;
393
+ });
394
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
546
395
  return { nodes, shouldIncludeDeviceIdentity };
547
396
  };
397
+ const createParticipantNodes = async (recipientWireJids, message, extraAttrs, dsmMessage) => {
398
+ const sessionMap = await resolveSessionJids(recipientWireJids);
399
+ return createParticipantNodesWithSessionMap(recipientWireJids, sessionMap, message, extraAttrs, dsmMessage);
400
+ };
548
401
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
549
402
  const meId = authState.creds.me.id;
550
403
  const meLid = authState.creds.me?.lid;
551
- // ADDRESSING CONSISTENCY: Keep envelope addressing as user provided, handle LID migration in encryption
552
- let shouldIncludeDeviceIdentity = false;
553
- const { user, server } = jidDecode(jid);
404
+ const isRetryResend = Boolean(participant?.jid);
405
+ let shouldIncludeDeviceIdentity = isRetryResend;
554
406
  const statusJid = 'status@broadcast';
407
+ const { user, server } = jidDecode(jid);
555
408
  const isGroup = server === 'g.us';
556
409
  const isStatus = jid === statusJid;
557
410
  const isLid = server === 'lid';
558
411
  const isNewsletter = server === 'newsletter';
559
- // Keep user's original JID choice for envelope addressing
560
412
  const finalJid = jid;
561
413
  // ADDRESSING CONSISTENCY: Match own identity to conversation context
562
414
  // TODO: investigate if this is true
@@ -584,17 +436,14 @@ export const makeMessagesSocket = (config) => {
584
436
  };
585
437
  const extraAttrs = {};
586
438
  if (participant) {
587
- // when the retry request is not for a group
588
- // only send to the specific device that asked for a retry
589
- // otherwise the message is sent out to every device that should be a recipient
590
439
  if (!isGroup && !isStatus) {
591
440
  additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
592
441
  }
593
- const { user, device } = jidDecode(participant.jid); // rajeh: how does this even make sense TODO check out
442
+ const { user, device } = jidDecode(participant.jid);
594
443
  devices.push({
595
444
  user,
596
445
  device,
597
- wireJid: participant.jid // Use the participant JID as wire JID
446
+ wireJid: participant.jid
598
447
  });
599
448
  }
600
449
  await authState.keys.transaction(async () => {
@@ -603,7 +452,6 @@ export const makeMessagesSocket = (config) => {
603
452
  extraAttrs['mediatype'] = mediaType;
604
453
  }
605
454
  if (isNewsletter) {
606
- // Patch message if needed, then encode as plaintext
607
455
  const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
608
456
  const bytes = encodeNewsletterMessage(patched);
609
457
  binaryNodeContent.push({
@@ -663,12 +511,17 @@ export const makeMessagesSocket = (config) => {
663
511
  const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
664
512
  devices.push(...additionalDevices);
665
513
  }
514
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
515
+ additionalAttributes = {
516
+ ...additionalAttributes,
517
+ expiration: groupData.ephemeralDuration.toString()
518
+ };
519
+ }
666
520
  const patched = await patchMessageBeforeSending(message);
667
521
  if (Array.isArray(patched)) {
668
522
  throw new Boom('Per-jid patching is not supported in groups');
669
523
  }
670
524
  const bytes = encodeWAMessage(patched);
671
- // This should match the group's addressing mode and conversation context
672
525
  const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn');
673
526
  const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
674
527
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
@@ -676,39 +529,53 @@ export const makeMessagesSocket = (config) => {
676
529
  data: bytes,
677
530
  meId: groupSenderIdentity
678
531
  });
679
- const senderKeyJids = [];
680
- // ensure a connection is established with every device
532
+ const deviceSessionMap = await resolveSessionJids(devices.map(d => d.wireJid));
533
+ const senderKeyRecipients = [];
681
534
  for (const device of devices) {
682
- // This preserves the LID migration results from getUSyncDevices
683
535
  const deviceJid = device.wireJid;
684
536
  const hasKey = !!senderKeyMap[deviceJid];
685
- if (!hasKey || !!participant) {
686
- senderKeyJids.push(deviceJid);
687
- // store that this person has had the sender keys sent to them
537
+ if (!hasKey && !isRetryResend) {
538
+ senderKeyRecipients.push(deviceJid);
688
539
  senderKeyMap[deviceJid] = true;
689
540
  }
690
541
  }
691
- // if there are some participants with whom the session has not been established
692
- // if there are, we re-send the senderkey
693
- if (senderKeyJids.length) {
694
- logger.debug({ senderKeyJids }, 'sending new sender key');
542
+ if (senderKeyRecipients.length) {
543
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
695
544
  const senderKeyMsg = {
696
545
  senderKeyDistributionMessage: {
697
546
  axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
698
547
  groupId: destinationJid
699
548
  }
700
549
  };
701
- await assertSessions(senderKeyJids, false);
702
- const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs);
550
+ const senderKeySessionTargets = senderKeyRecipients.map(jid => deviceSessionMap.get(jid) ?? jid);
551
+ await assertSessions(senderKeySessionTargets);
552
+ const result = await createParticipantNodesWithSessionMap(senderKeyRecipients, deviceSessionMap, senderKeyMsg, extraAttrs);
703
553
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
704
554
  participants.push(...result.nodes);
705
555
  }
706
- binaryNodeContent.push({
707
- tag: 'enc',
708
- attrs: { v: '2', type: 'skmsg' },
709
- content: ciphertext
710
- });
711
- await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
556
+ if (isRetryResend) {
557
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
558
+ data: bytes,
559
+ jid: participant?.jid
560
+ });
561
+ binaryNodeContent.push({
562
+ tag: 'enc',
563
+ attrs: {
564
+ v: '2',
565
+ type,
566
+ count: participant.count.toString()
567
+ },
568
+ content: encryptedContent
569
+ });
570
+ }
571
+ else {
572
+ binaryNodeContent.push({
573
+ tag: 'enc',
574
+ attrs: { v: '2', type: 'skmsg', ...extraAttrs },
575
+ content: ciphertext
576
+ });
577
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
578
+ }
712
579
  }
713
580
  else {
714
581
  const { user: ownUser } = jidDecode(ownId);
@@ -737,7 +604,7 @@ export const makeMessagesSocket = (config) => {
737
604
  ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
738
605
  : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
739
606
  // Enumerate devices for sender and target with consistent addressing
740
- const sessionDevices = await getUSyncDevices([senderIdentity, jid], false, false);
607
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
741
608
  devices.push(...sessionDevices);
742
609
  logger.debug({
743
610
  deviceCount: devices.length,
@@ -745,9 +612,9 @@ export const makeMessagesSocket = (config) => {
745
612
  }, 'Device enumeration complete with unified addressing');
746
613
  }
747
614
  }
748
- const allJids = [];
749
- const meJids = [];
750
- const otherJids = [];
615
+ const allRecipients = [];
616
+ const meRecipients = [];
617
+ const otherRecipients = [];
751
618
  const { user: mePnUser } = jidDecode(meId);
752
619
  const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
753
620
  for (const { user, wireJid } of devices) {
@@ -758,25 +625,26 @@ export const makeMessagesSocket = (config) => {
758
625
  }
759
626
  // Check if this is our device (could match either PN or LID user)
760
627
  const isMe = user === mePnUser || (meLidUser && user === meLidUser);
761
- const jid = wireJid;
762
628
  if (isMe) {
763
- meJids.push(jid);
629
+ meRecipients.push(wireJid);
764
630
  }
765
631
  else {
766
- otherJids.push(jid);
632
+ otherRecipients.push(wireJid);
767
633
  }
768
- allJids.push(jid);
634
+ allRecipients.push(wireJid);
769
635
  }
770
- await assertSessions([...otherJids, ...meJids], false);
636
+ const deviceSessionMap = await resolveSessionJids(devices.map(d => d.wireJid));
637
+ const sessionTargets = allRecipients.map(jid => deviceSessionMap.get(jid) ?? jid);
638
+ await assertSessions(sessionTargets);
771
639
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
772
640
  // For own devices: use DSM if available (1:1 chats only)
773
- createParticipantNodes(meJids, meMsg || message, extraAttrs),
774
- createParticipantNodes(otherJids, message, extraAttrs, meMsg)
641
+ createParticipantNodesWithSessionMap(meRecipients, deviceSessionMap, meMsg || message, extraAttrs),
642
+ createParticipantNodesWithSessionMap(otherRecipients, deviceSessionMap, message, extraAttrs, meMsg)
775
643
  ]);
776
644
  participants.push(...meNodes);
777
645
  participants.push(...otherNodes);
778
- if (meJids.length > 0 || otherJids.length > 0) {
779
- extraAttrs['phash'] = generateParticipantHashV2([...meJids, ...otherJids]);
646
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
647
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
780
648
  }
781
649
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
782
650
  }
@@ -853,6 +721,9 @@ export const makeMessagesSocket = (config) => {
853
721
  if (message.eventMessage) {
854
722
  return 'event';
855
723
  }
724
+ if (getMediaType(message) !== '') {
725
+ return 'media';
726
+ }
856
727
  return 'text';
857
728
  };
858
729
  const getMediaType = (message) => {
@@ -901,6 +772,7 @@ export const makeMessagesSocket = (config) => {
901
772
  else if (message.groupInviteMessage) {
902
773
  return 'url';
903
774
  }
775
+ return '';
904
776
  };
905
777
  const getPrivacyTokens = async (jids) => {
906
778
  const t = unixTimestampSeconds().toString();
@@ -1062,9 +934,6 @@ export const makeMessagesSocket = (config) => {
1062
934
  }
1063
935
  });
1064
936
  }
1065
- if ('cachedGroupMetadata' in options) {
1066
- console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
1067
- }
1068
937
  await relayMessage(jid, fullMsg.message, {
1069
938
  messageId: fullMsg.key.id,
1070
939
  useCachedGroupMetadata: options.useCachedGroupMetadata,