@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.
- package/README.md +75 -1499
- package/lib/Defaults/index.d.ts +62 -0
- package/lib/Defaults/index.js +2 -2
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
- package/lib/Signal/Group/group-session-builder.d.ts +15 -0
- package/lib/Signal/Group/group_cipher.d.ts +17 -0
- package/lib/Signal/Group/index.d.ts +12 -0
- package/lib/Signal/Group/keyhelper.d.ts +11 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
- package/lib/Signal/Group/sender-key-message.d.ts +19 -0
- package/lib/Signal/Group/sender-key-name.d.ts +18 -0
- package/lib/Signal/Group/sender-key-record.d.ts +31 -0
- package/lib/Signal/Group/sender-key-state.d.ts +39 -0
- package/lib/Signal/Group/sender-message-key.d.ts +12 -0
- package/lib/Signal/libsignal.d.ts +5 -0
- package/lib/Signal/lid-mapping.d.ts +23 -0
- package/lib/Socket/Client/index.d.ts +3 -0
- package/lib/Socket/Client/types.d.ts +16 -0
- package/lib/Socket/Client/websocket.d.ts +13 -0
- package/lib/Socket/business.d.ts +190 -0
- package/lib/Socket/chats.d.ts +100 -0
- package/lib/Socket/chats.js +14 -13
- package/lib/Socket/communities.d.ts +246 -0
- package/lib/Socket/groups.d.ts +139 -0
- package/lib/Socket/groups.js +2 -3
- package/lib/Socket/hydra.js +1 -2
- package/lib/Socket/index.d.ts +233 -0
- package/lib/Socket/messages-recv.d.ts +175 -0
- package/lib/Socket/messages-recv.js +325 -515
- package/lib/Socket/messages-send.d.ts +171 -0
- package/lib/Socket/messages-send.js +104 -467
- package/lib/Socket/mex.d.ts +3 -0
- package/lib/Socket/newsletter.d.ts +149 -0
- package/lib/Socket/socket.d.ts +53 -0
- package/lib/Socket/socket.js +52 -51
- package/lib/Store/index.d.ts +4 -0
- package/lib/Store/make-cache-manager-store.d.ts +14 -0
- package/lib/Store/make-in-memory-store.d.ts +123 -0
- package/lib/Store/make-ordered-dictionary.d.ts +12 -0
- package/lib/Store/object-repository.d.ts +10 -0
- package/lib/Types/Auth.d.ts +115 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Call.d.ts +14 -0
- package/lib/Types/Chat.d.ts +123 -0
- package/lib/Types/Contact.d.ts +24 -0
- package/lib/Types/Events.d.ts +202 -0
- package/lib/Types/GroupMetadata.d.ts +67 -0
- package/lib/Types/Label.d.ts +47 -0
- package/lib/Types/LabelAssociation.d.ts +30 -0
- package/lib/Types/Message.d.ts +382 -0
- package/lib/Types/Newsletter.d.ts +135 -0
- package/lib/Types/Product.d.ts +79 -0
- package/lib/Types/Signal.d.ts +76 -0
- package/lib/Types/Socket.d.ts +133 -0
- package/lib/Types/State.d.ts +39 -0
- package/lib/Types/USync.d.ts +26 -0
- package/lib/Types/index.d.ts +65 -0
- package/lib/Utils/auth-utils.d.ts +19 -0
- package/lib/Utils/browser-utils.d.ts +4 -0
- package/lib/Utils/business.d.ts +23 -0
- package/lib/Utils/chat-utils.d.ts +70 -0
- package/lib/Utils/crypto.d.ts +41 -0
- package/lib/Utils/decode-wa-message.d.ts +48 -0
- package/lib/Utils/decode-wa-message.js +5 -7
- package/lib/Utils/event-buffer.d.ts +34 -0
- package/lib/Utils/generics.d.ts +90 -0
- package/lib/Utils/history.d.ts +19 -0
- package/lib/Utils/index.d.ts +19 -0
- package/lib/Utils/link-preview.d.ts +21 -0
- package/lib/Utils/logger.d.ts +12 -0
- package/lib/Utils/lt-hash.d.ts +13 -0
- package/lib/Utils/make-mutex.d.ts +8 -0
- package/lib/Utils/message-retry-manager.d.ts +82 -0
- package/lib/Utils/messages-media.d.ts +114 -0
- package/lib/Utils/messages-media.js +69 -33
- package/lib/Utils/messages.d.ts +89 -0
- package/lib/Utils/messages.js +42 -12
- package/lib/Utils/noise-handler.d.ts +20 -0
- package/lib/Utils/pre-key-manager.d.ts +28 -0
- package/lib/Utils/process-message.d.ts +60 -0
- package/lib/Utils/signal.d.ts +34 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
- package/lib/Utils/validate-connection.d.ts +11 -0
- package/lib/WABinary/constants.d.ts +28 -0
- package/lib/WABinary/decode.d.ts +7 -0
- package/lib/WABinary/encode.d.ts +3 -0
- package/lib/WABinary/generic-utils.d.ts +15 -0
- package/lib/WABinary/generic-utils.js +7 -0
- package/lib/WABinary/index.d.ts +6 -0
- package/lib/WABinary/jid-utils.d.ts +48 -0
- package/lib/WABinary/types.d.ts +19 -0
- package/lib/WAM/BinaryInfo.d.ts +9 -0
- package/lib/WAM/constants.d.ts +40 -0
- package/lib/WAM/encode.d.ts +3 -0
- package/lib/WAM/index.d.ts +4 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/index.d.ts +5 -0
- package/lib/WAUSync/USyncQuery.d.ts +29 -0
- package/lib/WAUSync/USyncUser.d.ts +13 -0
- package/lib/WAUSync/index.d.ts +4 -0
- package/lib/index.d.ts +13 -0
- 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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
414
|
-
|
|
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 = (
|
|
431
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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 =
|
|
456
|
+
const willSendMessageAgain = (id, participant) => {
|
|
745
457
|
const key = `${id}:${participant}`;
|
|
746
|
-
const retryCount =
|
|
458
|
+
const retryCount = msgRetryCache.get(key) || 0;
|
|
747
459
|
return retryCount < maxMsgRetryCount;
|
|
748
460
|
};
|
|
749
|
-
const updateSendMessageAgainCount =
|
|
461
|
+
const updateSendMessageAgainCount = (id, participant) => {
|
|
750
462
|
const key = `${id}:${participant}`;
|
|
751
|
-
const newValue = (
|
|
752
|
-
|
|
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
|
|
479
|
+
logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
|
|
813
480
|
for (const [i, msg] of msgs.entries()) {
|
|
814
|
-
if (
|
|
815
|
-
|
|
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 !==
|
|
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 (
|
|
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
|
|
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 !==
|
|
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 !==
|
|
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
|
|
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
|
|
620
|
+
await sendMessageAck(node);
|
|
960
621
|
return;
|
|
961
622
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
if (msg
|
|
992
|
-
msg
|
|
993
|
-
|
|
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
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1032
|
-
logger.
|
|
666
|
+
else {
|
|
667
|
+
logger.debug({ node }, 'connection closed, ignoring retry req');
|
|
1033
668
|
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
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 (
|
|
1053
|
-
participant = author;
|
|
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');
|
|
695
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1065
696
|
}
|
|
1066
697
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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
|
|
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
|
-
|
|
792
|
+
callOfferCache.set(call.id, call);
|
|
1102
793
|
}
|
|
1103
|
-
const existingCall =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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',
|
|
1212
|
-
|
|
1022
|
+
ws.on('CB:message', (node) => {
|
|
1023
|
+
processNode('message', node, 'processing message', handleMessage);
|
|
1213
1024
|
});
|
|
1214
1025
|
ws.on('CB:call', async (node) => {
|
|
1215
|
-
|
|
1026
|
+
processNode('call', node, 'handling call', handleCall);
|
|
1216
1027
|
});
|
|
1217
|
-
ws.on('CB:receipt',
|
|
1218
|
-
|
|
1028
|
+
ws.on('CB:receipt', node => {
|
|
1029
|
+
processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1219
1030
|
});
|
|
1220
1031
|
ws.on('CB:notification', async (node) => {
|
|
1221
|
-
|
|
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',
|
|
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
|
-
|
|
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
|