@twsxtd/baileys 7.0.0-rc.9.commit.71ed75d4aae0 → 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 +11 -3
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Signal/lid-mapping.d.ts +3 -1
- package/lib/Signal/lid-mapping.d.ts.map +1 -1
- package/lib/Signal/lid-mapping.js +34 -14
- 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 -5
- 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 +252 -35
- 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 +289 -61
- 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 +106 -12
- 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 -8
- 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 -1
- 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 +45 -0
- 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 -2
- package/lib/Utils/event-buffer.d.ts.map +1 -1
- package/lib/Utils/event-buffer.js +3 -16
- 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 +1 -0
- package/lib/Utils/identity-change-handler.js.map +1 -1
- package/lib/Utils/index.d.ts +1 -0
- package/lib/Utils/index.d.ts.map +1 -1
- package/lib/Utils/index.js +1 -0
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/message-retry-manager.d.ts +1 -0
- package/lib/Utils/message-retry-manager.d.ts.map +1 -1
- package/lib/Utils/message-retry-manager.js +10 -0
- 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 +70 -1
- 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 +3 -3
|
@@ -5,36 +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
27
|
useClones: false
|
|
25
28
|
});
|
|
26
|
-
const
|
|
27
|
-
|
|
29
|
+
const msgRetryCache = config.msgRetryCounterCache || internalMsgRetryCache;
|
|
30
|
+
const internalCallOfferCache = config.callOfferCache
|
|
31
|
+
? undefined
|
|
32
|
+
: new NodeCache({
|
|
28
33
|
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
29
34
|
useClones: false
|
|
30
35
|
});
|
|
31
|
-
const
|
|
32
|
-
|
|
36
|
+
const callOfferCache = config.callOfferCache || internalCallOfferCache;
|
|
37
|
+
const internalPlaceholderResendCache = config.placeholderResendCache
|
|
38
|
+
? undefined
|
|
39
|
+
: new NodeCache({
|
|
33
40
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
34
41
|
useClones: false
|
|
35
42
|
});
|
|
43
|
+
const placeholderResendCache = config.placeholderResendCache || internalPlaceholderResendCache;
|
|
36
44
|
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
37
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);
|
|
38
65
|
let sendActiveReceipts = false;
|
|
39
66
|
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
40
67
|
if (!authState.creds.me?.id) {
|
|
@@ -89,20 +116,34 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
89
116
|
// Handles mex newsletter notifications
|
|
90
117
|
const handleMexNewsletterNotification = async (node) => {
|
|
91
118
|
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
92
|
-
|
|
119
|
+
const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
|
|
120
|
+
const payloadNode = mexNode?.content ? mexNode : updateNode;
|
|
121
|
+
if (!payloadNode?.content) {
|
|
93
122
|
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
94
123
|
return;
|
|
95
124
|
}
|
|
96
125
|
let data;
|
|
97
126
|
try {
|
|
98
|
-
|
|
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());
|
|
99
134
|
}
|
|
100
135
|
catch (error) {
|
|
101
136
|
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
102
137
|
return;
|
|
103
138
|
}
|
|
104
|
-
const operation = data?.operation;
|
|
105
|
-
|
|
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
|
+
}
|
|
106
147
|
if (!updates || !operation) {
|
|
107
148
|
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
108
149
|
return;
|
|
@@ -132,6 +173,22 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
132
173
|
}
|
|
133
174
|
}
|
|
134
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;
|
|
135
192
|
default:
|
|
136
193
|
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
137
194
|
break;
|
|
@@ -376,6 +433,35 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
376
433
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
377
434
|
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
378
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
|
+
};
|
|
379
465
|
const handleEncryptNotification = async (node) => {
|
|
380
466
|
const from = node.attrs.from;
|
|
381
467
|
if (from === S_WHATSAPP_NET) {
|
|
@@ -394,7 +480,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
394
480
|
validateSession: signalRepository.validateSession,
|
|
395
481
|
assertSessions,
|
|
396
482
|
debounceCache: identityAssertDebounce,
|
|
397
|
-
logger
|
|
483
|
+
logger,
|
|
484
|
+
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
398
485
|
});
|
|
399
486
|
if (result.action === 'no_identity_node') {
|
|
400
487
|
logger.info({ node }, 'unknown encrypt notification');
|
|
@@ -405,6 +492,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
405
492
|
// TODO: Support PN/LID (Here is only LID now)
|
|
406
493
|
const actingParticipantLid = fullNode.attrs.participant;
|
|
407
494
|
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
495
|
+
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
408
496
|
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
409
497
|
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
410
498
|
switch (child?.tag) {
|
|
@@ -424,7 +512,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
424
512
|
{
|
|
425
513
|
...metadata,
|
|
426
514
|
author: actingParticipantLid,
|
|
427
|
-
authorPn: actingParticipantPn
|
|
515
|
+
authorPn: actingParticipantPn,
|
|
516
|
+
authorUsername: actingParticipantUsername
|
|
428
517
|
}
|
|
429
518
|
]);
|
|
430
519
|
break;
|
|
@@ -455,6 +544,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
455
544
|
id: attrs.jid,
|
|
456
545
|
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
457
546
|
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
547
|
+
username: attrs.participant_username || attrs.username || undefined,
|
|
458
548
|
admin: (attrs.type || null)
|
|
459
549
|
};
|
|
460
550
|
});
|
|
@@ -680,27 +770,70 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
680
770
|
return result;
|
|
681
771
|
}
|
|
682
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
|
+
}
|
|
683
818
|
const handlePrivacyTokenNotification = async (node) => {
|
|
684
819
|
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
685
|
-
const from = jidNormalizedUser(node.attrs.from);
|
|
686
820
|
if (!tokensNode)
|
|
687
821
|
return;
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
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
|
+
});
|
|
704
837
|
};
|
|
705
838
|
async function decipherLinkPublicKey(data) {
|
|
706
839
|
const buffer = toRequiredBuffer(data);
|
|
@@ -819,11 +952,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
819
952
|
fromMe,
|
|
820
953
|
participant: attrs.participant
|
|
821
954
|
};
|
|
822
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
823
|
-
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
824
|
-
await sendMessageAck(node);
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
955
|
const ids = [attrs.id];
|
|
828
956
|
if (Array.isArray(content)) {
|
|
829
957
|
const items = getBinaryNodeChildren(content[0], 'item');
|
|
@@ -888,11 +1016,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
888
1016
|
};
|
|
889
1017
|
const handleNotification = async (node) => {
|
|
890
1018
|
const remoteJid = node.attrs.from;
|
|
891
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
892
|
-
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
893
|
-
await sendMessageAck(node);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
1019
|
try {
|
|
897
1020
|
await Promise.all([
|
|
898
1021
|
notificationMutex.mutex(async () => {
|
|
@@ -905,6 +1028,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
905
1028
|
fromMe,
|
|
906
1029
|
participant: node.attrs.participant,
|
|
907
1030
|
participantAlt,
|
|
1031
|
+
participantUsername: node.attrs.participant_username,
|
|
908
1032
|
addressingMode,
|
|
909
1033
|
id: node.attrs.id,
|
|
910
1034
|
...(msg.key || {})
|
|
@@ -922,11 +1046,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
922
1046
|
}
|
|
923
1047
|
};
|
|
924
1048
|
const handleMessage = async (node) => {
|
|
925
|
-
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== S_WHATSAPP_NET) {
|
|
926
|
-
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
927
|
-
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
1049
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
931
1050
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
932
1051
|
if (encNode?.attrs.type === 'msmsg') {
|
|
@@ -1144,6 +1263,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1144
1263
|
offline: !!attrs.offline,
|
|
1145
1264
|
status
|
|
1146
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
|
+
}
|
|
1147
1273
|
if (status === 'offer') {
|
|
1148
1274
|
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1149
1275
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
@@ -1188,7 +1314,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1188
1314
|
// error in acknowledgement,
|
|
1189
1315
|
// device could not display the message
|
|
1190
1316
|
if (attrs.error) {
|
|
1191
|
-
|
|
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
|
+
}
|
|
1192
1330
|
ev.emit('messages.update', [
|
|
1193
1331
|
{
|
|
1194
1332
|
key,
|
|
@@ -1198,19 +1336,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1198
1336
|
}
|
|
1199
1337
|
}
|
|
1200
1338
|
]);
|
|
1201
|
-
// resend the message with device_fanout=false, use at your own risk
|
|
1202
|
-
// if (attrs.error === '475') {
|
|
1203
|
-
// const msg = await getMessage(key)
|
|
1204
|
-
// if (msg) {
|
|
1205
|
-
// await relayMessage(key.remoteJid!, msg, {
|
|
1206
|
-
// messageId: key.id!,
|
|
1207
|
-
// useUserDevicesCache: false,
|
|
1208
|
-
// additionalAttributes: {
|
|
1209
|
-
// device_fanout: 'false'
|
|
1210
|
-
// }
|
|
1211
|
-
// })
|
|
1212
|
-
// }
|
|
1213
|
-
// }
|
|
1214
1339
|
}
|
|
1215
1340
|
};
|
|
1216
1341
|
/// processes a node with the given function
|
|
@@ -1234,6 +1359,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1234
1359
|
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1235
1360
|
});
|
|
1236
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
|
+
}
|
|
1237
1375
|
const isOffline = !!node.attrs.offline;
|
|
1238
1376
|
if (isOffline) {
|
|
1239
1377
|
offlineNodeProcessor.enqueue(type, node);
|
|
@@ -1289,14 +1427,104 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1289
1427
|
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1290
1428
|
}
|
|
1291
1429
|
});
|
|
1292
|
-
|
|
1430
|
+
/** timestamp of last tctoken prune run — throttles to once per 24h */
|
|
1431
|
+
let lastTcTokenPruneTs = 0;
|
|
1432
|
+
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1293
1433
|
if (typeof isOnline !== 'undefined') {
|
|
1294
1434
|
sendActiveReceipts = isOnline;
|
|
1295
1435
|
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1296
1436
|
}
|
|
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
|
+
}
|
|
1459
|
+
}
|
|
1297
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
|
+
}
|
|
1298
1522
|
return {
|
|
1299
1523
|
...sock,
|
|
1524
|
+
end: async (error) => {
|
|
1525
|
+
await cleanupInternalCaches();
|
|
1526
|
+
await sock.end(error);
|
|
1527
|
+
},
|
|
1300
1528
|
sendMessageAck,
|
|
1301
1529
|
sendRetryRequest,
|
|
1302
1530
|
rejectCall,
|