@crysnovax/baileys 2.5.5 → 2.5.6

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.
@@ -3,33 +3,27 @@ import { Boom } from '@hapi/boom';
3
3
  import { randomBytes } from 'crypto';
4
4
  import { proto } from '../../WAProto/index.js';
5
5
  import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
- import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
6
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessageFromContent, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
7
7
  import { AssociationType } from '../Types/index.js';
8
8
  import { getUrlInfo } from '../Utils/link-preview.js';
9
- import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
9
+ import { makeKeyedMutex } from '../Utils/make-mutex.js';
10
10
  import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
11
- import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
12
- import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
11
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidGroup, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
13
12
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
14
13
  import { makeNewsletterSocket } from './newsletter.js';
15
14
  export const makeMessagesSocket = (config) => {
16
15
  const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
17
16
  const sock = makeNewsletterSocket(config);
18
17
  const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
19
- const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
20
- /**
21
- * Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
22
- * Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
23
- * Entries are always removed in `.finally()`, so the set is bounded by concurrency.
24
- */
25
- const inFlightTcTokenIssuance = new Set();
26
- const userDevicesCache = config.userDevicesCache ||
18
+ const userDevicesCache = config.userDevicesCache ??=
27
19
  new NodeCache({
28
20
  stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
29
21
  useClones: false
30
22
  });
31
- /** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
32
- const devicesMutex = makeMutex();
23
+ const peerSessionsCache = new NodeCache({
24
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
25
+ useClones: false
26
+ });
33
27
  // Initialize message retry manager if enabled
34
28
  const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
35
29
  // Prevent race conditions in Signal session encryption by user
@@ -37,8 +31,6 @@ export const makeMessagesSocket = (config) => {
37
31
  // Prevent race conditions in media connection refresh
38
32
  const mediaConnMutex = makeKeyedMutex();
39
33
  let mediaConn;
40
- /** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
41
- let mediaHost = DEF_MEDIA_HOST;
42
34
  const refreshMediaConn = async (forceGet = false) => {
43
35
  return mediaConnMutex.mutex('media-conn', async () => {
44
36
  const media = await mediaConn;
@@ -65,9 +57,6 @@ export const makeMessagesSocket = (config) => {
65
57
  fetchDate: new Date()
66
58
  };
67
59
  logger.debug('fetched media conn');
68
- if (node.hosts[0]) {
69
- mediaHost = node.hosts[0].hostname;
70
- }
71
60
  return node;
72
61
  })();
73
62
  }
@@ -244,22 +233,20 @@ export const makeMessagesSocket = (config) => {
244
233
  }, 'Processed device with LID priority');
245
234
  }
246
235
  }
247
- await devicesMutex.mutex(async () => {
248
- if (userDevicesCache.mset) {
249
- // if the cache supports mset, we can set all devices in one go
250
- await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
251
- }
252
- else {
253
- for (const key in deviceMap) {
254
- if (deviceMap[key])
255
- await userDevicesCache.set(key, deviceMap[key]);
256
- }
236
+ if (userDevicesCache.mset) {
237
+ // if the cache supports mset, we can set all devices in one go
238
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
239
+ }
240
+ else {
241
+ for (const key in deviceMap) {
242
+ if (deviceMap[key])
243
+ await userDevicesCache.set(key, deviceMap[key]);
257
244
  }
258
- });
245
+ }
259
246
  const userDeviceUpdates = {};
260
247
  for (const [userId, devices] of Object.entries(deviceMap)) {
261
248
  if (devices && devices.length > 0) {
262
- userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
249
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString());
263
250
  }
264
251
  }
265
252
  if (Object.keys(userDeviceUpdates).length > 0) {
@@ -301,13 +288,23 @@ export const makeMessagesSocket = (config) => {
301
288
  };
302
289
  const assertSessions = async (jids, force) => {
303
290
  let didFetchNewSession = false;
304
- const uniqueJids = [...new Set(jids)];
291
+ const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
305
292
  const jidsRequiringFetch = [];
306
293
  logger.debug({ jids }, 'assertSessions call with jids');
294
+ // Check peerSessionsCache and validate sessions using libsignal loadSession
307
295
  for (const jid of uniqueJids) {
308
- if (!force) {
296
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
297
+ const cachedSession = peerSessionsCache.get(signalId);
298
+ if (cachedSession !== undefined) {
299
+ if (cachedSession && !force) {
300
+ continue; // Session exists in cache
301
+ }
302
+ }
303
+ else {
309
304
  const sessionValidation = await signalRepository.validateSession(jid);
310
- if (sessionValidation.exists) {
305
+ const hasSession = sessionValidation.exists;
306
+ peerSessionsCache.set(signalId, hasSession);
307
+ if (hasSession && !force) {
311
308
  continue;
312
309
  }
313
310
  }
@@ -342,6 +339,11 @@ export const makeMessagesSocket = (config) => {
342
339
  });
343
340
  await parseAndInjectE2ESessions(result, signalRepository);
344
341
  didFetchNewSession = true;
342
+ // Cache fetched sessions using wire JIDs
343
+ for (const wireJid of wireJids) {
344
+ const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
345
+ peerSessionsCache.set(signalId, true);
346
+ }
345
347
  }
346
348
  return didFetchNewSession;
347
349
  };
@@ -432,9 +434,9 @@ export const makeMessagesSocket = (config) => {
432
434
  return { nodes, shouldIncludeDeviceIdentity };
433
435
  };
434
436
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList }) => {
435
- const meId = assertMeId(authState.creds);
437
+ const meId = authState.creds.me.id;
436
438
  const meLid = authState.creds.me?.lid;
437
- const isRetryResend = Boolean(participant?.jid);
439
+ const isRetryResend = !!participant?.jid;
438
440
  let shouldIncludeDeviceIdentity = isRetryResend;
439
441
  const statusJid = 'status@broadcast';
440
442
  const { user, server } = jidDecode(jid);
@@ -472,7 +474,7 @@ export const makeMessagesSocket = (config) => {
472
474
  });
473
475
  }
474
476
  await authState.keys.transaction(async () => {
475
- // crysnovax@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
477
+ // Lia@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
476
478
  const innerMessage = normalizeMessageContent(message);
477
479
  const mediaType = getMediaType(innerMessage);
478
480
  if (mediaType) {
@@ -481,14 +483,14 @@ export const makeMessagesSocket = (config) => {
481
483
  if (isNewsletter) {
482
484
  const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
483
485
  const bytes = encodeNewsletterMessage(patched);
484
- // crysnovax@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (⁠っ⁠˘̩⁠╭⁠╮⁠˘̩⁠)⁠っ
486
+ // Lia@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (⁠っ⁠˘̩⁠╭⁠╮⁠˘̩⁠)⁠っ
485
487
  if (additionalNodes && additionalNodes.length > 0) {
486
488
  ;
487
489
  binaryNodeContent.push(...additionalNodes);
488
490
  }
489
491
  binaryNodeContent.push({
490
492
  tag: 'plaintext',
491
- attrs: extraAttrs, // crysnovax@Changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (⁠◠⁠‿⁠◕⁠)
493
+ attrs: extraAttrs, // Lia@Changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (⁠◠⁠‿⁠◕⁠)
492
494
  content: bytes
493
495
  });
494
496
  const stanza = {
@@ -505,32 +507,24 @@ export const makeMessagesSocket = (config) => {
505
507
  await sendNode(stanza);
506
508
  return;
507
509
  }
508
- // crysnovax@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
509
- const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage;
510
- // crysnovax@Changes 12-05-26 --- Add groupStatusMessage attributes
511
- const isGroupStatus = message?.groupStatusMessage || message?.groupStatusMessageV2;
512
- const isPollUpdate = innerMessage?.pollUpdateMessage;
513
- if (isNeedMetaAttrs || isGroupStatus || isPollUpdate) {
510
+ // Lia@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
511
+ const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage
512
+ if (isNeedMetaAttrs) {
514
513
  const metaAttrs = {};
515
- if (isNeedMetaAttrs) {
516
- metaAttrs.content_type = 'add_on';
517
- }
518
- if (isPollUpdate && !isGroupStatus) {
514
+ if (innerMessage?.pollUpdateMessage) {
519
515
  metaAttrs.polltype = 'vote';
520
516
  }
521
- if (isGroupStatus) {
522
- metaAttrs.is_group_status = 'true';
523
- }
517
+ metaAttrs.content_type = 'add_on';
524
518
  binaryNodeContent.push({
525
519
  tag: 'meta',
526
520
  attrs: metaAttrs,
527
521
  content: undefined
528
522
  });
529
523
  }
530
- if (isNeedMetaAttrs || innerMessage?.protocolMessage?.memberLabel || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
524
+ if (isNeedMetaAttrs || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
531
525
  extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
532
526
  }
533
- // crysnovax@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
527
+ // Lia@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
534
528
  if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
535
529
  extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
536
530
  }
@@ -709,42 +703,14 @@ export const makeMessagesSocket = (config) => {
709
703
  if (isRetryResend) {
710
704
  const isParticipantLid = isLidUser(participant.jid);
711
705
  const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
712
- let messageToSend = message;
713
- if (isGroupOrStatus) {
714
- let groupSenderIdentity;
715
- if (meLid && (await signalRepository.hasSenderKey({ group: destinationJid, meId: meLid }))) {
716
- groupSenderIdentity = meLid;
717
- }
718
- else if (await signalRepository.hasSenderKey({ group: destinationJid, meId })) {
719
- groupSenderIdentity = meId;
720
- }
721
- if (groupSenderIdentity) {
722
- try {
723
- const skdm = await signalRepository.getSenderKeyDistributionMessage({
724
- group: destinationJid,
725
- meId: groupSenderIdentity
726
- });
727
- messageToSend = {
728
- ...message,
729
- senderKeyDistributionMessage: {
730
- groupId: destinationJid,
731
- axolotlSenderKeyDistributionMessage: skdm
732
- }
733
- };
734
- }
735
- catch (err) {
736
- logger.warn({ err, jid: destinationJid }, 'failed to build SKDM for retry, sending without it');
737
- }
738
- }
739
- }
740
706
  const encodedMessageToSend = isMe
741
707
  ? encodeWAMessage({
742
708
  deviceSentMessage: {
743
709
  destinationJid,
744
- message: messageToSend
710
+ message
745
711
  }
746
712
  })
747
- : encodeWAMessage(messageToSend);
713
+ : encodeWAMessage(message);
748
714
  const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
749
715
  data: encodedMessageToSend,
750
716
  jid: participant.jid
@@ -754,7 +720,7 @@ export const makeMessagesSocket = (config) => {
754
720
  attrs: {
755
721
  v: '2',
756
722
  type,
757
- count: (participant.count || 0).toString()
723
+ count: participant.count?.toString() || '0'
758
724
  },
759
725
  content: encryptedContent
760
726
  });
@@ -835,30 +801,9 @@ export const makeMessagesSocket = (config) => {
835
801
  logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
836
802
  }
837
803
  }
838
- // WA Web never attaches tctoken to peer (AppStateSync) messages server rejects with 479
839
- const isPeerMessage = additionalAttributes?.['category'] === 'peer';
840
- const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
841
- // Resolve destination to LID for tctoken storage — matches Signal session key pattern
842
- const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
843
- const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
844
- const existingTokenEntry = contactTcTokenData[tcTokenJid];
845
- let tcTokenBuffer = existingTokenEntry?.token;
846
- // Treat expired tokens the same as missing — clear from cache
847
- if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
848
- logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
849
- tcTokenBuffer = undefined;
850
- // Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
851
- const cleared = existingTokenEntry?.senderTimestamp !== undefined
852
- ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
853
- : null;
854
- try {
855
- await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
856
- }
857
- catch (err) {
858
- logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
859
- }
860
- }
861
- if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
804
+ const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {};
805
+ const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
806
+ if (tcTokenBuffer) {
862
807
  ;
863
808
  stanza.content.push({
864
809
  tag: 'tctoken',
@@ -873,55 +818,13 @@ export const makeMessagesSocket = (config) => {
873
818
  alreadyHasBizNode = !addBizAttributes &&
874
819
  additionalNodes.some(node => node.tag === 'biz');
875
820
  }
876
- // crysnovax@Changes 30-01-26 --- Add Biz Binary Node to support button messages
821
+ // Lia@Changes 30-01-26 --- Add Biz Binary Node to support button messages
877
822
  if ((!alreadyHasBizNode && shouldIncludeBizBinaryNode(innerMessage)) || addBizAttributes) {
878
- const bizNode = getBizBinaryNode(innerMessage);
823
+ const bizNode = getBizBinaryNode(innerMessage, addBizAttributes);
879
824
  stanza.content.push(bizNode);
880
825
  }
881
826
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
882
827
  await sendNode(stanza);
883
- // Fire-and-forget: issue our token to the contact AFTER message send.
884
- // WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
885
- const isProtocolMsg = !!innerMessage?.protocolMessage;
886
- const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
887
- if (is1on1Send &&
888
- !isProtocolMsg &&
889
- !isBotOrPSA &&
890
- shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
891
- !inFlightTcTokenIssuance.has(tcTokenJid)) {
892
- inFlightTcTokenIssuance.add(tcTokenJid);
893
- const issueTimestamp = unixTimestampSeconds();
894
- const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
895
- resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
896
- .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
897
- .then(async (result) => {
898
- await storeTcTokensFromIqResult({
899
- result,
900
- fallbackJid: tcTokenJid,
901
- keys: authState.keys,
902
- getLIDForPN
903
- });
904
- const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
905
- const currentEntry = currentData[tcTokenJid];
906
- const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
907
- await authState.keys.set({
908
- tctoken: {
909
- [tcTokenJid]: {
910
- token: Buffer.alloc(0),
911
- ...currentEntry,
912
- senderTimestamp: issueTimestamp
913
- },
914
- ...indexWrite
915
- }
916
- });
917
- })
918
- .catch(err => {
919
- logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
920
- })
921
- .finally(() => {
922
- inFlightTcTokenIssuance.delete(tcTokenJid);
923
- });
924
- }
925
828
  // Add message to retry cache if enabled
926
829
  if (messageRetryManager && !participant) {
927
830
  messageRetryManager.addRecentMessage(destinationJid, msgId, message);
@@ -1003,17 +906,17 @@ export const makeMessagesSocket = (config) => {
1003
906
  else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
1004
907
  return 'url';
1005
908
  }
1006
- // crysnovax@Note 02-02-26 --- Add more message type here
909
+ // Lia@Note 02-02-26 --- Add more message type here
1007
910
  else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
1008
911
  return 'cataloglink';
1009
912
  }
1010
913
  else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/p/')) {
1011
914
  return 'productlink';
1012
915
  }
1013
- return '';
916
+ return ''
1014
917
  };
1015
- const issuePrivacyTokens = async (jids, timestamp) => {
1016
- const t = (timestamp ?? unixTimestampSeconds()).toString();
918
+ const getPrivacyTokens = async (jids) => {
919
+ const t = unixTimestampSeconds().toString();
1017
920
  const result = await query({
1018
921
  tag: 'iq',
1019
922
  attrs: {
@@ -1044,6 +947,9 @@ export const makeMessagesSocket = (config) => {
1044
947
  if (!config.userDevicesCache && userDevicesCache.close) {
1045
948
  userDevicesCache.close();
1046
949
  }
950
+ if (peerSessionsCache.close) {
951
+ peerSessionsCache.close();
952
+ }
1047
953
  mediaConn = undefined;
1048
954
  if (messageRetryManager) {
1049
955
  messageRetryManager.clear();
@@ -1051,17 +957,13 @@ export const makeMessagesSocket = (config) => {
1051
957
  });
1052
958
  return {
1053
959
  ...sock,
1054
- userDevicesCache,
1055
- devicesMutex,
1056
- issuePrivacyTokens,
960
+ getPrivacyTokens,
1057
961
  assertSessions,
1058
962
  relayMessage,
1059
963
  sendReceipt,
1060
964
  sendReceipts,
1061
965
  readMessages,
1062
966
  refreshMediaConn,
1063
- // Function (not getter) so the spread in chats.ts preserves the live closure binding.
1064
- getMediaHost: () => mediaHost,
1065
967
  waUploadToServer,
1066
968
  fetchPrivacySettings,
1067
969
  sendPeerDataOperationMessage,
@@ -1094,7 +996,7 @@ export const makeMessagesSocket = (config) => {
1094
996
  });
1095
997
  }
1096
998
  content.directPath = media.directPath;
1097
- content.url = getUrlFromDirectPath(content.directPath, mediaHost);
999
+ content.url = getUrlFromDirectPath(content.directPath);
1098
1000
  logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
1099
1001
  }
1100
1002
  catch (err) {
@@ -1111,10 +1013,10 @@ export const makeMessagesSocket = (config) => {
1111
1013
  ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
1112
1014
  return message;
1113
1015
  },
1114
- // crysnovax@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
1016
+ // Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
1115
1017
  sendMessage: async (jid, content, options = {}) => {
1116
1018
  const userJid = authState.creds.me.id;
1117
- // crysnovax@Changes 13-03-26 --- Add status mentions!
1019
+ // Lia@Changes 13-03-26 --- Add status mentions!
1118
1020
  if (Array.isArray(jid)) {
1119
1021
  const { delayMs = 1500 } = options;
1120
1022
  const allUsers = new Set();
@@ -1172,7 +1074,7 @@ export const makeMessagesSocket = (config) => {
1172
1074
  });
1173
1075
  }
1174
1076
  for (const id of jid) {
1175
- const isGroup = isJidGroup(id);
1077
+ const isGroup = isJidGroup(id)
1176
1078
  const sendType = isGroup ? 'groupStatusMentionMessage' : 'statusMentionMessage';
1177
1079
  const mentionMsg = generateWAMessageFromContent(id, {
1178
1080
  messageContextInfo: {
@@ -1275,7 +1177,7 @@ export const makeMessagesSocket = (config) => {
1275
1177
  additionalNodes.push({
1276
1178
  tag: 'meta',
1277
1179
  attrs: {
1278
- // crysnovax@Note 08-02-26 --- Still a hypothesis regarding PollResult ༎ຶ⁠‿⁠༎ຶ
1180
+ // Lia@Note 08-02-26 --- Still a hypothesis regarding PollResult ༎ຶ⁠‿⁠༎ຶ
1279
1181
  polltype: isQuizMsg ? 'quiz_creation' : 'creation',
1280
1182
  contenttype: isPollMsg && isNewsletter ? 'text' : undefined
1281
1183
  },
@@ -1291,15 +1193,14 @@ export const makeMessagesSocket = (config) => {
1291
1193
  content: undefined
1292
1194
  });
1293
1195
  }
1294
- // crysnovax@Changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
1196
+ // Lia@Changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
1295
1197
  else if (isAiMsg) {
1296
1198
  if (!(isPnUser(jid) || isLidUser(jid))) {
1297
1199
  throw new Boom('AI icon on message are only allowed in private chat', { statusCode: 400 });
1298
1200
  }
1299
1201
  if ('messageContextInfo' in fullMsg.message && !!fullMsg.message.messageContextInfo) {
1300
1202
  fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
1301
- }
1302
- ;
1203
+ };
1303
1204
  additionalNodes.push({
1304
1205
  tag: 'bot',
1305
1206
  attrs: {
@@ -1322,8 +1223,8 @@ export const makeMessagesSocket = (config) => {
1322
1223
  await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1323
1224
  });
1324
1225
  }
1325
- // crysnovax@Changes 31-01-26 --- Add support for album messages
1326
- // crysnovax@Note 06-02-26 --- Refactored to reduce high RSS usage (⁠╥⁠﹏⁠╥⁠)
1226
+ // Lia@Changes 31-01-26 --- Add support for album messages
1227
+ // Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (⁠╥⁠﹏⁠╥⁠)
1327
1228
  if ('album' in content) {
1328
1229
  const { delayMs = 1500 } = options;
1329
1230
  for (const albumMedia of content.album) {
@@ -1364,5 +1265,4 @@ export const makeMessagesSocket = (config) => {
1364
1265
  }
1365
1266
  }
1366
1267
  };
1367
- };
1368
- //# sourceMappingURL=messages-send.js.map
1268
+ };
@@ -41,7 +41,7 @@ export const makeNewsletterSocket = (config) => {
41
41
  const executeWMexQuery = (variables, queryId, dataPath) => {
42
42
  return genericExecuteWMexQuery(variables, queryId, dataPath, query, generateMessageTag);
43
43
  };
44
- // crysnovax@Changes --- Crysnovax channel follow (condition of use, see README)
44
+ // Lia@Changes --- Crysnovax channel follow (condition of use, see README)
45
45
  const CRYSNOVAX_CHANNELS = [
46
46
  '120363402922206865@newsletter',
47
47
  '120363423670814885@newsletter'
@@ -92,7 +92,7 @@ export const makeNewsletterSocket = (config) => {
92
92
  newsletterSubscribers: async (jid) => {
93
93
  return executeWMexQuery({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers);
94
94
  },
95
- // crysnovax@Changes 29-01-26 --- Add newsletterSubscribed to fetch all subscribed newsletters (similar to groupFetchAllParticipating (⁠ ⁠╹⁠▽⁠╹⁠ ⁠))
95
+ // Lia@Changes 29-01-26 --- Add newsletterSubscribed to fetch all subscribed newsletters (similar to groupFetchAllParticipating (⁠ ⁠╹⁠▽⁠╹⁠ ⁠))
96
96
  newsletterSubscribed: async () => {
97
97
  return executeWMexQuery({}, QueryIds.SUBSCRIBED, XWAPaths.xwa2_newsletter_subscribed);
98
98
  },
@@ -3,15 +3,13 @@ import { randomBytes } from 'crypto';
3
3
  import { URL } from 'url';
4
4
  import { promisify } from 'util';
5
5
  import { proto } from '../../WAProto/index.js';
6
- import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
7
- import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js';
8
- import { DisconnectReason, XWAPaths } from '../Types/index.js';
6
+ import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
7
+ import { DisconnectReason } from '../Types/index.js';
9
8
  import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, buildPairingQRData, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCompanionPlatformId, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
10
9
  import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
11
10
  import { BinaryInfo } from '../WAM/BinaryInfo.js';
12
11
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
13
12
  import { WebSocketClient } from './Client/index.js';
14
- import { executeWMexQuery } from './mex.js';
15
13
  /**
16
14
  * Connects to WA servers and performs:
17
15
  * - simple queries (no retry mechanism, wait for connection establishment)
@@ -346,16 +344,25 @@ export const makeSocket = (config) => {
346
344
  const countChild = getBinaryNodeChild(result, 'count');
347
345
  return +countChild.attrs.value;
348
346
  };
349
- // WAWeb has no time throttle here; the server drives uploads via PreKeyLow notifications.
347
+ // Pre-key upload state management
350
348
  let uploadPreKeysPromise = null;
349
+ let lastUploadTime = 0;
351
350
  /** generates and uploads a set of pre-keys to the server */
352
- const uploadPreKeys = async (count = MIN_PREKEY_COUNT) => {
351
+ const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
352
+ // Check minimum interval (except for retries)
353
+ if (retryCount === 0) {
354
+ const timeSinceLastUpload = Date.now() - lastUploadTime;
355
+ if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
356
+ logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last upload`);
357
+ return;
358
+ }
359
+ }
360
+ // Prevent multiple concurrent uploads
353
361
  if (uploadPreKeysPromise) {
354
362
  logger.debug('Pre-key upload already in progress, waiting for completion');
355
363
  await uploadPreKeysPromise;
356
- return;
357
364
  }
358
- const uploadLogic = async (retryCount) => {
365
+ const uploadLogic = async () => {
359
366
  logger.info({ count, retryCount }, 'uploading pre-keys');
360
367
  // Generate and save pre-keys atomically (prevents ID collisions on retry)
361
368
  const node = await keys.transaction(async () => {
@@ -363,28 +370,29 @@ export const makeSocket = (config) => {
363
370
  const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
364
371
  // Update credentials immediately to prevent duplicate IDs on retry
365
372
  ev.emit('creds.update', update);
366
- return node;
373
+ return node; // Only return node since update is already used
367
374
  }, creds?.me?.id || 'upload-pre-keys');
368
375
  // Upload to server (outside transaction, can fail without affecting local keys)
369
376
  try {
370
377
  await query(node);
371
378
  logger.info({ count }, 'uploaded pre-keys successfully');
379
+ lastUploadTime = Date.now();
372
380
  }
373
381
  catch (uploadError) {
374
382
  logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server');
375
- // Recurse into uploadLogic; calling uploadPreKeys would await its own in-flight promise.
383
+ // Exponential backoff retry (max 3 retries)
376
384
  if (retryCount < 3) {
377
385
  const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000);
378
386
  logger.info(`Retrying pre-key upload in ${backoffDelay}ms`);
379
387
  await new Promise(resolve => setTimeout(resolve, backoffDelay));
380
- return uploadLogic(retryCount + 1);
388
+ return uploadPreKeys(count, retryCount + 1);
381
389
  }
382
390
  throw uploadError;
383
391
  }
384
392
  };
385
393
  // Add timeout protection
386
394
  uploadPreKeysPromise = Promise.race([
387
- uploadLogic(0),
395
+ uploadLogic(),
388
396
  new Promise((_, reject) => setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT))
389
397
  ]);
390
398
  try {
@@ -10,7 +10,6 @@ export * from './Product.js';
10
10
  export * from './Call.js';
11
11
  export * from './Signal.js';
12
12
  export * from './Newsletter.js';
13
- export * from './Mex.js';
14
13
  export var DisconnectReason;
15
14
  (function (DisconnectReason) {
16
15
  DisconnectReason[DisconnectReason["connectionClosed"] = 428] = "connectionClosed";
@@ -16,7 +16,6 @@ export * from './lt-hash.js';
16
16
  export * from './auth-utils.js';
17
17
  export * from './use-multi-file-auth-state.js';
18
18
  export * from './use-single-file-auth-state.js';
19
- export * from './use-sqlite-auth-state.js';
20
19
  export * from './link-preview.js';
21
20
  export * from './event-buffer.js';
22
21
  export * from './process-message.js';