@kelvdra/baileys 1.0.3 → 1.0.4

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 (109) hide show
  1. package/README.md +75 -1499
  2. package/lib/Defaults/index.d.ts +62 -0
  3. package/lib/Defaults/index.js +2 -2
  4. package/lib/Defaults/phonenumber-mcc.json +223 -0
  5. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  6. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  7. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  8. package/lib/Signal/Group/index.d.ts +12 -0
  9. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  10. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  11. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  12. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  13. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  14. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  15. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  16. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  17. package/lib/Signal/libsignal.d.ts +5 -0
  18. package/lib/Signal/lid-mapping.d.ts +23 -0
  19. package/lib/Socket/Client/index.d.ts +3 -0
  20. package/lib/Socket/Client/types.d.ts +16 -0
  21. package/lib/Socket/Client/websocket.d.ts +13 -0
  22. package/lib/Socket/business.d.ts +190 -0
  23. package/lib/Socket/chats.d.ts +100 -0
  24. package/lib/Socket/chats.js +14 -13
  25. package/lib/Socket/communities.d.ts +246 -0
  26. package/lib/Socket/groups.d.ts +139 -0
  27. package/lib/Socket/groups.js +2 -3
  28. package/lib/Socket/hydra.js +1 -2
  29. package/lib/Socket/index.d.ts +233 -0
  30. package/lib/Socket/messages-recv.d.ts +175 -0
  31. package/lib/Socket/messages-recv.js +325 -515
  32. package/lib/Socket/messages-send.d.ts +171 -0
  33. package/lib/Socket/messages-send.js +104 -467
  34. package/lib/Socket/mex.d.ts +3 -0
  35. package/lib/Socket/newsletter.d.ts +149 -0
  36. package/lib/Socket/socket.d.ts +53 -0
  37. package/lib/Socket/socket.js +52 -51
  38. package/lib/Store/index.d.ts +4 -0
  39. package/lib/Store/make-cache-manager-store.d.ts +14 -0
  40. package/lib/Store/make-in-memory-store.d.ts +123 -0
  41. package/lib/Store/make-ordered-dictionary.d.ts +12 -0
  42. package/lib/Store/object-repository.d.ts +10 -0
  43. package/lib/Types/Auth.d.ts +115 -0
  44. package/lib/Types/Bussines.d.ts +25 -0
  45. package/lib/Types/Call.d.ts +14 -0
  46. package/lib/Types/Chat.d.ts +123 -0
  47. package/lib/Types/Contact.d.ts +24 -0
  48. package/lib/Types/Events.d.ts +202 -0
  49. package/lib/Types/GroupMetadata.d.ts +67 -0
  50. package/lib/Types/Label.d.ts +47 -0
  51. package/lib/Types/LabelAssociation.d.ts +30 -0
  52. package/lib/Types/Message.d.ts +382 -0
  53. package/lib/Types/Newsletter.d.ts +135 -0
  54. package/lib/Types/Product.d.ts +79 -0
  55. package/lib/Types/Signal.d.ts +76 -0
  56. package/lib/Types/Socket.d.ts +133 -0
  57. package/lib/Types/State.d.ts +39 -0
  58. package/lib/Types/USync.d.ts +26 -0
  59. package/lib/Types/index.d.ts +65 -0
  60. package/lib/Utils/auth-utils.d.ts +19 -0
  61. package/lib/Utils/browser-utils.d.ts +4 -0
  62. package/lib/Utils/business.d.ts +23 -0
  63. package/lib/Utils/chat-utils.d.ts +70 -0
  64. package/lib/Utils/crypto.d.ts +41 -0
  65. package/lib/Utils/decode-wa-message.d.ts +48 -0
  66. package/lib/Utils/decode-wa-message.js +5 -7
  67. package/lib/Utils/event-buffer.d.ts +34 -0
  68. package/lib/Utils/generics.d.ts +90 -0
  69. package/lib/Utils/history.d.ts +19 -0
  70. package/lib/Utils/index.d.ts +19 -0
  71. package/lib/Utils/link-preview.d.ts +21 -0
  72. package/lib/Utils/logger.d.ts +12 -0
  73. package/lib/Utils/lt-hash.d.ts +13 -0
  74. package/lib/Utils/make-mutex.d.ts +8 -0
  75. package/lib/Utils/message-retry-manager.d.ts +82 -0
  76. package/lib/Utils/messages-media.d.ts +114 -0
  77. package/lib/Utils/messages-media.js +69 -33
  78. package/lib/Utils/messages.d.ts +89 -0
  79. package/lib/Utils/messages.js +42 -12
  80. package/lib/Utils/noise-handler.d.ts +20 -0
  81. package/lib/Utils/pre-key-manager.d.ts +28 -0
  82. package/lib/Utils/process-message.d.ts +60 -0
  83. package/lib/Utils/signal.d.ts +34 -0
  84. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  85. package/lib/Utils/validate-connection.d.ts +11 -0
  86. package/lib/WABinary/constants.d.ts +28 -0
  87. package/lib/WABinary/decode.d.ts +7 -0
  88. package/lib/WABinary/encode.d.ts +3 -0
  89. package/lib/WABinary/generic-utils.d.ts +15 -0
  90. package/lib/WABinary/generic-utils.js +7 -0
  91. package/lib/WABinary/index.d.ts +6 -0
  92. package/lib/WABinary/jid-utils.d.ts +48 -0
  93. package/lib/WABinary/types.d.ts +19 -0
  94. package/lib/WAM/BinaryInfo.d.ts +9 -0
  95. package/lib/WAM/constants.d.ts +40 -0
  96. package/lib/WAM/encode.d.ts +3 -0
  97. package/lib/WAM/index.d.ts +4 -0
  98. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  99. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  100. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  101. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  102. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  103. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  104. package/lib/WAUSync/Protocols/index.d.ts +5 -0
  105. package/lib/WAUSync/USyncQuery.d.ts +29 -0
  106. package/lib/WAUSync/USyncUser.d.ts +13 -0
  107. package/lib/WAUSync/index.d.ts +4 -0
  108. package/lib/index.d.ts +13 -0
  109. package/package.json +3 -34
@@ -5,15 +5,15 @@ import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
7
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, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
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';
10
+ import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidStatusBroadcast, isPnUser, isLidUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
11
11
  import { extractGroupMetadata } from './groups.js';
12
12
  import { makeMessagesSocket } from './messages-send.js';
13
13
  export const makeMessagesRecvSocket = (config) => {
14
- const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
14
+ const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
15
15
  const sock = makeMessagesSocket(config);
16
- const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
16
+ const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage } = sock;
17
17
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
18
18
  const retryMutex = makeMutex();
19
19
  const msgRetryCache = config.msgRetryCounterCache ||
@@ -31,190 +31,7 @@ export const makeMessagesRecvSocket = (config) => {
31
31
  stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
32
32
  useClones: false
33
33
  });
34
- // Debounce identity-change session refreshes per JID to avoid bursts
35
- const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
36
34
  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) => {
54
- if (!authState.creds.me?.id) {
55
- throw new Boom('Not authenticated');
56
- }
57
- if (placeholderResendCache.get(messageKey?.id)) {
58
- logger.debug({ messageKey }, 'already requested resend');
59
- return;
60
- }
61
- else {
62
- await placeholderResendCache.set(messageKey?.id, true);
63
- }
64
- await delay(5000);
65
- if (!placeholderResendCache.get(messageKey?.id)) {
66
- logger.debug({ messageKey }, 'message received while resend requested');
67
- return 'RESOLVED';
68
- }
69
- const pdoMessage = {
70
- placeholderMessageResendRequest: [
71
- {
72
- messageKey
73
- }
74
- ],
75
- peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
76
- };
77
- setTimeout(async () => {
78
- if (placeholderResendCache.get(messageKey?.id)) {
79
- logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
80
- await placeholderResendCache.del(messageKey?.id);
81
- }
82
- }, 15000);
83
- return sendPeerDataOperationMessage(pdoMessage);
84
- };
85
- // Handles mex newsletter notifications
86
- const handleMexNewsletterNotification = async (node) => {
87
- const mexNode = getBinaryNodeChild(node, 'mex');
88
- if (!mexNode?.content) {
89
- logger.warn({ node }, 'Invalid mex newsletter notification');
90
- return;
91
- }
92
- let data;
93
- try {
94
- data = JSON.parse(mexNode.content.toString());
95
- }
96
- catch (error) {
97
- logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
98
- return;
99
- }
100
- const operation = data?.operation;
101
- const updates = data?.updates;
102
- if (!updates || !operation) {
103
- logger.warn({ data }, 'Invalid mex newsletter notification content');
104
- return;
105
- }
106
- logger.info({ operation, updates }, 'got mex newsletter notification');
107
- switch (operation) {
108
- case 'NotificationNewsletterUpdate':
109
- for (const update of updates) {
110
- if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
111
- ev.emit('newsletter-settings.update', {
112
- id: update.jid,
113
- update: update.settings
114
- });
115
- }
116
- }
117
- break;
118
- case 'NotificationNewsletterAdminPromote':
119
- for (const update of updates) {
120
- if (update.jid && update.user) {
121
- ev.emit('newsletter-participants.update', {
122
- id: update.jid,
123
- author: node.attrs.from,
124
- user: update.user,
125
- new_role: 'ADMIN',
126
- action: 'promote'
127
- });
128
- }
129
- }
130
- break;
131
- default:
132
- logger.info({ operation, data }, 'Unhandled mex newsletter notification');
133
- break;
134
- }
135
- };
136
- // Handles newsletter notifications
137
- const handleNewsletterNotification = async (node) => {
138
- const from = node.attrs.from;
139
- const child = getAllBinaryNodeChildren(node)[0];
140
- const author = node.attrs.participant;
141
- logger.info({ from, child }, 'got newsletter notification');
142
- switch (child.tag) {
143
- case 'reaction':
144
- const reactionUpdate = {
145
- id: from,
146
- server_id: child.attrs.message_id,
147
- reaction: {
148
- code: getBinaryNodeChildString(child, 'reaction'),
149
- count: 1
150
- }
151
- };
152
- ev.emit('newsletter.reaction', reactionUpdate);
153
- break;
154
- case 'view':
155
- const viewUpdate = {
156
- id: from,
157
- server_id: child.attrs.message_id,
158
- count: parseInt(child.content?.toString() || '0', 10)
159
- };
160
- ev.emit('newsletter.view', viewUpdate);
161
- break;
162
- case 'participant':
163
- const participantUpdate = {
164
- id: from,
165
- author,
166
- user: child.attrs.jid,
167
- action: child.attrs.action,
168
- new_role: child.attrs.role
169
- };
170
- ev.emit('newsletter-participants.update', participantUpdate);
171
- break;
172
- case 'update':
173
- const settingsNode = getBinaryNodeChild(child, 'settings');
174
- if (settingsNode) {
175
- const update = {};
176
- const nameNode = getBinaryNodeChild(settingsNode, 'name');
177
- if (nameNode?.content)
178
- update.name = nameNode.content.toString();
179
- const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
180
- if (descriptionNode?.content)
181
- update.description = descriptionNode.content.toString();
182
- ev.emit('newsletter-settings.update', {
183
- id: from,
184
- update
185
- });
186
- }
187
- break;
188
- case 'message':
189
- const plaintextNode = getBinaryNodeChild(child, 'plaintext');
190
- if (plaintextNode?.content) {
191
- try {
192
- const contentBuf = typeof plaintextNode.content === 'string'
193
- ? Buffer.from(plaintextNode.content, 'binary')
194
- : Buffer.from(plaintextNode.content);
195
- const messageProto = proto.Message.decode(contentBuf).toJSON();
196
- const fullMessage = proto.WebMessageInfo.fromObject({
197
- key: {
198
- remoteJid: from,
199
- id: child.attrs.message_id || child.attrs.server_id,
200
- fromMe: false // TODO: is this really true though
201
- },
202
- message: messageProto,
203
- messageTimestamp: +child.attrs.t
204
- }).toJSON();
205
- await upsertMessage(fullMessage, 'append');
206
- logger.info('Processed plaintext newsletter message');
207
- }
208
- catch (error) {
209
- logger.error({ error }, 'Failed to decode plaintext newsletter message');
210
- }
211
- }
212
- break;
213
- default:
214
- logger.warn({ node }, 'Unknown newsletter notification');
215
- break;
216
- }
217
- };
218
35
  const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
219
36
  const stanza = {
220
37
  tag: 'ack',
@@ -268,76 +85,20 @@ export const makeMessagesRecvSocket = (config) => {
268
85
  const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
269
86
  const { key: msgKey } = fullMessage;
270
87
  const msgId = msgKey.id;
271
- if (messageRetryManager) {
272
- // Check if we've exceeded max retries using the new system
273
- if (messageRetryManager.hasExceededMaxRetries(msgId)) {
274
- logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
275
- messageRetryManager.markRetryFailed(msgId);
276
- return;
277
- }
278
- // Increment retry count using new system
279
- const retryCount = messageRetryManager.incrementRetryCount(msgId);
280
- // Use the new retry count for the rest of the logic
281
- const key = `${msgId}:${msgKey?.participant}`;
282
- await msgRetryCache.set(key, retryCount);
283
- }
284
- else {
285
- // Fallback to old system
286
- const key = `${msgId}:${msgKey?.participant}`;
287
- let retryCount = (await msgRetryCache.get(key)) || 0;
288
- if (retryCount >= maxMsgRetryCount) {
289
- logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
290
- await msgRetryCache.del(key);
291
- return;
292
- }
293
- retryCount += 1;
294
- await msgRetryCache.set(key, retryCount);
295
- }
296
88
  const key = `${msgId}:${msgKey?.participant}`;
297
- const retryCount = (await msgRetryCache.get(key)) || 1;
298
- const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
299
- const fromJid = node.attrs.from;
300
- // Check if we should recreate the session
301
- let shouldRecreateSession = false;
302
- let recreateReason = '';
303
- if (enableAutoSessionRecreation && messageRetryManager) {
304
- try {
305
- // Check if we have a session with this JID
306
- const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
307
- const hasSession = await signalRepository.validateSession(fromJid);
308
- const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists);
309
- shouldRecreateSession = result.recreate;
310
- recreateReason = result.reason;
311
- if (shouldRecreateSession) {
312
- logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
313
- // Delete existing session to force recreation
314
- await authState.keys.set({ session: { [sessionId]: null } });
315
- forceIncludeKeys = true;
316
- }
317
- }
318
- catch (error) {
319
- logger.warn({ error, fromJid }, 'failed to check session recreation');
320
- }
89
+ let retryCount = msgRetryCache.get(key) || 0;
90
+ if (retryCount >= maxMsgRetryCount) {
91
+ logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
92
+ msgRetryCache.del(key);
93
+ return;
321
94
  }
322
- if (retryCount <= 2) {
323
- // Use new retry manager for phone requests if available
324
- if (messageRetryManager) {
325
- // Schedule phone request with delay (like whatsmeow)
326
- messageRetryManager.schedulePhoneRequest(msgId, async () => {
327
- try {
328
- const requestId = await requestPlaceholderResend(msgKey);
329
- logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
330
- }
331
- catch (error) {
332
- logger.warn({ error, msgId }, 'failed to send scheduled phone request');
333
- }
334
- });
335
- }
336
- else {
337
- // Fallback to immediate request
338
- const msgId = await requestPlaceholderResend(msgKey);
339
- logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
340
- }
95
+ retryCount += 1;
96
+ msgRetryCache.set(key, retryCount);
97
+ const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
98
+ if (retryCount === 1) {
99
+ //request a resend via phone
100
+ const msgId = await requestPlaceholderResend(msgKey);
101
+ logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
341
102
  }
342
103
  const deviceIdentity = encodeSignedDeviceIdentity(account, true);
343
104
  await authState.keys.transaction(async () => {
@@ -355,9 +116,7 @@ export const makeMessagesRecvSocket = (config) => {
355
116
  count: retryCount.toString(),
356
117
  id: node.attrs.id,
357
118
  t: node.attrs.t,
358
- v: '1',
359
- // ADD ERROR FIELD
360
- error: '0'
119
+ v: '1'
361
120
  }
362
121
  },
363
122
  {
@@ -373,7 +132,7 @@ export const makeMessagesRecvSocket = (config) => {
373
132
  if (node.attrs.participant) {
374
133
  receipt.attrs.participant = node.attrs.participant;
375
134
  }
376
- if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
135
+ if (retryCount > 1 || forceIncludeKeys) {
377
136
  const { update, preKeys } = await getNextPreKeys(authState, 1);
378
137
  const [keyId] = Object.keys(preKeys);
379
138
  const key = preKeys[+keyId];
@@ -393,7 +152,7 @@ export const makeMessagesRecvSocket = (config) => {
393
152
  }
394
153
  await sendNode(receipt);
395
154
  logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
396
- }, authState?.creds?.me?.id || 'sendRetryRequest');
155
+ });
397
156
  };
398
157
  const handleEncryptNotification = async (node) => {
399
158
  const from = node.attrs.from;
@@ -410,35 +169,22 @@ export const makeMessagesRecvSocket = (config) => {
410
169
  const identityNode = getBinaryNodeChild(node, 'identity');
411
170
  if (identityNode) {
412
171
  logger.info({ jid: from }, 'identity changed');
413
- if (identityAssertDebounce.get(from)) {
414
- logger.debug({ jid: from }, 'skipping identity assert (debounced)');
415
- return;
416
- }
417
- identityAssertDebounce.set(from, true);
418
- try {
419
- await assertSessions([from], true);
420
- }
421
- catch (error) {
422
- logger.warn({ error, jid: from }, 'failed to assert sessions after identity change');
423
- }
172
+ // not handling right now
173
+ // signal will override new identity anyway
424
174
  }
425
175
  else {
426
176
  logger.info({ node }, 'unknown encrypt notification');
427
177
  }
428
178
  }
429
179
  };
430
- const handleGroupNotification = (fullNode, child, msg) => {
431
- // TODO: Support PN/LID (Here is only LID now)
432
- const actingParticipantLid = fullNode.attrs.participant;
433
- const actingParticipantPn = fullNode.attrs.participant_pn;
434
- const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
435
- const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
180
+ const handleGroupNotification = (participant, child, msg) => {
181
+ const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant;
436
182
  switch (child?.tag) {
437
183
  case 'create':
438
184
  const metadata = extractGroupMetadata(child);
439
185
  msg.messageStubType = WAMessageStubType.GROUP_CREATE;
440
186
  msg.messageStubParameters = [metadata.subject];
441
- msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
187
+ msg.key = { participant: metadata.owner };
442
188
  ev.emit('chats.upsert', [
443
189
  {
444
190
  id: metadata.id,
@@ -449,8 +195,7 @@ export const makeMessagesRecvSocket = (config) => {
449
195
  ev.emit('groups.upsert', [
450
196
  {
451
197
  ...metadata,
452
- author: actingParticipantLid,
453
- authorPn: actingParticipantPn
198
+ author: participant
454
199
  }
455
200
  ]);
456
201
  break;
@@ -475,24 +220,15 @@ export const makeMessagesRecvSocket = (config) => {
475
220
  case 'leave':
476
221
  const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
477
222
  msg.messageStubType = WAMessageStubType[stubType];
478
- const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
479
- // TODO: Store LID MAPPINGS
480
- return {
481
- id: attrs.jid,
482
- phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
483
- lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
484
- admin: (attrs.type || null)
485
- };
486
- });
223
+ const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid);
487
224
  if (participants.length === 1 &&
488
225
  // if recv. "remove" message and sender removed themselves
489
226
  // mark as left
490
- (areJidsSameUser(participants[0].id, actingParticipantLid) ||
491
- areJidsSameUser(participants[0].id, actingParticipantPn)) &&
227
+ areJidsSameUser(participants[0], participant) &&
492
228
  child.tag === 'remove') {
493
229
  msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
494
230
  }
495
- msg.messageStubParameters = participants.map(a => JSON.stringify(a));
231
+ msg.messageStubParameters = participants;
496
232
  break;
497
233
  case 'subject':
498
234
  msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
@@ -533,20 +269,12 @@ export const makeMessagesRecvSocket = (config) => {
533
269
  break;
534
270
  case 'created_membership_requests':
535
271
  msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
536
- msg.messageStubParameters = [
537
- JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
538
- 'created',
539
- child.attrs.request_method
540
- ];
272
+ msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method];
541
273
  break;
542
274
  case 'revoked_membership_requests':
543
- const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
544
- // TODO: LIDMAPPING SUPPORT
275
+ const isDenied = areJidsSameUser(participantJid, participant);
545
276
  msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
546
- msg.messageStubParameters = [
547
- JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
548
- isDenied ? 'revoked' : 'rejected'
549
- ];
277
+ msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'];
550
278
  break;
551
279
  }
552
280
  };
@@ -556,6 +284,19 @@ export const makeMessagesRecvSocket = (config) => {
556
284
  const nodeType = node.attrs.type;
557
285
  const from = jidNormalizedUser(node.attrs.from);
558
286
  switch (nodeType) {
287
+ case 'privacy_token':
288
+ const tokenList = getBinaryNodeChildren(child, 'token');
289
+ for (const { attrs, content } of tokenList) {
290
+ const jid = attrs.jid;
291
+ ev.emit('chats.update', [
292
+ {
293
+ id: jid,
294
+ tcToken: content
295
+ }
296
+ ]);
297
+ logger.debug({ jid }, 'got privacy token update');
298
+ }
299
+ break;
559
300
  case 'newsletter':
560
301
  await handleNewsletterNotification(node);
561
302
  break;
@@ -563,8 +304,7 @@ export const makeMessagesRecvSocket = (config) => {
563
304
  await handleMexNewsletterNotification(node);
564
305
  break;
565
306
  case 'w:gp2':
566
- // TODO: HANDLE PARTICIPANT_PN
567
- handleGroupNotification(node, child, result);
307
+ handleGroupNotification(node.attrs.participant, child, result);
568
308
  break;
569
309
  case 'mediaretry':
570
310
  const event = decodeMediaRetryNode(node);
@@ -575,12 +315,10 @@ export const makeMessagesRecvSocket = (config) => {
575
315
  break;
576
316
  case 'devices':
577
317
  const devices = getBinaryNodeChildren(child, 'device');
578
- if (areJidsSameUser(child.attrs.jid, authState.creds.me.id) ||
579
- areJidsSameUser(child.attrs.lid, authState.creds.me.lid)) {
580
- const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }));
581
- logger.info({ deviceData }, 'my own devices changed');
318
+ if (areJidsSameUser(child.attrs.jid, authState.creds.me.id)) {
319
+ const deviceJids = devices.map(d => d.attrs.jid);
320
+ logger.info({ deviceJids }, 'got my own devices');
582
321
  }
583
- //TODO: drop a new event, add hashes
584
322
  break;
585
323
  case 'server_sync':
586
324
  const update = getBinaryNodeChild(node, 'collection');
@@ -696,37 +434,11 @@ export const makeMessagesRecvSocket = (config) => {
696
434
  });
697
435
  authState.creds.registered = true;
698
436
  ev.emit('creds.update', authState.creds);
699
- break;
700
- case 'privacy_token':
701
- await handlePrivacyTokenNotification(node);
702
- break;
703
437
  }
704
438
  if (Object.keys(result).length) {
705
439
  return result;
706
440
  }
707
441
  };
708
- const handlePrivacyTokenNotification = async (node) => {
709
- const tokensNode = getBinaryNodeChild(node, 'tokens');
710
- const from = jidNormalizedUser(node.attrs.from);
711
- if (!tokensNode)
712
- return;
713
- const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
714
- for (const tokenNode of tokenNodes) {
715
- const { attrs, content } = tokenNode;
716
- const type = attrs.type;
717
- const timestamp = attrs.t;
718
- if (type === 'trusted_contact' && content instanceof Buffer) {
719
- logger.debug({
720
- from,
721
- timestamp,
722
- tcToken: content
723
- }, 'received trusted contact token');
724
- await authState.keys.set({
725
- tctoken: { [from]: { token: content, timestamp } }
726
- });
727
- }
728
- }
729
- };
730
442
  async function decipherLinkPublicKey(data) {
731
443
  const buffer = toRequiredBuffer(data);
732
444
  const salt = buffer.slice(0, 32);
@@ -741,80 +453,33 @@ export const makeMessagesRecvSocket = (config) => {
741
453
  }
742
454
  return data instanceof Buffer ? data : Buffer.from(data);
743
455
  }
744
- const willSendMessageAgain = async (id, participant) => {
456
+ const willSendMessageAgain = (id, participant) => {
745
457
  const key = `${id}:${participant}`;
746
- const retryCount = (await msgRetryCache.get(key)) || 0;
458
+ const retryCount = msgRetryCache.get(key) || 0;
747
459
  return retryCount < maxMsgRetryCount;
748
460
  };
749
- const updateSendMessageAgainCount = async (id, participant) => {
461
+ const updateSendMessageAgainCount = (id, participant) => {
750
462
  const key = `${id}:${participant}`;
751
- const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
752
- await msgRetryCache.set(key, newValue);
463
+ const newValue = (msgRetryCache.get(key) || 0) + 1;
464
+ msgRetryCache.set(key, newValue);
753
465
  };
754
466
  const sendMessagesAgain = async (key, ids, retryNode) => {
467
+ // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
468
+ const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
755
469
  const remoteJid = key.remoteJid;
756
470
  const participant = key.participant || remoteJid;
757
- const retryCount = +retryNode.attrs.count || 1;
758
- // Try to get messages from cache first, then fallback to getMessage
759
- const msgs = [];
760
- for (const id of ids) {
761
- let msg;
762
- // Try to get from retry cache first if enabled
763
- if (messageRetryManager) {
764
- const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
765
- if (cachedMsg) {
766
- msg = cachedMsg.message;
767
- logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
768
- // Mark retry as successful since we found the message
769
- messageRetryManager.markRetrySuccess(id);
770
- }
771
- }
772
- // Fallback to getMessage if not found in cache
773
- if (!msg) {
774
- msg = await getMessage({ ...key, id });
775
- if (msg) {
776
- logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
777
- // Also mark as successful if found via getMessage
778
- if (messageRetryManager) {
779
- messageRetryManager.markRetrySuccess(id);
780
- }
781
- }
782
- }
783
- msgs.push(msg);
784
- }
785
471
  // if it's the primary jid sending the request
786
472
  // just re-send the message to everyone
787
473
  // prevents the first message decryption failure
788
474
  const sendToAll = !jidDecode(participant)?.device;
789
- // Check if we should recreate session for this retry
790
- let shouldRecreateSession = false;
791
- let recreateReason = '';
792
- if (enableAutoSessionRecreation && messageRetryManager) {
793
- try {
794
- const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
795
- const hasSession = await signalRepository.validateSession(participant);
796
- const result = messageRetryManager.shouldRecreateSession(participant, retryCount, hasSession.exists);
797
- shouldRecreateSession = result.recreate;
798
- recreateReason = result.reason;
799
- if (shouldRecreateSession) {
800
- logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
801
- await authState.keys.set({ session: { [sessionId]: null } });
802
- }
803
- }
804
- catch (error) {
805
- logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
806
- }
807
- }
808
475
  await assertSessions([participant], true);
809
476
  if (isJidGroup(remoteJid)) {
810
477
  await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
811
478
  }
812
- logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
479
+ logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
813
480
  for (const [i, msg] of msgs.entries()) {
814
- if (!ids[i])
815
- continue;
816
- if (msg && (await willSendMessageAgain(ids[i], participant))) {
817
- await updateSendMessageAgainCount(ids[i], participant);
481
+ if (msg) {
482
+ updateSendMessageAgainCount(ids[i], participant);
818
483
  const msgRelayOpts = { messageId: ids[i] };
819
484
  if (sendToAll) {
820
485
  msgRelayOpts.useUserDevicesCache = false;
@@ -844,7 +509,7 @@ export const makeMessagesRecvSocket = (config) => {
844
509
  fromMe,
845
510
  participant: attrs.participant
846
511
  };
847
- if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
512
+ if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
848
513
  logger.debug({ remoteJid }, 'ignoring receipt from jid');
849
514
  await sendMessageAck(node);
850
515
  return;
@@ -885,15 +550,14 @@ export const makeMessagesRecvSocket = (config) => {
885
550
  // correctly set who is asking for the retry
886
551
  key.participant = key.participant || attrs.from;
887
552
  const retryNode = getBinaryNodeChild(node, 'retry');
888
- if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
553
+ if (willSendMessageAgain(ids[0], key.participant)) {
889
554
  if (key.fromMe) {
890
555
  try {
891
- await updateSendMessageAgainCount(ids[0], key.participant);
892
556
  logger.debug({ attrs, key }, 'recv retry request');
893
557
  await sendMessagesAgain(key, ids, retryNode);
894
558
  }
895
559
  catch (error) {
896
- logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
560
+ logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
897
561
  }
898
562
  }
899
563
  else {
@@ -913,7 +577,7 @@ export const makeMessagesRecvSocket = (config) => {
913
577
  };
914
578
  const handleNotification = async (node) => {
915
579
  const remoteJid = node.attrs.from;
916
- if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
580
+ if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
917
581
  logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
918
582
  await sendMessageAck(node);
919
583
  return;
@@ -924,13 +588,10 @@ export const makeMessagesRecvSocket = (config) => {
924
588
  const msg = await processNotification(node);
925
589
  if (msg) {
926
590
  const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
927
- const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
928
591
  msg.key = {
929
592
  remoteJid,
930
593
  fromMe,
931
594
  participant: node.attrs.participant,
932
- participantAlt,
933
- addressingMode,
934
595
  id: node.attrs.id,
935
596
  ...(msg.key || {})
936
597
  };
@@ -947,97 +608,67 @@ export const makeMessagesRecvSocket = (config) => {
947
608
  }
948
609
  };
949
610
  const handleMessage = async (node) => {
950
- if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== S_WHATSAPP_NET) {
611
+ if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
951
612
  logger.debug({ key: node.attrs.key }, 'ignored message');
952
- await sendMessageAck(node, NACK_REASONS.UnhandledError);
613
+ await sendMessageAck(node);
953
614
  return;
954
615
  }
955
616
  const encNode = getBinaryNodeChild(node, 'enc');
956
617
  // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
957
618
  if (encNode && encNode.attrs.type === 'msmsg') {
958
619
  logger.debug({ key: node.attrs.key }, 'ignored msmsg');
959
- await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
620
+ await sendMessageAck(node);
960
621
  return;
961
622
  }
962
- const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
963
- const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
964
- // store new mappings we didn't have before
965
- if (!!alt) {
966
- const altServer = jidDecode(alt)?.server;
967
- const primaryJid = msg.key.participant || msg.key.remoteJid;
968
- if (altServer === 'lid') {
969
- if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
970
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
971
- await signalRepository.migrateSession(primaryJid, alt);
972
- }
623
+ let response;
624
+ if (getBinaryNodeChild(node, 'unavailable') && !encNode) {
625
+ await sendMessageAck(node);
626
+ const { key } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage;
627
+ response = await requestPlaceholderResend(key);
628
+ if (response === 'RESOLVED') {
629
+ return;
973
630
  }
974
- else {
975
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
976
- await signalRepository.migrateSession(alt, primaryJid);
631
+ logger.debug('received unavailable message, acked and requested resend from phone');
632
+ }
633
+ else {
634
+ if (placeholderResendCache.get(node.attrs.id)) {
635
+ placeholderResendCache.del(node.attrs.id);
977
636
  }
978
637
  }
979
- if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
980
- messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
981
- logger.debug({
982
- jid: msg.key.remoteJid,
983
- id: msg.key.id
984
- }, 'Added message to recent cache for retry receipts');
638
+ const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
639
+ if (response && msg?.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
640
+ msg.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT, response];
641
+ }
642
+ if (msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER &&
643
+ node.attrs.sender_pn) {
644
+ ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
985
645
  }
986
646
  try {
987
- await processingMutex.mutex(async () => {
988
- await decrypt();
989
- // message failed to decrypt
990
- if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
991
- if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT ||
992
- msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
993
- return sendMessageAck(node);
994
- }
995
- const errorMessage = msg?.messageStubParameters?.[0] || '';
996
- const isPreKeyError = errorMessage.includes('PreKey');
997
- logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
998
- // Handle both pre-key and normal retries in single mutex
999
- await retryMutex.mutex(async () => {
1000
- try {
1001
- if (!ws.isOpen) {
1002
- logger.debug({ node }, 'Connection closed, skipping retry');
1003
- return;
1004
- }
1005
- // Handle pre-key errors with upload and delay
1006
- if (isPreKeyError) {
1007
- logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
1008
- try {
1009
- logger.debug('Uploading pre-keys for error recovery');
1010
- await uploadPreKeys(5);
1011
- logger.debug('Waiting for server to process new pre-keys');
1012
- await delay(1000);
1013
- }
1014
- catch (uploadErr) {
1015
- logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
1016
- }
1017
- }
1018
- const encNode = getBinaryNodeChild(node, 'enc');
1019
- await sendRetryRequest(node, !encNode);
1020
- if (retryRequestDelayMs) {
1021
- await delay(retryRequestDelayMs);
1022
- }
647
+ await Promise.all([
648
+ processingMutex.mutex(async () => {
649
+ await decrypt();
650
+ // message failed to decrypt
651
+ if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
652
+ if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
653
+ return sendMessageAck(node, NACK_REASONS.ParsingError);
1023
654
  }
1024
- catch (err) {
1025
- logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
1026
- // Still attempt retry even if pre-key upload failed
1027
- try {
655
+ retryMutex.mutex(async () => {
656
+ if (ws.isOpen) {
657
+ if (getBinaryNodeChild(node, 'unavailable')) {
658
+ return;
659
+ }
1028
660
  const encNode = getBinaryNodeChild(node, 'enc');
1029
661
  await sendRetryRequest(node, !encNode);
662
+ if (retryRequestDelayMs) {
663
+ await delay(retryRequestDelayMs);
664
+ }
1030
665
  }
1031
- catch (retryErr) {
1032
- logger.error({ retryErr }, 'Failed to send retry after error handling');
666
+ else {
667
+ logger.debug({ node }, 'connection closed, ignoring retry req');
1033
668
  }
1034
- }
1035
- await sendMessageAck(node, NACK_REASONS.UnhandledError);
1036
- });
1037
- }
1038
- else {
1039
- const isNewsletter = isJidNewsletter(msg.key.remoteJid);
1040
- if (!isNewsletter) {
669
+ });
670
+ }
671
+ else {
1041
672
  // no type in the receipt => message delivered
1042
673
  let type = undefined;
1043
674
  let participant = msg.key.participant;
@@ -1049,8 +680,8 @@ export const makeMessagesRecvSocket = (config) => {
1049
680
  // message was sent by us from a different device
1050
681
  type = 'sender';
1051
682
  // need to specially handle this case
1052
- if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
1053
- participant = author; // TODO: investigate sending receipts to LIDs and not PNs
683
+ if (isPnUser(msg.key.remoteJid)) {
684
+ participant = author;
1054
685
  }
1055
686
  }
1056
687
  else if (!sendActiveReceipts) {
@@ -1061,31 +692,91 @@ export const makeMessagesRecvSocket = (config) => {
1061
692
  const isAnyHistoryMsg = getHistoryMsg(msg.message);
1062
693
  if (isAnyHistoryMsg) {
1063
694
  const jid = jidNormalizedUser(msg.key.remoteJid);
1064
- await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
695
+ await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
1065
696
  }
1066
697
  }
1067
- else {
1068
- await sendMessageAck(node);
1069
- logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
1070
- }
1071
- }
1072
- cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
1073
- await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
1074
- });
698
+ cleanMessage(msg, authState.creds.me.id);
699
+ await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
700
+ })
701
+ ]);
1075
702
  }
1076
703
  catch (error) {
1077
- logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
704
+ logger.error({ error, node }, 'error in handling message');
705
+ }
706
+ };
707
+ const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
708
+ if (!authState.creds.me?.id) {
709
+ throw new Boom('Not authenticated');
710
+ }
711
+ const pdoMessage = {
712
+ historySyncOnDemandRequest: {
713
+ chatJid: oldestMsgKey.remoteJid,
714
+ oldestMsgFromMe: oldestMsgKey.fromMe,
715
+ oldestMsgId: oldestMsgKey.id,
716
+ oldestMsgTimestampMs: oldestMsgTimestamp,
717
+ onDemandMsgCount: count
718
+ },
719
+ peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
720
+ };
721
+ return sendPeerDataOperationMessage(pdoMessage);
722
+ };
723
+ const requestPlaceholderResend = async (messageKey) => {
724
+ if (!authState.creds.me?.id) {
725
+ throw new Boom('Not authenticated');
726
+ }
727
+ if (placeholderResendCache.get(messageKey?.id)) {
728
+ logger.debug({ messageKey }, 'already requested resend');
729
+ return;
730
+ }
731
+ else {
732
+ placeholderResendCache.set(messageKey?.id, true);
733
+ }
734
+ await delay(5000);
735
+ if (!placeholderResendCache.get(messageKey?.id)) {
736
+ logger.debug({ messageKey }, 'message received while resend requested');
737
+ return 'RESOLVED';
1078
738
  }
739
+ const pdoMessage = {
740
+ placeholderMessageResendRequest: [
741
+ {
742
+ messageKey
743
+ }
744
+ ],
745
+ peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
746
+ };
747
+ setTimeout(() => {
748
+ if (placeholderResendCache.get(messageKey?.id)) {
749
+ logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
750
+ placeholderResendCache.del(messageKey?.id);
751
+ }
752
+ }, 15000);
753
+ return sendPeerDataOperationMessage(pdoMessage);
1079
754
  };
1080
755
  const handleCall = async (node) => {
756
+ let status;
1081
757
  const { attrs } = node;
1082
758
  const [infoChild] = getAllBinaryNodeChildren(node);
1083
- const status = getCallStatusFromNode(infoChild);
1084
759
  if (!infoChild) {
1085
760
  throw new Boom('Missing call info in call node');
1086
761
  }
1087
762
  const callId = infoChild.attrs['call-id'];
1088
763
  const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
764
+ status = getCallStatusFromNode(infoChild);
765
+ if (isLidUser(from) && infoChild.tag === 'relaylatency') {
766
+ const verify = callOfferCache.get(callId);
767
+ if (!verify) {
768
+ status = 'offer';
769
+ const callLid = {
770
+ chatId: attrs.from,
771
+ from,
772
+ id: callId,
773
+ date: new Date(+attrs.t * 1000),
774
+ offline: !!attrs.offline,
775
+ status
776
+ };
777
+ callOfferCache.set(callId, callLid);
778
+ }
779
+ }
1089
780
  const call = {
1090
781
  chatId: attrs.from,
1091
782
  from,
@@ -1098,9 +789,9 @@ export const makeMessagesRecvSocket = (config) => {
1098
789
  call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
1099
790
  call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
1100
791
  call.groupJid = infoChild.attrs['group-jid'];
1101
- await callOfferCache.set(call.id, call);
792
+ callOfferCache.set(call.id, call);
1102
793
  }
1103
- const existingCall = await callOfferCache.get(call.id);
794
+ const existingCall = callOfferCache.get(call.id);
1104
795
  // use existing call info to populate this event
1105
796
  if (existingCall) {
1106
797
  call.isVideo = existingCall.isVideo;
@@ -1108,7 +799,7 @@ export const makeMessagesRecvSocket = (config) => {
1108
799
  }
1109
800
  // delete data once call has ended
1110
801
  if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
1111
- await callOfferCache.del(call.id);
802
+ callOfferCache.del(call.id);
1112
803
  }
1113
804
  ev.emit('call', [call]);
1114
805
  await sendMessageAck(node);
@@ -1141,19 +832,6 @@ export const makeMessagesRecvSocket = (config) => {
1141
832
  }
1142
833
  }
1143
834
  ]);
1144
- // resend the message with device_fanout=false, use at your own risk
1145
- // if (attrs.error === '475') {
1146
- // const msg = await getMessage(key)
1147
- // if (msg) {
1148
- // await relayMessage(key.remoteJid!, msg, {
1149
- // messageId: key.id!,
1150
- // useUserDevicesCache: false,
1151
- // additionalAttributes: {
1152
- // device_fanout: 'false'
1153
- // }
1154
- // })
1155
- // }
1156
- // }
1157
835
  }
1158
836
  };
1159
837
  /// processes a node with the given function
@@ -1198,32 +876,165 @@ export const makeMessagesRecvSocket = (config) => {
1198
876
  return { enqueue };
1199
877
  };
1200
878
  const offlineNodeProcessor = makeOfflineNodeProcessor();
1201
- const processNode = async (type, node, identifier, exec) => {
879
+ const processNode = (type, node, identifier, exec) => {
1202
880
  const isOffline = !!node.attrs.offline;
1203
881
  if (isOffline) {
1204
882
  offlineNodeProcessor.enqueue(type, node);
1205
883
  }
1206
884
  else {
1207
- await processNodeWithBuffer(node, identifier, exec);
885
+ processNodeWithBuffer(node, identifier, exec);
1208
886
  }
1209
887
  };
888
+ // Handles newsletter notifications
889
+ async function handleNewsletterNotification(node) {
890
+ const from = node.attrs.from;
891
+ const child = getAllBinaryNodeChildren(node)[0];
892
+ const author = node.attrs.participant;
893
+ logger.info({ from, child }, 'got newsletter notification');
894
+ switch (child.tag) {
895
+ case 'reaction':
896
+ const reactionUpdate = {
897
+ id: from,
898
+ server_id: child.attrs.message_id,
899
+ reaction: {
900
+ code: getBinaryNodeChildString(child, 'reaction'),
901
+ count: 1
902
+ }
903
+ };
904
+ ev.emit('newsletter.reaction', reactionUpdate);
905
+ break;
906
+ case 'view':
907
+ const viewUpdate = {
908
+ id: from,
909
+ server_id: child.attrs.message_id,
910
+ count: parseInt(child.content?.toString() || '0', 10)
911
+ };
912
+ ev.emit('newsletter.view', viewUpdate);
913
+ break;
914
+ case 'participant':
915
+ const participantUpdate = {
916
+ id: from,
917
+ author,
918
+ user: child.attrs.jid,
919
+ action: child.attrs.action,
920
+ new_role: child.attrs.role
921
+ };
922
+ ev.emit('newsletter-participants.update', participantUpdate);
923
+ break;
924
+ case 'update':
925
+ const settingsNode = getBinaryNodeChild(child, 'settings');
926
+ if (settingsNode) {
927
+ const update = {};
928
+ const nameNode = getBinaryNodeChild(settingsNode, 'name');
929
+ if (nameNode?.content)
930
+ update.name = nameNode.content.toString();
931
+ const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
932
+ if (descriptionNode?.content)
933
+ update.description = descriptionNode.content.toString();
934
+ ev.emit('newsletter-settings.update', {
935
+ id: from,
936
+ update
937
+ });
938
+ }
939
+ break;
940
+ case 'message':
941
+ const plaintextNode = getBinaryNodeChild(child, 'plaintext');
942
+ if (plaintextNode?.content) {
943
+ try {
944
+ const contentBuf = typeof plaintextNode.content === 'string'
945
+ ? Buffer.from(plaintextNode.content, 'binary')
946
+ : Buffer.from(plaintextNode.content);
947
+ const messageProto = proto.Message.decode(contentBuf);
948
+ const fullMessage = proto.WebMessageInfo.fromObject({
949
+ key: {
950
+ remoteJid: from,
951
+ id: child.attrs.message_id || child.attrs.server_id,
952
+ fromMe: false
953
+ },
954
+ message: messageProto,
955
+ messageTimestamp: +child.attrs.t
956
+ });
957
+ await upsertMessage(fullMessage, 'append');
958
+ logger.info('Processed plaintext newsletter message');
959
+ }
960
+ catch (error) {
961
+ logger.error({ error }, 'Failed to decode plaintext newsletter message');
962
+ }
963
+ }
964
+ break;
965
+ default:
966
+ logger.warn({ node }, 'Unknown newsletter notification');
967
+ break;
968
+ }
969
+ }
970
+ // Handles mex newsletter notifications
971
+ async function handleMexNewsletterNotification(node) {
972
+ const mexNode = getBinaryNodeChild(node, 'mex');
973
+ if (!mexNode?.content) {
974
+ logger.warn({ node }, 'Invalid mex newsletter notification');
975
+ return;
976
+ }
977
+ let data;
978
+ try {
979
+ data = JSON.parse(mexNode.content.toString());
980
+ }
981
+ catch (error) {
982
+ logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
983
+ return;
984
+ }
985
+ const operation = data?.operation;
986
+ const updates = data?.updates;
987
+ if (!updates || !operation) {
988
+ logger.warn({ data }, 'Invalid mex newsletter notification content');
989
+ return;
990
+ }
991
+ logger.info({ operation, updates }, 'got mex newsletter notification');
992
+ switch (operation) {
993
+ case 'NotificationNewsletterUpdate':
994
+ for (const update of updates) {
995
+ if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
996
+ ev.emit('newsletter-settings.update', {
997
+ id: update.jid,
998
+ update: update.settings
999
+ });
1000
+ }
1001
+ }
1002
+ break;
1003
+ case 'NotificationNewsletterAdminPromote':
1004
+ for (const update of updates) {
1005
+ if (update.jid && update.user) {
1006
+ ev.emit('newsletter-participants.update', {
1007
+ id: update.jid,
1008
+ author: node.attrs.from,
1009
+ user: update.user,
1010
+ new_role: 'ADMIN',
1011
+ action: 'promote'
1012
+ });
1013
+ }
1014
+ }
1015
+ break;
1016
+ default:
1017
+ logger.info({ operation, data }, 'Unhandled mex newsletter notification');
1018
+ break;
1019
+ }
1020
+ }
1210
1021
  // recv a message
1211
- ws.on('CB:message', async (node) => {
1212
- await processNode('message', node, 'processing message', handleMessage);
1022
+ ws.on('CB:message', (node) => {
1023
+ processNode('message', node, 'processing message', handleMessage);
1213
1024
  });
1214
1025
  ws.on('CB:call', async (node) => {
1215
- await processNode('call', node, 'handling call', handleCall);
1026
+ processNode('call', node, 'handling call', handleCall);
1216
1027
  });
1217
- ws.on('CB:receipt', async (node) => {
1218
- await processNode('receipt', node, 'handling receipt', handleReceipt);
1028
+ ws.on('CB:receipt', node => {
1029
+ processNode('receipt', node, 'handling receipt', handleReceipt);
1219
1030
  });
1220
1031
  ws.on('CB:notification', async (node) => {
1221
- await processNode('notification', node, 'handling notification', handleNotification);
1032
+ processNode('notification', node, 'handling notification', handleNotification);
1222
1033
  });
1223
1034
  ws.on('CB:ack,class:message', (node) => {
1224
1035
  handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
1225
1036
  });
1226
- ev.on('call', async ([call]) => {
1037
+ ev.on('call', ([call]) => {
1227
1038
  if (!call) {
1228
1039
  return;
1229
1040
  }
@@ -1251,7 +1062,7 @@ export const makeMessagesRecvSocket = (config) => {
1251
1062
  msg.message = { call: { callKey: Buffer.from(call.id) } };
1252
1063
  }
1253
1064
  const protoMsg = proto.WebMessageInfo.fromObject(msg);
1254
- await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1065
+ upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1255
1066
  }
1256
1067
  });
1257
1068
  ev.on('connection.update', ({ isOnline }) => {
@@ -1266,8 +1077,7 @@ export const makeMessagesRecvSocket = (config) => {
1266
1077
  sendRetryRequest,
1267
1078
  rejectCall,
1268
1079
  fetchMessageHistory,
1269
- requestPlaceholderResend,
1270
- messageRetryManager
1080
+ requestPlaceholderResend
1271
1081
  };
1272
1082
  };
1273
- //# sourceMappingURL=messages-recv.js.map
1083
+ //# sourceMappingURL=messages-recv.js.map