@periskope/baileys 7.0.0-alpha-7 → 7.0.0-alpha-9

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 (99) hide show
  1. package/README.md +3 -0
  2. package/lib/Defaults/index.js +1 -1
  3. package/lib/Defaults/index.js.map +1 -1
  4. package/lib/Signal/Group/group_cipher.d.ts +1 -1
  5. package/lib/Signal/Group/group_cipher.d.ts.map +1 -1
  6. package/lib/Signal/Group/group_cipher.js +1 -4
  7. package/lib/Signal/Group/group_cipher.js.map +1 -1
  8. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -1
  9. package/lib/Signal/Group/sender-chain-key.js +1 -13
  10. package/lib/Signal/Group/sender-chain-key.js.map +1 -1
  11. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -1
  12. package/lib/Signal/Group/sender-key-distribution-message.js +6 -11
  13. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -1
  14. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -1
  15. package/lib/Signal/Group/sender-key-message.js +3 -5
  16. package/lib/Signal/Group/sender-key-message.js.map +1 -1
  17. package/lib/Signal/Group/sender-key-record.d.ts +1 -1
  18. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -1
  19. package/lib/Signal/Group/sender-key-record.js +2 -11
  20. package/lib/Signal/Group/sender-key-record.js.map +1 -1
  21. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -1
  22. package/lib/Signal/Group/sender-key-state.js +9 -52
  23. package/lib/Signal/Group/sender-key-state.js.map +1 -1
  24. package/lib/Signal/libsignal.d.ts +2 -5
  25. package/lib/Signal/libsignal.d.ts.map +1 -1
  26. package/lib/Signal/libsignal.js +109 -88
  27. package/lib/Signal/libsignal.js.map +1 -1
  28. package/lib/Signal/lid-mapping.d.ts +3 -6
  29. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  30. package/lib/Signal/lid-mapping.js +19 -33
  31. package/lib/Signal/lid-mapping.js.map +1 -1
  32. package/lib/Socket/business.d.ts +2 -2
  33. package/lib/Socket/communities.d.ts +2 -2
  34. package/lib/Socket/index.d.ts +2 -2
  35. package/lib/Socket/messages-recv.d.ts +2 -2
  36. package/lib/Socket/messages-recv.d.ts.map +1 -1
  37. package/lib/Socket/messages-recv.js +10 -31
  38. package/lib/Socket/messages-recv.js.map +1 -1
  39. package/lib/Socket/messages-send.d.ts +2 -2
  40. package/lib/Socket/messages-send.d.ts.map +1 -1
  41. package/lib/Socket/messages-send.js +125 -280
  42. package/lib/Socket/messages-send.js.map +1 -1
  43. package/lib/Socket/socket.d.ts.map +1 -1
  44. package/lib/Socket/socket.js +22 -9
  45. package/lib/Socket/socket.js.map +1 -1
  46. package/lib/Types/Auth.d.ts +1 -0
  47. package/lib/Types/Auth.d.ts.map +1 -1
  48. package/lib/Types/Signal.d.ts +1 -11
  49. package/lib/Types/Signal.d.ts.map +1 -1
  50. package/lib/Types/Socket.d.ts +1 -1
  51. package/lib/Types/Socket.d.ts.map +1 -1
  52. package/lib/Utils/auth-utils.js +1 -1
  53. package/lib/Utils/auth-utils.js.map +1 -1
  54. package/lib/Utils/browser-utils.d.ts +4 -0
  55. package/lib/Utils/browser-utils.d.ts.map +1 -0
  56. package/lib/Utils/browser-utils.js +28 -0
  57. package/lib/Utils/browser-utils.js.map +1 -0
  58. package/lib/Utils/chat-utils.d.ts.map +1 -1
  59. package/lib/Utils/chat-utils.js +6 -5
  60. package/lib/Utils/chat-utils.js.map +1 -1
  61. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  62. package/lib/Utils/decode-wa-message.js +8 -5
  63. package/lib/Utils/decode-wa-message.js.map +1 -1
  64. package/lib/Utils/generics.d.ts +1 -3
  65. package/lib/Utils/generics.d.ts.map +1 -1
  66. package/lib/Utils/generics.js +11 -29
  67. package/lib/Utils/generics.js.map +1 -1
  68. package/lib/Utils/history.d.ts.map +1 -1
  69. package/lib/Utils/history.js +2 -1
  70. package/lib/Utils/history.js.map +1 -1
  71. package/lib/Utils/index.d.ts +2 -0
  72. package/lib/Utils/index.d.ts.map +1 -1
  73. package/lib/Utils/index.js +2 -0
  74. package/lib/Utils/index.js.map +1 -1
  75. package/lib/Utils/messages-media.d.ts.map +1 -1
  76. package/lib/Utils/messages-media.js +2 -1
  77. package/lib/Utils/messages-media.js.map +1 -1
  78. package/lib/Utils/messages.d.ts.map +1 -1
  79. package/lib/Utils/messages.js +3 -2
  80. package/lib/Utils/messages.js.map +1 -1
  81. package/lib/Utils/noise-handler.d.ts.map +1 -1
  82. package/lib/Utils/noise-handler.js +2 -1
  83. package/lib/Utils/noise-handler.js.map +1 -1
  84. package/lib/Utils/process-message.d.ts.map +1 -1
  85. package/lib/Utils/process-message.js +8 -2
  86. package/lib/Utils/process-message.js.map +1 -1
  87. package/lib/Utils/proto-utils.d.ts +7 -0
  88. package/lib/Utils/proto-utils.d.ts.map +1 -0
  89. package/lib/Utils/proto-utils.js +7 -0
  90. package/lib/Utils/proto-utils.js.map +1 -0
  91. package/lib/Utils/signal.d.ts.map +1 -1
  92. package/lib/Utils/signal.js.map +1 -1
  93. package/lib/Utils/validate-connection.d.ts.map +1 -1
  94. package/lib/Utils/validate-connection.js +4 -3
  95. package/lib/Utils/validate-connection.js.map +1 -1
  96. package/lib/WABinary/generic-utils.d.ts.map +1 -1
  97. package/lib/WABinary/generic-utils.js +2 -1
  98. package/lib/WABinary/generic-utils.js.map +1 -1
  99. 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);
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
272
276
  }
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, []);
288
- }
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,26 @@ 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
404
  let shouldIncludeDeviceIdentity = false;
553
- const { user, server } = jidDecode(jid);
554
405
  const statusJid = 'status@broadcast';
406
+ const { user, server } = jidDecode(jid);
555
407
  const isGroup = server === 'g.us';
556
408
  const isStatus = jid === statusJid;
557
409
  const isLid = server === 'lid';
558
410
  const isNewsletter = server === 'newsletter';
559
- // Keep user's original JID choice for envelope addressing
560
411
  const finalJid = jid;
561
412
  // ADDRESSING CONSISTENCY: Match own identity to conversation context
562
413
  // TODO: investigate if this is true
@@ -584,17 +435,14 @@ export const makeMessagesSocket = (config) => {
584
435
  };
585
436
  const extraAttrs = {};
586
437
  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
438
  if (!isGroup && !isStatus) {
591
439
  additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
592
440
  }
593
- const { user, device } = jidDecode(participant.jid); // rajeh: how does this even make sense TODO check out
441
+ const { user, device } = jidDecode(participant.jid);
594
442
  devices.push({
595
443
  user,
596
444
  device,
597
- wireJid: participant.jid // Use the participant JID as wire JID
445
+ wireJid: participant.jid
598
446
  });
599
447
  }
600
448
  await authState.keys.transaction(async () => {
@@ -603,7 +451,6 @@ export const makeMessagesSocket = (config) => {
603
451
  extraAttrs['mediatype'] = mediaType;
604
452
  }
605
453
  if (isNewsletter) {
606
- // Patch message if needed, then encode as plaintext
607
454
  const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
608
455
  const bytes = encodeNewsletterMessage(patched);
609
456
  binaryNodeContent.push({
@@ -668,7 +515,6 @@ export const makeMessagesSocket = (config) => {
668
515
  throw new Boom('Per-jid patching is not supported in groups');
669
516
  }
670
517
  const bytes = encodeWAMessage(patched);
671
- // This should match the group's addressing mode and conversation context
672
518
  const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn');
673
519
  const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
674
520
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
@@ -676,30 +522,27 @@ export const makeMessagesSocket = (config) => {
676
522
  data: bytes,
677
523
  meId: groupSenderIdentity
678
524
  });
679
- const senderKeyJids = [];
680
- // ensure a connection is established with every device
525
+ const deviceSessionMap = await resolveSessionJids(devices.map(d => d.wireJid));
526
+ const senderKeyRecipients = [];
681
527
  for (const device of devices) {
682
- // This preserves the LID migration results from getUSyncDevices
683
528
  const deviceJid = device.wireJid;
684
529
  const hasKey = !!senderKeyMap[deviceJid];
685
530
  if (!hasKey || !!participant) {
686
- senderKeyJids.push(deviceJid);
687
- // store that this person has had the sender keys sent to them
531
+ senderKeyRecipients.push(deviceJid);
688
532
  senderKeyMap[deviceJid] = true;
689
533
  }
690
534
  }
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');
535
+ if (senderKeyRecipients.length) {
536
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
695
537
  const senderKeyMsg = {
696
538
  senderKeyDistributionMessage: {
697
539
  axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
698
540
  groupId: destinationJid
699
541
  }
700
542
  };
701
- await assertSessions(senderKeyJids, false);
702
- const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs);
543
+ const senderKeySessionTargets = senderKeyRecipients.map(jid => deviceSessionMap.get(jid) ?? jid);
544
+ await assertSessions(senderKeySessionTargets);
545
+ const result = await createParticipantNodesWithSessionMap(senderKeyRecipients, deviceSessionMap, senderKeyMsg, extraAttrs);
703
546
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
704
547
  participants.push(...result.nodes);
705
548
  }
@@ -737,7 +580,7 @@ export const makeMessagesSocket = (config) => {
737
580
  ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
738
581
  : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
739
582
  // Enumerate devices for sender and target with consistent addressing
740
- const sessionDevices = await getUSyncDevices([senderIdentity, jid], false, false);
583
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
741
584
  devices.push(...sessionDevices);
742
585
  logger.debug({
743
586
  deviceCount: devices.length,
@@ -745,9 +588,9 @@ export const makeMessagesSocket = (config) => {
745
588
  }, 'Device enumeration complete with unified addressing');
746
589
  }
747
590
  }
748
- const allJids = [];
749
- const meJids = [];
750
- const otherJids = [];
591
+ const allRecipients = [];
592
+ const meRecipients = [];
593
+ const otherRecipients = [];
751
594
  const { user: mePnUser } = jidDecode(meId);
752
595
  const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
753
596
  for (const { user, wireJid } of devices) {
@@ -758,25 +601,26 @@ export const makeMessagesSocket = (config) => {
758
601
  }
759
602
  // Check if this is our device (could match either PN or LID user)
760
603
  const isMe = user === mePnUser || (meLidUser && user === meLidUser);
761
- const jid = wireJid;
762
604
  if (isMe) {
763
- meJids.push(jid);
605
+ meRecipients.push(wireJid);
764
606
  }
765
607
  else {
766
- otherJids.push(jid);
608
+ otherRecipients.push(wireJid);
767
609
  }
768
- allJids.push(jid);
610
+ allRecipients.push(wireJid);
769
611
  }
770
- await assertSessions([...otherJids, ...meJids], false);
612
+ const deviceSessionMap = await resolveSessionJids(devices.map(d => d.wireJid));
613
+ const sessionTargets = allRecipients.map(jid => deviceSessionMap.get(jid) ?? jid);
614
+ await assertSessions(sessionTargets);
771
615
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
772
616
  // For own devices: use DSM if available (1:1 chats only)
773
- createParticipantNodes(meJids, meMsg || message, extraAttrs),
774
- createParticipantNodes(otherJids, message, extraAttrs, meMsg)
617
+ createParticipantNodesWithSessionMap(meRecipients, deviceSessionMap, meMsg || message, extraAttrs),
618
+ createParticipantNodesWithSessionMap(otherRecipients, deviceSessionMap, message, extraAttrs, meMsg)
775
619
  ]);
776
620
  participants.push(...meNodes);
777
621
  participants.push(...otherNodes);
778
- if (meJids.length > 0 || otherJids.length > 0) {
779
- extraAttrs['phash'] = generateParticipantHashV2([...meJids, ...otherJids]);
622
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
623
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
780
624
  }
781
625
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
782
626
  }
@@ -853,6 +697,9 @@ export const makeMessagesSocket = (config) => {
853
697
  if (message.eventMessage) {
854
698
  return 'event';
855
699
  }
700
+ if (getMediaType(message) !== 'text') {
701
+ return 'media';
702
+ }
856
703
  return 'text';
857
704
  };
858
705
  const getMediaType = (message) => {
@@ -901,6 +748,7 @@ export const makeMessagesSocket = (config) => {
901
748
  else if (message.groupInviteMessage) {
902
749
  return 'url';
903
750
  }
751
+ return 'text';
904
752
  };
905
753
  const getPrivacyTokens = async (jids) => {
906
754
  const t = unixTimestampSeconds().toString();
@@ -1062,9 +910,6 @@ export const makeMessagesSocket = (config) => {
1062
910
  }
1063
911
  });
1064
912
  }
1065
- if ('cachedGroupMetadata' in options) {
1066
- console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
1067
- }
1068
913
  await relayMessage(jid, fullMsg.message, {
1069
914
  messageId: fullMsg.key.id,
1070
915
  useCachedGroupMetadata: options.useCachedGroupMetadata,