@ubedsockets/baileys 1.6.5 → 1.6.7

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 (203) hide show
  1. package/WAProto/GenerateStatics.sh +4 -3
  2. package/WAProto/WAProto.proto +511 -1215
  3. package/WAProto/index.js +131318 -59331
  4. package/lib/Defaults/index.d.ts +75 -0
  5. package/lib/Defaults/index.d.ts.map +1 -0
  6. package/lib/Defaults/index.js.map +1 -0
  7. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  8. package/lib/Signal/Group/ciphertext-message.d.ts.map +1 -0
  9. package/lib/Signal/Group/ciphertext-message.js.map +1 -0
  10. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  11. package/lib/Signal/Group/group-session-builder.d.ts.map +1 -0
  12. package/lib/Signal/Group/group-session-builder.js.map +1 -0
  13. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  14. package/lib/Signal/Group/group_cipher.d.ts.map +1 -0
  15. package/lib/Signal/Group/group_cipher.js.map +1 -0
  16. package/lib/Signal/Group/index.d.ts +12 -0
  17. package/lib/Signal/Group/index.d.ts.map +1 -0
  18. package/lib/Signal/Group/index.js.map +1 -0
  19. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  20. package/lib/Signal/Group/keyhelper.d.ts.map +1 -0
  21. package/lib/Signal/Group/keyhelper.js.map +1 -0
  22. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  23. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -0
  24. package/lib/Signal/Group/sender-chain-key.js.map +1 -0
  25. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  26. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -0
  27. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -0
  28. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  29. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -0
  30. package/lib/Signal/Group/sender-key-message.js.map +1 -0
  31. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  32. package/lib/Signal/Group/sender-key-name.d.ts.map +1 -0
  33. package/lib/Signal/Group/sender-key-name.js.map +1 -0
  34. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  35. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -0
  36. package/lib/Signal/Group/sender-key-record.js.map +1 -0
  37. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  38. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -0
  39. package/lib/Signal/Group/sender-key-state.js.map +1 -0
  40. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  41. package/lib/Signal/Group/sender-message-key.d.ts.map +1 -0
  42. package/lib/Signal/Group/sender-message-key.js.map +1 -0
  43. package/lib/Signal/libsignal.d.ts +5 -0
  44. package/lib/Signal/libsignal.d.ts.map +1 -0
  45. package/lib/Signal/libsignal.js.map +1 -0
  46. package/lib/Signal/lid-mapping.d.ts +23 -0
  47. package/lib/Signal/lid-mapping.d.ts.map +1 -0
  48. package/lib/Signal/lid-mapping.js +277 -0
  49. package/lib/Signal/lid-mapping.js.map +1 -0
  50. package/lib/Socket/Client/index.js +18 -3
  51. package/lib/Socket/Client/types.js +6 -4
  52. package/lib/Socket/Client/websocket.js +78 -21
  53. package/lib/Socket/business.js +43 -162
  54. package/lib/Socket/chats.js +249 -571
  55. package/lib/Socket/groups.js +99 -114
  56. package/lib/Socket/index.js +9 -11
  57. package/lib/Socket/messages-recv.js +559 -1277
  58. package/lib/Socket/messages-send.js +669 -674
  59. package/lib/Socket/newsletter.js +286 -149
  60. package/lib/Types/Auth.d.ts +117 -0
  61. package/lib/Types/Auth.d.ts.map +1 -0
  62. package/lib/Types/Auth.js.map +1 -0
  63. package/lib/Types/Bussines.d.ts +25 -0
  64. package/lib/Types/Bussines.d.ts.map +1 -0
  65. package/lib/Types/Bussines.js +2 -0
  66. package/lib/Types/Bussines.js.map +1 -0
  67. package/lib/Types/Call.d.ts +15 -0
  68. package/lib/Types/Call.d.ts.map +1 -0
  69. package/lib/Types/Call.js.map +1 -0
  70. package/lib/Types/Chat.d.ts +124 -0
  71. package/lib/Types/Chat.d.ts.map +1 -0
  72. package/lib/Types/Chat.js.map +1 -0
  73. package/lib/Types/Contact.d.ts +26 -0
  74. package/lib/Types/Contact.d.ts.map +1 -0
  75. package/lib/Types/Contact.js.map +1 -0
  76. package/lib/Types/Events.d.ts +256 -0
  77. package/lib/Types/Events.d.ts.map +1 -0
  78. package/lib/Types/Events.js.map +1 -0
  79. package/lib/Types/GroupMetadata.d.ts +71 -0
  80. package/lib/Types/GroupMetadata.d.ts.map +1 -0
  81. package/lib/Types/GroupMetadata.js.map +1 -0
  82. package/lib/Types/Label.d.ts +47 -0
  83. package/lib/Types/Label.d.ts.map +1 -0
  84. package/lib/Types/Label.js.map +1 -0
  85. package/lib/Types/LabelAssociation.d.ts +30 -0
  86. package/lib/Types/LabelAssociation.d.ts.map +1 -0
  87. package/lib/Types/LabelAssociation.js.map +1 -0
  88. package/lib/Types/Message.d.ts +320 -0
  89. package/lib/Types/Message.d.ts.map +1 -0
  90. package/lib/Types/Message.js.map +1 -0
  91. package/lib/Types/Mex.d.ts +141 -0
  92. package/lib/Types/Mex.d.ts.map +1 -0
  93. package/lib/Types/Mex.js +37 -0
  94. package/lib/Types/Mex.js.map +1 -0
  95. package/lib/Types/Product.d.ts +79 -0
  96. package/lib/Types/Product.d.ts.map +1 -0
  97. package/lib/Types/Product.js.map +1 -0
  98. package/lib/Types/Signal.d.ts +87 -0
  99. package/lib/Types/Signal.d.ts.map +1 -0
  100. package/lib/Types/Signal.js.map +1 -0
  101. package/lib/Types/Socket.d.ts +136 -0
  102. package/lib/Types/Socket.d.ts.map +1 -0
  103. package/lib/Types/Socket.js.map +1 -0
  104. package/lib/Types/State.d.ts +97 -0
  105. package/lib/Types/State.d.ts.map +1 -0
  106. package/lib/Types/State.js.map +1 -0
  107. package/lib/Types/USync.d.ts +26 -0
  108. package/lib/Types/USync.d.ts.map +1 -0
  109. package/lib/Types/USync.js.map +1 -0
  110. package/lib/Types/index.d.ts +65 -0
  111. package/lib/Types/index.d.ts.map +1 -0
  112. package/lib/Types/index.js.map +1 -0
  113. package/lib/Utils/auth-utils.js +136 -239
  114. package/lib/Utils/business.js +69 -66
  115. package/lib/Utils/chat-utils.js +238 -380
  116. package/lib/Utils/crypto.js +127 -56
  117. package/lib/Utils/decode-wa-message.js +63 -173
  118. package/lib/Utils/event-buffer.js +70 -174
  119. package/lib/Utils/generics.js +207 -159
  120. package/lib/Utils/history.js +53 -93
  121. package/lib/Utils/index.js +33 -22
  122. package/lib/Utils/link-preview.js +58 -17
  123. package/lib/Utils/logger.js +7 -3
  124. package/lib/Utils/lt-hash.js +46 -3
  125. package/lib/Utils/make-mutex.js +34 -24
  126. package/lib/Utils/messages-media.js +744 -483
  127. package/lib/Utils/messages.js +621 -411
  128. package/lib/Utils/noise-handler.js +99 -149
  129. package/lib/Utils/process-message.js +150 -459
  130. package/lib/Utils/signal.js +73 -112
  131. package/lib/Utils/use-multi-file-auth-state.js +27 -23
  132. package/lib/Utils/validate-connection.js +104 -74
  133. package/lib/WABinary/constants.d.ts +28 -0
  134. package/lib/WABinary/constants.d.ts.map +1 -0
  135. package/lib/WABinary/constants.js.map +1 -0
  136. package/lib/WABinary/decode.d.ts +7 -0
  137. package/lib/WABinary/decode.d.ts.map +1 -0
  138. package/lib/WABinary/decode.js.map +1 -0
  139. package/lib/WABinary/encode.d.ts +3 -0
  140. package/lib/WABinary/encode.d.ts.map +1 -0
  141. package/lib/WABinary/encode.js.map +1 -0
  142. package/lib/WABinary/generic-utils.d.ts +15 -0
  143. package/lib/WABinary/generic-utils.d.ts.map +1 -0
  144. package/lib/WABinary/generic-utils.js.map +1 -0
  145. package/lib/WABinary/index.d.ts +6 -0
  146. package/lib/WABinary/index.d.ts.map +1 -0
  147. package/lib/WABinary/index.js.map +1 -0
  148. package/lib/WABinary/jid-utils.d.ts +48 -0
  149. package/lib/WABinary/jid-utils.d.ts.map +1 -0
  150. package/lib/WABinary/jid-utils.js.map +1 -0
  151. package/lib/WABinary/types.d.ts +19 -0
  152. package/lib/WABinary/types.d.ts.map +1 -0
  153. package/lib/WABinary/types.js.map +1 -0
  154. package/lib/WAM/BinaryInfo.d.ts +9 -0
  155. package/lib/WAM/BinaryInfo.d.ts.map +1 -0
  156. package/lib/WAM/BinaryInfo.js.map +1 -0
  157. package/lib/WAM/constants.d.ts +40 -0
  158. package/lib/WAM/constants.d.ts.map +1 -0
  159. package/lib/WAM/constants.js.map +1 -0
  160. package/lib/WAM/encode.d.ts +3 -0
  161. package/lib/WAM/encode.d.ts.map +1 -0
  162. package/lib/WAM/encode.js.map +1 -0
  163. package/lib/WAM/index.d.ts +4 -0
  164. package/lib/WAM/index.d.ts.map +1 -0
  165. package/lib/WAM/index.js.map +1 -0
  166. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  167. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -0
  168. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -0
  169. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  170. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +1 -0
  171. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +1 -0
  172. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  173. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +1 -0
  174. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +1 -0
  175. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  176. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +1 -0
  177. package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +1 -0
  178. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  179. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
  180. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  181. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
  182. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  183. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +1 -0
  184. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +1 -0
  185. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  186. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -0
  187. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -0
  188. package/lib/WAUSync/Protocols/index.d.ts +6 -0
  189. package/lib/WAUSync/Protocols/index.d.ts.map +1 -0
  190. package/lib/WAUSync/Protocols/index.js.map +1 -0
  191. package/lib/WAUSync/USyncQuery.d.ts +30 -0
  192. package/lib/WAUSync/USyncQuery.d.ts.map +1 -0
  193. package/lib/WAUSync/USyncQuery.js.map +1 -0
  194. package/lib/WAUSync/USyncUser.d.ts +17 -0
  195. package/lib/WAUSync/USyncUser.d.ts.map +1 -0
  196. package/lib/WAUSync/USyncUser.js.map +1 -0
  197. package/lib/WAUSync/index.d.ts +4 -0
  198. package/lib/WAUSync/index.d.ts.map +1 -0
  199. package/lib/WAUSync/index.js.map +1 -0
  200. package/lib/index.d.ts +12 -0
  201. package/lib/index.d.ts.map +1 -0
  202. package/lib/index.js.map +1 -0
  203. package/package.json +1 -1
@@ -1,465 +1,163 @@
1
- import NodeCache from '@cacheable/node-cache';
2
- import { Boom } from '@hapi/boom';
3
- import { randomBytes } from 'crypto';
4
- import Long from 'long';
5
- import { proto } from '../../WAProto/index.js';
6
- import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
7
- import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
- import { makeMutex } from '../Utils/make-mutex.js';
10
- import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
11
- import { buildAckStanza } from '../Utils/stanza-ack.js';
12
- import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
13
- import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
14
- import { extractGroupMetadata } from './groups.js';
15
- import { makeMessagesSocket } from './messages-send.js';
16
- const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
17
- function isValidEnforcementType(value) {
18
- return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
19
- }
20
- export const makeMessagesRecvSocket = (config) => {
21
- const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
22
- const sock = makeMessagesSocket(config);
23
- const { userDevicesCache, devicesMutex, ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, registerSocketEndHandler, issuePrivacyTokens, fetchAccountReachoutTimelock, placeholderResendCache } = sock;
24
- const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.makeMessagesRecvSocket = void 0;
7
+ const boom_1 = require("@hapi/boom");
8
+ const crypto_1 = require("crypto");
9
+ const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
10
+ const WAProto_1 = require("../../WAProto");
11
+ const Defaults_1 = require("../Defaults");
12
+ const Types_1 = require("../Types");
13
+ const Utils_1 = require("../Utils");
14
+ const make_mutex_1 = require("../Utils/make-mutex");
15
+ const WABinary_1 = require("../WABinary");
16
+ const groups_1 = require("./groups");
17
+ const messages_send_1 = require("./messages-send");
18
+ const makeMessagesRecvSocket = (config) => {
19
+ const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
20
+ const sock = (0, messages_send_1.makeMessagesSocket)(config);
21
+ const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, groupMetadata, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, createParticipantNodes, getUSyncDevices, sendPeerDataOperationMessage, } = sock;
25
22
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
26
- const retryMutex = makeMutex();
27
- const msgRetryCache = config.msgRetryCounterCache ||
28
- new NodeCache({
29
- stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
30
- useClones: false
31
- });
32
- const callOfferCache = config.callOfferCache ||
33
- new NodeCache({
34
- stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
35
- useClones: false
36
- });
37
- // Debounce identity-change session refreshes per JID to avoid bursts
38
- const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
23
+ const retryMutex = (0, make_mutex_1.makeMutex)();
24
+ const msgRetryCache = config.msgRetryCounterCache || new node_cache_1.default({
25
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
26
+ useClones: false
27
+ });
28
+ const callOfferCache = config.callOfferCache || new node_cache_1.default({
29
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
30
+ useClones: false
31
+ });
32
+ const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
33
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
34
+ useClones: false
35
+ });
39
36
  let sendActiveReceipts = false;
40
- const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
41
- if (!authState.creds.me?.id) {
42
- throw new Boom('Not authenticated');
43
- }
44
- const pdoMessage = {
45
- historySyncOnDemandRequest: {
46
- chatJid: oldestMsgKey.remoteJid,
47
- oldestMsgFromMe: oldestMsgKey.fromMe,
48
- oldestMsgId: oldestMsgKey.id,
49
- oldestMsgTimestampMs: oldestMsgTimestamp,
50
- onDemandMsgCount: count
51
- },
52
- peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
37
+ const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
38
+ const stanza = {
39
+ tag: 'ack',
40
+ attrs: {
41
+ id: attrs.id,
42
+ to: attrs.from,
43
+ class: tag
44
+ }
53
45
  };
54
- return sendPeerDataOperationMessage(pdoMessage);
55
- };
56
- const requestPlaceholderResend = async (messageKey, msgData) => {
57
- if (!authState.creds.me?.id) {
58
- throw new Boom('Not authenticated');
46
+ if (!!errorCode) {
47
+ stanza.attrs.error = errorCode.toString();
59
48
  }
60
- if (await placeholderResendCache.get(messageKey?.id)) {
61
- logger.debug({ messageKey }, 'already requested resend');
62
- return;
49
+ if (!!attrs.participant) {
50
+ stanza.attrs.participant = attrs.participant;
63
51
  }
64
- else {
65
- // Store original message data so PDO response handler can preserve
66
- // metadata (LID details, timestamps, etc.) that the phone may omit
67
- await placeholderResendCache.set(messageKey?.id, msgData || true);
52
+ if (!!attrs.recipient) {
53
+ stanza.attrs.recipient = attrs.recipient;
68
54
  }
69
- await delay(2000);
70
- if (!(await placeholderResendCache.get(messageKey?.id))) {
71
- logger.debug({ messageKey }, 'message received while resend requested');
72
- return 'RESOLVED';
55
+ if (!!attrs.type && (tag !== 'message' || (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
56
+ stanza.attrs.type = attrs.type;
73
57
  }
74
- const pdoMessage = {
75
- placeholderMessageResendRequest: [
76
- {
77
- messageKey
78
- }
79
- ],
80
- peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
81
- };
82
- setTimeout(async () => {
83
- if (await placeholderResendCache.get(messageKey?.id)) {
84
- logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
85
- await placeholderResendCache.del(messageKey?.id);
86
- }
87
- }, 8000);
88
- return sendPeerDataOperationMessage(pdoMessage);
89
- };
90
- const handleMexNotification = async (node) => {
91
- const updateNode = getBinaryNodeChild(node, 'update');
92
- if (updateNode) {
93
- const opName = updateNode.attrs?.op_name;
94
- if (!opName) {
95
- logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name, fallback to legacy');
96
- await handleLegacyMexNewsletterNotification(node);
97
- return;
98
- }
99
- let mexResponse;
100
- try {
101
- mexResponse = JSON.parse(updateNode.content.toString());
102
- }
103
- catch (error) {
104
- logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
105
- return;
106
- }
107
- if (mexResponse.errors?.length) {
108
- logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
109
- return;
110
- }
111
- const data = mexResponse.data;
112
- if (!data) {
113
- logger.warn({ opName }, 'mex notification has null data');
114
- return;
115
- }
116
- logger.debug({ opName }, 'processing mex notification');
117
- switch (opName) {
118
- case 'NotificationUserReachoutTimelockUpdate':
119
- handleReachoutTimelockNotification(data);
120
- break;
121
- case 'MessageCappingInfoNotification':
122
- handleMessageCappingNotification(data);
123
- break;
124
- // newsletter ops still use the legacy <mex> child structure
125
- case 'NotificationNewsletterUpdate':
126
- case 'NotificationLinkedProfilesUpdates':
127
- case 'NotificationNewsletterAdminPromote':
128
- case 'NotificationNewsletterAdminDemote':
129
- case 'NotificationNewsletterUserSettingChange':
130
- case 'NotificationNewsletterJoin':
131
- case 'NotificationNewsletterLeave':
132
- case 'NotificationNewsletterStateChange':
133
- case 'NotificationNewsletterAdminMetadataUpdate':
134
- case 'NotificationNewsletterOwnerUpdate':
135
- case 'NotificationNewsletterAdminInviteRevoke':
136
- case 'NotificationNewsletterWamoSubStatusChange':
137
- case 'NotificationNewsletterBlockUser':
138
- case 'NotificationNewsletterPaidPartnership':
139
- case 'NotificationNewsletterMilestone':
140
- case 'NewsletterResponseStateUpdate':
141
- await handleLegacyMexNewsletterNotification(node);
142
- break;
143
- default:
144
- logger.debug({ opName }, 'unhandled mex notification');
145
- break;
146
- }
147
- return;
58
+ if (tag === 'message' && (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable')) {
59
+ stanza.attrs.from = authState.creds.me.id;
148
60
  }
149
- await handleLegacyMexNewsletterNotification(node);
61
+ logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
62
+ await sendNode(stanza);
150
63
  };
151
- const handleReachoutTimelockNotification = (data) => {
152
- const payload = data.xwa2_notify_account_reachout_timelock;
153
- if (!payload) {
154
- logger.warn('reachout timelock notification missing payload');
155
- return;
156
- }
157
- if (!payload.is_active) {
158
- logger.info('reachout timelock restriction lifted');
159
- ev.emit('connection.update', {
160
- reachoutTimeLock: {
161
- isActive: false,
162
- enforcementType: ReachoutTimelockEnforcementType.DEFAULT
64
+ const offerCall = async (toJid, isVideo = false) => {
65
+ const callId = (0, crypto_1.randomBytes)(16).toString('hex').toUpperCase().substring(0, 64);
66
+ const offerContent = [];
67
+ offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined });
68
+ offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined });
69
+ if (isVideo) {
70
+ offerContent.push({
71
+ tag: 'video',
72
+ attrs: {
73
+ orientation: '0',
74
+ 'screen_width': '1920',
75
+ 'screen_height': '1080',
76
+ 'device_orientation': '0',
77
+ enc: 'vp8',
78
+ dec: 'vp8',
163
79
  }
164
80
  });
165
- return;
166
81
  }
167
- // WA Web defaults to now+60s when the server omits the expiry
168
- const timeEnforcementEnds = payload.time_enforcement_ends
169
- ? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
170
- : new Date(Date.now() + 60000);
171
- const enforcementType = isValidEnforcementType(payload.enforcement_type)
172
- ? payload.enforcement_type
173
- : ReachoutTimelockEnforcementType.DEFAULT;
174
- logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
175
- ev.emit('connection.update', {
176
- reachoutTimeLock: {
177
- isActive: true,
178
- timeEnforcementEnds,
179
- enforcementType
82
+ offerContent.push({ tag: 'net', attrs: { medium: '3' }, content: undefined });
83
+ offerContent.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) });
84
+ offerContent.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
85
+ const encKey = (0, crypto_1.randomBytes)(32);
86
+ const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => (0, WABinary_1.jidEncode)(user, 's.whatsapp.net', device));
87
+ await assertSessions(devices, true);
88
+ const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
89
+ call: {
90
+ callKey: encKey
180
91
  }
181
92
  });
182
- };
183
- const handleMessageCappingNotification = (data) => {
184
- const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
185
- if (!payload) {
186
- logger.warn('message capping notification missing payload');
187
- return;
188
- }
189
- logger.info({ payload }, 'received message capping update');
190
- ev.emit('message-capping.update', payload);
191
- };
192
- const handleLegacyMexNewsletterNotification = async (node) => {
193
- const mexNode = getBinaryNodeChild(node, 'mex');
194
- const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
195
- const payloadNode = mexNode?.content ? mexNode : updateNode;
196
- if (!payloadNode?.content) {
197
- logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
198
- return;
199
- }
200
- let data;
201
- try {
202
- const payloadContent = payloadNode.content;
203
- if (Array.isArray(payloadContent)) {
204
- logger.warn({ payloadNode }, 'invalid mex newsletter notification payload format');
205
- return;
206
- }
207
- const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
208
- data = JSON.parse(contentBuf.toString());
209
- }
210
- catch (error) {
211
- logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
212
- return;
213
- }
214
- const operation = data?.operation ?? payloadNode?.attrs?.op_name;
215
- let updates = data?.updates;
216
- if (!updates) {
217
- const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
218
- if (linkedProfiles) {
219
- updates = [linkedProfiles];
220
- }
221
- }
222
- if (!updates || !operation) {
223
- logger.warn({ data }, 'invalid mex newsletter notification content');
224
- return;
225
- }
226
- logger.info({ operation, updates }, 'got mex newsletter notification');
227
- switch (operation) {
228
- case 'NotificationNewsletterUpdate':
229
- for (const update of updates) {
230
- if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
231
- ev.emit('newsletter-settings.update', {
232
- id: update.jid,
233
- update: update.settings
234
- });
235
- }
236
- }
237
- break;
238
- case 'NotificationNewsletterAdminPromote':
239
- for (const update of updates) {
240
- if (update.jid && update.user) {
241
- ev.emit('newsletter-participants.update', {
242
- id: update.jid,
243
- author: node.attrs.from,
244
- user: update.user,
245
- new_role: 'ADMIN',
246
- action: 'promote'
247
- });
248
- }
249
- }
250
- break;
251
- case 'NotificationLinkedProfilesUpdates':
252
- for (const update of updates) {
253
- const lid = update?.jid;
254
- const addedProfiles = Array.isArray(update?.added_profiles) ? update.added_profiles : [];
255
- const mappings = [];
256
- for (const profile of addedProfiles) {
257
- const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
258
- if (lid && pn) {
259
- const mapping = { lid, pn };
260
- ev.emit('lid-mapping.update', mapping);
261
- mappings.push(mapping);
262
- }
263
- }
264
- await signalRepository.lidMapping.storeLIDPNMappings(mappings);
265
- }
266
- break;
267
- default:
268
- logger.info({ operation, data }, 'unhandled mex newsletter notification');
269
- break;
270
- }
271
- };
272
- // Handles newsletter notifications
273
- const handleNewsletterNotification = async (node) => {
274
- const from = node.attrs.from;
275
- const children = getAllBinaryNodeChildren(node);
276
- const author = node.attrs.participant;
277
- for (const child of children) {
278
- logger.debug({ from, child }, 'got newsletter notification');
279
- switch (child.tag) {
280
- case 'reaction': {
281
- const reactionUpdate = {
282
- id: from,
283
- server_id: child.attrs.message_id,
284
- reaction: {
285
- code: getBinaryNodeChildString(child, 'reaction'),
286
- count: 1
287
- }
288
- };
289
- ev.emit('newsletter.reaction', reactionUpdate);
290
- break;
291
- }
292
- case 'view': {
293
- const viewUpdate = {
294
- id: from,
295
- server_id: child.attrs.message_id,
296
- count: parseInt(child.content?.toString() || '0', 10)
297
- };
298
- ev.emit('newsletter.view', viewUpdate);
299
- break;
300
- }
301
- case 'participant': {
302
- const participantUpdate = {
303
- id: from,
304
- author,
305
- user: child.attrs.jid,
306
- action: child.attrs.action,
307
- new_role: child.attrs.role
308
- };
309
- ev.emit('newsletter-participants.update', participantUpdate);
310
- break;
311
- }
312
- case 'update': {
313
- const settingsNode = getBinaryNodeChild(child, 'settings');
314
- if (settingsNode) {
315
- const update = {};
316
- const nameNode = getBinaryNodeChild(settingsNode, 'name');
317
- if (nameNode?.content)
318
- update.name = nameNode.content.toString();
319
- const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
320
- if (descriptionNode?.content)
321
- update.description = descriptionNode.content.toString();
322
- ev.emit('newsletter-settings.update', {
323
- id: from,
324
- update
325
- });
326
- }
327
- break;
328
- }
329
- case 'message': {
330
- const plaintextNode = getBinaryNodeChild(child, 'plaintext');
331
- if (plaintextNode?.content) {
332
- try {
333
- const contentBuf = typeof plaintextNode.content === 'string'
334
- ? Buffer.from(plaintextNode.content, 'binary')
335
- : Buffer.from(plaintextNode.content);
336
- const messageProto = proto.Message.decode(contentBuf).toJSON();
337
- const fullMessage = proto.WebMessageInfo.fromObject({
338
- key: {
339
- remoteJid: from,
340
- id: child.attrs.message_id || child.attrs.server_id,
341
- fromMe: false // TODO: is this really true though
342
- },
343
- message: messageProto,
344
- messageTimestamp: +child.attrs.t
345
- }).toJSON();
346
- await upsertMessage(fullMessage, 'append');
347
- logger.debug('Processed plaintext newsletter message');
348
- }
349
- catch (error) {
350
- logger.error({ error }, 'Failed to decode plaintext newsletter message');
351
- }
352
- }
353
- break;
354
- }
355
- default:
356
- logger.warn({ node, child }, 'Unknown newsletter notification child');
357
- break;
358
- }
93
+ offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
94
+ if (shouldIncludeDeviceIdentity) {
95
+ offerContent.push({
96
+ tag: 'device-identity',
97
+ attrs: {},
98
+ content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
99
+ });
359
100
  }
360
- };
361
- const sendMessageAck = async (node, errorCode) => {
362
- const stanza = buildAckStanza(node, errorCode, authState.creds.me.id);
363
- logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack');
364
- await sendNode(stanza);
101
+ const stanza = ({
102
+ tag: 'call',
103
+ attrs: {
104
+ to: toJid,
105
+ },
106
+ content: [{
107
+ tag: 'offer',
108
+ attrs: {
109
+ 'call-id': callId,
110
+ 'call-creator': authState.creds.me.id,
111
+ },
112
+ content: offerContent,
113
+ }],
114
+ });
115
+ await query(stanza);
116
+ return {
117
+ callId,
118
+ toJid,
119
+ isVideo,
120
+ };
365
121
  };
366
122
  const rejectCall = async (callId, callFrom) => {
367
- const stanza = {
123
+ const stanza = ({
368
124
  tag: 'call',
369
125
  attrs: {
370
126
  from: authState.creds.me.id,
371
- to: callFrom
127
+ to: callFrom,
372
128
  },
373
- content: [
374
- {
129
+ content: [{
375
130
  tag: 'reject',
376
131
  attrs: {
377
132
  'call-id': callId,
378
133
  'call-creator': callFrom,
379
- count: '0'
134
+ count: '0',
380
135
  },
381
- content: undefined
382
- }
383
- ]
384
- };
136
+ content: undefined,
137
+ }],
138
+ });
385
139
  await query(stanza);
386
140
  };
387
141
  const sendRetryRequest = async (node, forceIncludeKeys = false) => {
388
- const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
142
+ const { fullMessage } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
389
143
  const { key: msgKey } = fullMessage;
390
144
  const msgId = msgKey.id;
391
- if (messageRetryManager) {
392
- // Check if we've exceeded max retries using the new system
393
- if (messageRetryManager.hasExceededMaxRetries(msgId)) {
394
- logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
395
- messageRetryManager.markRetryFailed(msgId);
396
- return;
397
- }
398
- // Increment retry count using new system
399
- const retryCount = messageRetryManager.incrementRetryCount(msgId);
400
- // Use the new retry count for the rest of the logic
401
- const key = `${msgId}:${msgKey?.participant}`;
402
- await msgRetryCache.set(key, retryCount);
403
- }
404
- else {
405
- // Fallback to old system
406
- const key = `${msgId}:${msgKey?.participant}`;
407
- let retryCount = (await msgRetryCache.get(key)) || 0;
408
- if (retryCount >= maxMsgRetryCount) {
409
- logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
410
- await msgRetryCache.del(key);
411
- return;
412
- }
413
- retryCount += 1;
414
- await msgRetryCache.set(key, retryCount);
145
+ const key = `${msgId}:${msgKey === null || msgKey === void 0 ? void 0 : msgKey.participant}`;
146
+ let retryCount = msgRetryCache.get(key) || 0;
147
+ if (retryCount >= maxMsgRetryCount) {
148
+ logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
149
+ msgRetryCache.del(key);
150
+ return;
415
151
  }
416
- const key = `${msgId}:${msgKey?.participant}`;
417
- const retryCount = (await msgRetryCache.get(key)) || 1;
152
+ retryCount += 1;
153
+ msgRetryCache.set(key, retryCount);
418
154
  const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
419
- const fromJid = node.attrs.from;
420
- // Check if we should recreate the session
421
- let shouldRecreateSession = false;
422
- let recreateReason = '';
423
- if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
424
- try {
425
- // Check if we have a session with this JID
426
- const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
427
- const hasSession = await signalRepository.validateSession(fromJid);
428
- const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
429
- shouldRecreateSession = result.recreate;
430
- recreateReason = result.reason;
431
- if (shouldRecreateSession) {
432
- logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
433
- // Delete existing session to force recreation
434
- await authState.keys.set({ session: { [sessionId]: null } });
435
- forceIncludeKeys = true;
436
- }
437
- }
438
- catch (error) {
439
- logger.warn({ error, fromJid }, 'failed to check session recreation');
440
- }
155
+ if (retryCount === 1) {
156
+ //request a resend via phone
157
+ const msgId = await requestPlaceholderResend(msgKey);
158
+ logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
441
159
  }
442
- if (retryCount <= 2) {
443
- // Use new retry manager for phone requests if available
444
- if (messageRetryManager) {
445
- // Schedule phone request with delay (like whatsmeow)
446
- messageRetryManager.schedulePhoneRequest(msgId, async () => {
447
- try {
448
- const requestId = await requestPlaceholderResend(msgKey);
449
- logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
450
- }
451
- catch (error) {
452
- logger.warn({ error, msgId }, 'failed to send scheduled phone request');
453
- }
454
- });
455
- }
456
- else {
457
- // Fallback to immediate request
458
- const msgId = await requestPlaceholderResend(msgKey);
459
- logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
460
- }
461
- }
462
- const deviceIdentity = encodeSignedDeviceIdentity(account, true);
160
+ const deviceIdentity = (0, Utils_1.encodeSignedDeviceIdentity)(account, true);
463
161
  await authState.keys.transaction(async () => {
464
162
  const receipt = {
465
163
  tag: 'receipt',
@@ -475,15 +173,13 @@ export const makeMessagesRecvSocket = (config) => {
475
173
  count: retryCount.toString(),
476
174
  id: node.attrs.id,
477
175
  t: node.attrs.t,
478
- v: '1',
479
- // ADD ERROR FIELD
480
- error: '0'
176
+ v: '1'
481
177
  }
482
178
  },
483
179
  {
484
180
  tag: 'registration',
485
181
  attrs: {},
486
- content: encodeBigEndian(authState.creds.registrationId)
182
+ content: (0, Utils_1.encodeBigEndian)(authState.creds.registrationId)
487
183
  }
488
184
  ]
489
185
  };
@@ -493,8 +189,8 @@ export const makeMessagesRecvSocket = (config) => {
493
189
  if (node.attrs.participant) {
494
190
  receipt.attrs.participant = node.attrs.participant;
495
191
  }
496
- if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
497
- const { update, preKeys } = await getNextPreKeys(authState, 1);
192
+ if (retryCount > 1 || forceIncludeKeys) {
193
+ const { update, preKeys } = await (0, Utils_1.getNextPreKeys)(authState, 1);
498
194
  const [keyId] = Object.keys(preKeys);
499
195
  const key = preKeys[+keyId];
500
196
  const content = receipt.content;
@@ -502,10 +198,10 @@ export const makeMessagesRecvSocket = (config) => {
502
198
  tag: 'keys',
503
199
  attrs: {},
504
200
  content: [
505
- { tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
201
+ { tag: 'type', attrs: {}, content: Buffer.from(Defaults_1.KEY_BUNDLE_TYPE) },
506
202
  { tag: 'identity', attrs: {}, content: identityKey.public },
507
- xmppPreKey(key, +keyId),
508
- xmppSignedPreKey(signedPreKey),
203
+ (0, Utils_1.xmppPreKey)(key, +keyId),
204
+ (0, Utils_1.xmppSignedPreKey)(signedPreKey),
509
205
  { tag: 'device-identity', attrs: {}, content: deviceIdentity }
510
206
  ]
511
207
  });
@@ -513,119 +209,63 @@ export const makeMessagesRecvSocket = (config) => {
513
209
  }
514
210
  await sendNode(receipt);
515
211
  logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
516
- }, authState?.creds?.me?.id || 'sendRetryRequest');
517
- };
518
- // Mirrors WAWeb/Handle/PreKeyLow.js: skip a re-issued notification with the same stanza id.
519
- const inFlightPreKeyLow = new Set();
520
- /**
521
- * Fire-and-forget tctoken re-issuance after a peer's device identity changed.
522
- * Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
523
- * the session refresh (not after it).
524
- */
525
- const reissueTcTokenAfterIdentityChange = (from) => {
526
- void (async () => {
527
- const normalizedJid = jidNormalizedUser(from);
528
- const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
529
- const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
530
- const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
531
- if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
532
- return;
533
- }
534
- logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
535
- const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
536
- const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
537
- const result = await issuePrivacyTokens([issueJid], senderTs);
538
- await storeTcTokensFromIqResult({
539
- result,
540
- fallbackJid: tcJid,
541
- keys: authState.keys,
542
- getLIDForPN,
543
- onNewJidStored: trackTcTokenJid
544
- });
545
- })().catch(err => {
546
- logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
547
212
  });
548
213
  };
549
214
  const handleEncryptNotification = async (node) => {
550
215
  const from = node.attrs.from;
551
- if (from === S_WHATSAPP_NET) {
552
- const stanzaId = node.attrs.id;
553
- if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
554
- return;
555
- }
556
- const countChild = getBinaryNodeChild(node, 'count');
216
+ if (from === WABinary_1.S_WHATSAPP_NET) {
217
+ const countChild = (0, WABinary_1.getBinaryNodeChild)(node, 'count');
557
218
  const count = +countChild.attrs.value;
558
- const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
219
+ const shouldUploadMorePreKeys = count < Defaults_1.MIN_PREKEY_COUNT;
559
220
  logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
560
221
  if (shouldUploadMorePreKeys) {
561
- if (stanzaId)
562
- inFlightPreKeyLow.add(stanzaId);
563
- try {
564
- await uploadPreKeys();
565
- }
566
- finally {
567
- if (stanzaId)
568
- inFlightPreKeyLow.delete(stanzaId);
569
- }
222
+ await uploadPreKeys();
570
223
  }
571
224
  }
572
225
  else {
573
- const result = await handleIdentityChange(node, {
574
- meId: authState.creds.me?.id,
575
- meLid: authState.creds.me?.lid,
576
- validateSession: signalRepository.validateSession,
577
- assertSessions,
578
- debounceCache: identityAssertDebounce,
579
- logger,
580
- onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
581
- });
582
- if (result.action === 'no_identity_node') {
226
+ const identityNode = (0, WABinary_1.getBinaryNodeChild)(node, 'identity');
227
+ if (identityNode) {
228
+ logger.info({ jid: from }, 'identity changed');
229
+ // not handling right now
230
+ // signal will override new identity anyway
231
+ }
232
+ else {
583
233
  logger.info({ node }, 'unknown encrypt notification');
584
234
  }
585
235
  }
586
236
  };
587
- const handleGroupNotification = (fullNode, child, msg) => {
588
- // TODO: Support PN/LID (Here is only LID now)
589
- const actingParticipantLid = fullNode.attrs.participant;
590
- const actingParticipantPn = fullNode.attrs.participant_pn;
591
- const actingParticipantUsername = fullNode.attrs.participant_username;
592
- const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
593
- const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
594
- switch (child?.tag) {
237
+ const handleGroupNotification = (participant, child, msg) => {
238
+ var _a, _b, _c, _d;
239
+ const participantJid = ((_b = (_a = (0, WABinary_1.getBinaryNodeChild)(child, 'participant')) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.jid) || participant;
240
+ switch (child === null || child === void 0 ? void 0 : child.tag) {
595
241
  case 'create':
596
- const metadata = extractGroupMetadata(child);
597
- msg.messageStubType = WAMessageStubType.GROUP_CREATE;
242
+ const metadata = (0, groups_1.extractGroupMetadata)(child);
243
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE;
598
244
  msg.messageStubParameters = [metadata.subject];
599
- msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
600
- ev.emit('chats.upsert', [
601
- {
245
+ msg.key = { participant: metadata.owner };
246
+ ev.emit('chats.upsert', [{
602
247
  id: metadata.id,
603
248
  name: metadata.subject,
604
- conversationTimestamp: metadata.creation
605
- }
606
- ]);
607
- ev.emit('groups.upsert', [
608
- {
249
+ conversationTimestamp: metadata.creation,
250
+ }]);
251
+ ev.emit('groups.upsert', [{
609
252
  ...metadata,
610
- author: actingParticipantLid,
611
- authorPn: actingParticipantPn,
612
- authorUsername: actingParticipantUsername
613
- }
614
- ]);
253
+ author: participant
254
+ }]);
615
255
  break;
616
256
  case 'ephemeral':
617
257
  case 'not_ephemeral':
618
258
  msg.message = {
619
259
  protocolMessage: {
620
- type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
260
+ type: WAProto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
621
261
  ephemeralExpiration: +(child.attrs.expiration || 0)
622
262
  }
623
263
  };
624
264
  break;
625
265
  case 'modify':
626
- const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid);
266
+ const oldNumber = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
627
267
  msg.messageStubParameters = oldNumber || [];
628
- msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
268
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
629
269
  break;
630
270
  case 'promote':
631
271
  case 'demote':
@@ -633,224 +273,179 @@ export const makeMessagesRecvSocket = (config) => {
633
273
  case 'add':
634
274
  case 'leave':
635
275
  const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
636
- msg.messageStubType = WAMessageStubType[stubType];
637
- const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
638
- // TODO: Store LID MAPPINGS
639
- return {
640
- id: attrs.jid,
641
- phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
642
- lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
643
- username: attrs.participant_username || attrs.username || undefined,
644
- admin: (attrs.type || null)
645
- };
646
- });
276
+ msg.messageStubType = Types_1.WAMessageStubType[stubType];
277
+ const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
647
278
  if (participants.length === 1 &&
648
279
  // if recv. "remove" message and sender removed themselves
649
280
  // mark as left
650
- (areJidsSameUser(participants[0].id, actingParticipantLid) ||
651
- areJidsSameUser(participants[0].id, actingParticipantPn)) &&
281
+ (0, WABinary_1.areJidsSameUser)(participants[0], participant) &&
652
282
  child.tag === 'remove') {
653
- msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
283
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
654
284
  }
655
- msg.messageStubParameters = participants.map(a => JSON.stringify(a));
285
+ msg.messageStubParameters = participants;
656
286
  break;
657
287
  case 'subject':
658
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
288
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_SUBJECT;
659
289
  msg.messageStubParameters = [child.attrs.subject];
660
290
  break;
661
291
  case 'description':
662
- const description = getBinaryNodeChild(child, 'body')?.content?.toString();
663
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
292
+ const description = (_d = (_c = (0, WABinary_1.getBinaryNodeChild)(child, 'body')) === null || _c === void 0 ? void 0 : _c.content) === null || _d === void 0 ? void 0 : _d.toString();
293
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
664
294
  msg.messageStubParameters = description ? [description] : undefined;
665
295
  break;
666
296
  case 'announcement':
667
297
  case 'not_announcement':
668
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
669
- msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
298
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
299
+ msg.messageStubParameters = [(child.tag === 'announcement') ? 'on' : 'off'];
670
300
  break;
671
301
  case 'locked':
672
302
  case 'unlocked':
673
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT;
674
- msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
303
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_RESTRICT;
304
+ msg.messageStubParameters = [(child.tag === 'locked') ? 'on' : 'off'];
675
305
  break;
676
306
  case 'invite':
677
- msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
307
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
678
308
  msg.messageStubParameters = [child.attrs.code];
679
309
  break;
680
310
  case 'member_add_mode':
681
311
  const addMode = child.content;
682
312
  if (addMode) {
683
- msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE;
313
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBER_ADD_MODE;
684
314
  msg.messageStubParameters = [addMode.toString()];
685
315
  }
686
316
  break;
687
317
  case 'membership_approval_mode':
688
- const approvalMode = getBinaryNodeChild(child, 'group_join');
318
+ const approvalMode = (0, WABinary_1.getBinaryNodeChild)(child, 'group_join');
689
319
  if (approvalMode) {
690
- msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
320
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
691
321
  msg.messageStubParameters = [approvalMode.attrs.state];
692
322
  }
693
323
  break;
694
324
  case 'created_membership_requests':
695
- msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
696
- msg.messageStubParameters = [
697
- JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
698
- 'created',
699
- child.attrs.request_method
700
- ];
325
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
326
+ msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method];
701
327
  break;
702
328
  case 'revoked_membership_requests':
703
- const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
704
- // TODO: LIDMAPPING SUPPORT
705
- msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
706
- msg.messageStubParameters = [
707
- JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
708
- isDenied ? 'revoked' : 'rejected'
709
- ];
329
+ const isDenied = (0, WABinary_1.areJidsSameUser)(participantJid, participant);
330
+ msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
331
+ msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'];
332
+ break;
710
333
  break;
334
+ default:
335
+ // console.log("BAILEYS-DEBUG:", JSON.stringify({ ...child, content: Buffer.isBuffer(child.content) ? child.content.toString() : child.content, participant }, null, 2))
711
336
  }
712
337
  };
713
- const handleDevicesNotification = async (node) => {
714
- const [child] = getAllBinaryNodeChildren(node);
715
- const from = jidNormalizedUser(node.attrs.from);
716
- if (!child) {
717
- logger.debug({ from }, 'devices notification missing child, skipping');
718
- return;
719
- }
720
- const tag = child.tag;
721
- const deviceHash = child.attrs.device_hash;
722
- const devices = getBinaryNodeChildren(child, 'device');
723
- if (areJidsSameUser(from, authState.creds.me.id) || areJidsSameUser(from, authState.creds.me.lid)) {
724
- const deviceJids = devices.map(d => d.attrs.jid);
725
- logger.info({ deviceJids }, 'got my own devices');
726
- }
727
- if (!devices.length) {
728
- logger.debug({ from, tag }, 'no devices in notification, skipping');
729
- return;
730
- }
731
- const decoded = [];
732
- for (const d of devices) {
733
- const jid = d.attrs.jid;
734
- if (!jid)
735
- continue;
736
- const parts = jidDecode(jid);
737
- if (!parts) {
738
- logger.debug({ jid }, 'failed to decode device jid, skipping');
739
- continue;
338
+ const handleNewsletterNotification = (id, node) => {
339
+ const messages = (0, WABinary_1.getBinaryNodeChild)(node, 'messages');
340
+ const message = (0, WABinary_1.getBinaryNodeChild)(messages, 'message');
341
+ const serverId = message.attrs.server_id;
342
+ const reactionsList = (0, WABinary_1.getBinaryNodeChild)(message, 'reactions');
343
+ const viewsList = (0, WABinary_1.getBinaryNodeChildren)(message, 'views_count');
344
+ if (reactionsList) {
345
+ const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsList, 'reaction');
346
+ if (reactions.length === 0) {
347
+ ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { removed: true } });
740
348
  }
741
- decoded.push({ jid, user: parts.user, server: parts.server, device: parts.device });
349
+ reactions.forEach(item => {
350
+ var _a, _b;
351
+ ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { code: (_a = item.attrs) === null || _a === void 0 ? void 0 : _a.code, count: +((_b = item.attrs) === null || _b === void 0 ? void 0 : _b.count) } });
352
+ });
742
353
  }
743
- if (!decoded.length)
744
- return;
745
- await devicesMutex.mutex(async () => {
746
- const byUser = new Map();
747
- for (const d of decoded) {
748
- const list = byUser.get(d.user) || [];
749
- list.push(d);
750
- byUser.set(d.user, list);
354
+ if (viewsList.length) {
355
+ viewsList.forEach(item => {
356
+ ev.emit('newsletter.view', { id, 'server_id': serverId, count: +item.attrs.count });
357
+ });
358
+ }
359
+ };
360
+ const handleMexNewsletterNotification = (id, node) => {
361
+ var _a;
362
+ const operation = node === null || node === void 0 ? void 0 : node.attrs.op_name;
363
+ const content = JSON.parse((_a = node === null || node === void 0 ? void 0 : node.content) === null || _a === void 0 ? void 0 : _a.toString());
364
+ let contentPath;
365
+ if (operation === Types_1.MexOperations.PROMOTE || operation === Types_1.MexOperations.DEMOTE) {
366
+ let action;
367
+ if (operation === Types_1.MexOperations.PROMOTE) {
368
+ action = 'promote';
369
+ contentPath = content.data[Types_1.XWAPaths.PROMOTE];
751
370
  }
752
- for (const [user, entries] of byUser) {
753
- if (tag === 'update') {
754
- logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
755
- await userDevicesCache?.del(user);
756
- continue;
757
- }
758
- if (tag === 'remove') {
759
- await signalRepository.deleteSession(entries.map(e => e.jid));
760
- }
761
- const existingCache = (await userDevicesCache?.get(user)) || [];
762
- if (!existingCache.length) {
763
- // No baseline yet; skip applying the delta so getUSyncDevices can
764
- // later fetch the full device list. Caching just the notification
765
- // entries would make a partial list look authoritative.
766
- logger.debug({ user, tag }, 'device list not cached, deferring to USync refresh');
767
- continue;
768
- }
769
- const affected = new Set(entries.map(e => e.device));
770
- let updatedDevices;
771
- switch (tag) {
772
- case 'add':
773
- logger.info({ deviceHash, count: entries.length }, 'devices added');
774
- updatedDevices = [
775
- ...existingCache.filter(d => !affected.has(d.device)),
776
- ...entries.map(e => ({ user: e.user, server: e.server, device: e.device }))
777
- ];
778
- break;
779
- case 'remove':
780
- logger.info({ deviceHash, count: entries.length }, 'devices removed');
781
- updatedDevices = existingCache.filter(d => !affected.has(d.device));
782
- break;
783
- default:
784
- logger.debug({ tag }, 'Unknown device list change tag');
785
- continue;
786
- }
787
- if (updatedDevices.length === 0) {
788
- await userDevicesCache?.del(user);
789
- }
790
- else {
791
- await userDevicesCache?.set(user, updatedDevices);
792
- }
371
+ if (operation === Types_1.MexOperations.DEMOTE) {
372
+ action = 'demote';
373
+ contentPath = content.data[Types_1.XWAPaths.DEMOTE];
793
374
  }
794
- });
375
+ ev.emit('newsletter-participants.update', { id, author: contentPath.actor.pn, user: contentPath.user.pn, new_role: contentPath.user_new_role, action });
376
+ }
377
+ if (operation === Types_1.MexOperations.UPDATE) {
378
+ contentPath = content.data[Types_1.XWAPaths.METADATA_UPDATE];
379
+ ev.emit('newsletter-settings.update', { id, update: contentPath.thread_metadata.settings });
380
+ }
795
381
  };
796
382
  const processNotification = async (node) => {
383
+ var _a, _b;
797
384
  const result = {};
798
- const [child] = getAllBinaryNodeChildren(node);
385
+ const [child] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
799
386
  const nodeType = node.attrs.type;
800
- const from = jidNormalizedUser(node.attrs.from);
387
+ const from = (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
801
388
  switch (nodeType) {
389
+ case 'privacy_token':
390
+ const tokenList = (0, WABinary_1.getBinaryNodeChildren)(child, 'token');
391
+ for (const { attrs, content } of tokenList) {
392
+ const jid = attrs.jid;
393
+ ev.emit('chats.update', [
394
+ {
395
+ id: jid,
396
+ tcToken: content
397
+ }
398
+ ]);
399
+ logger.debug({ jid }, 'got privacy token update');
400
+ }
401
+ break;
802
402
  case 'newsletter':
803
- await handleNewsletterNotification(node);
403
+ handleNewsletterNotification(node.attrs.from, child);
804
404
  break;
805
405
  case 'mex':
806
- await handleMexNotification(node);
406
+ handleMexNewsletterNotification(node.attrs.from, child);
807
407
  break;
808
408
  case 'w:gp2':
809
- // TODO: HANDLE PARTICIPANT_PN
810
- handleGroupNotification(node, child, result);
409
+ handleGroupNotification(node.attrs.participant, child, result);
811
410
  break;
812
411
  case 'mediaretry':
813
- const event = decodeMediaRetryNode(node);
412
+ const event = (0, Utils_1.decodeMediaRetryNode)(node);
814
413
  ev.emit('messages.media-update', [event]);
815
414
  break;
816
415
  case 'encrypt':
817
416
  await handleEncryptNotification(node);
818
417
  break;
819
418
  case 'devices':
820
- try {
821
- await handleDevicesNotification(node);
822
- }
823
- catch (error) {
824
- logger.error({ error, node }, 'failed to handle devices notification');
419
+ const devices = (0, WABinary_1.getBinaryNodeChildren)(child, 'device');
420
+ if ((0, WABinary_1.areJidsSameUser)(child.attrs.jid, authState.creds.me.id)) {
421
+ const deviceJids = devices.map(d => d.attrs.jid);
422
+ logger.info({ deviceJids }, 'got my own devices');
825
423
  }
826
424
  break;
827
425
  case 'server_sync':
828
- const update = getBinaryNodeChild(node, 'collection');
426
+ const update = (0, WABinary_1.getBinaryNodeChild)(node, 'collection');
829
427
  if (update) {
830
428
  const name = update.attrs.name;
831
429
  await resyncAppState([name], false);
832
430
  }
833
431
  break;
834
432
  case 'picture':
835
- const setPicture = getBinaryNodeChild(node, 'set');
836
- const delPicture = getBinaryNodeChild(node, 'delete');
837
- // TODO: WAJIDHASH stuff proper support inhouse
838
- ev.emit('contacts.update', [
839
- {
840
- id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
433
+ const setPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'set');
434
+ const delPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'delete');
435
+ ev.emit('contacts.update', [{
436
+ id: from || ((_b = (_a = (setPicture || delPicture)) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.hash) || '',
841
437
  imgUrl: setPicture ? 'changed' : 'removed'
842
- }
843
- ]);
844
- if (isJidGroup(from)) {
438
+ }]);
439
+ if ((0, WABinary_1.isJidGroup)(from)) {
845
440
  const node = setPicture || delPicture;
846
- result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
441
+ result.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ICON;
847
442
  if (setPicture) {
848
443
  result.messageStubParameters = [setPicture.attrs.id];
849
444
  }
850
- result.participant = node?.attrs.author;
445
+ result.participant = node === null || node === void 0 ? void 0 : node.attrs.author;
851
446
  result.key = {
852
- ...(result.key || {}),
853
- participant: setPicture?.attrs.author
447
+ ...result.key || {},
448
+ participant: setPicture === null || setPicture === void 0 ? void 0 : setPicture.attrs.author
854
449
  };
855
450
  }
856
451
  break;
@@ -864,48 +459,44 @@ export const makeMessagesRecvSocket = (config) => {
864
459
  ...authState.creds.accountSettings,
865
460
  defaultDisappearingMode: {
866
461
  ephemeralExpiration: newDuration,
867
- ephemeralSettingTimestamp: timestamp
868
- }
462
+ ephemeralSettingTimestamp: timestamp,
463
+ },
869
464
  }
870
465
  });
871
466
  }
872
467
  else if (child.tag === 'blocklist') {
873
- const blocklists = getBinaryNodeChildren(child, 'item');
468
+ const blocklists = (0, WABinary_1.getBinaryNodeChildren)(child, 'item');
874
469
  for (const { attrs } of blocklists) {
875
470
  const blocklist = [attrs.jid];
876
- const type = attrs.action === 'block' ? 'add' : 'remove';
471
+ const type = (attrs.action === 'block') ? 'add' : 'remove';
877
472
  ev.emit('blocklist.update', { blocklist, type });
878
473
  }
879
474
  }
880
475
  break;
881
476
  case 'link_code_companion_reg':
882
- const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg');
883
- const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'));
884
- const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'));
885
- const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
477
+ const linkCodeCompanionReg = (0, WABinary_1.getBinaryNodeChild)(node, 'link_code_companion_reg');
478
+ const ref = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_ref'));
479
+ const primaryIdentityPublicKey = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'primary_identity_pub'));
480
+ const primaryEphemeralPublicKeyWrapped = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
886
481
  const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
887
- const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
888
- const random = randomBytes(32);
889
- const linkCodeSalt = randomBytes(32);
890
- const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
482
+ const companionSharedKey = Utils_1.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
483
+ const random = (0, crypto_1.randomBytes)(32);
484
+ const linkCodeSalt = (0, crypto_1.randomBytes)(32);
485
+ const linkCodePairingExpanded = await (0, Utils_1.hkdf)(companionSharedKey, 32, {
891
486
  salt: linkCodeSalt,
892
487
  info: 'link_code_pairing_key_bundle_encryption_key'
893
488
  });
894
- const encryptPayload = Buffer.concat([
895
- Buffer.from(authState.creds.signedIdentityKey.public),
896
- primaryIdentityPublicKey,
897
- random
898
- ]);
899
- const encryptIv = randomBytes(12);
900
- const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
489
+ const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random]);
490
+ const encryptIv = (0, crypto_1.randomBytes)(12);
491
+ const encrypted = (0, Utils_1.aesEncryptGCM)(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
901
492
  const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
902
- const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
493
+ const identitySharedKey = Utils_1.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
903
494
  const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
904
- authState.creds.advSecretKey = Buffer.from(hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
495
+ authState.creds.advSecretKey = (await (0, Utils_1.hkdf)(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
905
496
  await query({
906
497
  tag: 'iq',
907
498
  attrs: {
908
- to: S_WHATSAPP_NET,
499
+ to: WABinary_1.S_WHATSAPP_NET,
909
500
  type: 'set',
910
501
  id: sock.generateMessageTag(),
911
502
  xmlns: 'md'
@@ -915,7 +506,7 @@ export const makeMessagesRecvSocket = (config) => {
915
506
  tag: 'link_code_companion_reg',
916
507
  attrs: {
917
508
  jid: authState.creds.me.id,
918
- stage: 'companion_finish'
509
+ stage: 'companion_finish',
919
510
  },
920
511
  content: [
921
512
  {
@@ -939,208 +530,53 @@ export const makeMessagesRecvSocket = (config) => {
939
530
  });
940
531
  authState.creds.registered = true;
941
532
  ev.emit('creds.update', authState.creds);
942
- break;
943
- case 'privacy_token':
944
- await handlePrivacyTokenNotification(node);
945
- break;
946
533
  }
947
534
  if (Object.keys(result).length) {
948
535
  return result;
949
536
  }
950
537
  };
951
- /**
952
- * In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
953
- * Used to coalesce writes during a session; pruning always re-reads the persisted index
954
- * to cover writes made by other layers (e.g. history sync).
955
- */
956
- const tcTokenKnownJids = new Set();
957
- const tcTokenIndexLoaded = (async () => {
958
- try {
959
- const jids = await readTcTokenIndex(authState.keys);
960
- for (const jid of jids)
961
- tcTokenKnownJids.add(jid);
962
- logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
963
- }
964
- catch (err) {
965
- logger.warn({ err: err?.message }, 'failed to load tctoken index');
966
- }
967
- })();
968
- let tcTokenIndexTimer;
969
- async function flushTcTokenIndex() {
970
- if (tcTokenIndexTimer) {
971
- clearTimeout(tcTokenIndexTimer);
972
- tcTokenIndexTimer = undefined;
973
- }
974
- // Merge with whatever is already persisted so we don't clobber writes from other
975
- // paths (history sync, concurrent sessions on the same store).
976
- const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
977
- return authState.keys.set({ tctoken: write });
978
- }
979
- function scheduleTcTokenIndexSave() {
980
- if (tcTokenIndexTimer) {
981
- clearTimeout(tcTokenIndexTimer);
982
- }
983
- tcTokenIndexTimer = setTimeout(() => {
984
- tcTokenIndexTimer = undefined;
985
- flushTcTokenIndex().catch(err => {
986
- logger.warn({ err: err?.message }, 'failed to save tctoken index');
987
- });
988
- }, 5000);
989
- }
990
- function trackTcTokenJid(jid) {
991
- if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
992
- tcTokenKnownJids.add(jid);
993
- scheduleTcTokenIndexSave();
994
- }
995
- }
996
- const handlePrivacyTokenNotification = async (node) => {
997
- const tokensNode = getBinaryNodeChild(node, 'tokens');
998
- if (!tokensNode)
999
- return;
1000
- const from = jidNormalizedUser(node.attrs.from);
1001
- // WA Web uses: senderLid ?? toLid(from) for the storage key
1002
- // The sender_lid attribute provides the LID directly when available
1003
- const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
1004
- ? jidNormalizedUser(node.attrs.sender_lid)
1005
- : undefined;
1006
- const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
1007
- logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
1008
- await storeTcTokensFromIqResult({
1009
- result: node,
1010
- fallbackJid,
1011
- keys: authState.keys,
1012
- getLIDForPN,
1013
- onNewJidStored: trackTcTokenJid
1014
- });
1015
- };
1016
538
  async function decipherLinkPublicKey(data) {
1017
539
  const buffer = toRequiredBuffer(data);
1018
540
  const salt = buffer.slice(0, 32);
1019
- const secretKey = await derivePairingCodeKey(authState.creds.pairingCode, salt);
541
+ const secretKey = await (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
1020
542
  const iv = buffer.slice(32, 48);
1021
543
  const payload = buffer.slice(48, 80);
1022
- return aesDecryptCTR(payload, secretKey, iv);
544
+ return (0, Utils_1.aesDecryptCTR)(payload, secretKey, iv);
1023
545
  }
1024
546
  function toRequiredBuffer(data) {
1025
547
  if (data === undefined) {
1026
- throw new Boom('Invalid buffer', { statusCode: 400 });
548
+ throw new boom_1.Boom('Invalid buffer', { statusCode: 400 });
1027
549
  }
1028
550
  return data instanceof Buffer ? data : Buffer.from(data);
1029
551
  }
1030
- const willSendMessageAgain = async (id, participant) => {
552
+ const willSendMessageAgain = (id, participant) => {
1031
553
  const key = `${id}:${participant}`;
1032
- const retryCount = (await msgRetryCache.get(key)) || 0;
554
+ const retryCount = msgRetryCache.get(key) || 0;
1033
555
  return retryCount < maxMsgRetryCount;
1034
556
  };
1035
- const updateSendMessageAgainCount = async (id, participant) => {
557
+ const updateSendMessageAgainCount = (id, participant) => {
1036
558
  const key = `${id}:${participant}`;
1037
- const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
1038
- await msgRetryCache.set(key, newValue);
559
+ const newValue = (msgRetryCache.get(key) || 0) + 1;
560
+ msgRetryCache.set(key, newValue);
1039
561
  };
1040
- const sendMessagesAgain = async (key, ids, retryNode, receiptNode) => {
562
+ const sendMessagesAgain = async (key, ids, retryNode) => {
563
+ var _a;
564
+ // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
565
+ const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
1041
566
  const remoteJid = key.remoteJid;
1042
567
  const participant = key.participant || remoteJid;
1043
- const retryCount = +retryNode.attrs.count || 1;
1044
- const msgId = ids[0];
1045
- // Try to get messages from cache first, then fallback to getMessage
1046
- const msgs = [];
1047
- for (const id of ids) {
1048
- let msg;
1049
- // Try to get from retry cache first if enabled
1050
- if (messageRetryManager) {
1051
- const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
1052
- if (cachedMsg) {
1053
- msg = cachedMsg.message;
1054
- logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
1055
- // Mark retry as successful since we found the message
1056
- messageRetryManager.markRetrySuccess(id);
1057
- }
1058
- }
1059
- // Fallback to getMessage if not found in cache
1060
- if (!msg) {
1061
- msg = await getMessage({ ...key, id });
1062
- if (msg) {
1063
- logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
1064
- // Also mark as successful if found via getMessage
1065
- if (messageRetryManager) {
1066
- messageRetryManager.markRetrySuccess(id);
1067
- }
1068
- }
1069
- }
1070
- msgs.push(msg);
1071
- }
1072
568
  // if it's the primary jid sending the request
1073
569
  // just re-send the message to everyone
1074
570
  // prevents the first message decryption failure
1075
- const sendToAll = !jidDecode(participant)?.device;
1076
- const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
1077
- let injectedFromBundle = false;
1078
- const bundle = extractE2ESessionFromRetryReceipt(receiptNode);
1079
- if (bundle) {
1080
- try {
1081
- await signalRepository.injectE2ESession({ jid: participant, session: bundle });
1082
- injectedFromBundle = true;
1083
- logger.debug({ participant, retryCount }, 'injected session from retry receipt key bundle');
1084
- }
1085
- catch (error) {
1086
- logger.warn({ error, participant }, 'failed to inject session from retry receipt');
1087
- }
1088
- }
1089
- if (!injectedFromBundle) {
1090
- const receivedRegId = getBinaryNodeChildUInt(receiptNode, 'registration', 4);
1091
- if (typeof receivedRegId === 'number' && Number.isInteger(receivedRegId)) {
1092
- const info = await signalRepository.getSessionInfo(participant);
1093
- if (info && info.registrationId !== 0 && info.registrationId !== receivedRegId) {
1094
- logger.info({ participant, stored: info.registrationId, received: receivedRegId }, 'reg id mismatch on retry without bundle, deleting session');
1095
- await authState.keys.set({ session: { [sessionId]: null } });
1096
- }
1097
- }
1098
- }
1099
- const BASE_KEY_CHECK_RETRY = 2;
1100
- if (msgId && messageRetryManager) {
1101
- const info = await signalRepository.getSessionInfo(participant);
1102
- if (info) {
1103
- if (retryCount === BASE_KEY_CHECK_RETRY) {
1104
- messageRetryManager.saveBaseKey(sessionId, msgId, info.baseKey);
1105
- }
1106
- else if (retryCount > BASE_KEY_CHECK_RETRY) {
1107
- if (messageRetryManager.hasSameBaseKey(sessionId, msgId, info.baseKey)) {
1108
- logger.warn({ participant, retryCount }, 'base key collision on retry, forcing fresh session');
1109
- await authState.keys.set({ session: { [sessionId]: null } });
1110
- }
1111
- messageRetryManager.deleteBaseKey(sessionId, msgId);
1112
- }
1113
- }
1114
- }
1115
- let shouldRecreateSession = false;
1116
- let recreateReason = '';
1117
- if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1 && !injectedFromBundle) {
1118
- try {
1119
- const hasSession = await signalRepository.validateSession(participant);
1120
- const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
1121
- shouldRecreateSession = result.recreate;
1122
- recreateReason = result.reason;
1123
- if (shouldRecreateSession) {
1124
- logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
1125
- await authState.keys.set({ session: { [sessionId]: null } });
1126
- }
1127
- }
1128
- catch (error) {
1129
- logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
1130
- }
1131
- }
1132
- if (!injectedFromBundle) {
1133
- await assertSessions([participant], true);
1134
- }
1135
- if (isJidGroup(remoteJid)) {
571
+ const sendToAll = !((_a = (0, WABinary_1.jidDecode)(participant)) === null || _a === void 0 ? void 0 : _a.device);
572
+ await assertSessions([participant], true);
573
+ if ((0, WABinary_1.isJidGroup)(remoteJid)) {
1136
574
  await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
1137
575
  }
1138
- logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason, injectedFromBundle }, 'prepared session for retry resend');
576
+ logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
1139
577
  for (const [i, msg] of msgs.entries()) {
1140
- if (!ids[i])
1141
- continue;
1142
- if (msg && (await willSendMessageAgain(ids[i], participant))) {
1143
- await updateSendMessageAgainCount(ids[i], participant);
578
+ if (msg) {
579
+ updateSendMessageAgainCount(ids[i], participant);
1144
580
  const msgRelayOpts = { messageId: ids[i] };
1145
581
  if (sendToAll) {
1146
582
  msgRelayOpts.useUserDevicesCache = false;
@@ -1159,10 +595,11 @@ export const makeMessagesRecvSocket = (config) => {
1159
595
  }
1160
596
  };
1161
597
  const handleReceipt = async (node) => {
598
+ var _a, _b;
1162
599
  const { attrs, content } = node;
1163
600
  const isLid = attrs.from.includes('lid');
1164
- const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
1165
- const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
601
+ const isNodeFromMe = (0, WABinary_1.areJidsSameUser)(attrs.participant || attrs.from, isLid ? (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.lid : (_b = authState.creds.me) === null || _b === void 0 ? void 0 : _b.id);
602
+ const remoteJid = !isNodeFromMe || (0, WABinary_1.isJidGroup)(attrs.from) ? attrs.from : attrs.recipient;
1166
603
  const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
1167
604
  const key = {
1168
605
  remoteJid,
@@ -1170,26 +607,33 @@ export const makeMessagesRecvSocket = (config) => {
1170
607
  fromMe,
1171
608
  participant: attrs.participant
1172
609
  };
610
+ if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
611
+ logger.debug({ remoteJid }, 'ignoring receipt from jid');
612
+ await sendMessageAck(node);
613
+ return;
614
+ }
1173
615
  const ids = [attrs.id];
1174
616
  if (Array.isArray(content)) {
1175
- const items = getBinaryNodeChildren(content[0], 'item');
617
+ const items = (0, WABinary_1.getBinaryNodeChildren)(content[0], 'item');
1176
618
  ids.push(...items.map(i => i.attrs.id));
1177
619
  }
1178
620
  try {
1179
621
  await Promise.all([
1180
- receiptMutex.mutex(async () => {
1181
- const status = getStatusFromReceiptType(attrs.type);
622
+ processingMutex.mutex(async () => {
623
+ const status = (0, Utils_1.getStatusFromReceiptType)(attrs.type);
1182
624
  if (typeof status !== 'undefined' &&
625
+ (
1183
626
  // basically, we only want to know when a message from us has been delivered to/read by the other person
1184
627
  // or another device of ours has read some messages
1185
- (status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
1186
- if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
628
+ status >= WAProto_1.proto.WebMessageInfo.Status.SERVER_ACK ||
629
+ !isNodeFromMe)) {
630
+ if ((0, WABinary_1.isJidGroup)(remoteJid) || (0, WABinary_1.isJidStatusBroadcast)(remoteJid)) {
1187
631
  if (attrs.participant) {
1188
- const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
632
+ const updateKey = status === WAProto_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
1189
633
  ev.emit('message-receipt.update', ids.map(id => ({
1190
634
  key: { ...key, id },
1191
635
  receipt: {
1192
- userJid: jidNormalizedUser(attrs.participant),
636
+ userJid: (0, WABinary_1.jidNormalizedUser)(attrs.participant),
1193
637
  [updateKey]: +attrs.t
1194
638
  }
1195
639
  })));
@@ -1198,23 +642,22 @@ export const makeMessagesRecvSocket = (config) => {
1198
642
  else {
1199
643
  ev.emit('messages.update', ids.map(id => ({
1200
644
  key: { ...key, id },
1201
- update: { status, messageTimestamp: toNumber(+(attrs.t ?? 0)) }
645
+ update: { status }
1202
646
  })));
1203
647
  }
1204
648
  }
1205
649
  if (attrs.type === 'retry') {
1206
650
  // correctly set who is asking for the retry
1207
651
  key.participant = key.participant || attrs.from;
1208
- const retryNode = getBinaryNodeChild(node, 'retry');
1209
- if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
652
+ const retryNode = (0, WABinary_1.getBinaryNodeChild)(node, 'retry');
653
+ if (willSendMessageAgain(ids[0], key.participant)) {
1210
654
  if (key.fromMe) {
1211
655
  try {
1212
- await updateSendMessageAgainCount(ids[0], key.participant);
1213
656
  logger.debug({ attrs, key }, 'recv retry request');
1214
- await sendMessagesAgain(key, ids, retryNode, node);
657
+ await sendMessagesAgain(key, ids, retryNode);
1215
658
  }
1216
659
  catch (error) {
1217
- logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
660
+ logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
1218
661
  }
1219
662
  }
1220
663
  else {
@@ -1229,338 +672,269 @@ export const makeMessagesRecvSocket = (config) => {
1229
672
  ]);
1230
673
  }
1231
674
  finally {
1232
- await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack receipt'));
675
+ await sendMessageAck(node);
1233
676
  }
1234
677
  };
1235
678
  const handleNotification = async (node) => {
1236
679
  const remoteJid = node.attrs.from;
680
+ if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
681
+ logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
682
+ await sendMessageAck(node);
683
+ return;
684
+ }
1237
685
  try {
1238
686
  await Promise.all([
1239
- notificationMutex.mutex(async () => {
687
+ processingMutex.mutex(async () => {
688
+ var _a;
1240
689
  const msg = await processNotification(node);
1241
690
  if (msg) {
1242
- const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
1243
- const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
691
+ const fromMe = (0, WABinary_1.areJidsSameUser)(node.attrs.participant || remoteJid, authState.creds.me.id);
1244
692
  msg.key = {
1245
693
  remoteJid,
1246
694
  fromMe,
1247
695
  participant: node.attrs.participant,
1248
- participantAlt,
1249
- participantUsername: node.attrs.participant_username,
1250
- addressingMode,
1251
696
  id: node.attrs.id,
1252
697
  ...(msg.key || {})
1253
698
  };
1254
- msg.participant ?? (msg.participant = node.attrs.participant);
699
+ (_a = msg.participant) !== null && _a !== void 0 ? _a : (msg.participant = node.attrs.participant);
1255
700
  msg.messageTimestamp = +node.attrs.t;
1256
- const fullMsg = proto.WebMessageInfo.fromObject(msg);
701
+ const fullMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
1257
702
  await upsertMessage(fullMsg, 'append');
1258
703
  }
1259
704
  })
1260
705
  ]);
1261
706
  }
1262
707
  finally {
1263
- await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack notification'));
708
+ await sendMessageAck(node);
1264
709
  }
1265
710
  };
1266
711
  const handleMessage = async (node) => {
1267
- const encNode = getBinaryNodeChild(node, 'enc');
712
+ var _a, _b, _c;
713
+ if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
714
+ logger.debug({ key: node.attrs.key }, 'ignored message');
715
+ await sendMessageAck(node);
716
+ return;
717
+ }
718
+ const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
1268
719
  // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
1269
- if (encNode?.attrs.type === 'msmsg') {
720
+ if (encNode && encNode.attrs.type === 'msmsg') {
1270
721
  logger.debug({ key: node.attrs.key }, 'ignored msmsg');
1271
- await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
722
+ await sendMessageAck(node);
1272
723
  return;
1273
724
  }
1274
- let acked = false;
1275
- try {
1276
- const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
1277
- const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
1278
- // store new mappings we didn't have before
1279
- if (!!alt) {
1280
- const altServer = jidDecode(alt)?.server;
1281
- const primaryJid = msg.key.participant || msg.key.remoteJid;
1282
- if (altServer === 'lid') {
1283
- if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
1284
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
1285
- await signalRepository.migrateSession(primaryJid, alt);
1286
- }
1287
- }
1288
- else {
1289
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
1290
- await signalRepository.migrateSession(alt, primaryJid);
1291
- }
725
+ let response;
726
+ if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable') && !encNode) {
727
+ await sendMessageAck(node);
728
+ const { key } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage;
729
+ response = await requestPlaceholderResend(key);
730
+ if (response === 'RESOLVED') {
731
+ return;
1292
732
  }
1293
- await messageMutex.mutex(async () => {
1294
- await decrypt();
1295
- if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
1296
- messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
1297
- }
1298
- // message failed to decrypt
1299
- if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
1300
- if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
1301
- acked = true;
1302
- return sendMessageAck(node, NACK_REASONS.ParsingError);
1303
- }
1304
- if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
1305
- // Message arrived without encryption (e.g. CTWA ads messages).
1306
- // Check if this is eligible for placeholder resend (matching WA Web filters).
1307
- const unavailableNode = getBinaryNodeChild(node, 'unavailable');
1308
- const unavailableType = unavailableNode?.attrs?.type;
1309
- if (unavailableType === 'bot_unavailable_fanout' ||
1310
- unavailableType === 'hosted_unavailable_fanout' ||
1311
- unavailableType === 'view_once_unavailable_fanout') {
1312
- logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
1313
- acked = true;
1314
- return sendMessageAck(node);
1315
- }
1316
- const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1317
- if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
1318
- logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
1319
- acked = true;
1320
- return sendMessageAck(node);
1321
- }
1322
- // Request the real content from the phone via placeholder resend PDO.
1323
- // Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
1324
- // and store the requestId in stubParameters[1] so users can correlate
1325
- // with the incoming PDO response event.
1326
- const cleanKey = {
1327
- remoteJid: msg.key.remoteJid,
1328
- fromMe: msg.key.fromMe,
1329
- id: msg.key.id,
1330
- participant: msg.key.participant
1331
- };
1332
- // Cache the original message metadata so the PDO response handler
1333
- // can preserve key fields (LID details etc.) that the phone may omit
1334
- const msgData = {
1335
- key: msg.key,
1336
- messageTimestamp: msg.messageTimestamp,
1337
- pushName: msg.pushName,
1338
- participant: msg.participant,
1339
- verifiedBizName: msg.verifiedBizName
1340
- };
1341
- requestPlaceholderResend(cleanKey, msgData)
1342
- .then(requestId => {
1343
- if (requestId && requestId !== 'RESOLVED') {
1344
- logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
1345
- ev.emit('messages.update', [
1346
- {
1347
- key: msg.key,
1348
- update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
1349
- }
1350
- ]);
1351
- }
1352
- })
1353
- .catch(err => {
1354
- logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
1355
- });
1356
- acked = true;
1357
- await sendMessageAck(node);
1358
- // Don't return — fall through to upsertMessage so the stub is emitted
1359
- }
1360
- else {
1361
- // Skip retry for expired status messages (>24h old)
1362
- if (isJidStatusBroadcast(msg.key.remoteJid)) {
1363
- const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1364
- if (messageAge > STATUS_EXPIRY_SECONDS) {
1365
- logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
1366
- acked = true;
1367
- return sendMessageAck(node);
1368
- }
733
+ logger.debug('received unavailable message, acked and requested resend from phone');
734
+ }
735
+ else {
736
+ if (placeholderResendCache.get(node.attrs.id)) {
737
+ placeholderResendCache.del(node.attrs.id);
738
+ }
739
+ }
740
+ const { fullMessage: msg, category, author, decrypt } = (0, Utils_1.decryptMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
741
+ if (response && ((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT) {
742
+ msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response];
743
+ }
744
+ if (((_c = (_b = msg.message) === null || _b === void 0 ? void 0 : _b.protocolMessage) === null || _c === void 0 ? void 0 : _c.type) === WAProto_1.proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) {
745
+ ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
746
+ }
747
+ try {
748
+ await Promise.all([
749
+ processingMutex.mutex(async () => {
750
+ var _a, _b, _c, _d, _e, _f;
751
+ await decrypt();
752
+ // message failed to decrypt
753
+ if (msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT) {
754
+ if (((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.MISSING_KEYS_ERROR_TEXT) {
755
+ return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError);
1369
756
  }
1370
- logger.debug('[handleMessage] Attempting retry request for failed decryption');
1371
- // WAWeb only retry-receipts here; server emits PreKeyLow if prekeys run low.
1372
- await retryMutex.mutex(async () => {
1373
- try {
1374
- if (!ws.isOpen) {
1375
- logger.debug({ node }, 'Connection closed, skipping retry');
757
+ retryMutex.mutex(async () => {
758
+ if (ws.isOpen) {
759
+ if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable')) {
1376
760
  return;
1377
761
  }
1378
- const encNode = getBinaryNodeChild(node, 'enc');
762
+ const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
1379
763
  await sendRetryRequest(node, !encNode);
1380
764
  if (retryRequestDelayMs) {
1381
- await delay(retryRequestDelayMs);
765
+ await (0, Utils_1.delay)(retryRequestDelayMs);
1382
766
  }
1383
767
  }
1384
- catch (err) {
1385
- logger.error({ err }, 'Failed to send retry');
768
+ else {
769
+ logger.debug({ node }, 'connection closed, ignoring retry req');
1386
770
  }
1387
- acked = true;
1388
- await sendMessageAck(node, NACK_REASONS.UnhandledError);
1389
771
  });
1390
772
  }
1391
- }
1392
- else {
1393
- if (messageRetryManager && msg.key.id) {
1394
- messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
1395
- }
1396
- const isNewsletter = isJidNewsletter(msg.key.remoteJid);
1397
- if (!isNewsletter) {
773
+ else {
1398
774
  // no type in the receipt => message delivered
1399
775
  let type = undefined;
776
+ if ((_b = msg.key.participant) === null || _b === void 0 ? void 0 : _b.endsWith('@lid')) {
777
+ msg.key.participant = node.attrs.participant_pn || authState.creds.me.id;
778
+ }
779
+ if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid) && ((_f = (_e = (_d = (_c = msg.message) === null || _c === void 0 ? void 0 : _c.extendedTextMessage) === null || _d === void 0 ? void 0 : _d.contextInfo) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.endsWith('@lid'))) {
780
+ if (msg.message.extendedTextMessage.contextInfo) {
781
+ const metadata = await groupMetadata(msg.key.remoteJid);
782
+ const sender = msg.message.extendedTextMessage.contextInfo.participant;
783
+ const found = metadata.participants.find(p => p.id === sender);
784
+ msg.message.extendedTextMessage.contextInfo.participant = (found === null || found === void 0 ? void 0 : found.jid) || sender;
785
+ }
786
+ }
787
+ if (!(0, WABinary_1.isJidGroup)(msg.key.remoteJid) && (0, WABinary_1.isLidUser)(msg.key.remoteJid)) {
788
+ msg.key.remoteJid = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
789
+ }
1400
790
  let participant = msg.key.participant;
1401
- if (category === 'peer') {
1402
- // special peer message
791
+ if (category === 'peer') { // special peer message
1403
792
  type = 'peer_msg';
1404
793
  }
1405
- else if (msg.key.fromMe) {
1406
- // message was sent by us from a different device
794
+ else if (msg.key.fromMe) { // message was sent by us from a different device
1407
795
  type = 'sender';
1408
796
  // need to specially handle this case
1409
- if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
1410
- participant = author; // TODO: investigate sending receipts to LIDs and not PNs
797
+ if ((0, WABinary_1.isJidUser)(msg.key.remoteJid)) {
798
+ participant = author;
1411
799
  }
1412
800
  }
1413
801
  else if (!sendActiveReceipts) {
1414
802
  type = 'inactive';
1415
803
  }
1416
- acked = true;
1417
804
  await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
1418
805
  // send ack for history message
1419
- const isAnyHistoryMsg = getHistoryMsg(msg.message);
806
+ const isAnyHistoryMsg = (0, Utils_1.getHistoryMsg)(msg.message);
1420
807
  if (isAnyHistoryMsg) {
1421
- const jid = jidNormalizedUser(msg.key.remoteJid);
1422
- await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
808
+ const jid = (0, WABinary_1.jidNormalizedUser)(msg.key.remoteJid);
809
+ await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
1423
810
  }
1424
811
  }
1425
- else {
1426
- acked = true;
1427
- await sendMessageAck(node);
1428
- logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
1429
- }
1430
- }
1431
- cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
1432
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
1433
- });
812
+ (0, Utils_1.cleanMessage)(msg, authState.creds.me.id);
813
+ await sendMessageAck(node);
814
+ await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
815
+ })
816
+ ]);
1434
817
  }
1435
818
  catch (error) {
1436
- logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
1437
- if (!acked) {
1438
- await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
1439
- }
819
+ logger.error({ error, node }, 'error in handling message');
1440
820
  }
1441
821
  };
1442
- const handleCall = async (node) => {
1443
- try {
1444
- const { attrs } = node;
1445
- const [infoChild] = getAllBinaryNodeChildren(node);
1446
- if (!infoChild) {
1447
- throw new Boom('Missing call info in call node');
1448
- }
1449
- const status = getCallStatusFromNode(infoChild);
1450
- const callId = infoChild.attrs['call-id'];
1451
- const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
1452
- const call = {
1453
- chatId: attrs.from,
1454
- from,
1455
- callerPn: infoChild.attrs['caller_pn'],
1456
- id: callId,
1457
- date: new Date(+attrs.t * 1000),
1458
- offline: !!attrs.offline,
1459
- status
1460
- };
1461
- if (status === 'relaylatency') {
1462
- const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
1463
- const latencyMs = latencyValue ? Number(latencyValue) : undefined;
1464
- if (Number.isFinite(latencyMs)) {
1465
- call.latencyMs = latencyMs;
1466
- }
1467
- }
1468
- if (status === 'offer') {
1469
- call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
1470
- call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
1471
- call.groupJid = infoChild.attrs['group-jid'];
1472
- await callOfferCache.set(call.id, call);
1473
- }
1474
- const existingCall = await callOfferCache.get(call.id);
1475
- // use existing call info to populate this event
1476
- if (existingCall) {
1477
- call.isVideo = existingCall.isVideo;
1478
- call.isGroup = existingCall.isGroup;
1479
- call.callerPn = call.callerPn || existingCall.callerPn;
1480
- }
1481
- // delete data once call has ended
1482
- if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
1483
- await callOfferCache.del(call.id);
1484
- }
1485
- ev.emit('call', [call]);
822
+ const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
823
+ var _a;
824
+ if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
825
+ throw new boom_1.Boom('Not authenticated');
1486
826
  }
1487
- catch (error) {
1488
- logger.error({ error, node: binaryNodeToString(node) }, 'error in handling call');
827
+ const pdoMessage = {
828
+ historySyncOnDemandRequest: {
829
+ chatJid: oldestMsgKey.remoteJid,
830
+ oldestMsgFromMe: oldestMsgKey.fromMe,
831
+ oldestMsgId: oldestMsgKey.id,
832
+ oldestMsgTimestampMs: oldestMsgTimestamp,
833
+ onDemandMsgCount: count
834
+ },
835
+ peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
836
+ };
837
+ return sendPeerDataOperationMessage(pdoMessage);
838
+ };
839
+ const requestPlaceholderResend = async (messageKey) => {
840
+ var _a;
841
+ if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
842
+ throw new boom_1.Boom('Not authenticated');
1489
843
  }
1490
- finally {
1491
- await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack call'));
844
+ if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
845
+ logger.debug({ messageKey }, 'already requested resend');
846
+ return;
847
+ }
848
+ else {
849
+ placeholderResendCache.set(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id, true);
1492
850
  }
851
+ await (0, Utils_1.delay)(5000);
852
+ if (!placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
853
+ logger.debug({ messageKey }, 'message received while resend requested');
854
+ return 'RESOLVED';
855
+ }
856
+ const pdoMessage = {
857
+ placeholderMessageResendRequest: [{
858
+ messageKey
859
+ }],
860
+ peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
861
+ };
862
+ setTimeout(() => {
863
+ if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
864
+ logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
865
+ placeholderResendCache.del(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id);
866
+ }
867
+ }, 15000);
868
+ return sendPeerDataOperationMessage(pdoMessage);
869
+ };
870
+ const handleCall = async (node) => {
871
+ const { attrs } = node;
872
+ const [infoChild] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
873
+ const callId = infoChild.attrs['call-id'];
874
+ const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
875
+ const status = (0, Utils_1.getCallStatusFromNode)(infoChild);
876
+ const call = {
877
+ chatId: attrs.from,
878
+ from,
879
+ id: callId,
880
+ date: new Date(+attrs.t * 1000),
881
+ offline: !!attrs.offline,
882
+ status,
883
+ };
884
+ if (status === 'offer') {
885
+ call.isVideo = !!(0, WABinary_1.getBinaryNodeChild)(infoChild, 'video');
886
+ call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
887
+ call.groupJid = infoChild.attrs['group-jid'];
888
+ callOfferCache.set(call.id, call);
889
+ }
890
+ const existingCall = callOfferCache.get(call.id);
891
+ // use existing call info to populate this event
892
+ if (existingCall) {
893
+ call.isVideo = existingCall.isVideo;
894
+ call.isGroup = existingCall.isGroup;
895
+ }
896
+ // delete data once call has ended
897
+ if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
898
+ callOfferCache.del(call.id);
899
+ }
900
+ ev.emit('call', [call]);
901
+ await sendMessageAck(node);
1493
902
  };
1494
903
  const handleBadAck = async ({ attrs }) => {
1495
- const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
1496
- // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1497
- // // current hypothesis is that if pash is sent in the ack
1498
- // // it means -- the message hasn't reached all devices yet
1499
- // // we'll retry sending the message here
1500
- // if(attrs.phash) {
1501
- // logger.info({ attrs }, 'received phash in ack, resending message...')
1502
- // const msg = await getMessage(key)
1503
- // if(msg) {
1504
- // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1505
- // } else {
1506
- // logger.warn({ attrs }, 'could not send message again, as it was not found')
1507
- // }
1508
- // }
1509
- // error in acknowledgement,
1510
- // device could not display the message
1511
- if (attrs.error) {
1512
- const isReachoutTimelocked = attrs.error === String(NACK_REASONS.SenderReachoutTimelocked);
1513
- if (attrs.error === SERVER_ERROR_CODES.MessageAccountRestriction) {
1514
- // 463 = 1:1 message missing privacy token (tctoken). Usually means the
1515
- // account is restricted: WhatsApp blocks starting new chats but preserves
1516
- // existing ones, since established chats already carry a tctoken.
1517
- // WA Web prevents this client-side (disables the compose bar).
1518
- // No retry — retrying counts as another "reach out" and worsens the restriction.
1519
- logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
1520
- const ackFrom = attrs.from;
1521
- if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
1522
- inFlight463Recoveries.add(ackFrom);
1523
- void (async () => {
1524
- try {
1525
- const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
1526
- const tcStorageJid = await resolveTcTokenJid(ackFrom, getLIDForPN);
1527
- const issueJid = await resolveIssuanceJid(ackFrom, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
1528
- const result = await issuePrivacyTokens([issueJid], unixTimestampSeconds());
1529
- await storeTcTokensFromIqResult({
1530
- result,
1531
- fallbackJid: tcStorageJid,
1532
- keys: authState.keys,
1533
- getLIDForPN,
1534
- onNewJidStored: trackTcTokenJid
1535
- });
1536
- logger.debug({ from: ackFrom }, 'completed 463 token recovery issuance');
1537
- }
1538
- catch (err) {
1539
- logger.debug({ from: ackFrom, err: err?.message }, 'failed 463 token recovery issuance');
1540
- }
1541
- finally {
1542
- inFlight463Recoveries.delete(ackFrom);
1543
- }
1544
- })();
1545
- }
1546
- }
1547
- else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
1548
- logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
904
+ const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id, 'server_id': attrs === null || attrs === void 0 ? void 0 : attrs.server_id };
905
+ // current hypothesis is that if pash is sent in the ack
906
+ // it means -- the message hasn't reached all devices yet
907
+ // we'll retry sending the message here
908
+ if (attrs.phash) {
909
+ logger.info({ attrs }, 'received phash in ack, resending message...');
910
+ const cacheKey = `${key.remoteJid}:${key.id}`;
911
+ if ((msgRetryCache.get(cacheKey) || 0) >= maxMsgRetryCount) {
912
+ logger.warn({ attrs }, 'reached max retry count, not sending message again');
913
+ msgRetryCache.del(cacheKey);
914
+ return;
1549
915
  }
1550
- else if (isReachoutTimelocked) {
1551
- // user is temporarily restricted, fetch current restriction details
1552
- await fetchAccountReachoutTimelock().catch(err => logger.warn({ err }, 'failed to fetch reachout timelock'));
1553
- logger.warn({ attrs }, 'received error in ack');
916
+ const retryCount = msgRetryCache.get(cacheKey) || 0;
917
+ const msg = await getMessage(key);
918
+ if (msg) {
919
+ await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false });
920
+ msgRetryCache.set(cacheKey, retryCount + 1);
1554
921
  }
1555
922
  else {
1556
- logger.warn({ attrs }, 'received error in ack');
923
+ logger.warn({ attrs }, 'could not send message again, as it was not found');
1557
924
  }
925
+ }
926
+ // error in acknowledgement,
927
+ // device could not display the message
928
+ if (attrs.error) {
929
+ logger.warn({ attrs }, 'received error in ack');
1558
930
  ev.emit('messages.update', [
1559
931
  {
1560
932
  key,
1561
933
  update: {
1562
- status: WAMessageStatus.ERROR,
1563
- messageStubParameters: isReachoutTimelocked ? [attrs.error, ACCOUNT_RESTRICTED_TEXT] : [attrs.error]
934
+ status: Types_1.WAMessageStatus.ERROR,
935
+ messageStubParameters: [
936
+ attrs.error
937
+ ]
1564
938
  }
1565
939
  }
1566
940
  ]);
@@ -1573,61 +947,69 @@ export const makeMessagesRecvSocket = (config) => {
1573
947
  await execTask();
1574
948
  ev.flush();
1575
949
  function execTask() {
1576
- return exec(node, false).catch(err => onUnexpectedError(err, identifier));
950
+ return exec(node, false)
951
+ .catch(err => onUnexpectedError(err, identifier));
1577
952
  }
1578
953
  };
1579
- const offlineNodeProcessor = makeOfflineNodeProcessor(new Map([
1580
- ['message', handleMessage],
1581
- ['call', handleCall],
1582
- ['receipt', handleReceipt],
1583
- ['notification', handleNotification]
1584
- ]), {
1585
- isWsOpen: () => ws.isOpen,
1586
- onUnexpectedError,
1587
- yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
1588
- });
1589
- const processNode = async (type, node, identifier, exec) => {
1590
- // Fast path: ack and drop ignored JIDs before entering the buffer/queue
1591
- const from = node.attrs.from;
1592
- let ignoreJid = from;
1593
- if (type === 'receipt' && from) {
1594
- const attrs = node.attrs;
1595
- const isLid = attrs.from.includes('lid');
1596
- const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
1597
- ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
1598
- }
1599
- if (ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
1600
- await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
1601
- return;
1602
- }
954
+ const makeOfflineNodeProcessor = () => {
955
+ const nodeProcessorMap = new Map([
956
+ ['message', handleMessage],
957
+ ['call', handleCall],
958
+ ['receipt', handleReceipt],
959
+ ['notification', handleNotification]
960
+ ]);
961
+ const nodes = [];
962
+ let isProcessing = false;
963
+ const enqueue = (type, node) => {
964
+ nodes.push({ type, node });
965
+ if (isProcessing) {
966
+ return;
967
+ }
968
+ isProcessing = true;
969
+ const promise = async () => {
970
+ while (nodes.length && ws.isOpen) {
971
+ const { type, node } = nodes.shift();
972
+ const nodeProcessor = nodeProcessorMap.get(type);
973
+ if (!nodeProcessor) {
974
+ onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
975
+ continue;
976
+ }
977
+ await nodeProcessor(node);
978
+ }
979
+ isProcessing = false;
980
+ };
981
+ promise().catch(error => onUnexpectedError(error, 'processing offline nodes'));
982
+ };
983
+ return { enqueue };
984
+ };
985
+ const offlineNodeProcessor = makeOfflineNodeProcessor();
986
+ const processNode = (type, node, identifier, exec) => {
1603
987
  const isOffline = !!node.attrs.offline;
1604
988
  if (isOffline) {
1605
989
  offlineNodeProcessor.enqueue(type, node);
1606
990
  }
1607
991
  else {
1608
- await processNodeWithBuffer(node, identifier, exec);
992
+ processNodeWithBuffer(node, identifier, exec);
1609
993
  }
1610
994
  };
1611
995
  // recv a message
1612
- ws.on('CB:message', async (node) => {
1613
- await processNode('message', node, 'processing message', handleMessage);
996
+ ws.on('CB:message', (node) => {
997
+ processNode('message', node, 'processing message', handleMessage);
1614
998
  });
1615
999
  ws.on('CB:call', async (node) => {
1616
- await processNode('call', node, 'handling call', handleCall);
1000
+ processNode('call', node, 'handling call', handleCall);
1617
1001
  });
1618
- ws.on('CB:receipt', async (node) => {
1619
- await processNode('receipt', node, 'handling receipt', handleReceipt);
1002
+ ws.on('CB:receipt', node => {
1003
+ processNode('receipt', node, 'handling receipt', handleReceipt);
1620
1004
  });
1621
1005
  ws.on('CB:notification', async (node) => {
1622
- await processNode('notification', node, 'handling notification', handleNotification);
1006
+ processNode('notification', node, 'handling notification', handleNotification);
1623
1007
  });
1624
1008
  ws.on('CB:ack,class:message', (node) => {
1625
- handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
1009
+ handleBadAck(node)
1010
+ .catch(error => onUnexpectedError(error, 'handling bad ack'));
1626
1011
  });
1627
- ev.on('call', async ([call]) => {
1628
- if (!call) {
1629
- return;
1630
- }
1012
+ ev.on('call', ([call]) => {
1631
1013
  // missed call + group call notification message generation
1632
1014
  if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
1633
1015
  const msg = {
@@ -1636,137 +1018,37 @@ export const makeMessagesRecvSocket = (config) => {
1636
1018
  id: call.id,
1637
1019
  fromMe: false
1638
1020
  },
1639
- messageTimestamp: unixTimestampSeconds(call.date)
1021
+ messageTimestamp: (0, Utils_1.unixTimestampSeconds)(call.date),
1640
1022
  };
1641
1023
  if (call.status === 'timeout') {
1642
1024
  if (call.isGroup) {
1643
- msg.messageStubType = call.isVideo
1644
- ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
1645
- : WAMessageStubType.CALL_MISSED_GROUP_VOICE;
1025
+ msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_GROUP_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_GROUP_VOICE;
1646
1026
  }
1647
1027
  else {
1648
- msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE;
1028
+ msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_VOICE;
1649
1029
  }
1650
1030
  }
1651
1031
  else {
1652
1032
  msg.message = { call: { callKey: Buffer.from(call.id) } };
1653
1033
  }
1654
- const protoMsg = proto.WebMessageInfo.fromObject(msg);
1655
- await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1034
+ const protoMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
1035
+ upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1656
1036
  }
1657
1037
  });
1658
- /** timestamp of last tctoken prune run — throttles to once per 24h */
1659
- let lastTcTokenPruneTs = 0;
1660
- /** dedupe in-flight 463 recovery token issuance by target JID */
1661
- const inFlight463Recoveries = new Set();
1662
- ev.on('connection.update', ({ isOnline, connection }) => {
1038
+ ev.on('connection.update', ({ isOnline }) => {
1663
1039
  if (typeof isOnline !== 'undefined') {
1664
1040
  sendActiveReceipts = isOnline;
1665
1041
  logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
1666
1042
  }
1667
- // Flush pending tctoken index save on disconnect to avoid writing after close
1668
- if (connection === 'close' && tcTokenIndexTimer) {
1669
- clearTimeout(tcTokenIndexTimer);
1670
- tcTokenIndexTimer = undefined;
1671
- // Best-effort flush — may fail if store is already closed
1672
- try {
1673
- void Promise.resolve(flushTcTokenIndex()).catch(() => { });
1674
- }
1675
- catch {
1676
- /* ignore sync errors */
1677
- }
1678
- }
1679
- // Prune expired tctokens when coming online, at most once per 24 hours
1680
- // Matches WA Web's CLEAN_TC_TOKENS task
1681
- // Note: don't gate on tcTokenKnownJids.size — the index may still be loading
1682
- if (isOnline) {
1683
- const now = Date.now();
1684
- const DAY_MS = 24 * 60 * 60 * 1000;
1685
- if (now - lastTcTokenPruneTs >= DAY_MS) {
1686
- lastTcTokenPruneTs = now;
1687
- void pruneExpiredTcTokens();
1688
- }
1689
- }
1690
1043
  });
1691
- registerSocketEndHandler(() => {
1692
- if (!config.msgRetryCounterCache && msgRetryCache.close) {
1693
- msgRetryCache.close();
1694
- }
1695
- if (!config.callOfferCache && callOfferCache.close) {
1696
- callOfferCache.close();
1697
- }
1698
- identityAssertDebounce.close();
1699
- sendActiveReceipts = false;
1700
- });
1701
- async function pruneExpiredTcTokens() {
1702
- try {
1703
- await tcTokenIndexLoaded;
1704
- // Union with the persisted index picks up JIDs added by other layers
1705
- // (history sync) without needing inter-module wiring.
1706
- const persisted = await readTcTokenIndex(authState.keys);
1707
- const allJids = new Set(tcTokenKnownJids);
1708
- for (const jid of persisted)
1709
- allJids.add(jid);
1710
- if (!allJids.size)
1711
- return;
1712
- const jids = [...allJids];
1713
- const allTokens = await authState.keys.get('tctoken', jids);
1714
- const writes = {};
1715
- const survivors = new Set();
1716
- let mutated = 0;
1717
- for (const jid of jids) {
1718
- const entry = allTokens[jid];
1719
- if (!entry) {
1720
- // Tracked but nothing in store — drop from index.
1721
- mutated++;
1722
- continue;
1723
- }
1724
- const hasPeerToken = !!entry.token?.length;
1725
- const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
1726
- const hasSenderTs = entry.senderTimestamp !== undefined;
1727
- const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
1728
- const keepPeerToken = hasPeerToken && !peerTokenExpired;
1729
- const keepSenderTs = hasSenderTs && !senderTsExpired;
1730
- if (!keepPeerToken && !keepSenderTs) {
1731
- writes[jid] = null;
1732
- mutated++;
1733
- }
1734
- else if (peerTokenExpired && keepSenderTs) {
1735
- writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
1736
- survivors.add(jid);
1737
- mutated++;
1738
- }
1739
- else {
1740
- survivors.add(jid);
1741
- }
1742
- }
1743
- if (mutated === 0)
1744
- return;
1745
- await authState.keys.set({
1746
- tctoken: {
1747
- ...writes,
1748
- [TC_TOKEN_INDEX_KEY]: {
1749
- token: Buffer.from(JSON.stringify([...survivors]))
1750
- }
1751
- }
1752
- });
1753
- tcTokenKnownJids.clear();
1754
- for (const jid of survivors)
1755
- tcTokenKnownJids.add(jid);
1756
- logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
1757
- }
1758
- catch (err) {
1759
- logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
1760
- }
1761
- }
1762
1044
  return {
1763
1045
  ...sock,
1764
1046
  sendMessageAck,
1765
1047
  sendRetryRequest,
1766
1048
  rejectCall,
1049
+ offerCall,
1767
1050
  fetchMessageHistory,
1768
1051
  requestPlaceholderResend,
1769
- messageRetryManager
1770
1052
  };
1771
1053
  };
1772
- //# sourceMappingURL=messages-recv.js.map
1054
+ exports.makeMessagesRecvSocket = makeMessagesRecvSocket;