violetics 7.0.1-alpha → 7.0.2-alpha

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 (114) hide show
  1. package/LICENSE +3 -2
  2. package/README.md +1001 -232
  3. package/WAProto/index.js +75379 -142631
  4. package/engine-requirements.js +11 -8
  5. package/lib/Defaults/index.js +132 -146
  6. package/lib/Signal/Group/ciphertext-message.js +2 -6
  7. package/lib/Signal/Group/group-session-builder.js +7 -42
  8. package/lib/Signal/Group/group_cipher.js +37 -52
  9. package/lib/Signal/Group/index.js +11 -57
  10. package/lib/Signal/Group/keyhelper.js +7 -45
  11. package/lib/Signal/Group/sender-chain-key.js +7 -16
  12. package/lib/Signal/Group/sender-key-distribution-message.js +8 -12
  13. package/lib/Signal/Group/sender-key-message.js +9 -13
  14. package/lib/Signal/Group/sender-key-name.js +2 -6
  15. package/lib/Signal/Group/sender-key-record.js +9 -22
  16. package/lib/Signal/Group/sender-key-state.js +27 -43
  17. package/lib/Signal/Group/sender-message-key.js +4 -8
  18. package/lib/Signal/libsignal.js +319 -94
  19. package/lib/Signal/lid-mapping.js +224 -139
  20. package/lib/Socket/Client/index.js +2 -19
  21. package/lib/Socket/Client/types.js +10 -0
  22. package/lib/Socket/Client/websocket.js +53 -0
  23. package/lib/Socket/business.js +162 -44
  24. package/lib/Socket/chats.js +477 -418
  25. package/lib/Socket/communities.js +430 -0
  26. package/lib/Socket/groups.js +110 -99
  27. package/lib/Socket/index.js +10 -10
  28. package/lib/Socket/messages-recv.js +884 -561
  29. package/lib/Socket/messages-send.js +859 -428
  30. package/lib/Socket/mex.js +41 -0
  31. package/lib/Socket/newsletter.js +195 -390
  32. package/lib/Socket/socket.js +465 -315
  33. package/lib/Store/index.js +3 -10
  34. package/lib/Store/make-in-memory-store.js +73 -79
  35. package/lib/Store/make-ordered-dictionary.js +4 -7
  36. package/lib/Store/object-repository.js +2 -6
  37. package/lib/Types/Auth.js +1 -2
  38. package/lib/Types/Bussines.js +1 -0
  39. package/lib/Types/Call.js +1 -2
  40. package/lib/Types/Chat.js +7 -4
  41. package/lib/Types/Contact.js +1 -2
  42. package/lib/Types/Events.js +1 -2
  43. package/lib/Types/GroupMetadata.js +1 -2
  44. package/lib/Types/Label.js +2 -5
  45. package/lib/Types/LabelAssociation.js +2 -5
  46. package/lib/Types/Message.js +17 -9
  47. package/lib/Types/Newsletter.js +33 -38
  48. package/lib/Types/Product.js +1 -2
  49. package/lib/Types/Signal.js +1 -2
  50. package/lib/Types/Socket.js +2 -2
  51. package/lib/Types/State.js +12 -2
  52. package/lib/Types/USync.js +1 -2
  53. package/lib/Types/index.js +14 -31
  54. package/lib/Utils/auth-utils.js +228 -152
  55. package/lib/Utils/browser-utils.js +28 -0
  56. package/lib/Utils/business.js +66 -70
  57. package/lib/Utils/chat-utils.js +331 -249
  58. package/lib/Utils/crypto.js +57 -91
  59. package/lib/Utils/decode-wa-message.js +168 -84
  60. package/lib/Utils/event-buffer.js +138 -80
  61. package/lib/Utils/generics.js +180 -297
  62. package/lib/Utils/history.js +83 -49
  63. package/lib/Utils/identity-change-handler.js +48 -0
  64. package/lib/Utils/index.js +19 -33
  65. package/lib/Utils/link-preview.js +14 -23
  66. package/lib/Utils/logger.js +2 -7
  67. package/lib/Utils/lt-hash.js +2 -46
  68. package/lib/Utils/make-mutex.js +24 -47
  69. package/lib/Utils/message-retry-manager.js +224 -0
  70. package/lib/Utils/messages-media.js +501 -496
  71. package/lib/Utils/messages.js +1428 -362
  72. package/lib/Utils/noise-handler.js +145 -100
  73. package/lib/Utils/pre-key-manager.js +105 -0
  74. package/lib/Utils/process-message.js +356 -150
  75. package/lib/Utils/reporting-utils.js +257 -0
  76. package/lib/Utils/signal.js +78 -73
  77. package/lib/Utils/sync-action-utils.js +47 -0
  78. package/lib/Utils/tc-token-utils.js +17 -0
  79. package/lib/Utils/use-multi-file-auth-state.js +35 -45
  80. package/lib/Utils/validate-connection.js +91 -107
  81. package/lib/WABinary/constants.js +1300 -1304
  82. package/lib/WABinary/decode.js +26 -48
  83. package/lib/WABinary/encode.js +109 -155
  84. package/lib/WABinary/generic-utils.js +161 -149
  85. package/lib/WABinary/index.js +5 -21
  86. package/lib/WABinary/jid-utils.js +73 -40
  87. package/lib/WABinary/types.js +1 -2
  88. package/lib/WAM/BinaryInfo.js +2 -6
  89. package/lib/WAM/constants.js +19070 -11568
  90. package/lib/WAM/encode.js +17 -23
  91. package/lib/WAM/index.js +3 -19
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +8 -12
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -15
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -13
  95. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -14
  96. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -23
  97. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -9
  98. package/lib/WAUSync/Protocols/index.js +4 -20
  99. package/lib/WAUSync/USyncQuery.js +40 -36
  100. package/lib/WAUSync/USyncUser.js +2 -6
  101. package/lib/WAUSync/index.js +3 -19
  102. package/lib/index.js +11 -44
  103. package/package.json +74 -107
  104. package/lib/Defaults/baileys-version.json +0 -3
  105. package/lib/Defaults/phonenumber-mcc.json +0 -223
  106. package/lib/Signal/Group/queue-job.js +0 -57
  107. package/lib/Socket/Client/abstract-socket-client.js +0 -13
  108. package/lib/Socket/Client/mobile-socket-client.js +0 -65
  109. package/lib/Socket/Client/web-socket-client.js +0 -118
  110. package/lib/Socket/groupStatus.js +0 -637
  111. package/lib/Socket/registration.js +0 -166
  112. package/lib/Socket/usync.js +0 -70
  113. package/lib/Store/make-cache-manager-store.js +0 -83
  114. package/lib/Utils/baileys-event-stream.js +0 -63
@@ -1,64 +1,222 @@
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 {
20
- logger,
21
- retryRequestDelayMs,
22
- maxMsgRetryCount,
23
- getMessage,
24
- shouldIgnoreJid
25
- } = config;
26
- const sock = (0, messages_send_1.makeMessagesSocket)(config);
27
- const {
28
- ev,
29
- authState,
30
- ws,
31
- processingMutex,
32
- signalRepository,
33
- query,
34
- upsertMessage,
35
- resyncAppState,
36
- groupMetadata,
37
- onUnexpectedError,
38
- assertSessions,
39
- sendNode,
40
- relayMessage,
41
- sendReceipt,
42
- uploadPreKeys,
43
- createParticipantNodes,
44
- getUSyncDevices,
45
- sendPeerDataOperationMessage
46
- } = sock;
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 { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
+ import { makeMutex } from '../Utils/make-mutex.js';
10
+ import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
11
+ import { extractGroupMetadata } from './groups.js';
12
+ import { makeMessagesSocket } from './messages-send.js';
13
+ export const makeMessagesRecvSocket = (config) => {
14
+ const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
15
+ const sock = makeMessagesSocket(config);
16
+ const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, generateMessageTag, messageRetryManager } = sock;
47
17
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
48
- const retryMutex = (0, make_mutex_1.makeMutex)();
49
- const msgRetryCache = config.msgRetryCounterCache || new node_cache_1.default({
50
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
51
- useClones: false
52
- });
53
- const callOfferCache = config.callOfferCache || new node_cache_1.default({
54
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
55
- useClones: false
56
- });
57
- const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
58
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
59
- useClones: false
60
- });
18
+ const retryMutex = makeMutex();
19
+ const msgRetryCache = config.msgRetryCounterCache ||
20
+ new NodeCache({
21
+ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
22
+ useClones: false
23
+ });
24
+ const callOfferCache = config.callOfferCache ||
25
+ new NodeCache({
26
+ stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
27
+ useClones: false
28
+ });
29
+ const placeholderResendCache = config.placeholderResendCache ||
30
+ new NodeCache({
31
+ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
32
+ useClones: false
33
+ });
34
+ // Debounce identity-change session refreshes per JID to avoid bursts
35
+ const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
61
36
  let sendActiveReceipts = false;
37
+ const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
38
+ if (!authState.creds.me?.id) {
39
+ throw new Boom('Not authenticated');
40
+ }
41
+ const pdoMessage = {
42
+ historySyncOnDemandRequest: {
43
+ chatJid: oldestMsgKey.remoteJid,
44
+ oldestMsgFromMe: oldestMsgKey.fromMe,
45
+ oldestMsgId: oldestMsgKey.id,
46
+ oldestMsgTimestampMs: oldestMsgTimestamp,
47
+ onDemandMsgCount: count
48
+ },
49
+ peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
50
+ };
51
+ return sendPeerDataOperationMessage(pdoMessage);
52
+ };
53
+ const requestPlaceholderResend = async (messageKey, msgData) => {
54
+ if (!authState.creds.me?.id) {
55
+ throw new Boom('Not authenticated');
56
+ }
57
+ if (await placeholderResendCache.get(messageKey?.id)) {
58
+ logger.debug({ messageKey }, 'already requested resend');
59
+ return;
60
+ }
61
+ else {
62
+ // Store original message data so PDO response handler can preserve
63
+ // metadata (LID details, timestamps, etc.) that the phone may omit
64
+ await placeholderResendCache.set(messageKey?.id, msgData || true);
65
+ }
66
+ await delay(2000);
67
+ if (!(await placeholderResendCache.get(messageKey?.id))) {
68
+ logger.debug({ messageKey }, 'message received while resend requested');
69
+ return 'RESOLVED';
70
+ }
71
+ const pdoMessage = {
72
+ placeholderMessageResendRequest: [
73
+ {
74
+ messageKey
75
+ }
76
+ ],
77
+ peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
78
+ };
79
+ setTimeout(async () => {
80
+ if (await placeholderResendCache.get(messageKey?.id)) {
81
+ logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
82
+ await placeholderResendCache.del(messageKey?.id);
83
+ }
84
+ }, 8000);
85
+ return sendPeerDataOperationMessage(pdoMessage);
86
+ };
87
+ // Handles mex newsletter notifications
88
+ const handleMexNewsletterNotification = async (node) => {
89
+ const mexNode = getBinaryNodeChild(node, 'mex');
90
+ if (!mexNode?.content) {
91
+ logger.warn({ node }, 'Invalid mex newsletter notification');
92
+ return;
93
+ }
94
+ let data;
95
+ try {
96
+ data = JSON.parse(mexNode.content.toString());
97
+ }
98
+ catch (error) {
99
+ logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
100
+ return;
101
+ }
102
+ const operation = data?.operation;
103
+ const updates = data?.updates;
104
+ if (!updates || !operation) {
105
+ logger.warn({ data }, 'Invalid mex newsletter notification content');
106
+ return;
107
+ }
108
+ logger.info({ operation, updates }, 'got mex newsletter notification');
109
+ switch (operation) {
110
+ case 'NotificationNewsletterUpdate':
111
+ for (const update of updates) {
112
+ if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
113
+ ev.emit('newsletter-settings.update', {
114
+ id: update.jid,
115
+ update: update.settings
116
+ });
117
+ }
118
+ }
119
+ break;
120
+ case 'NotificationNewsletterAdminPromote':
121
+ for (const update of updates) {
122
+ if (update.jid && update.user) {
123
+ ev.emit('newsletter-participants.update', {
124
+ id: update.jid,
125
+ author: node.attrs.from,
126
+ user: update.user,
127
+ new_role: 'ADMIN',
128
+ action: 'promote'
129
+ });
130
+ }
131
+ }
132
+ break;
133
+ default:
134
+ logger.info({ operation, data }, 'Unhandled mex newsletter notification');
135
+ break;
136
+ }
137
+ };
138
+ // Handles newsletter notifications
139
+ const handleNewsletterNotification = async (node) => {
140
+ const from = node.attrs.from;
141
+ const child = getAllBinaryNodeChildren(node)[0];
142
+ const author = node.attrs.participant;
143
+ logger.info({ from, child }, 'got newsletter notification');
144
+ switch (child.tag) {
145
+ case 'reaction':
146
+ const reactionUpdate = {
147
+ id: from,
148
+ server_id: child.attrs.message_id,
149
+ reaction: {
150
+ code: getBinaryNodeChildString(child, 'reaction'),
151
+ count: 1
152
+ }
153
+ };
154
+ ev.emit('newsletter.reaction', reactionUpdate);
155
+ break;
156
+ case 'view':
157
+ const viewUpdate = {
158
+ id: from,
159
+ server_id: child.attrs.message_id,
160
+ count: parseInt(child.content?.toString() || '0', 10)
161
+ };
162
+ ev.emit('newsletter.view', viewUpdate);
163
+ break;
164
+ case 'participant':
165
+ const participantUpdate = {
166
+ id: from,
167
+ author,
168
+ user: child.attrs.jid,
169
+ action: child.attrs.action,
170
+ new_role: child.attrs.role
171
+ };
172
+ ev.emit('newsletter-participants.update', participantUpdate);
173
+ break;
174
+ case 'update':
175
+ const settingsNode = getBinaryNodeChild(child, 'settings');
176
+ if (settingsNode) {
177
+ const update = {};
178
+ const nameNode = getBinaryNodeChild(settingsNode, 'name');
179
+ if (nameNode?.content)
180
+ update.name = nameNode.content.toString();
181
+ const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
182
+ if (descriptionNode?.content)
183
+ update.description = descriptionNode.content.toString();
184
+ ev.emit('newsletter-settings.update', {
185
+ id: from,
186
+ update
187
+ });
188
+ }
189
+ break;
190
+ case 'message':
191
+ const plaintextNode = getBinaryNodeChild(child, 'plaintext');
192
+ if (plaintextNode?.content) {
193
+ try {
194
+ const contentBuf = typeof plaintextNode.content === 'string'
195
+ ? Buffer.from(plaintextNode.content, 'binary')
196
+ : Buffer.from(plaintextNode.content);
197
+ const messageProto = proto.Message.decode(contentBuf).toJSON();
198
+ const fullMessage = proto.WebMessageInfo.fromObject({
199
+ key: {
200
+ remoteJid: from,
201
+ id: child.attrs.message_id || child.attrs.server_id,
202
+ fromMe: false // TODO: is this really true though
203
+ },
204
+ message: messageProto,
205
+ messageTimestamp: +child.attrs.t
206
+ }).toJSON();
207
+ await upsertMessage(fullMessage, 'append');
208
+ logger.info('Processed plaintext newsletter message');
209
+ }
210
+ catch (error) {
211
+ logger.error({ error }, 'Failed to decode plaintext newsletter message');
212
+ }
213
+ }
214
+ break;
215
+ default:
216
+ logger.warn({ node }, 'Unknown newsletter notification');
217
+ break;
218
+ }
219
+ };
62
220
  const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
63
221
  const stanza = {
64
222
  tag: 'ack',
@@ -67,7 +225,7 @@ const makeMessagesRecvSocket = (config) => {
67
225
  to: attrs.from,
68
226
  class: tag
69
227
  }
70
- }
228
+ };
71
229
  if (!!errorCode) {
72
230
  stanza.attrs.error = errorCode.toString();
73
231
  }
@@ -77,145 +235,204 @@ const makeMessagesRecvSocket = (config) => {
77
235
  if (!!attrs.recipient) {
78
236
  stanza.attrs.recipient = attrs.recipient;
79
237
  }
80
- if (!!attrs.type && (tag !== 'message' || (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
238
+ if (!!attrs.type &&
239
+ (tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
81
240
  stanza.attrs.type = attrs.type;
82
241
  }
83
- if (tag === 'message' && (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable')) {
242
+ if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
84
243
  stanza.attrs.from = authState.creds.me.id;
85
244
  }
86
- logger.debug({
87
- recv: {
88
- tag,
89
- attrs
90
- },
91
- sent: stanza.attrs
92
- }, 'sent ack');
245
+ logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
93
246
  await sendNode(stanza);
94
247
  };
95
- const offerCall = async (toJid, isVideo = false) => {
96
- const callId = (0, crypto_1.randomBytes)(16).toString('hex').toUpperCase().substring(0, 64);
97
- const offerContent = [];
98
- offerContent.push({
99
- tag: 'audio',
100
- attrs: {
101
- enc: 'opus',
102
- rate: '16000'
103
- }, content: undefined
104
- });
105
- offerContent.push({
106
- tag: 'audio',
248
+ const rejectCall = async (callId, callFrom) => {
249
+ const stanza = {
250
+ tag: 'call',
107
251
  attrs: {
108
- enc: 'opus',
109
- rate: '8000'
110
- }, content: undefined
111
- });
112
- if (isVideo) {
113
- offerContent.push({
114
- tag: 'video',
115
- attrs: {
116
- orientation: '0',
117
- 'screen_width': '1920',
118
- 'screen_height': '1080',
119
- 'device_orientation': '0',
120
- enc: 'vp8',
121
- dec: 'vp8',
252
+ from: authState.creds.me.id,
253
+ to: callFrom
254
+ },
255
+ content: [
256
+ {
257
+ tag: 'reject',
258
+ attrs: {
259
+ 'call-id': callId,
260
+ 'call-creator': callFrom,
261
+ count: '0'
262
+ },
263
+ content: undefined
122
264
  }
123
- });
124
- }
125
- offerContent.push({
126
- tag: 'net',
127
- attrs: {
128
- medium: '3'
129
- }, content: undefined
130
- });
131
- offerContent.push({
132
- tag: 'capability',
133
- attrs: {
134
- ver: '1'
135
- }, content: new Uint8Array([1, 4, 255, 131, 207, 4])
136
- });
137
- offerContent.push({
138
- tag: 'encopt',
139
- attrs: {
140
- keygen: '2'
141
- }, content: undefined
142
- })
143
- const encKey = (0, crypto_1.randomBytes)(32);
144
- const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => (0, WABinary_1.jidEncode)(user, 's.whatsapp.net', device));
145
- await assertSessions(devices, true);
146
- const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
147
- call: {
148
- callKey: encKey
149
- }
150
- });
151
- offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
152
- if (shouldIncludeDeviceIdentity) {
153
- offerContent.push({
154
- tag: 'device-identity',
155
- attrs: {},
156
- content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
157
- });
265
+ ]
266
+ };
267
+ await query(stanza);
268
+ };
269
+ // Lia@Note 01-03-26 --- Source: https://github.com/koptereli/Baileys/commit/575cd41e6f01a9b3e1d7e2708c2292fa93de91f2
270
+ const initiateCall = async (jid, options = {}) => {
271
+ const meId = authState.creds.me?.id;
272
+ if (!meId) {
273
+ throw new Boom('Not authenticated');
158
274
  }
159
- const stanza = ({
275
+ const callId = randomBytes(8).toString('hex');
276
+ const isVideo = !!options.isVideo;
277
+ const isGroup = isJidGroup(jid);
278
+ const stanza = {
160
279
  tag: 'call',
161
280
  attrs: {
162
- to: toJid,
281
+ id: generateMessageTag(),
282
+ from: meId,
283
+ to: jid,
284
+ t: String(unixTimestampSeconds()),
285
+ ...(authState.creds.me?.name ? { notify: authState.creds.me.name } : {})
163
286
  },
164
- content: [{
165
- tag: 'offer',
166
- attrs: {
167
- 'call-id': callId,
168
- 'call-creator': authState.creds.me.id,
169
- },
170
- content: offerContent,
171
- }],
172
- });
287
+ content: [
288
+ {
289
+ tag: 'offer',
290
+ attrs: {
291
+ 'call-id': callId,
292
+ 'call-creator': meId,
293
+ count: '0'
294
+ },
295
+ content: [
296
+ {
297
+ tag: isVideo ? 'video' : 'audio',
298
+ attrs: {}
299
+ },
300
+ {
301
+ tag: 'net',
302
+ attrs: {}
303
+ },
304
+ {
305
+ tag: 'encopt',
306
+ attrs: { key: randomBytes(2).toString('hex') }
307
+ },
308
+ {
309
+ tag: 'relaylatency',
310
+ attrs: {}
311
+ },
312
+ {
313
+ tag: 'te',
314
+ attrs: {}
315
+ }
316
+ ]
317
+ }
318
+ ]
319
+ };
173
320
  await query(stanza);
174
- return {
175
- callId,
176
- toJid,
321
+ await callOfferCache.set(callId, {
322
+ chatId: jid,
323
+ from: meId,
324
+ id: callId,
325
+ date: new Date(),
326
+ offline: false,
327
+ status: 'offer',
177
328
  isVideo,
178
- };
329
+ isGroup,
330
+ groupJid: isGroup ? jid : undefined
331
+ });
332
+ // TODO: implement ICE/DTLS-SRTP call media setup once full signaling requirements are mapped.
333
+ return { callId, to: jid, isVideo };
179
334
  };
180
- const rejectCall = async (callId, callFrom) => {
181
- const stanza = ({
335
+ const cancelCall = async (callId, callTo) => {
336
+ const meId = authState.creds.me?.id;
337
+ if (!meId) {
338
+ throw new Boom('Not authenticated');
339
+ }
340
+ const stanza = {
182
341
  tag: 'call',
183
342
  attrs: {
184
- from: authState.creds.me.id,
185
- to: callFrom,
343
+ from: meId,
344
+ to: callTo
186
345
  },
187
- content: [{
188
- tag: 'reject',
189
- attrs: {
190
- 'call-id': callId,
191
- 'call-creator': callFrom,
192
- count: '0',
193
- },
194
- content: undefined,
195
- }],
196
- });
346
+ content: [
347
+ {
348
+ tag: 'terminate',
349
+ attrs: {
350
+ 'call-id': callId,
351
+ 'call-creator': meId,
352
+ count: '0'
353
+ }
354
+ }
355
+ ]
356
+ };
197
357
  await query(stanza);
358
+ await callOfferCache.del(callId);
198
359
  };
199
360
  const sendRetryRequest = async (node, forceIncludeKeys = false) => {
200
- const { fullMessage } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
361
+ const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
201
362
  const { key: msgKey } = fullMessage;
202
363
  const msgId = msgKey.id;
203
- const key = `${msgId}:${msgKey === null || msgKey === void 0 ? void 0 : msgKey.participant}`;
204
- let retryCount = msgRetryCache.get(key) || 0;
205
- if (retryCount >= maxMsgRetryCount) {
206
- logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
207
- msgRetryCache.del(key);
208
- return;
364
+ if (messageRetryManager) {
365
+ // Check if we've exceeded max retries using the new system
366
+ if (messageRetryManager.hasExceededMaxRetries(msgId)) {
367
+ logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
368
+ messageRetryManager.markRetryFailed(msgId);
369
+ return;
370
+ }
371
+ // Increment retry count using new system
372
+ const retryCount = messageRetryManager.incrementRetryCount(msgId);
373
+ // Use the new retry count for the rest of the logic
374
+ const key = `${msgId}:${msgKey?.participant}`;
375
+ await msgRetryCache.set(key, retryCount);
376
+ }
377
+ else {
378
+ // Fallback to old system
379
+ const key = `${msgId}:${msgKey?.participant}`;
380
+ let retryCount = (await msgRetryCache.get(key)) || 0;
381
+ if (retryCount >= maxMsgRetryCount) {
382
+ logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
383
+ await msgRetryCache.del(key);
384
+ return;
385
+ }
386
+ retryCount += 1;
387
+ await msgRetryCache.set(key, retryCount);
209
388
  }
210
- retryCount += 1;
211
- msgRetryCache.set(key, retryCount);
389
+ const key = `${msgId}:${msgKey?.participant}`;
390
+ const retryCount = (await msgRetryCache.get(key)) || 1;
212
391
  const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
213
- if (retryCount === 1) {
214
- //request a resend via phone
215
- const msgId = await requestPlaceholderResend(msgKey);
216
- logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
392
+ const fromJid = node.attrs.from;
393
+ // Check if we should recreate the session
394
+ let shouldRecreateSession = false;
395
+ let recreateReason = '';
396
+ if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
397
+ try {
398
+ // Check if we have a session with this JID
399
+ const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
400
+ const hasSession = await signalRepository.validateSession(fromJid);
401
+ const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
402
+ shouldRecreateSession = result.recreate;
403
+ recreateReason = result.reason;
404
+ if (shouldRecreateSession) {
405
+ logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
406
+ // Delete existing session to force recreation
407
+ await authState.keys.set({ session: { [sessionId]: null } });
408
+ forceIncludeKeys = true;
409
+ }
410
+ }
411
+ catch (error) {
412
+ logger.warn({ error, fromJid }, 'failed to check session recreation');
413
+ }
414
+ }
415
+ if (retryCount <= 2) {
416
+ // Use new retry manager for phone requests if available
417
+ if (messageRetryManager) {
418
+ // Schedule phone request with delay (like whatsmeow)
419
+ messageRetryManager.schedulePhoneRequest(msgId, async () => {
420
+ try {
421
+ const requestId = await requestPlaceholderResend(msgKey);
422
+ logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
423
+ }
424
+ catch (error) {
425
+ logger.warn({ error, msgId }, 'failed to send scheduled phone request');
426
+ }
427
+ });
428
+ }
429
+ else {
430
+ // Fallback to immediate request
431
+ const msgId = await requestPlaceholderResend(msgKey);
432
+ logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
433
+ }
217
434
  }
218
- const deviceIdentity = (0, Utils_1.encodeSignedDeviceIdentity)(account, true);
435
+ const deviceIdentity = encodeSignedDeviceIdentity(account, true);
219
436
  await authState.keys.transaction(async () => {
220
437
  const receipt = {
221
438
  tag: 'receipt',
@@ -231,13 +448,15 @@ const makeMessagesRecvSocket = (config) => {
231
448
  count: retryCount.toString(),
232
449
  id: node.attrs.id,
233
450
  t: node.attrs.t,
234
- v: '1'
451
+ v: '1',
452
+ // ADD ERROR FIELD
453
+ error: '0'
235
454
  }
236
455
  },
237
456
  {
238
457
  tag: 'registration',
239
458
  attrs: {},
240
- content: (0, Utils_1.encodeBigEndian)(authState.creds.registrationId)
459
+ content: encodeBigEndian(authState.creds.registrationId)
241
460
  }
242
461
  ]
243
462
  };
@@ -247,8 +466,8 @@ const makeMessagesRecvSocket = (config) => {
247
466
  if (node.attrs.participant) {
248
467
  receipt.attrs.participant = node.attrs.participant;
249
468
  }
250
- if (retryCount > 1 || forceIncludeKeys) {
251
- const { update, preKeys } = await (0, Utils_1.getNextPreKeys)(authState, 1);
469
+ if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
470
+ const { update, preKeys } = await getNextPreKeys(authState, 1);
252
471
  const [keyId] = Object.keys(preKeys);
253
472
  const key = preKeys[+keyId];
254
473
  const content = receipt.content;
@@ -256,10 +475,10 @@ const makeMessagesRecvSocket = (config) => {
256
475
  tag: 'keys',
257
476
  attrs: {},
258
477
  content: [
259
- { tag: 'type', attrs: {}, content: Buffer.from(Defaults_1.KEY_BUNDLE_TYPE) },
478
+ { tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
260
479
  { tag: 'identity', attrs: {}, content: identityKey.public },
261
- (0, Utils_1.xmppPreKey)(key, +keyId),
262
- (0, Utils_1.xmppSignedPreKey)(signedPreKey),
480
+ xmppPreKey(key, +keyId),
481
+ xmppSignedPreKey(signedPreKey),
263
482
  { tag: 'device-identity', attrs: {}, content: deviceIdentity }
264
483
  ]
265
484
  });
@@ -267,63 +486,73 @@ const makeMessagesRecvSocket = (config) => {
267
486
  }
268
487
  await sendNode(receipt);
269
488
  logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
270
- });
489
+ }, authState?.creds?.me?.id || 'sendRetryRequest');
271
490
  };
272
491
  const handleEncryptNotification = async (node) => {
273
492
  const from = node.attrs.from;
274
- if (from === WABinary_1.S_WHATSAPP_NET) {
275
- const countChild = (0, WABinary_1.getBinaryNodeChild)(node, 'count');
493
+ if (from === S_WHATSAPP_NET) {
494
+ const countChild = getBinaryNodeChild(node, 'count');
276
495
  const count = +countChild.attrs.value;
277
- const shouldUploadMorePreKeys = count < Defaults_1.MIN_PREKEY_COUNT;
496
+ const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
278
497
  logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
279
498
  if (shouldUploadMorePreKeys) {
280
499
  await uploadPreKeys();
281
500
  }
282
501
  }
283
502
  else {
284
- const identityNode = (0, WABinary_1.getBinaryNodeChild)(node, 'identity');
285
- if (identityNode) {
286
- logger.info({ jid: from }, 'identity changed');
287
- // not handling right now
288
- // signal will override new identity anyway
289
- }
290
- else {
503
+ const result = await handleIdentityChange(node, {
504
+ meId: authState.creds.me?.id,
505
+ meLid: authState.creds.me?.lid,
506
+ validateSession: signalRepository.validateSession,
507
+ assertSessions,
508
+ debounceCache: identityAssertDebounce,
509
+ logger
510
+ });
511
+ if (result.action === 'no_identity_node') {
291
512
  logger.info({ node }, 'unknown encrypt notification');
292
513
  }
293
514
  }
294
515
  };
295
- const handleGroupNotification = (participant, child, msg) => {
296
- var _a, _b, _c, _d;
297
- 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;
298
- switch (child === null || child === void 0 ? void 0 : child.tag) {
516
+ const handleGroupNotification = (fullNode, child, msg) => {
517
+ // TODO: Support PN/LID (Here is only LID now)
518
+ const actingParticipantLid = fullNode.attrs.participant;
519
+ const actingParticipantPn = fullNode.attrs.participant_pn;
520
+ const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
521
+ const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
522
+ switch (child?.tag) {
299
523
  case 'create':
300
- const metadata = (0, groups_1.extractGroupMetadata)(child);
301
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE;
524
+ const metadata = extractGroupMetadata(child);
525
+ msg.messageStubType = WAMessageStubType.GROUP_CREATE;
302
526
  msg.messageStubParameters = [metadata.subject];
303
- msg.key = { participant: metadata.owner };
304
- ev.emit('chats.upsert', [{
305
- id: metadata.id,
306
- name: metadata.subject,
307
- conversationTimestamp: metadata.creation,
308
- }]);
309
- ev.emit('groups.upsert', [{
310
- ...metadata,
311
- author: participant
312
- }]);
527
+ msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
528
+ ev.emit('chats.upsert', [
529
+ {
530
+ id: metadata.id,
531
+ name: metadata.subject,
532
+ conversationTimestamp: metadata.creation
533
+ }
534
+ ]);
535
+ ev.emit('groups.upsert', [
536
+ {
537
+ ...metadata,
538
+ author: actingParticipantLid,
539
+ authorPn: actingParticipantPn
540
+ }
541
+ ]);
313
542
  break;
314
543
  case 'ephemeral':
315
544
  case 'not_ephemeral':
316
545
  msg.message = {
317
546
  protocolMessage: {
318
- type: WAProto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
547
+ type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
319
548
  ephemeralExpiration: +(child.attrs.expiration || 0)
320
549
  }
321
550
  };
322
551
  break;
323
552
  case 'modify':
324
- const oldNumber = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
553
+ const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid);
325
554
  msg.messageStubParameters = oldNumber || [];
326
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
555
+ msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
327
556
  break;
328
557
  case 'promote':
329
558
  case 'demote':
@@ -331,181 +560,141 @@ const makeMessagesRecvSocket = (config) => {
331
560
  case 'add':
332
561
  case 'leave':
333
562
  const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
334
- msg.messageStubType = Types_1.WAMessageStubType[stubType];
335
- const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
563
+ msg.messageStubType = WAMessageStubType[stubType];
564
+ const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
565
+ // TODO: Store LID MAPPINGS
566
+ return {
567
+ id: attrs.jid,
568
+ phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
569
+ lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
570
+ admin: (attrs.type || null)
571
+ };
572
+ });
336
573
  if (participants.length === 1 &&
337
574
  // if recv. "remove" message and sender removed themselves
338
575
  // mark as left
339
- (0, WABinary_1.areJidsSameUser)(participants[0], participant) &&
576
+ (areJidsSameUser(participants[0].id, actingParticipantLid) ||
577
+ areJidsSameUser(participants[0].id, actingParticipantPn)) &&
340
578
  child.tag === 'remove') {
341
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
579
+ msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
342
580
  }
343
- msg.messageStubParameters = participants;
581
+ msg.messageStubParameters = participants.map(a => JSON.stringify(a));
344
582
  break;
345
583
  case 'subject':
346
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_SUBJECT;
584
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
347
585
  msg.messageStubParameters = [child.attrs.subject];
348
586
  break;
349
587
  case 'description':
350
- 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();
351
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
588
+ const description = getBinaryNodeChild(child, 'body')?.content?.toString();
589
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
352
590
  msg.messageStubParameters = description ? [description] : undefined;
353
591
  break;
354
592
  case 'announcement':
355
593
  case 'not_announcement':
356
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
357
- msg.messageStubParameters = [(child.tag === 'announcement') ? 'on' : 'off'];
594
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
595
+ msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
358
596
  break;
359
597
  case 'locked':
360
598
  case 'unlocked':
361
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_RESTRICT;
362
- msg.messageStubParameters = [(child.tag === 'locked') ? 'on' : 'off'];
599
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT;
600
+ msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
363
601
  break;
364
602
  case 'invite':
365
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
603
+ msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
366
604
  msg.messageStubParameters = [child.attrs.code];
367
605
  break;
368
606
  case 'member_add_mode':
369
607
  const addMode = child.content;
370
608
  if (addMode) {
371
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBER_ADD_MODE;
609
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE;
372
610
  msg.messageStubParameters = [addMode.toString()];
373
611
  }
374
612
  break;
375
613
  case 'membership_approval_mode':
376
- const approvalMode = (0, WABinary_1.getBinaryNodeChild)(child, 'group_join');
614
+ const approvalMode = getBinaryNodeChild(child, 'group_join');
377
615
  if (approvalMode) {
378
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
616
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
379
617
  msg.messageStubParameters = [approvalMode.attrs.state];
380
618
  }
381
619
  break;
382
620
  case 'created_membership_requests':
383
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
384
- msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method];
621
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
622
+ msg.messageStubParameters = [
623
+ JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
624
+ 'created',
625
+ child.attrs.request_method
626
+ ];
385
627
  break;
386
628
  case 'revoked_membership_requests':
387
- const isDenied = (0, WABinary_1.areJidsSameUser)(participantJid, participant);
388
- msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
389
- msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'];
390
- break;
629
+ const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
630
+ // TODO: LIDMAPPING SUPPORT
631
+ msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
632
+ msg.messageStubParameters = [
633
+ JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
634
+ isDenied ? 'revoked' : 'rejected'
635
+ ];
391
636
  break;
392
- default:
393
- // console.log("BAILEYS-DEBUG:", JSON.stringify({ ...child, content: Buffer.isBuffer(child.content) ? child.content.toString() : child.content, participant }, null, 2))
394
- }
395
- };
396
- const handleNewsletterNotification = (id, node) => {
397
- const messages = (0, WABinary_1.getBinaryNodeChild)(node, 'messages');
398
- const message = (0, WABinary_1.getBinaryNodeChild)(messages, 'message');
399
- const serverId = message.attrs.server_id;
400
- const reactionsList = (0, WABinary_1.getBinaryNodeChild)(message, 'reactions');
401
- const viewsList = (0, WABinary_1.getBinaryNodeChildren)(message, 'views_count');
402
- if (reactionsList) {
403
- const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsList, 'reaction');
404
- if (reactions.length === 0) {
405
- ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { removed: true } });
406
- }
407
- reactions.forEach(item => {
408
- var _a, _b;
409
- 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) } });
410
- });
411
- }
412
- if (viewsList.length) {
413
- viewsList.forEach(item => {
414
- ev.emit('newsletter.view', { id, 'server_id': serverId, count: +item.attrs.count });
415
- });
416
- }
417
- };
418
- const handleMexNewsletterNotification = (id, node) => {
419
- var _a;
420
- const operation = node === null || node === void 0 ? void 0 : node.attrs.op_name;
421
- const content = JSON.parse((_a = node === null || node === void 0 ? void 0 : node.content) === null || _a === void 0 ? void 0 : _a.toString());
422
- let contentPath;
423
- if (operation === Types_1.MexOperations.PROMOTE || operation === Types_1.MexOperations.DEMOTE) {
424
- let action;
425
- if (operation === Types_1.MexOperations.PROMOTE) {
426
- action = 'promote';
427
- contentPath = content.data[Types_1.XWAPaths.PROMOTE];
428
- }
429
- if (operation === Types_1.MexOperations.DEMOTE) {
430
- action = 'demote';
431
- contentPath = content.data[Types_1.XWAPaths.DEMOTE];
432
- }
433
- ev.emit('newsletter-participants.update', { id, author: contentPath.actor.pn, user: contentPath.user.pn, new_role: contentPath.user_new_role, action });
434
- }
435
- if (operation === Types_1.MexOperations.UPDATE) {
436
- contentPath = content.data[Types_1.XWAPaths.METADATA_UPDATE];
437
- ev.emit('newsletter-settings.update', { id, update: contentPath.thread_metadata.settings });
438
637
  }
439
638
  };
440
639
  const processNotification = async (node) => {
441
- var _a, _b;
442
640
  const result = {};
443
- const [child] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
641
+ const [child] = getAllBinaryNodeChildren(node);
444
642
  const nodeType = node.attrs.type;
445
- const from = (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
643
+ const from = jidNormalizedUser(node.attrs.from);
446
644
  switch (nodeType) {
447
- case 'privacy_token':
448
- const tokenList = (0, WABinary_1.getBinaryNodeChildren)(child, 'token');
449
- for (const { attrs, content } of tokenList) {
450
- const jid = attrs.jid;
451
- ev.emit('chats.update', [
452
- {
453
- id: jid,
454
- tcToken: content
455
- }
456
- ]);
457
- logger.debug({ jid }, 'got privacy token update');
458
- }
459
- break;
460
645
  case 'newsletter':
461
- handleNewsletterNotification(node.attrs.from, child);
646
+ await handleNewsletterNotification(node);
462
647
  break;
463
648
  case 'mex':
464
- handleMexNewsletterNotification(node.attrs.from, child);
649
+ await handleMexNewsletterNotification(node);
465
650
  break;
466
651
  case 'w:gp2':
467
- handleGroupNotification(node.attrs.participant, child, result);
652
+ // TODO: HANDLE PARTICIPANT_PN
653
+ handleGroupNotification(node, child, result);
468
654
  break;
469
655
  case 'mediaretry':
470
- const event = (0, Utils_1.decodeMediaRetryNode)(node);
656
+ const event = decodeMediaRetryNode(node);
471
657
  ev.emit('messages.media-update', [event]);
472
658
  break;
473
659
  case 'encrypt':
474
660
  await handleEncryptNotification(node);
475
661
  break;
476
662
  case 'devices':
477
- const devices = (0, WABinary_1.getBinaryNodeChildren)(child, 'device');
478
- if ((0, WABinary_1.areJidsSameUser)(child.attrs.jid, authState.creds.me.id)) {
479
- const deviceJids = devices.map(d => d.attrs.jid);
480
- logger.info({ deviceJids }, 'got my own devices');
663
+ const devices = getBinaryNodeChildren(child, 'device');
664
+ if (areJidsSameUser(child.attrs.jid, authState.creds.me.id) ||
665
+ areJidsSameUser(child.attrs.lid, authState.creds.me.lid)) {
666
+ const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }));
667
+ logger.info({ deviceData }, 'my own devices changed');
481
668
  }
669
+ //TODO: drop a new event, add hashes
482
670
  break;
483
671
  case 'server_sync':
484
- const update = (0, WABinary_1.getBinaryNodeChild)(node, 'collection');
672
+ const update = getBinaryNodeChild(node, 'collection');
485
673
  if (update) {
486
674
  const name = update.attrs.name;
487
- // Fire in background — do NOT await inside processingMutex
488
- // Sync runs concurrently with real-time message delivery
489
- resyncAppState([name], false).catch(e => logger.warn({ e, name }, 'background server_sync resync failed'));
675
+ await resyncAppState([name], false);
490
676
  }
491
677
  break;
492
678
  case 'picture':
493
- const setPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'set');
494
- const delPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'delete');
495
- ev.emit('contacts.update', [{
496
- id: from || ((_b = (_a = (setPicture || delPicture)) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.hash) || '',
497
- imgUrl: setPicture ? 'changed' : 'removed'
498
- }]);
499
- if ((0, WABinary_1.isJidGroup)(from)) {
679
+ const setPicture = getBinaryNodeChild(node, 'set');
680
+ const delPicture = getBinaryNodeChild(node, 'delete');
681
+ // TODO: WAJIDHASH stuff proper support inhouse
682
+ ev.emit('contacts.update', [
683
+ {
684
+ id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
685
+ imgUrl: setPicture ? 'changed' : 'removed'
686
+ }
687
+ ]);
688
+ if (isJidGroup(from)) {
500
689
  const node = setPicture || delPicture;
501
- result.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ICON;
690
+ result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
502
691
  if (setPicture) {
503
692
  result.messageStubParameters = [setPicture.attrs.id];
504
693
  }
505
- result.participant = node === null || node === void 0 ? void 0 : node.attrs.author;
694
+ result.participant = node?.attrs.author;
506
695
  result.key = {
507
- ...result.key || {},
508
- participant: setPicture === null || setPicture === void 0 ? void 0 : setPicture.attrs.author
696
+ ...(result.key || {}),
697
+ participant: setPicture?.attrs.author
509
698
  };
510
699
  }
511
700
  break;
@@ -519,44 +708,48 @@ const makeMessagesRecvSocket = (config) => {
519
708
  ...authState.creds.accountSettings,
520
709
  defaultDisappearingMode: {
521
710
  ephemeralExpiration: newDuration,
522
- ephemeralSettingTimestamp: timestamp,
523
- },
711
+ ephemeralSettingTimestamp: timestamp
712
+ }
524
713
  }
525
714
  });
526
715
  }
527
716
  else if (child.tag === 'blocklist') {
528
- const blocklists = (0, WABinary_1.getBinaryNodeChildren)(child, 'item');
717
+ const blocklists = getBinaryNodeChildren(child, 'item');
529
718
  for (const { attrs } of blocklists) {
530
719
  const blocklist = [attrs.jid];
531
- const type = (attrs.action === 'block') ? 'add' : 'remove';
720
+ const type = attrs.action === 'block' ? 'add' : 'remove';
532
721
  ev.emit('blocklist.update', { blocklist, type });
533
722
  }
534
723
  }
535
724
  break;
536
725
  case 'link_code_companion_reg':
537
- const linkCodeCompanionReg = (0, WABinary_1.getBinaryNodeChild)(node, 'link_code_companion_reg');
538
- const ref = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_ref'));
539
- const primaryIdentityPublicKey = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'primary_identity_pub'));
540
- const primaryEphemeralPublicKeyWrapped = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
726
+ const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg');
727
+ const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'));
728
+ const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'));
729
+ const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
541
730
  const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
542
- const companionSharedKey = Utils_1.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
543
- const random = (0, crypto_1.randomBytes)(32);
544
- const linkCodeSalt = (0, crypto_1.randomBytes)(32);
545
- const linkCodePairingExpanded = await (0, Utils_1.hkdf)(companionSharedKey, 32, {
731
+ const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
732
+ const random = randomBytes(32);
733
+ const linkCodeSalt = randomBytes(32);
734
+ const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
546
735
  salt: linkCodeSalt,
547
736
  info: 'link_code_pairing_key_bundle_encryption_key'
548
737
  });
549
- const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random]);
550
- const encryptIv = (0, crypto_1.randomBytes)(12);
551
- const encrypted = (0, Utils_1.aesEncryptGCM)(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
738
+ const encryptPayload = Buffer.concat([
739
+ Buffer.from(authState.creds.signedIdentityKey.public),
740
+ primaryIdentityPublicKey,
741
+ random
742
+ ]);
743
+ const encryptIv = randomBytes(12);
744
+ const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
552
745
  const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
553
- const identitySharedKey = Utils_1.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
746
+ const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
554
747
  const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
555
- authState.creds.advSecretKey = (await (0, Utils_1.hkdf)(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
748
+ authState.creds.advSecretKey = Buffer.from(hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
556
749
  await query({
557
750
  tag: 'iq',
558
751
  attrs: {
559
- to: WABinary_1.S_WHATSAPP_NET,
752
+ to: S_WHATSAPP_NET,
560
753
  type: 'set',
561
754
  id: sock.generateMessageTag(),
562
755
  xmlns: 'md'
@@ -566,7 +759,7 @@ const makeMessagesRecvSocket = (config) => {
566
759
  tag: 'link_code_companion_reg',
567
760
  attrs: {
568
761
  jid: authState.creds.me.id,
569
- stage: 'companion_finish',
762
+ stage: 'companion_finish'
570
763
  },
571
764
  content: [
572
765
  {
@@ -590,53 +783,125 @@ const makeMessagesRecvSocket = (config) => {
590
783
  });
591
784
  authState.creds.registered = true;
592
785
  ev.emit('creds.update', authState.creds);
786
+ break;
787
+ case 'privacy_token':
788
+ await handlePrivacyTokenNotification(node);
789
+ break;
593
790
  }
594
791
  if (Object.keys(result).length) {
595
792
  return result;
596
793
  }
597
794
  };
795
+ const handlePrivacyTokenNotification = async (node) => {
796
+ const tokensNode = getBinaryNodeChild(node, 'tokens');
797
+ const from = jidNormalizedUser(node.attrs.from);
798
+ if (!tokensNode)
799
+ return;
800
+ const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
801
+ for (const tokenNode of tokenNodes) {
802
+ const { attrs, content } = tokenNode;
803
+ const type = attrs.type;
804
+ const timestamp = attrs.t;
805
+ if (type === 'trusted_contact' && content instanceof Buffer) {
806
+ logger.debug({
807
+ from,
808
+ timestamp,
809
+ tcToken: content
810
+ }, 'received trusted contact token');
811
+ await authState.keys.set({
812
+ tctoken: { [from]: { token: content, timestamp } }
813
+ });
814
+ }
815
+ }
816
+ };
598
817
  async function decipherLinkPublicKey(data) {
599
818
  const buffer = toRequiredBuffer(data);
600
819
  const salt = buffer.slice(0, 32);
601
- const secretKey = await (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
820
+ const secretKey = await derivePairingCodeKey(authState.creds.pairingCode, salt);
602
821
  const iv = buffer.slice(32, 48);
603
822
  const payload = buffer.slice(48, 80);
604
- return (0, Utils_1.aesDecryptCTR)(payload, secretKey, iv);
823
+ return aesDecryptCTR(payload, secretKey, iv);
605
824
  }
606
825
  function toRequiredBuffer(data) {
607
826
  if (data === undefined) {
608
- throw new boom_1.Boom('Invalid buffer', { statusCode: 400 });
827
+ throw new Boom('Invalid buffer', { statusCode: 400 });
609
828
  }
610
829
  return data instanceof Buffer ? data : Buffer.from(data);
611
830
  }
612
- const willSendMessageAgain = (id, participant) => {
831
+ const willSendMessageAgain = async (id, participant) => {
613
832
  const key = `${id}:${participant}`;
614
- const retryCount = msgRetryCache.get(key) || 0;
833
+ const retryCount = (await msgRetryCache.get(key)) || 0;
615
834
  return retryCount < maxMsgRetryCount;
616
835
  };
617
- const updateSendMessageAgainCount = (id, participant) => {
836
+ const updateSendMessageAgainCount = async (id, participant) => {
618
837
  const key = `${id}:${participant}`;
619
- const newValue = (msgRetryCache.get(key) || 0) + 1;
620
- msgRetryCache.set(key, newValue);
838
+ const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
839
+ await msgRetryCache.set(key, newValue);
621
840
  };
622
841
  const sendMessagesAgain = async (key, ids, retryNode) => {
623
- var _a;
624
- // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
625
- const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
626
842
  const remoteJid = key.remoteJid;
627
843
  const participant = key.participant || remoteJid;
844
+ const retryCount = +retryNode.attrs.count || 1;
845
+ // Try to get messages from cache first, then fallback to getMessage
846
+ const msgs = [];
847
+ for (const id of ids) {
848
+ let msg;
849
+ // Try to get from retry cache first if enabled
850
+ if (messageRetryManager) {
851
+ const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
852
+ if (cachedMsg) {
853
+ msg = cachedMsg.message;
854
+ logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
855
+ // Mark retry as successful since we found the message
856
+ messageRetryManager.markRetrySuccess(id);
857
+ }
858
+ }
859
+ // Fallback to getMessage if not found in cache
860
+ if (!msg) {
861
+ msg = await getMessage({ ...key, id });
862
+ if (msg) {
863
+ logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
864
+ // Also mark as successful if found via getMessage
865
+ if (messageRetryManager) {
866
+ messageRetryManager.markRetrySuccess(id);
867
+ }
868
+ }
869
+ }
870
+ msgs.push(msg);
871
+ }
628
872
  // if it's the primary jid sending the request
629
873
  // just re-send the message to everyone
630
874
  // prevents the first message decryption failure
631
- const sendToAll = !((_a = (0, WABinary_1.jidDecode)(participant)) === null || _a === void 0 ? void 0 : _a.device);
875
+ const sendToAll = !jidDecode(participant)?.device;
876
+ // Check if we should recreate session for this retry
877
+ let shouldRecreateSession = false;
878
+ let recreateReason = '';
879
+ if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
880
+ try {
881
+ const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
882
+ const hasSession = await signalRepository.validateSession(participant);
883
+ const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
884
+ shouldRecreateSession = result.recreate;
885
+ recreateReason = result.reason;
886
+ if (shouldRecreateSession) {
887
+ logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
888
+ await authState.keys.set({ session: { [sessionId]: null } });
889
+ }
890
+ }
891
+ catch (error) {
892
+ logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
893
+ }
894
+ }
632
895
  await assertSessions([participant], true);
633
- if ((0, WABinary_1.isJidGroup)(remoteJid)) {
896
+ if (isJidGroup(remoteJid)) {
634
897
  await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
635
898
  }
636
- logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
899
+ logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
637
900
  for (const [i, msg] of msgs.entries()) {
638
- if (msg) {
639
- updateSendMessageAgainCount(ids[i], participant);
901
+ if (!ids[i])
902
+ continue;
903
+ if (msg && (await willSendMessageAgain(ids[i], participant))) {
904
+ await updateSendMessageAgainCount(ids[i], participant);
640
905
  const msgRelayOpts = { messageId: ids[i] };
641
906
  if (sendToAll) {
642
907
  msgRelayOpts.useUserDevicesCache = false;
@@ -655,11 +920,10 @@ const makeMessagesRecvSocket = (config) => {
655
920
  }
656
921
  };
657
922
  const handleReceipt = async (node) => {
658
- var _a, _b;
659
923
  const { attrs, content } = node;
660
924
  const isLid = attrs.from.includes('lid');
661
- 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);
662
- const remoteJid = !isNodeFromMe || (0, WABinary_1.isJidGroup)(attrs.from) ? attrs.from : attrs.recipient;
925
+ const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
926
+ const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
663
927
  const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
664
928
  const key = {
665
929
  remoteJid,
@@ -667,33 +931,31 @@ const makeMessagesRecvSocket = (config) => {
667
931
  fromMe,
668
932
  participant: attrs.participant
669
933
  };
670
- if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
934
+ if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
671
935
  logger.debug({ remoteJid }, 'ignoring receipt from jid');
672
936
  await sendMessageAck(node);
673
937
  return;
674
938
  }
675
939
  const ids = [attrs.id];
676
940
  if (Array.isArray(content)) {
677
- const items = (0, WABinary_1.getBinaryNodeChildren)(content[0], 'item');
941
+ const items = getBinaryNodeChildren(content[0], 'item');
678
942
  ids.push(...items.map(i => i.attrs.id));
679
943
  }
680
944
  try {
681
945
  await Promise.all([
682
- processingMutex.mutex(async () => {
683
- const status = (0, Utils_1.getStatusFromReceiptType)(attrs.type);
946
+ receiptMutex.mutex(async () => {
947
+ const status = getStatusFromReceiptType(attrs.type);
684
948
  if (typeof status !== 'undefined' &&
685
- (
686
- // basically, we only want to know when a message from us has been delivered to/read by the other person
687
- // or another device of ours has read some messages
688
- status >= WAProto_1.proto.WebMessageInfo.Status.SERVER_ACK ||
689
- !isNodeFromMe)) {
690
- if ((0, WABinary_1.isJidGroup)(remoteJid) || (0, WABinary_1.isJidStatusBroadcast)(remoteJid)) {
949
+ // basically, we only want to know when a message from us has been delivered to/read by the other person
950
+ // or another device of ours has read some messages
951
+ (status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
952
+ if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
691
953
  if (attrs.participant) {
692
- const updateKey = status === WAProto_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
954
+ const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
693
955
  ev.emit('message-receipt.update', ids.map(id => ({
694
956
  key: { ...key, id },
695
957
  receipt: {
696
- userJid: (0, WABinary_1.jidNormalizedUser)(attrs.participant),
958
+ userJid: jidNormalizedUser(attrs.participant),
697
959
  [updateKey]: +attrs.t
698
960
  }
699
961
  })));
@@ -702,22 +964,23 @@ const makeMessagesRecvSocket = (config) => {
702
964
  else {
703
965
  ev.emit('messages.update', ids.map(id => ({
704
966
  key: { ...key, id },
705
- update: { status }
967
+ update: { status, messageTimestamp: toNumber(+(attrs.t ?? 0)) }
706
968
  })));
707
969
  }
708
970
  }
709
971
  if (attrs.type === 'retry') {
710
972
  // correctly set who is asking for the retry
711
973
  key.participant = key.participant || attrs.from;
712
- const retryNode = (0, WABinary_1.getBinaryNodeChild)(node, 'retry');
713
- if (willSendMessageAgain(ids[0], key.participant)) {
974
+ const retryNode = getBinaryNodeChild(node, 'retry');
975
+ if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
714
976
  if (key.fromMe) {
715
977
  try {
978
+ await updateSendMessageAgainCount(ids[0], key.participant);
716
979
  logger.debug({ attrs, key }, 'recv retry request');
717
980
  await sendMessagesAgain(key, ids, retryNode);
718
981
  }
719
982
  catch (error) {
720
- logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
983
+ logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
721
984
  }
722
985
  }
723
986
  else {
@@ -737,28 +1000,30 @@ const makeMessagesRecvSocket = (config) => {
737
1000
  };
738
1001
  const handleNotification = async (node) => {
739
1002
  const remoteJid = node.attrs.from;
740
- if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
1003
+ if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
741
1004
  logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
742
1005
  await sendMessageAck(node);
743
1006
  return;
744
1007
  }
745
1008
  try {
746
1009
  await Promise.all([
747
- processingMutex.mutex(async () => {
748
- var _a;
1010
+ notificationMutex.mutex(async () => {
749
1011
  const msg = await processNotification(node);
750
1012
  if (msg) {
751
- const fromMe = (0, WABinary_1.areJidsSameUser)(node.attrs.participant || remoteJid, authState.creds.me.id);
1013
+ const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
1014
+ const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
752
1015
  msg.key = {
753
1016
  remoteJid,
754
1017
  fromMe,
755
1018
  participant: node.attrs.participant,
1019
+ participantAlt,
1020
+ addressingMode,
756
1021
  id: node.attrs.id,
757
1022
  ...(msg.key || {})
758
1023
  };
759
- (_a = msg.participant) !== null && _a !== void 0 ? _a : (msg.participant = node.attrs.participant);
1024
+ msg.participant ?? (msg.participant = node.attrs.participant);
760
1025
  msg.messageTimestamp = +node.attrs.t;
761
- const fullMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
1026
+ const fullMsg = proto.WebMessageInfo.fromObject(msg);
762
1027
  await upsertMessage(fullMsg, 'append');
763
1028
  }
764
1029
  })
@@ -769,222 +1034,254 @@ const makeMessagesRecvSocket = (config) => {
769
1034
  }
770
1035
  };
771
1036
  const handleMessage = async (node) => {
772
- var _a, _b, _c;
773
- if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
1037
+ if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== S_WHATSAPP_NET) {
774
1038
  logger.debug({ key: node.attrs.key }, 'ignored message');
775
- await sendMessageAck(node);
1039
+ await sendMessageAck(node, NACK_REASONS.UnhandledError);
776
1040
  return;
777
1041
  }
778
- const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
1042
+ const encNode = getBinaryNodeChild(node, 'enc');
779
1043
  // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
780
- if (encNode && encNode.attrs.type === 'msmsg') {
1044
+ if (encNode?.attrs.type === 'msmsg') {
781
1045
  logger.debug({ key: node.attrs.key }, 'ignored msmsg');
782
- await sendMessageAck(node);
1046
+ await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
783
1047
  return;
784
1048
  }
785
- let response;
786
- if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable') && !encNode) {
787
- await sendMessageAck(node);
788
- const { key } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage;
789
- response = await requestPlaceholderResend(key);
790
- if (response === 'RESOLVED') {
791
- return;
1049
+ const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
1050
+ const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
1051
+ // store new mappings we didn't have before
1052
+ if (!!alt) {
1053
+ const altServer = jidDecode(alt)?.server;
1054
+ const primaryJid = msg.key.participant || msg.key.remoteJid;
1055
+ if (altServer === 'lid') {
1056
+ if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
1057
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
1058
+ await signalRepository.migrateSession(primaryJid, alt);
1059
+ }
792
1060
  }
793
- logger.debug('received unavailable message, acked and requested resend from phone');
794
- }
795
- else {
796
- if (placeholderResendCache.get(node.attrs.id)) {
797
- placeholderResendCache.del(node.attrs.id);
1061
+ else {
1062
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
1063
+ await signalRepository.migrateSession(alt, primaryJid);
798
1064
  }
799
1065
  }
800
- const { fullMessage: msg, category, author, decrypt } = (0, Utils_1.decryptMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
801
- 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) {
802
- msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response];
803
- }
804
- 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) {
805
- ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
1066
+ if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
1067
+ messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
1068
+ logger.debug({
1069
+ jid: msg.key.remoteJid,
1070
+ id: msg.key.id
1071
+ }, 'Added message to recent cache for retry receipts');
806
1072
  }
807
1073
  try {
808
- await Promise.all([
809
- processingMutex.mutex(async () => {
810
- var _a, _b, _c, _d, _e, _f;
811
- await decrypt();
812
- // message failed to decrypt
813
- if (msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT) {
814
- 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) {
815
- return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError);
1074
+ await messageMutex.mutex(async () => {
1075
+ await decrypt();
1076
+ // message failed to decrypt
1077
+ if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
1078
+ if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
1079
+ return sendMessageAck(node, NACK_REASONS.ParsingError);
1080
+ }
1081
+ if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
1082
+ // Message arrived without encryption (e.g. CTWA ads messages).
1083
+ // Check if this is eligible for placeholder resend (matching WA Web filters).
1084
+ const unavailableNode = getBinaryNodeChild(node, 'unavailable');
1085
+ const unavailableType = unavailableNode?.attrs?.type;
1086
+ if (unavailableType === 'bot_unavailable_fanout' ||
1087
+ unavailableType === 'hosted_unavailable_fanout' ||
1088
+ unavailableType === 'view_once_unavailable_fanout') {
1089
+ logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
1090
+ return sendMessageAck(node);
816
1091
  }
817
- retryMutex.mutex(async () => {
818
- if (ws.isOpen) {
819
- if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable')) {
1092
+ const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1093
+ if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
1094
+ logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
1095
+ return sendMessageAck(node);
1096
+ }
1097
+ // Request the real content from the phone via placeholder resend PDO.
1098
+ // Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
1099
+ // and store the requestId in stubParameters[1] so users can correlate
1100
+ // with the incoming PDO response event.
1101
+ const cleanKey = {
1102
+ remoteJid: msg.key.remoteJid,
1103
+ fromMe: msg.key.fromMe,
1104
+ id: msg.key.id,
1105
+ participant: msg.key.participant
1106
+ };
1107
+ // Cache the original message metadata so the PDO response handler
1108
+ // can preserve key fields (LID details etc.) that the phone may omit
1109
+ const msgData = {
1110
+ key: msg.key,
1111
+ messageTimestamp: msg.messageTimestamp,
1112
+ pushName: msg.pushName,
1113
+ participant: msg.participant,
1114
+ verifiedBizName: msg.verifiedBizName
1115
+ };
1116
+ requestPlaceholderResend(cleanKey, msgData)
1117
+ .then(requestId => {
1118
+ if (requestId && requestId !== 'RESOLVED') {
1119
+ logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
1120
+ ev.emit('messages.update', [
1121
+ {
1122
+ key: msg.key,
1123
+ update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
1124
+ }
1125
+ ]);
1126
+ }
1127
+ })
1128
+ .catch(err => {
1129
+ logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
1130
+ });
1131
+ await sendMessageAck(node);
1132
+ // Don't return — fall through to upsertMessage so the stub is emitted
1133
+ }
1134
+ else {
1135
+ // Skip retry for expired status messages (>24h old)
1136
+ if (isJidStatusBroadcast(msg.key.remoteJid)) {
1137
+ const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
1138
+ if (messageAge > STATUS_EXPIRY_SECONDS) {
1139
+ logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
1140
+ return sendMessageAck(node);
1141
+ }
1142
+ }
1143
+ const errorMessage = msg?.messageStubParameters?.[0] || '';
1144
+ const isPreKeyError = errorMessage.includes('PreKey');
1145
+ logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
1146
+ // Handle both pre-key and normal retries in single mutex
1147
+ await retryMutex.mutex(async () => {
1148
+ try {
1149
+ if (!ws.isOpen) {
1150
+ logger.debug({ node }, 'Connection closed, skipping retry');
820
1151
  return;
821
1152
  }
822
- const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
1153
+ // Handle pre-key errors with upload and delay
1154
+ if (isPreKeyError) {
1155
+ logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
1156
+ try {
1157
+ logger.debug('Uploading pre-keys for error recovery');
1158
+ await uploadPreKeys(5);
1159
+ logger.debug('Waiting for server to process new pre-keys');
1160
+ await delay(1000);
1161
+ }
1162
+ catch (uploadErr) {
1163
+ logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
1164
+ }
1165
+ }
1166
+ const encNode = getBinaryNodeChild(node, 'enc');
823
1167
  await sendRetryRequest(node, !encNode);
824
1168
  if (retryRequestDelayMs) {
825
- await (0, Utils_1.delay)(retryRequestDelayMs);
1169
+ await delay(retryRequestDelayMs);
826
1170
  }
827
1171
  }
828
- else {
829
- logger.debug({ node }, 'connection closed, ignoring retry req');
1172
+ catch (err) {
1173
+ logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
1174
+ // Still attempt retry even if pre-key upload failed
1175
+ try {
1176
+ const encNode = getBinaryNodeChild(node, 'enc');
1177
+ await sendRetryRequest(node, !encNode);
1178
+ }
1179
+ catch (retryErr) {
1180
+ logger.error({ retryErr }, 'Failed to send retry after error handling');
1181
+ }
830
1182
  }
1183
+ await sendMessageAck(node, NACK_REASONS.UnhandledError);
831
1184
  });
832
1185
  }
833
- else {
1186
+ }
1187
+ else {
1188
+ if (messageRetryManager && msg.key.id) {
1189
+ messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
1190
+ }
1191
+ const isNewsletter = isJidNewsletter(msg.key.remoteJid);
1192
+ if (!isNewsletter) {
834
1193
  // no type in the receipt => message delivered
835
1194
  let type = undefined;
836
- if ((_b = msg.key.participant) === null || _b === void 0 ? void 0 : _b.endsWith('@lid')) {
837
- msg.key.participant = node.attrs.participant_pn || authState.creds.me.id;
838
- }
839
- 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'))) {
840
- if (msg.message.extendedTextMessage.contextInfo) {
841
- const metadata = await groupMetadata(msg.key.remoteJid);
842
- const sender = msg.message.extendedTextMessage.contextInfo.participant;
843
- const found = metadata.participants.find(p => p.id === sender);
844
- msg.message.extendedTextMessage.contextInfo.participant = (found === null || found === void 0 ? void 0 : found.jid) || sender;
845
- }
846
- }
847
- if (!(0, WABinary_1.isJidGroup)(msg.key.remoteJid) && (0, WABinary_1.isLidUser)(msg.key.remoteJid)) {
848
- msg.key.remoteJid = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
849
- }
850
1195
  let participant = msg.key.participant;
851
- if (category === 'peer') { // special peer message
1196
+ if (category === 'peer') {
1197
+ // special peer message
852
1198
  type = 'peer_msg';
853
1199
  }
854
- else if (msg.key.fromMe) { // message was sent by us from a different device
1200
+ else if (msg.key.fromMe) {
1201
+ // message was sent by us from a different device
855
1202
  type = 'sender';
856
1203
  // need to specially handle this case
857
- if ((0, WABinary_1.isJidUser)(msg.key.remoteJid)) {
858
- participant = author;
1204
+ if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
1205
+ participant = author; // TODO: investigate sending receipts to LIDs and not PNs
859
1206
  }
860
1207
  }
861
1208
  else if (!sendActiveReceipts) {
862
1209
  type = 'inactive';
863
1210
  }
864
- // Run receipt + message upsert in parallel — bot processes message immediately
865
- // without waiting for WA server to ack the receipt first
866
- const isAnyHistoryMsg = (0, Utils_1.getHistoryMsg)(msg.message);
867
- await Promise.all([
868
- sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type),
869
- isAnyHistoryMsg
870
- ? sendReceipt((0, WABinary_1.jidNormalizedUser)(msg.key.remoteJid), undefined, [msg.key.id], 'hist_sync')
871
- : Promise.resolve()
872
- ]);
1211
+ await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
1212
+ // send ack for history message
1213
+ const isAnyHistoryMsg = getHistoryMsg(msg.message);
1214
+ if (isAnyHistoryMsg) {
1215
+ const jid = jidNormalizedUser(msg.key.remoteJid);
1216
+ await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
1217
+ }
873
1218
  }
874
- (0, Utils_1.cleanMessage)(msg, authState.creds.me.id);
875
- await sendMessageAck(node);
876
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
877
- })
878
- ]);
1219
+ else {
1220
+ await sendMessageAck(node);
1221
+ logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
1222
+ }
1223
+ }
1224
+ cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
1225
+ await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
1226
+ });
879
1227
  }
880
1228
  catch (error) {
881
- logger.error({ error, node }, 'error in handling message');
1229
+ logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
882
1230
  }
883
1231
  };
884
- const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
885
- var _a;
886
- if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
887
- throw new boom_1.Boom('Not authenticated');
888
- }
889
- const pdoMessage = {
890
- historySyncOnDemandRequest: {
891
- chatJid: oldestMsgKey.remoteJid,
892
- oldestMsgFromMe: oldestMsgKey.fromMe,
893
- oldestMsgId: oldestMsgKey.id,
894
- oldestMsgTimestampMs: oldestMsgTimestamp,
895
- onDemandMsgCount: count
896
- },
897
- peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
898
- };
899
- return sendPeerDataOperationMessage(pdoMessage);
900
- };
901
- const requestPlaceholderResend = async (messageKey) => {
902
- var _a;
903
- if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
904
- throw new boom_1.Boom('Not authenticated');
905
- }
906
- if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
907
- logger.debug({ messageKey }, 'already requested resend');
908
- return;
909
- }
910
- else {
911
- placeholderResendCache.set(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id, true);
912
- }
913
- await (0, Utils_1.delay)(800); // reduced from 5000ms — wait briefly for message to arrive before requesting resend
914
- if (!placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
915
- logger.debug({ messageKey }, 'message received while resend requested');
916
- return 'RESOLVED';
917
- }
918
- const pdoMessage = {
919
- placeholderMessageResendRequest: [{
920
- messageKey
921
- }],
922
- peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
923
- };
924
- setTimeout(() => {
925
- if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
926
- logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
927
- placeholderResendCache.del(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id);
928
- }
929
- }, 15000);
930
- return sendPeerDataOperationMessage(pdoMessage);
931
- };
932
1232
  const handleCall = async (node) => {
933
1233
  const { attrs } = node;
934
- const [infoChild] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
1234
+ const [infoChild] = getAllBinaryNodeChildren(node);
1235
+ const status = getCallStatusFromNode(infoChild);
1236
+ if (!infoChild) {
1237
+ throw new Boom('Missing call info in call node');
1238
+ }
935
1239
  const callId = infoChild.attrs['call-id'];
936
1240
  const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
937
- const status = (0, Utils_1.getCallStatusFromNode)(infoChild);
938
1241
  const call = {
939
1242
  chatId: attrs.from,
940
1243
  from,
1244
+ callerPn: infoChild.attrs['caller_pn'],
941
1245
  id: callId,
942
1246
  date: new Date(+attrs.t * 1000),
943
1247
  offline: !!attrs.offline,
944
- status,
1248
+ status
945
1249
  };
946
1250
  if (status === 'offer') {
947
- call.isVideo = !!(0, WABinary_1.getBinaryNodeChild)(infoChild, 'video');
1251
+ call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
948
1252
  call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
949
1253
  call.groupJid = infoChild.attrs['group-jid'];
950
- callOfferCache.set(call.id, call);
1254
+ await callOfferCache.set(call.id, call);
951
1255
  }
952
- const existingCall = callOfferCache.get(call.id);
1256
+ const existingCall = await callOfferCache.get(call.id);
953
1257
  // use existing call info to populate this event
954
1258
  if (existingCall) {
955
1259
  call.isVideo = existingCall.isVideo;
956
1260
  call.isGroup = existingCall.isGroup;
1261
+ call.callerPn = call.callerPn || existingCall.callerPn;
957
1262
  }
958
1263
  // delete data once call has ended
959
1264
  if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
960
- callOfferCache.del(call.id);
1265
+ await callOfferCache.del(call.id);
961
1266
  }
962
1267
  ev.emit('call', [call]);
963
1268
  await sendMessageAck(node);
964
1269
  };
965
1270
  const handleBadAck = async ({ attrs }) => {
966
- const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id, 'server_id': attrs === null || attrs === void 0 ? void 0 : attrs.server_id };
967
- // current hypothesis is that if pash is sent in the ack
968
- // it means -- the message hasn't reached all devices yet
969
- // we'll retry sending the message here
970
- if (attrs.phash) {
971
- logger.info({ attrs }, 'received phash in ack, resending message...');
972
- const cacheKey = `${key.remoteJid}:${key.id}`;
973
- if ((msgRetryCache.get(cacheKey) || 0) >= maxMsgRetryCount) {
974
- logger.warn({ attrs }, 'reached max retry count, not sending message again');
975
- msgRetryCache.del(cacheKey);
976
- return;
977
- }
978
- const retryCount = msgRetryCache.get(cacheKey) || 0;
979
- const msg = await getMessage(key);
980
- if (msg) {
981
- await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false });
982
- msgRetryCache.set(cacheKey, retryCount + 1);
983
- }
984
- else {
985
- logger.warn({ attrs }, 'could not send message again, as it was not found');
986
- }
987
- }
1271
+ const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
1272
+ // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1273
+ // // current hypothesis is that if pash is sent in the ack
1274
+ // // it means -- the message hasn't reached all devices yet
1275
+ // // we'll retry sending the message here
1276
+ // if(attrs.phash) {
1277
+ // logger.info({ attrs }, 'received phash in ack, resending message...')
1278
+ // const msg = await getMessage(key)
1279
+ // if(msg) {
1280
+ // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1281
+ // } else {
1282
+ // logger.warn({ attrs }, 'could not send message again, as it was not found')
1283
+ // }
1284
+ // }
988
1285
  // error in acknowledgement,
989
1286
  // device could not display the message
990
1287
  if (attrs.error) {
@@ -993,24 +1290,39 @@ const makeMessagesRecvSocket = (config) => {
993
1290
  {
994
1291
  key,
995
1292
  update: {
996
- status: Types_1.WAMessageStatus.ERROR,
997
- messageStubParameters: [
998
- attrs.error
999
- ]
1293
+ status: WAMessageStatus.ERROR,
1294
+ messageStubParameters: [attrs.error]
1000
1295
  }
1001
1296
  }
1002
1297
  ]);
1298
+ // resend the message with device_fanout=false, use at your own risk
1299
+ // if (attrs.error === '475') {
1300
+ // const msg = await getMessage(key)
1301
+ // if (msg) {
1302
+ // await relayMessage(key.remoteJid!, msg, {
1303
+ // messageId: key.id!,
1304
+ // useUserDevicesCache: false,
1305
+ // additionalAttributes: {
1306
+ // device_fanout: 'false'
1307
+ // }
1308
+ // })
1309
+ // }
1310
+ // }
1003
1311
  }
1004
1312
  };
1005
1313
  /// processes a node with the given function
1006
1314
  /// and adds the task to the existing buffer if we're buffering events
1007
- /// Uses createBufferedFunction to be nested-buffer-safe: won't underflow
1008
- /// the buffersInProgress counter that the offline phase is holding open
1009
1315
  const processNodeWithBuffer = async (node, identifier, exec) => {
1010
- const runBuffered = ev.createBufferedFunction(() =>
1011
- exec(node, false).catch(err => onUnexpectedError(err, identifier))
1012
- );
1013
- await runBuffered();
1316
+ ev.buffer();
1317
+ await execTask();
1318
+ ev.flush();
1319
+ function execTask() {
1320
+ return exec(node, false).catch(err => onUnexpectedError(err, identifier));
1321
+ }
1322
+ };
1323
+ /** Yields control to the event loop to prevent blocking */
1324
+ const yieldToEventLoop = () => {
1325
+ return new Promise(resolve => setImmediate(resolve));
1014
1326
  };
1015
1327
  const makeOfflineNodeProcessor = () => {
1016
1328
  const nodeProcessorMap = new Map([
@@ -1021,6 +1333,8 @@ const makeMessagesRecvSocket = (config) => {
1021
1333
  ]);
1022
1334
  const nodes = [];
1023
1335
  let isProcessing = false;
1336
+ // Number of nodes to process before yielding to event loop
1337
+ const BATCH_SIZE = 10;
1024
1338
  const enqueue = (type, node) => {
1025
1339
  nodes.push({ type, node });
1026
1340
  if (isProcessing) {
@@ -1028,11 +1342,8 @@ const makeMessagesRecvSocket = (config) => {
1028
1342
  }
1029
1343
  isProcessing = true;
1030
1344
  const promise = async () => {
1031
- // NOTE: intentionally NOT checking ws.isOpen here.
1032
- // Nodes are already in memory — we must process them all to completion.
1033
- // Individual handlers (handleMessage, handleReceipt, etc.) already
1034
- // guard against sending ACKs on a closed connection.
1035
- while (nodes.length) {
1345
+ let processedInBatch = 0;
1346
+ while (nodes.length && ws.isOpen) {
1036
1347
  const { type, node } = nodes.shift();
1037
1348
  const nodeProcessor = nodeProcessorMap.get(type);
1038
1349
  if (!nodeProcessor) {
@@ -1040,6 +1351,13 @@ const makeMessagesRecvSocket = (config) => {
1040
1351
  continue;
1041
1352
  }
1042
1353
  await nodeProcessor(node);
1354
+ processedInBatch++;
1355
+ // Yield to event loop after processing a batch
1356
+ // This prevents blocking the event loop for too long when there are many offline nodes
1357
+ if (processedInBatch >= BATCH_SIZE) {
1358
+ processedInBatch = 0;
1359
+ await yieldToEventLoop();
1360
+ }
1043
1361
  }
1044
1362
  isProcessing = false;
1045
1363
  };
@@ -1048,33 +1366,35 @@ const makeMessagesRecvSocket = (config) => {
1048
1366
  return { enqueue };
1049
1367
  };
1050
1368
  const offlineNodeProcessor = makeOfflineNodeProcessor();
1051
- const processNode = (type, node, identifier, exec) => {
1369
+ const processNode = async (type, node, identifier, exec) => {
1052
1370
  const isOffline = !!node.attrs.offline;
1053
1371
  if (isOffline) {
1054
1372
  offlineNodeProcessor.enqueue(type, node);
1055
1373
  }
1056
1374
  else {
1057
- processNodeWithBuffer(node, identifier, exec);
1375
+ await processNodeWithBuffer(node, identifier, exec);
1058
1376
  }
1059
1377
  };
1060
1378
  // recv a message
1061
- ws.on('CB:message', (node) => {
1062
- processNode('message', node, 'processing message', handleMessage);
1379
+ ws.on('CB:message', async (node) => {
1380
+ await processNode('message', node, 'processing message', handleMessage);
1063
1381
  });
1064
1382
  ws.on('CB:call', async (node) => {
1065
- processNode('call', node, 'handling call', handleCall);
1383
+ await processNode('call', node, 'handling call', handleCall);
1066
1384
  });
1067
- ws.on('CB:receipt', node => {
1068
- processNode('receipt', node, 'handling receipt', handleReceipt);
1385
+ ws.on('CB:receipt', async (node) => {
1386
+ await processNode('receipt', node, 'handling receipt', handleReceipt);
1069
1387
  });
1070
1388
  ws.on('CB:notification', async (node) => {
1071
- processNode('notification', node, 'handling notification', handleNotification);
1389
+ await processNode('notification', node, 'handling notification', handleNotification);
1072
1390
  });
1073
1391
  ws.on('CB:ack,class:message', (node) => {
1074
- handleBadAck(node)
1075
- .catch(error => onUnexpectedError(error, 'handling bad ack'));
1392
+ handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
1076
1393
  });
1077
- ev.on('call', ([call]) => {
1394
+ ev.on('call', async ([call]) => {
1395
+ if (!call) {
1396
+ return;
1397
+ }
1078
1398
  // missed call + group call notification message generation
1079
1399
  if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
1080
1400
  const msg = {
@@ -1083,21 +1403,23 @@ const makeMessagesRecvSocket = (config) => {
1083
1403
  id: call.id,
1084
1404
  fromMe: false
1085
1405
  },
1086
- messageTimestamp: (0, Utils_1.unixTimestampSeconds)(call.date),
1406
+ messageTimestamp: unixTimestampSeconds(call.date)
1087
1407
  };
1088
1408
  if (call.status === 'timeout') {
1089
1409
  if (call.isGroup) {
1090
- msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_GROUP_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_GROUP_VOICE;
1410
+ msg.messageStubType = call.isVideo
1411
+ ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
1412
+ : WAMessageStubType.CALL_MISSED_GROUP_VOICE;
1091
1413
  }
1092
1414
  else {
1093
- msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_VOICE;
1415
+ msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE;
1094
1416
  }
1095
1417
  }
1096
1418
  else {
1097
1419
  msg.message = { call: { callKey: Buffer.from(call.id) } };
1098
1420
  }
1099
- const protoMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
1100
- upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1421
+ const protoMsg = proto.WebMessageInfo.fromObject(msg);
1422
+ await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1101
1423
  }
1102
1424
  });
1103
1425
  ev.on('connection.update', ({ isOnline }) => {
@@ -1111,9 +1433,10 @@ const makeMessagesRecvSocket = (config) => {
1111
1433
  sendMessageAck,
1112
1434
  sendRetryRequest,
1113
1435
  rejectCall,
1114
- offerCall,
1436
+ initiateCall,
1437
+ cancelCall,
1115
1438
  fetchMessageHistory,
1116
1439
  requestPlaceholderResend,
1440
+ messageRetryManager
1117
1441
  };
1118
- };
1119
- exports.makeMessagesRecvSocket = makeMessagesRecvSocket;
1442
+ };