@twsxtd/baileys 7.0.0-rc.9.commit.7a91cac1d4ec → 7.0.0-rc.9.commit.8d1e795328c7
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/WAProto/fix-imports.js +22 -18
- package/WAProto/index.js +22 -18
- package/lib/Defaults/index.d.ts +2 -0
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +3 -1
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/libsignal.d.ts.map +1 -1
- package/lib/Signal/libsignal.js +15 -12
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Signal/lid-mapping.d.ts +3 -5
- package/lib/Signal/lid-mapping.d.ts.map +1 -1
- package/lib/Signal/lid-mapping.js +37 -22
- package/lib/Signal/lid-mapping.js.map +1 -1
- package/lib/Socket/Client/websocket.d.ts.map +1 -1
- package/lib/Socket/Client/websocket.js +3 -6
- package/lib/Socket/Client/websocket.js.map +1 -1
- package/lib/Socket/business.d.ts +7 -4
- package/lib/Socket/business.d.ts.map +1 -1
- package/lib/Socket/chats.d.ts +9 -3
- package/lib/Socket/chats.d.ts.map +1 -1
- package/lib/Socket/chats.js +253 -47
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +7 -4
- package/lib/Socket/communities.d.ts.map +1 -1
- package/lib/Socket/groups.d.ts +8 -4
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +20 -0
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +7 -4
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/messages-recv.d.ts +7 -4
- package/lib/Socket/messages-recv.d.ts.map +1 -1
- package/lib/Socket/messages-recv.js +296 -103
- package/lib/Socket/messages-recv.js.map +1 -1
- package/lib/Socket/messages-send.d.ts +7 -4
- package/lib/Socket/messages-send.d.ts.map +1 -1
- package/lib/Socket/messages-send.js +113 -50
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/newsletter.d.ts +6 -3
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/newsletter.js.map +1 -1
- package/lib/Socket/socket.d.ts +0 -2
- package/lib/Socket/socket.d.ts.map +1 -1
- package/lib/Socket/socket.js +15 -9
- package/lib/Socket/socket.js.map +1 -1
- package/lib/Types/Auth.d.ts +2 -0
- package/lib/Types/Auth.d.ts.map +1 -1
- package/lib/Types/Call.d.ts +1 -1
- package/lib/Types/Call.d.ts.map +1 -1
- package/lib/Types/Contact.d.ts +2 -0
- package/lib/Types/Contact.d.ts.map +1 -1
- package/lib/Types/Events.d.ts +16 -0
- package/lib/Types/Events.d.ts.map +1 -1
- package/lib/Types/GroupMetadata.d.ts +4 -0
- package/lib/Types/GroupMetadata.d.ts.map +1 -1
- package/lib/Types/Message.d.ts +15 -2
- package/lib/Types/Message.d.ts.map +1 -1
- package/lib/Types/Message.js.map +1 -1
- package/lib/Types/Newsletter.d.ts +4 -2
- package/lib/Types/Newsletter.d.ts.map +1 -1
- package/lib/Types/Newsletter.js +4 -2
- package/lib/Types/Newsletter.js.map +1 -1
- package/lib/Types/Signal.d.ts +1 -2
- package/lib/Types/Signal.d.ts.map +1 -1
- package/lib/Utils/auth-utils.d.ts +5 -0
- package/lib/Utils/auth-utils.d.ts.map +1 -1
- package/lib/Utils/auth-utils.js +49 -14
- package/lib/Utils/auth-utils.js.map +1 -1
- package/lib/Utils/chat-utils.d.ts +30 -0
- package/lib/Utils/chat-utils.d.ts.map +1 -1
- package/lib/Utils/chat-utils.js +34 -8
- package/lib/Utils/chat-utils.js.map +1 -1
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
- package/lib/Utils/companion-reg-client-utils.js +41 -0
- package/lib/Utils/companion-reg-client-utils.js.map +1 -0
- package/lib/Utils/decode-wa-message.d.ts +12 -0
- package/lib/Utils/decode-wa-message.d.ts.map +1 -1
- package/lib/Utils/decode-wa-message.js +22 -0
- package/lib/Utils/decode-wa-message.js.map +1 -1
- package/lib/Utils/event-buffer.d.ts +0 -7
- package/lib/Utils/event-buffer.d.ts.map +1 -1
- package/lib/Utils/event-buffer.js +9 -29
- package/lib/Utils/event-buffer.js.map +1 -1
- package/lib/Utils/generics.d.ts +1 -0
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +22 -1
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/history.d.ts.map +1 -1
- package/lib/Utils/history.js +11 -9
- package/lib/Utils/history.js.map +1 -1
- package/lib/Utils/identity-change-handler.d.ts +7 -0
- package/lib/Utils/identity-change-handler.d.ts.map +1 -1
- package/lib/Utils/identity-change-handler.js +2 -6
- package/lib/Utils/identity-change-handler.js.map +1 -1
- package/lib/Utils/index.d.ts +15 -15
- package/lib/Utils/index.d.ts.map +1 -1
- package/lib/Utils/index.js +15 -15
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/message-retry-manager.d.ts +1 -5
- package/lib/Utils/message-retry-manager.d.ts.map +1 -1
- package/lib/Utils/message-retry-manager.js +7 -14
- package/lib/Utils/message-retry-manager.js.map +1 -1
- package/lib/Utils/messages-media.js +1 -1
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +22 -1
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/process-message.d.ts.map +1 -1
- package/lib/Utils/process-message.js +74 -7
- package/lib/Utils/process-message.js.map +1 -1
- package/lib/Utils/sync-action-utils.d.ts.map +1 -1
- package/lib/Utils/sync-action-utils.js +1 -0
- package/lib/Utils/sync-action-utils.js.map +1 -1
- package/lib/Utils/tc-token-utils.d.ts +26 -1
- package/lib/Utils/tc-token-utils.d.ts.map +1 -1
- package/lib/Utils/tc-token-utils.js +149 -4
- package/lib/Utils/tc-token-utils.js.map +1 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -1
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/index.d.ts +1 -0
- package/lib/WAUSync/Protocols/index.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/index.js +1 -0
- package/lib/WAUSync/Protocols/index.js.map +1 -1
- package/lib/WAUSync/USyncQuery.d.ts +1 -0
- package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
- package/lib/WAUSync/USyncQuery.js +5 -1
- package/lib/WAUSync/USyncQuery.js.map +1 -1
- package/lib/WAUSync/USyncUser.d.ts +4 -0
- package/lib/WAUSync/USyncUser.d.ts.map +1 -1
- package/lib/WAUSync/USyncUser.js +8 -0
- package/lib/WAUSync/USyncUser.js.map +1 -1
- package/package.json +5 -3
|
@@ -5,39 +5,63 @@ import Long from 'long';
|
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } 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, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
8
|
+
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, bindCleanupOnConnectionClose, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
10
|
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
11
|
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
12
|
+
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
|
|
12
13
|
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
13
14
|
import { extractGroupMetadata } from './groups.js';
|
|
14
15
|
import { makeMessagesSocket } from './messages-send.js';
|
|
15
16
|
export const makeMessagesRecvSocket = (config) => {
|
|
16
17
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
17
18
|
const sock = makeMessagesSocket(config);
|
|
18
|
-
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
|
|
19
|
+
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, issuePrivacyTokens } = sock;
|
|
20
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
19
21
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
20
22
|
const retryMutex = makeMutex();
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
+
const internalMsgRetryCache = config.msgRetryCounterCache
|
|
24
|
+
? undefined
|
|
25
|
+
: new NodeCache({
|
|
23
26
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
24
|
-
useClones: false
|
|
25
|
-
maxKeys: 5000
|
|
27
|
+
useClones: false
|
|
26
28
|
});
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
+
const msgRetryCache = config.msgRetryCounterCache || internalMsgRetryCache;
|
|
30
|
+
const internalCallOfferCache = config.callOfferCache
|
|
31
|
+
? undefined
|
|
32
|
+
: new NodeCache({
|
|
29
33
|
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
30
|
-
useClones: false
|
|
31
|
-
maxKeys: 1000
|
|
34
|
+
useClones: false
|
|
32
35
|
});
|
|
33
|
-
const
|
|
34
|
-
|
|
36
|
+
const callOfferCache = config.callOfferCache || internalCallOfferCache;
|
|
37
|
+
const internalPlaceholderResendCache = config.placeholderResendCache
|
|
38
|
+
? undefined
|
|
39
|
+
: new NodeCache({
|
|
35
40
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
36
|
-
useClones: false
|
|
37
|
-
maxKeys: 5000
|
|
41
|
+
useClones: false
|
|
38
42
|
});
|
|
43
|
+
const placeholderResendCache = config.placeholderResendCache || internalPlaceholderResendCache;
|
|
39
44
|
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
40
|
-
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false
|
|
45
|
+
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
|
|
46
|
+
let cleanedUp = false;
|
|
47
|
+
const cleanupInternalCaches = async () => {
|
|
48
|
+
if (cleanedUp) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
cleanedUp = true;
|
|
52
|
+
const closers = [Promise.resolve(identityAssertDebounce.close())];
|
|
53
|
+
if (internalMsgRetryCache) {
|
|
54
|
+
closers.push(Promise.resolve(internalMsgRetryCache.close()));
|
|
55
|
+
}
|
|
56
|
+
if (internalCallOfferCache) {
|
|
57
|
+
closers.push(Promise.resolve(internalCallOfferCache.close()));
|
|
58
|
+
}
|
|
59
|
+
if (internalPlaceholderResendCache) {
|
|
60
|
+
closers.push(Promise.resolve(internalPlaceholderResendCache.close()));
|
|
61
|
+
}
|
|
62
|
+
await Promise.allSettled(closers);
|
|
63
|
+
};
|
|
64
|
+
bindCleanupOnConnectionClose(ev, cleanupInternalCaches);
|
|
41
65
|
let sendActiveReceipts = false;
|
|
42
66
|
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
43
67
|
if (!authState.creds.me?.id) {
|
|
@@ -66,12 +90,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
66
90
|
else {
|
|
67
91
|
// Store original message data so PDO response handler can preserve
|
|
68
92
|
// metadata (LID details, timestamps, etc.) that the phone may omit
|
|
69
|
-
|
|
70
|
-
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
/* maxKeys exceeded */
|
|
74
|
-
}
|
|
93
|
+
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
75
94
|
}
|
|
76
95
|
await delay(2000);
|
|
77
96
|
if (!(await placeholderResendCache.get(messageKey?.id))) {
|
|
@@ -97,20 +116,34 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
97
116
|
// Handles mex newsletter notifications
|
|
98
117
|
const handleMexNewsletterNotification = async (node) => {
|
|
99
118
|
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
100
|
-
|
|
119
|
+
const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
|
|
120
|
+
const payloadNode = mexNode?.content ? mexNode : updateNode;
|
|
121
|
+
if (!payloadNode?.content) {
|
|
101
122
|
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
102
123
|
return;
|
|
103
124
|
}
|
|
104
125
|
let data;
|
|
105
126
|
try {
|
|
106
|
-
|
|
127
|
+
const payloadContent = payloadNode.content;
|
|
128
|
+
if (Array.isArray(payloadContent)) {
|
|
129
|
+
logger.warn({ payloadNode }, 'Invalid mex newsletter notification payload format');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
|
|
133
|
+
data = JSON.parse(contentBuf.toString());
|
|
107
134
|
}
|
|
108
135
|
catch (error) {
|
|
109
136
|
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
110
137
|
return;
|
|
111
138
|
}
|
|
112
|
-
const operation = data?.operation;
|
|
113
|
-
|
|
139
|
+
const operation = data?.operation ?? payloadNode?.attrs?.op_name;
|
|
140
|
+
let updates = data?.updates;
|
|
141
|
+
if (!updates) {
|
|
142
|
+
const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
|
|
143
|
+
if (linkedProfiles) {
|
|
144
|
+
updates = [linkedProfiles];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
114
147
|
if (!updates || !operation) {
|
|
115
148
|
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
116
149
|
return;
|
|
@@ -140,6 +173,22 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
140
173
|
}
|
|
141
174
|
}
|
|
142
175
|
break;
|
|
176
|
+
case 'NotificationLinkedProfilesUpdates':
|
|
177
|
+
for (const update of updates) {
|
|
178
|
+
const lid = update?.jid;
|
|
179
|
+
const addedProfiles = Array.isArray(update?.added_profiles) ? update.added_profiles : [];
|
|
180
|
+
const mappings = [];
|
|
181
|
+
for (const profile of addedProfiles) {
|
|
182
|
+
const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
|
|
183
|
+
if (lid && pn) {
|
|
184
|
+
const mapping = { lid, pn };
|
|
185
|
+
ev.emit('lid-mapping.update', mapping);
|
|
186
|
+
mappings.push(mapping);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
await signalRepository.lidMapping.storeLIDPNMappings(mappings);
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
143
192
|
default:
|
|
144
193
|
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
145
194
|
break;
|
|
@@ -268,12 +317,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
268
317
|
const retryCount = messageRetryManager.incrementRetryCount(msgId);
|
|
269
318
|
// Use the new retry count for the rest of the logic
|
|
270
319
|
const key = `${msgId}:${msgKey?.participant}`;
|
|
271
|
-
|
|
272
|
-
await msgRetryCache.set(key, retryCount);
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
/* maxKeys exceeded */
|
|
276
|
-
}
|
|
320
|
+
await msgRetryCache.set(key, retryCount);
|
|
277
321
|
}
|
|
278
322
|
else {
|
|
279
323
|
// Fallback to old system
|
|
@@ -285,12 +329,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
285
329
|
return;
|
|
286
330
|
}
|
|
287
331
|
retryCount += 1;
|
|
288
|
-
|
|
289
|
-
await msgRetryCache.set(key, retryCount);
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
/* maxKeys exceeded */
|
|
293
|
-
}
|
|
332
|
+
await msgRetryCache.set(key, retryCount);
|
|
294
333
|
}
|
|
295
334
|
const key = `${msgId}:${msgKey?.participant}`;
|
|
296
335
|
const retryCount = (await msgRetryCache.get(key)) || 1;
|
|
@@ -394,6 +433,35 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
394
433
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
395
434
|
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
396
435
|
};
|
|
436
|
+
/**
|
|
437
|
+
* Fire-and-forget tctoken re-issuance after a peer's device identity changed.
|
|
438
|
+
* Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
|
|
439
|
+
* the session refresh (not after it).
|
|
440
|
+
*/
|
|
441
|
+
const reissueTcTokenAfterIdentityChange = (from) => {
|
|
442
|
+
void (async () => {
|
|
443
|
+
const normalizedJid = jidNormalizedUser(from);
|
|
444
|
+
const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
|
|
445
|
+
const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
|
|
446
|
+
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
|
|
447
|
+
if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
|
|
451
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
452
|
+
const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
453
|
+
const result = await issuePrivacyTokens([issueJid], senderTs);
|
|
454
|
+
await storeTcTokensFromIqResult({
|
|
455
|
+
result,
|
|
456
|
+
fallbackJid: tcJid,
|
|
457
|
+
keys: authState.keys,
|
|
458
|
+
getLIDForPN,
|
|
459
|
+
onNewJidStored: trackTcTokenJid
|
|
460
|
+
});
|
|
461
|
+
})().catch(err => {
|
|
462
|
+
logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
|
|
463
|
+
});
|
|
464
|
+
};
|
|
397
465
|
const handleEncryptNotification = async (node) => {
|
|
398
466
|
const from = node.attrs.from;
|
|
399
467
|
if (from === S_WHATSAPP_NET) {
|
|
@@ -412,7 +480,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
412
480
|
validateSession: signalRepository.validateSession,
|
|
413
481
|
assertSessions,
|
|
414
482
|
debounceCache: identityAssertDebounce,
|
|
415
|
-
logger
|
|
483
|
+
logger,
|
|
484
|
+
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
416
485
|
});
|
|
417
486
|
if (result.action === 'no_identity_node') {
|
|
418
487
|
logger.info({ node }, 'unknown encrypt notification');
|
|
@@ -423,6 +492,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
423
492
|
// TODO: Support PN/LID (Here is only LID now)
|
|
424
493
|
const actingParticipantLid = fullNode.attrs.participant;
|
|
425
494
|
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
495
|
+
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
426
496
|
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
427
497
|
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
428
498
|
switch (child?.tag) {
|
|
@@ -442,7 +512,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
442
512
|
{
|
|
443
513
|
...metadata,
|
|
444
514
|
author: actingParticipantLid,
|
|
445
|
-
authorPn: actingParticipantPn
|
|
515
|
+
authorPn: actingParticipantPn,
|
|
516
|
+
authorUsername: actingParticipantUsername
|
|
446
517
|
}
|
|
447
518
|
]);
|
|
448
519
|
break;
|
|
@@ -473,6 +544,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
473
544
|
id: attrs.jid,
|
|
474
545
|
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
475
546
|
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
547
|
+
username: attrs.participant_username || attrs.username || undefined,
|
|
476
548
|
admin: (attrs.type || null)
|
|
477
549
|
};
|
|
478
550
|
});
|
|
@@ -698,27 +770,70 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
698
770
|
return result;
|
|
699
771
|
}
|
|
700
772
|
};
|
|
773
|
+
/**
|
|
774
|
+
* In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
|
|
775
|
+
* Used to coalesce writes during a session; pruning always re-reads the persisted index
|
|
776
|
+
* to cover writes made by other layers (e.g. history sync).
|
|
777
|
+
*/
|
|
778
|
+
const tcTokenKnownJids = new Set();
|
|
779
|
+
const tcTokenIndexLoaded = (async () => {
|
|
780
|
+
try {
|
|
781
|
+
const jids = await readTcTokenIndex(authState.keys);
|
|
782
|
+
for (const jid of jids)
|
|
783
|
+
tcTokenKnownJids.add(jid);
|
|
784
|
+
logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
logger.warn({ err: err?.message }, 'failed to load tctoken index');
|
|
788
|
+
}
|
|
789
|
+
})();
|
|
790
|
+
let tcTokenIndexTimer;
|
|
791
|
+
async function flushTcTokenIndex() {
|
|
792
|
+
if (tcTokenIndexTimer) {
|
|
793
|
+
clearTimeout(tcTokenIndexTimer);
|
|
794
|
+
tcTokenIndexTimer = undefined;
|
|
795
|
+
}
|
|
796
|
+
// Merge with whatever is already persisted so we don't clobber writes from other
|
|
797
|
+
// paths (history sync, concurrent sessions on the same store).
|
|
798
|
+
const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
|
|
799
|
+
return authState.keys.set({ tctoken: write });
|
|
800
|
+
}
|
|
801
|
+
function scheduleTcTokenIndexSave() {
|
|
802
|
+
if (tcTokenIndexTimer) {
|
|
803
|
+
clearTimeout(tcTokenIndexTimer);
|
|
804
|
+
}
|
|
805
|
+
tcTokenIndexTimer = setTimeout(() => {
|
|
806
|
+
tcTokenIndexTimer = undefined;
|
|
807
|
+
flushTcTokenIndex().catch(err => {
|
|
808
|
+
logger.warn({ err: err?.message }, 'failed to save tctoken index');
|
|
809
|
+
});
|
|
810
|
+
}, 5000);
|
|
811
|
+
}
|
|
812
|
+
function trackTcTokenJid(jid) {
|
|
813
|
+
if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
814
|
+
tcTokenKnownJids.add(jid);
|
|
815
|
+
scheduleTcTokenIndexSave();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
701
818
|
const handlePrivacyTokenNotification = async (node) => {
|
|
702
819
|
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
703
|
-
const from = jidNormalizedUser(node.attrs.from);
|
|
704
820
|
if (!tokensNode)
|
|
705
821
|
return;
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
822
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
823
|
+
// WA Web uses: senderLid ?? toLid(from) for the storage key
|
|
824
|
+
// The sender_lid attribute provides the LID directly when available
|
|
825
|
+
const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
|
|
826
|
+
? jidNormalizedUser(node.attrs.sender_lid)
|
|
827
|
+
: undefined;
|
|
828
|
+
const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
|
|
829
|
+
logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
|
|
830
|
+
await storeTcTokensFromIqResult({
|
|
831
|
+
result: node,
|
|
832
|
+
fallbackJid,
|
|
833
|
+
keys: authState.keys,
|
|
834
|
+
getLIDForPN,
|
|
835
|
+
onNewJidStored: trackTcTokenJid
|
|
836
|
+
});
|
|
722
837
|
};
|
|
723
838
|
async function decipherLinkPublicKey(data) {
|
|
724
839
|
const buffer = toRequiredBuffer(data);
|
|
@@ -742,12 +857,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
742
857
|
const updateSendMessageAgainCount = async (id, participant) => {
|
|
743
858
|
const key = `${id}:${participant}`;
|
|
744
859
|
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
745
|
-
|
|
746
|
-
await msgRetryCache.set(key, newValue);
|
|
747
|
-
}
|
|
748
|
-
catch {
|
|
749
|
-
/* maxKeys exceeded */
|
|
750
|
-
}
|
|
860
|
+
await msgRetryCache.set(key, newValue);
|
|
751
861
|
};
|
|
752
862
|
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
753
863
|
const remoteJid = key.remoteJid;
|
|
@@ -842,11 +952,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
842
952
|
fromMe,
|
|
843
953
|
participant: attrs.participant
|
|
844
954
|
};
|
|
845
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
846
|
-
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
847
|
-
await sendMessageAck(node);
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
955
|
const ids = [attrs.id];
|
|
851
956
|
if (Array.isArray(content)) {
|
|
852
957
|
const items = getBinaryNodeChildren(content[0], 'item');
|
|
@@ -911,11 +1016,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
911
1016
|
};
|
|
912
1017
|
const handleNotification = async (node) => {
|
|
913
1018
|
const remoteJid = node.attrs.from;
|
|
914
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
915
|
-
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
916
|
-
await sendMessageAck(node);
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
1019
|
try {
|
|
920
1020
|
await Promise.all([
|
|
921
1021
|
notificationMutex.mutex(async () => {
|
|
@@ -928,6 +1028,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
928
1028
|
fromMe,
|
|
929
1029
|
participant: node.attrs.participant,
|
|
930
1030
|
participantAlt,
|
|
1031
|
+
participantUsername: node.attrs.participant_username,
|
|
931
1032
|
addressingMode,
|
|
932
1033
|
id: node.attrs.id,
|
|
933
1034
|
...(msg.key || {})
|
|
@@ -945,11 +1046,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
945
1046
|
}
|
|
946
1047
|
};
|
|
947
1048
|
const handleMessage = async (node) => {
|
|
948
|
-
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== S_WHATSAPP_NET) {
|
|
949
|
-
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
950
|
-
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
1049
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
954
1050
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
955
1051
|
if (encNode?.attrs.type === 'msmsg') {
|
|
@@ -1167,16 +1263,18 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1167
1263
|
offline: !!attrs.offline,
|
|
1168
1264
|
status
|
|
1169
1265
|
};
|
|
1266
|
+
if (status === 'relaylatency') {
|
|
1267
|
+
const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
|
|
1268
|
+
const latencyMs = latencyValue ? Number(latencyValue) : undefined;
|
|
1269
|
+
if (Number.isFinite(latencyMs)) {
|
|
1270
|
+
call.latencyMs = latencyMs;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1170
1273
|
if (status === 'offer') {
|
|
1171
1274
|
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1172
1275
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
1173
1276
|
call.groupJid = infoChild.attrs['group-jid'];
|
|
1174
|
-
|
|
1175
|
-
await callOfferCache.set(call.id, call);
|
|
1176
|
-
}
|
|
1177
|
-
catch {
|
|
1178
|
-
/* maxKeys exceeded */
|
|
1179
|
-
}
|
|
1277
|
+
await callOfferCache.set(call.id, call);
|
|
1180
1278
|
}
|
|
1181
1279
|
const existingCall = await callOfferCache.get(call.id);
|
|
1182
1280
|
// use existing call info to populate this event
|
|
@@ -1216,7 +1314,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1216
1314
|
// error in acknowledgement,
|
|
1217
1315
|
// device could not display the message
|
|
1218
1316
|
if (attrs.error) {
|
|
1219
|
-
|
|
1317
|
+
if (attrs.error === SERVER_ERROR_CODES.MissingTcToken) {
|
|
1318
|
+
// 463 = account restricted + no tctoken for this contact.
|
|
1319
|
+
// WA Web prevents this client-side (disables compose bar).
|
|
1320
|
+
// No retry — retrying worsens the restriction by counting
|
|
1321
|
+
// as another "reach out" to an unknown contact.
|
|
1322
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
1323
|
+
}
|
|
1324
|
+
else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
|
|
1325
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1329
|
+
}
|
|
1220
1330
|
ev.emit('messages.update', [
|
|
1221
1331
|
{
|
|
1222
1332
|
key,
|
|
@@ -1226,19 +1336,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1226
1336
|
}
|
|
1227
1337
|
}
|
|
1228
1338
|
]);
|
|
1229
|
-
// resend the message with device_fanout=false, use at your own risk
|
|
1230
|
-
// if (attrs.error === '475') {
|
|
1231
|
-
// const msg = await getMessage(key)
|
|
1232
|
-
// if (msg) {
|
|
1233
|
-
// await relayMessage(key.remoteJid!, msg, {
|
|
1234
|
-
// messageId: key.id!,
|
|
1235
|
-
// useUserDevicesCache: false,
|
|
1236
|
-
// additionalAttributes: {
|
|
1237
|
-
// device_fanout: 'false'
|
|
1238
|
-
// }
|
|
1239
|
-
// })
|
|
1240
|
-
// }
|
|
1241
|
-
// }
|
|
1242
1339
|
}
|
|
1243
1340
|
};
|
|
1244
1341
|
/// processes a node with the given function
|
|
@@ -1262,6 +1359,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1262
1359
|
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1263
1360
|
});
|
|
1264
1361
|
const processNode = async (type, node, identifier, exec) => {
|
|
1362
|
+
// Fast path: ack and drop ignored JIDs before entering the buffer/queue
|
|
1363
|
+
const from = node.attrs.from;
|
|
1364
|
+
let ignoreJid = from;
|
|
1365
|
+
if (type === 'receipt' && from) {
|
|
1366
|
+
const attrs = node.attrs;
|
|
1367
|
+
const isLid = attrs.from.includes('lid');
|
|
1368
|
+
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1369
|
+
ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
1370
|
+
}
|
|
1371
|
+
if (ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
|
|
1372
|
+
await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1265
1375
|
const isOffline = !!node.attrs.offline;
|
|
1266
1376
|
if (isOffline) {
|
|
1267
1377
|
offlineNodeProcessor.enqueue(type, node);
|
|
@@ -1317,21 +1427,104 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1317
1427
|
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1318
1428
|
}
|
|
1319
1429
|
});
|
|
1430
|
+
/** timestamp of last tctoken prune run — throttles to once per 24h */
|
|
1431
|
+
let lastTcTokenPruneTs = 0;
|
|
1320
1432
|
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1321
1433
|
if (typeof isOnline !== 'undefined') {
|
|
1322
1434
|
sendActiveReceipts = isOnline;
|
|
1323
1435
|
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1324
1436
|
}
|
|
1325
|
-
//
|
|
1326
|
-
if (connection === 'close') {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1437
|
+
// Flush pending tctoken index save on disconnect to avoid writing after close
|
|
1438
|
+
if (connection === 'close' && tcTokenIndexTimer) {
|
|
1439
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1440
|
+
tcTokenIndexTimer = undefined;
|
|
1441
|
+
// Best-effort flush — may fail if store is already closed
|
|
1442
|
+
try {
|
|
1443
|
+
void Promise.resolve(flushTcTokenIndex()).catch(() => { });
|
|
1444
|
+
}
|
|
1445
|
+
catch {
|
|
1446
|
+
/* ignore sync errors */
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
// Prune expired tctokens when coming online, at most once per 24 hours
|
|
1450
|
+
// Matches WA Web's CLEAN_TC_TOKENS task
|
|
1451
|
+
// Note: don't gate on tcTokenKnownJids.size — the index may still be loading
|
|
1452
|
+
if (isOnline) {
|
|
1453
|
+
const now = Date.now();
|
|
1454
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
1455
|
+
if (now - lastTcTokenPruneTs >= DAY_MS) {
|
|
1456
|
+
lastTcTokenPruneTs = now;
|
|
1457
|
+
void pruneExpiredTcTokens();
|
|
1458
|
+
}
|
|
1331
1459
|
}
|
|
1332
1460
|
});
|
|
1461
|
+
async function pruneExpiredTcTokens() {
|
|
1462
|
+
try {
|
|
1463
|
+
await tcTokenIndexLoaded;
|
|
1464
|
+
// Union with the persisted index picks up JIDs added by other layers
|
|
1465
|
+
// (history sync) without needing inter-module wiring.
|
|
1466
|
+
const persisted = await readTcTokenIndex(authState.keys);
|
|
1467
|
+
const allJids = new Set(tcTokenKnownJids);
|
|
1468
|
+
for (const jid of persisted)
|
|
1469
|
+
allJids.add(jid);
|
|
1470
|
+
if (!allJids.size)
|
|
1471
|
+
return;
|
|
1472
|
+
const jids = [...allJids];
|
|
1473
|
+
const allTokens = await authState.keys.get('tctoken', jids);
|
|
1474
|
+
const writes = {};
|
|
1475
|
+
const survivors = new Set();
|
|
1476
|
+
let mutated = 0;
|
|
1477
|
+
for (const jid of jids) {
|
|
1478
|
+
const entry = allTokens[jid];
|
|
1479
|
+
if (!entry) {
|
|
1480
|
+
// Tracked but nothing in store — drop from index.
|
|
1481
|
+
mutated++;
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
const hasPeerToken = !!entry.token?.length;
|
|
1485
|
+
const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
|
|
1486
|
+
const hasSenderTs = entry.senderTimestamp !== undefined;
|
|
1487
|
+
const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
|
|
1488
|
+
const keepPeerToken = hasPeerToken && !peerTokenExpired;
|
|
1489
|
+
const keepSenderTs = hasSenderTs && !senderTsExpired;
|
|
1490
|
+
if (!keepPeerToken && !keepSenderTs) {
|
|
1491
|
+
writes[jid] = null;
|
|
1492
|
+
mutated++;
|
|
1493
|
+
}
|
|
1494
|
+
else if (peerTokenExpired && keepSenderTs) {
|
|
1495
|
+
writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
|
|
1496
|
+
survivors.add(jid);
|
|
1497
|
+
mutated++;
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
survivors.add(jid);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
if (mutated === 0)
|
|
1504
|
+
return;
|
|
1505
|
+
await authState.keys.set({
|
|
1506
|
+
tctoken: {
|
|
1507
|
+
...writes,
|
|
1508
|
+
[TC_TOKEN_INDEX_KEY]: {
|
|
1509
|
+
token: Buffer.from(JSON.stringify([...survivors]))
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
tcTokenKnownJids.clear();
|
|
1514
|
+
for (const jid of survivors)
|
|
1515
|
+
tcTokenKnownJids.add(jid);
|
|
1516
|
+
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
|
|
1517
|
+
}
|
|
1518
|
+
catch (err) {
|
|
1519
|
+
logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1333
1522
|
return {
|
|
1334
1523
|
...sock,
|
|
1524
|
+
end: async (error) => {
|
|
1525
|
+
await cleanupInternalCaches();
|
|
1526
|
+
await sock.end(error);
|
|
1527
|
+
},
|
|
1335
1528
|
sendMessageAck,
|
|
1336
1529
|
sendRetryRequest,
|
|
1337
1530
|
rejectCall,
|