@fer2809fl/baileys 7.0.1 → 7.0.2
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 +259 -595
- package/WAProto/WAProto.proto +916 -360
- package/WAProto/fix-imports.js +18 -22
- package/WAProto/index.d.ts +15471 -8638
- package/WAProto/index.js +80576 -45941
- package/lib/Defaults/index.d.ts +14 -18
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +110 -115
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/Group/group_cipher.js.map +1 -1
- package/lib/Signal/Group/keyhelper.d.ts.map +1 -1
- package/lib/Signal/Group/keyhelper.js.map +1 -1
- package/lib/Signal/Group/sender-chain-key.js.map +1 -1
- package/lib/Signal/Group/sender-key-message.js.map +1 -1
- package/lib/Signal/Group/sender-message-key.js.map +1 -1
- package/lib/Signal/libsignal.d.ts.map +1 -1
- package/lib/Signal/libsignal.js +17 -45
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Signal/lid-mapping.d.ts +0 -4
- package/lib/Signal/lid-mapping.d.ts.map +1 -1
- package/lib/Signal/lid-mapping.js +0 -6
- package/lib/Signal/lid-mapping.js.map +1 -1
- package/lib/Socket/business.d.ts +532 -280
- package/lib/Socket/business.d.ts.map +1 -1
- package/lib/Socket/business.js +401 -358
- package/lib/Socket/chats.d.ts +2 -22
- package/lib/Socket/chats.d.ts.map +1 -1
- package/lib/Socket/chats.js +43 -314
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +641 -336
- package/lib/Socket/communities.d.ts.map +1 -1
- package/lib/Socket/communities.js +3 -6
- package/lib/Socket/communities.js.map +1 -1
- package/lib/Socket/dugong.d.ts +130 -0
- package/lib/Socket/dugong.d.ts.map +1 -0
- package/lib/Socket/dugong.js +772 -0
- package/lib/Socket/dugong.js.map +1 -0
- package/lib/Socket/groups.d.ts +2 -23
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +8 -171
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +631 -322
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/index.js +12 -9
- package/lib/Socket/index.js.map +1 -1
- package/lib/Socket/messages-recv.d.ts +52 -84
- package/lib/Socket/messages-recv.d.ts.map +1 -1
- package/lib/Socket/messages-recv.js +185 -686
- package/lib/Socket/messages-recv.js.map +1 -1
- package/lib/Socket/messages-send.d.ts +643 -262
- package/lib/Socket/messages-send.d.ts.map +1 -1
- package/lib/Socket/messages-send.js +1559 -1359
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/mex.d.ts +0 -1
- package/lib/Socket/mex.d.ts.map +1 -1
- package/lib/Socket/mex.js +8 -22
- package/lib/Socket/mex.js.map +1 -1
- package/lib/Socket/native-buttons.js +231 -0
- package/lib/Socket/newsletter.d.ts +27 -42
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/newsletter.js +439 -402
- package/lib/Socket/newsletter.js.map +1 -1
- package/lib/Socket/socket.d.ts +2 -6
- package/lib/Socket/socket.d.ts.map +1 -1
- package/lib/Socket/socket.js +26 -62
- package/lib/Socket/socket.js.map +1 -1
- package/lib/Types/Auth.d.ts +0 -1
- package/lib/Types/Auth.d.ts.map +1 -1
- package/lib/Types/Bussines.d.ts +34 -22
- package/lib/Types/Call.d.ts +1 -1
- package/lib/Types/Call.d.ts.map +1 -1
- package/lib/Types/Chat.d.ts +0 -1
- package/lib/Types/Chat.d.ts.map +1 -1
- package/lib/Types/Contact.d.ts +0 -2
- package/lib/Types/Contact.d.ts.map +1 -1
- package/lib/Types/Events.d.ts +1 -20
- package/lib/Types/Events.d.ts.map +1 -1
- package/lib/Types/GroupMetadata.d.ts +1 -4
- package/lib/Types/GroupMetadata.d.ts.map +1 -1
- package/lib/Types/Message.d.ts +348 -354
- package/lib/Types/Message.d.ts.map +1 -1
- package/lib/Types/Message.js +0 -4
- package/lib/Types/Message.js.map +1 -1
- package/lib/Types/{Mex.d.ts → Newsletter.d.ts} +7 -26
- package/lib/Types/Newsletter.d.ts.map +1 -0
- package/lib/Types/{Mex.js → Newsletter.js} +3 -11
- package/lib/Types/Newsletter.js.map +1 -0
- package/lib/Types/Signal.d.ts +1 -12
- package/lib/Types/Signal.d.ts.map +1 -1
- package/lib/Types/Socket.d.ts +148 -135
- package/lib/Types/Socket.d.ts.map +1 -1
- package/lib/Types/Socket.js.map +1 -1
- package/lib/Types/State.d.ts +0 -65
- package/lib/Types/State.d.ts.map +1 -1
- package/lib/Types/State.js +0 -43
- package/lib/Types/State.js.map +1 -1
- package/lib/Types/index.d.ts +52 -52
- package/lib/Types/index.d.ts.map +1 -1
- package/lib/Types/index.js +30 -23
- package/lib/Types/index.js.map +1 -1
- package/lib/Utils/auth-utils.d.ts +0 -5
- package/lib/Utils/auth-utils.d.ts.map +1 -1
- package/lib/Utils/auth-utils.js +0 -12
- package/lib/Utils/auth-utils.js.map +1 -1
- package/lib/Utils/browser-utils.d.ts.map +1 -1
- package/lib/Utils/browser-utils.js +0 -1
- package/lib/Utils/browser-utils.js.map +1 -1
- package/lib/Utils/chat-utils.d.ts +4 -34
- package/lib/Utils/chat-utils.d.ts.map +1 -1
- package/lib/Utils/chat-utils.js +20 -80
- package/lib/Utils/chat-utils.js.map +1 -1
- package/lib/Utils/crypto.d.ts +12 -12
- package/lib/Utils/crypto.d.ts.map +1 -1
- package/lib/Utils/crypto.js.map +1 -1
- package/lib/Utils/decode-wa-message.d.ts +0 -18
- package/lib/Utils/decode-wa-message.d.ts.map +1 -1
- package/lib/Utils/decode-wa-message.js +0 -34
- 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 +1 -49
- package/lib/Utils/event-buffer.js.map +1 -1
- package/lib/Utils/generics.d.ts +2 -3
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +1 -10
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/history.d.ts +0 -2
- package/lib/Utils/history.d.ts.map +1 -1
- package/lib/Utils/history.js +9 -12
- package/lib/Utils/history.js.map +1 -1
- package/lib/Utils/identity-change-handler.d.ts +0 -7
- package/lib/Utils/identity-change-handler.d.ts.map +1 -1
- package/lib/Utils/identity-change-handler.js +0 -1
- package/lib/Utils/identity-change-handler.js.map +1 -1
- package/lib/Utils/index.d.ts +23 -30
- package/lib/Utils/index.d.ts.map +1 -1
- package/lib/Utils/index.js +2 -9
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/link-preview.js +2 -2
- package/lib/Utils/link-preview.js.map +1 -1
- package/lib/Utils/message-retry-manager.d.ts +0 -5
- package/lib/Utils/message-retry-manager.d.ts.map +1 -1
- package/lib/Utils/message-retry-manager.js +0 -40
- package/lib/Utils/message-retry-manager.js.map +1 -1
- package/lib/Utils/messages-media.d.ts +13 -45
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +98 -293
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts +3 -16
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +1123 -1219
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/noise-handler.d.ts +1 -1
- package/lib/Utils/process-message.d.ts.map +1 -1
- package/lib/Utils/process-message.js +1 -103
- package/lib/Utils/process-message.js.map +1 -1
- package/lib/Utils/rich-messages.d.ts +277 -0
- package/lib/Utils/rich-messages.d.ts.map +1 -0
- package/lib/Utils/rich-messages.js +1144 -0
- package/lib/Utils/rich-messages.js.map +1 -0
- package/lib/Utils/signal.d.ts +0 -13
- package/lib/Utils/signal.d.ts.map +1 -1
- package/lib/Utils/signal.js +0 -42
- package/lib/Utils/signal.js.map +1 -1
- package/lib/Utils/sticker-pack.d.ts +10 -0
- package/lib/Utils/sticker-pack.js +111 -0
- package/lib/Utils/sync-action-utils.d.ts.map +1 -1
- package/lib/Utils/sync-action-utils.js +0 -1
- package/lib/Utils/sync-action-utils.js.map +1 -1
- package/lib/Utils/tc-token-utils.d.ts +1 -26
- package/lib/Utils/tc-token-utils.d.ts.map +1 -1
- package/lib/Utils/tc-token-utils.js +5 -151
- package/lib/Utils/tc-token-utils.js.map +1 -1
- package/lib/Utils/validate-connection.d.ts +0 -1
- package/lib/Utils/validate-connection.d.ts.map +1 -1
- package/lib/Utils/validate-connection.js +3 -13
- package/lib/Utils/validate-connection.js.map +1 -1
- package/lib/WABinary/decode.d.ts +1 -1
- package/lib/WABinary/decode.d.ts.map +1 -1
- package/lib/WABinary/generic-utils.d.ts +3 -1
- package/lib/WABinary/generic-utils.d.ts.map +1 -1
- package/lib/WABinary/generic-utils.js +78 -0
- package/lib/WABinary/generic-utils.js.map +1 -1
- package/lib/WABinary/jid-utils.d.ts +0 -11
- package/lib/WABinary/jid-utils.d.ts.map +1 -1
- package/lib/WABinary/jid-utils.js +0 -33
- package/lib/WABinary/jid-utils.js.map +1 -1
- package/lib/WAM/encode.d.ts +1 -1
- package/lib/WAM/encode.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +3 -26
- package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -1
- package/lib/WAUSync/Protocols/index.d.ts +0 -1
- package/lib/WAUSync/Protocols/index.d.ts.map +1 -1
- package/lib/WAUSync/Protocols/index.js +0 -1
- package/lib/WAUSync/Protocols/index.js.map +1 -1
- package/lib/WAUSync/USyncQuery.d.ts +0 -1
- package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
- package/lib/WAUSync/USyncQuery.js +2 -6
- package/lib/WAUSync/USyncQuery.js.map +1 -1
- package/lib/WAUSync/USyncUser.d.ts +0 -4
- package/lib/WAUSync/USyncUser.d.ts.map +1 -1
- package/lib/WAUSync/USyncUser.js +0 -8
- package/lib/WAUSync/USyncUser.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -4
- package/lib/index.js.map +1 -1
- package/package.json +30 -34
- package/LICENSE +0 -21
- package/engine-requirements.js +0 -10
- package/lib/Socket/smgss.d.ts +0 -119
- package/lib/Socket/smgss.d.ts.map +0 -1
- package/lib/Socket/smgss.js +0 -453
- package/lib/Socket/smgss.js.map +0 -1
- package/lib/Store/index.d.ts +0 -4
- package/lib/Store/index.d.ts.map +0 -1
- package/lib/Store/index.js +0 -4
- package/lib/Store/index.js.map +0 -1
- package/lib/Store/make-in-memory-store.d.ts +0 -132
- package/lib/Store/make-in-memory-store.d.ts.map +0 -1
- package/lib/Store/make-in-memory-store.js +0 -450
- package/lib/Store/make-in-memory-store.js.map +0 -1
- package/lib/Store/make-ordered-dictionary.d.ts +0 -14
- package/lib/Store/make-ordered-dictionary.d.ts.map +0 -1
- package/lib/Store/make-ordered-dictionary.js +0 -80
- package/lib/Store/make-ordered-dictionary.js.map +0 -1
- package/lib/Store/object-repository.d.ts +0 -13
- package/lib/Store/object-repository.d.ts.map +0 -1
- package/lib/Store/object-repository.js +0 -24
- package/lib/Store/object-repository.js.map +0 -1
- package/lib/Types/Mex.d.ts.map +0 -1
- package/lib/Types/Mex.js.map +0 -1
- package/lib/Utils/button-handler.d.ts +0 -28
- package/lib/Utils/button-handler.d.ts.map +0 -1
- package/lib/Utils/button-handler.js +0 -140
- package/lib/Utils/button-handler.js.map +0 -1
- package/lib/Utils/companion-reg-client-utils.d.ts +0 -17
- package/lib/Utils/companion-reg-client-utils.d.ts.map +0 -1
- package/lib/Utils/companion-reg-client-utils.js +0 -35
- package/lib/Utils/companion-reg-client-utils.js.map +0 -1
- package/lib/Utils/interactive.d.ts +0 -60
- package/lib/Utils/interactive.d.ts.map +0 -1
- package/lib/Utils/interactive.js +0 -224
- package/lib/Utils/interactive.js.map +0 -1
- package/lib/Utils/message-composer.d.ts +0 -93
- package/lib/Utils/message-composer.d.ts.map +0 -1
- package/lib/Utils/message-composer.js +0 -311
- package/lib/Utils/message-composer.js.map +0 -1
- package/lib/Utils/messages-newsletter.d.ts +0 -74
- package/lib/Utils/messages-newsletter.d.ts.map +0 -1
- package/lib/Utils/messages-newsletter.js +0 -128
- package/lib/Utils/messages-newsletter.js.map +0 -1
- package/lib/Utils/shortcake-crypto.d.ts +0 -36
- package/lib/Utils/shortcake-crypto.d.ts.map +0 -1
- package/lib/Utils/shortcake-crypto.js +0 -71
- package/lib/Utils/shortcake-crypto.js.map +0 -1
- package/lib/Utils/shortcake.d.ts +0 -36
- package/lib/Utils/shortcake.d.ts.map +0 -1
- package/lib/Utils/shortcake.js +0 -162
- package/lib/Utils/shortcake.js.map +0 -1
- package/lib/Utils/use-single-file-auth-state.d.ts +0 -6
- package/lib/Utils/use-single-file-auth-state.d.ts.map +0 -1
- package/lib/Utils/use-single-file-auth-state.js +0 -111
- package/lib/Utils/use-single-file-auth-state.js.map +0 -1
- package/lib/Utils/use-sqlite-auth-state.d.ts +0 -14
- package/lib/Utils/use-sqlite-auth-state.d.ts.map +0 -1
- package/lib/Utils/use-sqlite-auth-state.js +0 -107
- package/lib/Utils/use-sqlite-auth-state.js.map +0 -1
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +0 -10
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +0 -25
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +0 -1
|
@@ -4,56 +4,18 @@ import { randomBytes } from 'crypto';
|
|
|
4
4
|
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
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
+
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
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 {
|
|
13
|
-
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
12
|
+
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
14
13
|
import { extractGroupMetadata } from './groups.js';
|
|
15
14
|
import { makeMessagesSocket } from './messages-send.js';
|
|
16
|
-
const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
|
|
17
|
-
function isValidEnforcementType(value) {
|
|
18
|
-
return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
|
|
19
|
-
}
|
|
20
15
|
export const makeMessagesRecvSocket = (config) => {
|
|
21
16
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
22
17
|
const sock = makeMessagesSocket(config);
|
|
23
|
-
const {
|
|
24
|
-
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
25
|
-
/** Shortcake passkey-linking flow, only when a `signPasskeyAssertion` is configured. */
|
|
26
|
-
const shortcakeFlow = config.signPasskeyAssertion
|
|
27
|
-
? makeShortcakeFlow({
|
|
28
|
-
logger,
|
|
29
|
-
query,
|
|
30
|
-
signAssertion: config.signPasskeyAssertion,
|
|
31
|
-
getCreds: () => authState.creds,
|
|
32
|
-
updateCreds: patch => ev.emit('creds.update', patch),
|
|
33
|
-
deviceType: getPlatformType(config.browser[1]),
|
|
34
|
-
emitVerificationCode: code => logger.debug({ code }, 'shortcake verification code')
|
|
35
|
-
})
|
|
36
|
-
: null;
|
|
37
|
-
registerSocketEndHandler(() => shortcakeFlow?.clearSession());
|
|
38
|
-
/** Serializes prologue -> continuation off the notification mutex, so the ack isn't blocked by the handshake. */
|
|
39
|
-
const shortcakeMutex = makeMutex();
|
|
40
|
-
/** Runs the forced-passkey handshake (detached); with no signer, surfaces `passkeyRequired` and warns. */
|
|
41
|
-
const handleShortcakeNotification = (node) => {
|
|
42
|
-
if (node.attrs.type === 'passkey_prologue_request') {
|
|
43
|
-
ev.emit('connection.update', { passkeyRequired: { hasSigner: !!shortcakeFlow } });
|
|
44
|
-
}
|
|
45
|
-
if (!shortcakeFlow) {
|
|
46
|
-
if (node.attrs.type === 'passkey_prologue_request') {
|
|
47
|
-
logger.warn({ id: node.attrs.id }, 'server requested passkey prologue but no signPasskeyAssertion configured');
|
|
48
|
-
}
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
// run detached + serialized: the notification is acked immediately, and the
|
|
52
|
-
// handshake (external signer + follow-up IQs) never blocks other notifications
|
|
53
|
-
shortcakeMutex
|
|
54
|
-
.mutex(() => shortcakeFlow.handleIncomingNotification(node))
|
|
55
|
-
.catch(err => logger.error({ err, id: node.attrs.id }, 'shortcake handshake failed'));
|
|
56
|
-
};
|
|
18
|
+
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
|
|
57
19
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
58
20
|
const retryMutex = makeMutex();
|
|
59
21
|
const msgRetryCache = config.msgRetryCounterCache ||
|
|
@@ -66,6 +28,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
66
28
|
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
67
29
|
useClones: false
|
|
68
30
|
});
|
|
31
|
+
const placeholderResendCache = config.placeholderResendCache ||
|
|
32
|
+
new NodeCache({
|
|
33
|
+
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
34
|
+
useClones: false
|
|
35
|
+
});
|
|
69
36
|
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
70
37
|
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
|
|
71
38
|
let sendActiveReceipts = false;
|
|
@@ -119,140 +86,25 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
119
86
|
}, 8000);
|
|
120
87
|
return sendPeerDataOperationMessage(pdoMessage);
|
|
121
88
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (updateNode) {
|
|
125
|
-
const opName = updateNode.attrs?.op_name;
|
|
126
|
-
if (!opName) {
|
|
127
|
-
logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name, fallback to legacy');
|
|
128
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
let mexResponse;
|
|
132
|
-
try {
|
|
133
|
-
mexResponse = JSON.parse(updateNode.content.toString());
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (mexResponse.errors?.length) {
|
|
140
|
-
logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const data = mexResponse.data;
|
|
144
|
-
if (!data) {
|
|
145
|
-
logger.warn({ opName }, 'mex notification has null data');
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
logger.debug({ opName }, 'processing mex notification');
|
|
149
|
-
switch (opName) {
|
|
150
|
-
case 'NotificationUserReachoutTimelockUpdate':
|
|
151
|
-
handleReachoutTimelockNotification(data);
|
|
152
|
-
break;
|
|
153
|
-
case 'MessageCappingInfoNotification':
|
|
154
|
-
handleMessageCappingNotification(data);
|
|
155
|
-
break;
|
|
156
|
-
// newsletter ops still use the legacy <mex> child structure
|
|
157
|
-
case 'NotificationNewsletterUpdate':
|
|
158
|
-
case 'NotificationLinkedProfilesUpdates':
|
|
159
|
-
case 'NotificationNewsletterAdminPromote':
|
|
160
|
-
case 'NotificationNewsletterAdminDemote':
|
|
161
|
-
case 'NotificationNewsletterUserSettingChange':
|
|
162
|
-
case 'NotificationNewsletterJoin':
|
|
163
|
-
case 'NotificationNewsletterLeave':
|
|
164
|
-
case 'NotificationNewsletterStateChange':
|
|
165
|
-
case 'NotificationNewsletterAdminMetadataUpdate':
|
|
166
|
-
case 'NotificationNewsletterOwnerUpdate':
|
|
167
|
-
case 'NotificationNewsletterAdminInviteRevoke':
|
|
168
|
-
case 'NotificationNewsletterWamoSubStatusChange':
|
|
169
|
-
case 'NotificationNewsletterBlockUser':
|
|
170
|
-
case 'NotificationNewsletterPaidPartnership':
|
|
171
|
-
case 'NotificationNewsletterMilestone':
|
|
172
|
-
case 'NewsletterResponseStateUpdate':
|
|
173
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
174
|
-
break;
|
|
175
|
-
default:
|
|
176
|
-
logger.debug({ opName }, 'unhandled mex notification');
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
182
|
-
};
|
|
183
|
-
const handleReachoutTimelockNotification = (data) => {
|
|
184
|
-
const payload = data.xwa2_notify_account_reachout_timelock;
|
|
185
|
-
if (!payload) {
|
|
186
|
-
logger.warn('reachout timelock notification missing payload');
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (!payload.is_active) {
|
|
190
|
-
logger.info('reachout timelock restriction lifted');
|
|
191
|
-
ev.emit('connection.update', {
|
|
192
|
-
reachoutTimeLock: {
|
|
193
|
-
isActive: false,
|
|
194
|
-
enforcementType: ReachoutTimelockEnforcementType.DEFAULT
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
// WA Web defaults to now+60s when the server omits the expiry
|
|
200
|
-
const timeEnforcementEnds = payload.time_enforcement_ends
|
|
201
|
-
? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
|
|
202
|
-
: new Date(Date.now() + 60000);
|
|
203
|
-
const enforcementType = isValidEnforcementType(payload.enforcement_type)
|
|
204
|
-
? payload.enforcement_type
|
|
205
|
-
: ReachoutTimelockEnforcementType.DEFAULT;
|
|
206
|
-
logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
|
|
207
|
-
ev.emit('connection.update', {
|
|
208
|
-
reachoutTimeLock: {
|
|
209
|
-
isActive: true,
|
|
210
|
-
timeEnforcementEnds,
|
|
211
|
-
enforcementType
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
};
|
|
215
|
-
const handleMessageCappingNotification = (data) => {
|
|
216
|
-
const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
|
|
217
|
-
if (!payload) {
|
|
218
|
-
logger.warn('message capping notification missing payload');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
logger.info({ payload }, 'received message capping update');
|
|
222
|
-
ev.emit('message-capping.update', payload);
|
|
223
|
-
};
|
|
224
|
-
const handleLegacyMexNewsletterNotification = async (node) => {
|
|
89
|
+
// Handles mex newsletter notifications
|
|
90
|
+
const handleMexNewsletterNotification = async (node) => {
|
|
225
91
|
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (!payloadNode?.content) {
|
|
229
|
-
logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
|
|
92
|
+
if (!mexNode?.content) {
|
|
93
|
+
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
230
94
|
return;
|
|
231
95
|
}
|
|
232
96
|
let data;
|
|
233
97
|
try {
|
|
234
|
-
|
|
235
|
-
if (Array.isArray(payloadContent)) {
|
|
236
|
-
logger.warn({ payloadNode }, 'invalid mex newsletter notification payload format');
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
|
|
240
|
-
data = JSON.parse(contentBuf.toString());
|
|
98
|
+
data = JSON.parse(mexNode.content.toString());
|
|
241
99
|
}
|
|
242
100
|
catch (error) {
|
|
243
|
-
logger.error({ err: error, node
|
|
101
|
+
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
244
102
|
return;
|
|
245
103
|
}
|
|
246
|
-
const operation = data?.operation
|
|
247
|
-
|
|
248
|
-
if (!updates) {
|
|
249
|
-
const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
|
|
250
|
-
if (linkedProfiles) {
|
|
251
|
-
updates = [linkedProfiles];
|
|
252
|
-
}
|
|
253
|
-
}
|
|
104
|
+
const operation = data?.operation;
|
|
105
|
+
const updates = data?.updates;
|
|
254
106
|
if (!updates || !operation) {
|
|
255
|
-
logger.warn({ data }, '
|
|
107
|
+
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
256
108
|
return;
|
|
257
109
|
}
|
|
258
110
|
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
@@ -280,114 +132,91 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
280
132
|
}
|
|
281
133
|
}
|
|
282
134
|
break;
|
|
283
|
-
case 'NotificationLinkedProfilesUpdates':
|
|
284
|
-
for (const update of updates) {
|
|
285
|
-
const lid = update?.jid;
|
|
286
|
-
const addedProfiles = Array.isArray(update?.added_profiles) ? update.added_profiles : [];
|
|
287
|
-
const mappings = [];
|
|
288
|
-
for (const profile of addedProfiles) {
|
|
289
|
-
const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
|
|
290
|
-
if (lid && pn) {
|
|
291
|
-
const mapping = { lid, pn };
|
|
292
|
-
ev.emit('lid-mapping.update', mapping);
|
|
293
|
-
mappings.push(mapping);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
await signalRepository.lidMapping.storeLIDPNMappings(mappings);
|
|
297
|
-
}
|
|
298
|
-
break;
|
|
299
135
|
default:
|
|
300
|
-
logger.info({ operation, data }, '
|
|
136
|
+
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
301
137
|
break;
|
|
302
138
|
}
|
|
303
139
|
};
|
|
304
140
|
// Handles newsletter notifications
|
|
305
141
|
const handleNewsletterNotification = async (node) => {
|
|
306
142
|
const from = node.attrs.from;
|
|
307
|
-
const
|
|
143
|
+
const child = getAllBinaryNodeChildren(node)[0];
|
|
308
144
|
const author = node.attrs.participant;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
145
|
+
logger.info({ from, child }, 'got newsletter notification');
|
|
146
|
+
switch (child.tag) {
|
|
147
|
+
case 'reaction':
|
|
148
|
+
const reactionUpdate = {
|
|
149
|
+
id: from,
|
|
150
|
+
server_id: child.attrs.message_id,
|
|
151
|
+
reaction: {
|
|
152
|
+
code: getBinaryNodeChildString(child, 'reaction'),
|
|
153
|
+
count: 1
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
ev.emit('newsletter.reaction', reactionUpdate);
|
|
157
|
+
break;
|
|
158
|
+
case 'view':
|
|
159
|
+
const viewUpdate = {
|
|
160
|
+
id: from,
|
|
161
|
+
server_id: child.attrs.message_id,
|
|
162
|
+
count: parseInt(child.content?.toString() || '0', 10)
|
|
163
|
+
};
|
|
164
|
+
ev.emit('newsletter.view', viewUpdate);
|
|
165
|
+
break;
|
|
166
|
+
case 'participant':
|
|
167
|
+
const participantUpdate = {
|
|
168
|
+
id: from,
|
|
169
|
+
author,
|
|
170
|
+
user: child.attrs.jid,
|
|
171
|
+
action: child.attrs.action,
|
|
172
|
+
new_role: child.attrs.role
|
|
173
|
+
};
|
|
174
|
+
ev.emit('newsletter-participants.update', participantUpdate);
|
|
175
|
+
break;
|
|
176
|
+
case 'update':
|
|
177
|
+
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
178
|
+
if (settingsNode) {
|
|
179
|
+
const update = {};
|
|
180
|
+
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
181
|
+
if (nameNode?.content)
|
|
182
|
+
update.name = nameNode.content.toString();
|
|
183
|
+
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
184
|
+
if (descriptionNode?.content)
|
|
185
|
+
update.description = descriptionNode.content.toString();
|
|
186
|
+
ev.emit('newsletter-settings.update', {
|
|
335
187
|
id: from,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
action: child.attrs.action,
|
|
339
|
-
new_role: child.attrs.role
|
|
340
|
-
};
|
|
341
|
-
ev.emit('newsletter-participants.update', participantUpdate);
|
|
342
|
-
break;
|
|
188
|
+
update
|
|
189
|
+
});
|
|
343
190
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
191
|
+
break;
|
|
192
|
+
case 'message':
|
|
193
|
+
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
194
|
+
if (plaintextNode?.content) {
|
|
195
|
+
try {
|
|
196
|
+
const contentBuf = typeof plaintextNode.content === 'string'
|
|
197
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
198
|
+
: Buffer.from(plaintextNode.content);
|
|
199
|
+
const messageProto = proto.Message.decode(contentBuf).toJSON();
|
|
200
|
+
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
201
|
+
key: {
|
|
202
|
+
remoteJid: from,
|
|
203
|
+
id: child.attrs.message_id || child.attrs.server_id,
|
|
204
|
+
fromMe: false // TODO: is this really true though
|
|
205
|
+
},
|
|
206
|
+
message: messageProto,
|
|
207
|
+
messageTimestamp: +child.attrs.t
|
|
208
|
+
}).toJSON();
|
|
209
|
+
await upsertMessage(fullMessage, 'append');
|
|
210
|
+
logger.info('Processed plaintext newsletter message');
|
|
358
211
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
case 'message': {
|
|
362
|
-
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
363
|
-
if (plaintextNode?.content) {
|
|
364
|
-
try {
|
|
365
|
-
const contentBuf = typeof plaintextNode.content === 'string'
|
|
366
|
-
? Buffer.from(plaintextNode.content, 'binary')
|
|
367
|
-
: Buffer.from(plaintextNode.content);
|
|
368
|
-
const messageProto = proto.Message.decode(contentBuf).toJSON();
|
|
369
|
-
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
370
|
-
key: {
|
|
371
|
-
remoteJid: from,
|
|
372
|
-
id: child.attrs.message_id || child.attrs.server_id,
|
|
373
|
-
fromMe: false // TODO: is this really true though
|
|
374
|
-
},
|
|
375
|
-
message: messageProto,
|
|
376
|
-
messageTimestamp: +child.attrs.t
|
|
377
|
-
}).toJSON();
|
|
378
|
-
await upsertMessage(fullMessage, 'append');
|
|
379
|
-
logger.debug('Processed plaintext newsletter message');
|
|
380
|
-
}
|
|
381
|
-
catch (error) {
|
|
382
|
-
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
383
|
-
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
384
214
|
}
|
|
385
|
-
break;
|
|
386
215
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
logger.warn({ node }, 'Unknown newsletter notification');
|
|
219
|
+
break;
|
|
391
220
|
}
|
|
392
221
|
};
|
|
393
222
|
const sendMessageAck = async (node, errorCode) => {
|
|
@@ -547,58 +376,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
547
376
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
548
377
|
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
549
378
|
};
|
|
550
|
-
// Mirrors WAWeb/Handle/PreKeyLow.js: skip a re-issued notification with the same stanza id.
|
|
551
|
-
const inFlightPreKeyLow = new Set();
|
|
552
|
-
/**
|
|
553
|
-
* Fire-and-forget tctoken re-issuance after a peer's device identity changed.
|
|
554
|
-
* Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
|
|
555
|
-
* the session refresh (not after it).
|
|
556
|
-
*/
|
|
557
|
-
const reissueTcTokenAfterIdentityChange = (from) => {
|
|
558
|
-
void (async () => {
|
|
559
|
-
const normalizedJid = jidNormalizedUser(from);
|
|
560
|
-
const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
|
|
561
|
-
const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
|
|
562
|
-
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
|
|
563
|
-
if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
|
|
567
|
-
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
568
|
-
const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
569
|
-
const result = await issuePrivacyTokens([issueJid], senderTs);
|
|
570
|
-
await storeTcTokensFromIqResult({
|
|
571
|
-
result,
|
|
572
|
-
fallbackJid: tcJid,
|
|
573
|
-
keys: authState.keys,
|
|
574
|
-
getLIDForPN,
|
|
575
|
-
onNewJidStored: trackTcTokenJid
|
|
576
|
-
});
|
|
577
|
-
})().catch(err => {
|
|
578
|
-
logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
|
|
579
|
-
});
|
|
580
|
-
};
|
|
581
379
|
const handleEncryptNotification = async (node) => {
|
|
582
380
|
const from = node.attrs.from;
|
|
583
381
|
if (from === S_WHATSAPP_NET) {
|
|
584
|
-
const stanzaId = node.attrs.id;
|
|
585
|
-
if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
382
|
const countChild = getBinaryNodeChild(node, 'count');
|
|
589
383
|
const count = +countChild.attrs.value;
|
|
590
384
|
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
|
|
591
385
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
592
386
|
if (shouldUploadMorePreKeys) {
|
|
593
|
-
|
|
594
|
-
inFlightPreKeyLow.add(stanzaId);
|
|
595
|
-
try {
|
|
596
|
-
await uploadPreKeys();
|
|
597
|
-
}
|
|
598
|
-
finally {
|
|
599
|
-
if (stanzaId)
|
|
600
|
-
inFlightPreKeyLow.delete(stanzaId);
|
|
601
|
-
}
|
|
387
|
+
await uploadPreKeys();
|
|
602
388
|
}
|
|
603
389
|
}
|
|
604
390
|
else {
|
|
@@ -608,8 +394,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
608
394
|
validateSession: signalRepository.validateSession,
|
|
609
395
|
assertSessions,
|
|
610
396
|
debounceCache: identityAssertDebounce,
|
|
611
|
-
logger
|
|
612
|
-
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
397
|
+
logger
|
|
613
398
|
});
|
|
614
399
|
if (result.action === 'no_identity_node') {
|
|
615
400
|
logger.info({ node }, 'unknown encrypt notification');
|
|
@@ -620,7 +405,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
620
405
|
// TODO: Support PN/LID (Here is only LID now)
|
|
621
406
|
const actingParticipantLid = fullNode.attrs.participant;
|
|
622
407
|
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
623
|
-
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
624
408
|
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
625
409
|
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
626
410
|
switch (child?.tag) {
|
|
@@ -640,8 +424,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
640
424
|
{
|
|
641
425
|
...metadata,
|
|
642
426
|
author: actingParticipantLid,
|
|
643
|
-
authorPn: actingParticipantPn
|
|
644
|
-
authorUsername: actingParticipantUsername
|
|
427
|
+
authorPn: actingParticipantPn
|
|
645
428
|
}
|
|
646
429
|
]);
|
|
647
430
|
break;
|
|
@@ -667,13 +450,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
667
450
|
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
668
451
|
msg.messageStubType = WAMessageStubType[stubType];
|
|
669
452
|
const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
|
|
670
|
-
|
|
671
|
-
const hasPn = isPnUser(attrs.phone_number);
|
|
453
|
+
// TODO: Store LID MAPPINGS
|
|
672
454
|
return {
|
|
673
|
-
id:
|
|
674
|
-
phoneNumber:
|
|
675
|
-
lid:
|
|
676
|
-
username: attrs.participant_username || attrs.username || undefined,
|
|
455
|
+
id: attrs.jid,
|
|
456
|
+
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
457
|
+
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
677
458
|
admin: (attrs.type || null)
|
|
678
459
|
};
|
|
679
460
|
});
|
|
@@ -743,89 +524,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
743
524
|
break;
|
|
744
525
|
}
|
|
745
526
|
};
|
|
746
|
-
const handleDevicesNotification = async (node) => {
|
|
747
|
-
const [child] = getAllBinaryNodeChildren(node);
|
|
748
|
-
const from = jidNormalizedUser(node.attrs.from);
|
|
749
|
-
if (!child) {
|
|
750
|
-
logger.debug({ from }, 'devices notification missing child, skipping');
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
const tag = child.tag;
|
|
754
|
-
const deviceHash = child.attrs.device_hash;
|
|
755
|
-
const devices = getBinaryNodeChildren(child, 'device');
|
|
756
|
-
if (areJidsSameUser(from, authState.creds.me.id) || areJidsSameUser(from, authState.creds.me.lid)) {
|
|
757
|
-
const deviceJids = devices.map(d => d.attrs.jid);
|
|
758
|
-
logger.info({ deviceJids }, 'got my own devices');
|
|
759
|
-
}
|
|
760
|
-
if (!devices.length) {
|
|
761
|
-
logger.debug({ from, tag }, 'no devices in notification, skipping');
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
const decoded = [];
|
|
765
|
-
for (const d of devices) {
|
|
766
|
-
const jid = d.attrs.jid;
|
|
767
|
-
if (!jid)
|
|
768
|
-
continue;
|
|
769
|
-
const parts = jidDecode(jid);
|
|
770
|
-
if (!parts) {
|
|
771
|
-
logger.debug({ jid }, 'failed to decode device jid, skipping');
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
decoded.push({ jid, user: parts.user, server: parts.server, device: parts.device });
|
|
775
|
-
}
|
|
776
|
-
if (!decoded.length)
|
|
777
|
-
return;
|
|
778
|
-
await devicesMutex.mutex(async () => {
|
|
779
|
-
const byUser = new Map();
|
|
780
|
-
for (const d of decoded) {
|
|
781
|
-
const list = byUser.get(d.user) || [];
|
|
782
|
-
list.push(d);
|
|
783
|
-
byUser.set(d.user, list);
|
|
784
|
-
}
|
|
785
|
-
for (const [user, entries] of byUser) {
|
|
786
|
-
if (tag === 'update') {
|
|
787
|
-
logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
|
|
788
|
-
await userDevicesCache?.del(user);
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
if (tag === 'remove') {
|
|
792
|
-
await signalRepository.deleteSession(entries.map(e => e.jid));
|
|
793
|
-
}
|
|
794
|
-
const existingCache = (await userDevicesCache?.get(user)) || [];
|
|
795
|
-
if (!existingCache.length) {
|
|
796
|
-
// No baseline yet; skip applying the delta so getUSyncDevices can
|
|
797
|
-
// later fetch the full device list. Caching just the notification
|
|
798
|
-
// entries would make a partial list look authoritative.
|
|
799
|
-
logger.debug({ user, tag }, 'device list not cached, deferring to USync refresh');
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
const affected = new Set(entries.map(e => e.device));
|
|
803
|
-
let updatedDevices;
|
|
804
|
-
switch (tag) {
|
|
805
|
-
case 'add':
|
|
806
|
-
logger.info({ deviceHash, count: entries.length }, 'devices added');
|
|
807
|
-
updatedDevices = [
|
|
808
|
-
...existingCache.filter(d => !affected.has(d.device)),
|
|
809
|
-
...entries.map(e => ({ user: e.user, server: e.server, device: e.device }))
|
|
810
|
-
];
|
|
811
|
-
break;
|
|
812
|
-
case 'remove':
|
|
813
|
-
logger.info({ deviceHash, count: entries.length }, 'devices removed');
|
|
814
|
-
updatedDevices = existingCache.filter(d => !affected.has(d.device));
|
|
815
|
-
break;
|
|
816
|
-
default:
|
|
817
|
-
logger.debug({ tag }, 'Unknown device list change tag');
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
if (updatedDevices.length === 0) {
|
|
821
|
-
await userDevicesCache?.del(user);
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
await userDevicesCache?.set(user, updatedDevices);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
};
|
|
829
527
|
const processNotification = async (node) => {
|
|
830
528
|
const result = {};
|
|
831
529
|
const [child] = getAllBinaryNodeChildren(node);
|
|
@@ -836,7 +534,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
836
534
|
await handleNewsletterNotification(node);
|
|
837
535
|
break;
|
|
838
536
|
case 'mex':
|
|
839
|
-
await
|
|
537
|
+
await handleMexNewsletterNotification(node);
|
|
840
538
|
break;
|
|
841
539
|
case 'w:gp2':
|
|
842
540
|
// TODO: HANDLE PARTICIPANT_PN
|
|
@@ -850,12 +548,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
850
548
|
await handleEncryptNotification(node);
|
|
851
549
|
break;
|
|
852
550
|
case 'devices':
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
logger.
|
|
551
|
+
const devices = getBinaryNodeChildren(child, 'device');
|
|
552
|
+
if (areJidsSameUser(child.attrs.jid, authState.creds.me.id) ||
|
|
553
|
+
areJidsSameUser(child.attrs.lid, authState.creds.me.lid)) {
|
|
554
|
+
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }));
|
|
555
|
+
logger.info({ deviceData }, 'my own devices changed');
|
|
858
556
|
}
|
|
557
|
+
//TODO: drop a new event, add hashes
|
|
859
558
|
break;
|
|
860
559
|
case 'server_sync':
|
|
861
560
|
const update = getBinaryNodeChild(node, 'collection');
|
|
@@ -973,10 +672,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
973
672
|
authState.creds.registered = true;
|
|
974
673
|
ev.emit('creds.update', authState.creds);
|
|
975
674
|
break;
|
|
976
|
-
case 'passkey_prologue_request':
|
|
977
|
-
case 'crsc_continuation':
|
|
978
|
-
handleShortcakeNotification(node);
|
|
979
|
-
break;
|
|
980
675
|
case 'privacy_token':
|
|
981
676
|
await handlePrivacyTokenNotification(node);
|
|
982
677
|
break;
|
|
@@ -985,70 +680,27 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
985
680
|
return result;
|
|
986
681
|
}
|
|
987
682
|
};
|
|
988
|
-
/**
|
|
989
|
-
* In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
|
|
990
|
-
* Used to coalesce writes during a session; pruning always re-reads the persisted index
|
|
991
|
-
* to cover writes made by other layers (e.g. history sync).
|
|
992
|
-
*/
|
|
993
|
-
const tcTokenKnownJids = new Set();
|
|
994
|
-
const tcTokenIndexLoaded = (async () => {
|
|
995
|
-
try {
|
|
996
|
-
const jids = await readTcTokenIndex(authState.keys);
|
|
997
|
-
for (const jid of jids)
|
|
998
|
-
tcTokenKnownJids.add(jid);
|
|
999
|
-
logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
|
|
1000
|
-
}
|
|
1001
|
-
catch (err) {
|
|
1002
|
-
logger.warn({ err: err?.message }, 'failed to load tctoken index');
|
|
1003
|
-
}
|
|
1004
|
-
})();
|
|
1005
|
-
let tcTokenIndexTimer;
|
|
1006
|
-
async function flushTcTokenIndex() {
|
|
1007
|
-
if (tcTokenIndexTimer) {
|
|
1008
|
-
clearTimeout(tcTokenIndexTimer);
|
|
1009
|
-
tcTokenIndexTimer = undefined;
|
|
1010
|
-
}
|
|
1011
|
-
// Merge with whatever is already persisted so we don't clobber writes from other
|
|
1012
|
-
// paths (history sync, concurrent sessions on the same store).
|
|
1013
|
-
const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
|
|
1014
|
-
return authState.keys.set({ tctoken: write });
|
|
1015
|
-
}
|
|
1016
|
-
function scheduleTcTokenIndexSave() {
|
|
1017
|
-
if (tcTokenIndexTimer) {
|
|
1018
|
-
clearTimeout(tcTokenIndexTimer);
|
|
1019
|
-
}
|
|
1020
|
-
tcTokenIndexTimer = setTimeout(() => {
|
|
1021
|
-
tcTokenIndexTimer = undefined;
|
|
1022
|
-
flushTcTokenIndex().catch(err => {
|
|
1023
|
-
logger.warn({ err: err?.message }, 'failed to save tctoken index');
|
|
1024
|
-
});
|
|
1025
|
-
}, 5000);
|
|
1026
|
-
}
|
|
1027
|
-
function trackTcTokenJid(jid) {
|
|
1028
|
-
if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
1029
|
-
tcTokenKnownJids.add(jid);
|
|
1030
|
-
scheduleTcTokenIndexSave();
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
683
|
const handlePrivacyTokenNotification = async (node) => {
|
|
1034
684
|
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
685
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
1035
686
|
if (!tokensNode)
|
|
1036
687
|
return;
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
688
|
+
const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
|
|
689
|
+
for (const tokenNode of tokenNodes) {
|
|
690
|
+
const { attrs, content } = tokenNode;
|
|
691
|
+
const type = attrs.type;
|
|
692
|
+
const timestamp = attrs.t;
|
|
693
|
+
if (type === 'trusted_contact' && content instanceof Buffer) {
|
|
694
|
+
logger.debug({
|
|
695
|
+
from,
|
|
696
|
+
timestamp,
|
|
697
|
+
tcToken: content
|
|
698
|
+
}, 'received trusted contact token');
|
|
699
|
+
await authState.keys.set({
|
|
700
|
+
tctoken: { [from]: { token: content, timestamp } }
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
1052
704
|
};
|
|
1053
705
|
async function decipherLinkPublicKey(data) {
|
|
1054
706
|
const buffer = toRequiredBuffer(data);
|
|
@@ -1074,11 +726,10 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1074
726
|
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
1075
727
|
await msgRetryCache.set(key, newValue);
|
|
1076
728
|
};
|
|
1077
|
-
const sendMessagesAgain = async (key, ids, retryNode
|
|
729
|
+
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
1078
730
|
const remoteJid = key.remoteJid;
|
|
1079
731
|
const participant = key.participant || remoteJid;
|
|
1080
732
|
const retryCount = +retryNode.attrs.count || 1;
|
|
1081
|
-
const msgId = ids[0];
|
|
1082
733
|
// Try to get messages from cache first, then fallback to getMessage
|
|
1083
734
|
const msgs = [];
|
|
1084
735
|
for (const id of ids) {
|
|
@@ -1110,49 +761,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1110
761
|
// just re-send the message to everyone
|
|
1111
762
|
// prevents the first message decryption failure
|
|
1112
763
|
const sendToAll = !jidDecode(participant)?.device;
|
|
1113
|
-
|
|
1114
|
-
let injectedFromBundle = false;
|
|
1115
|
-
const bundle = extractE2ESessionFromRetryReceipt(receiptNode);
|
|
1116
|
-
if (bundle) {
|
|
1117
|
-
try {
|
|
1118
|
-
await signalRepository.injectE2ESession({ jid: participant, session: bundle });
|
|
1119
|
-
injectedFromBundle = true;
|
|
1120
|
-
logger.debug({ participant, retryCount }, 'injected session from retry receipt key bundle');
|
|
1121
|
-
}
|
|
1122
|
-
catch (error) {
|
|
1123
|
-
logger.warn({ error, participant }, 'failed to inject session from retry receipt');
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
if (!injectedFromBundle) {
|
|
1127
|
-
const receivedRegId = getBinaryNodeChildUInt(receiptNode, 'registration', 4);
|
|
1128
|
-
if (typeof receivedRegId === 'number' && Number.isInteger(receivedRegId)) {
|
|
1129
|
-
const info = await signalRepository.getSessionInfo(participant);
|
|
1130
|
-
if (info && info.registrationId !== 0 && info.registrationId !== receivedRegId) {
|
|
1131
|
-
logger.info({ participant, stored: info.registrationId, received: receivedRegId }, 'reg id mismatch on retry without bundle, deleting session');
|
|
1132
|
-
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
const BASE_KEY_CHECK_RETRY = 2;
|
|
1137
|
-
if (msgId && messageRetryManager) {
|
|
1138
|
-
const info = await signalRepository.getSessionInfo(participant);
|
|
1139
|
-
if (info) {
|
|
1140
|
-
if (retryCount === BASE_KEY_CHECK_RETRY) {
|
|
1141
|
-
messageRetryManager.saveBaseKey(sessionId, msgId, info.baseKey);
|
|
1142
|
-
}
|
|
1143
|
-
else if (retryCount > BASE_KEY_CHECK_RETRY) {
|
|
1144
|
-
if (messageRetryManager.hasSameBaseKey(sessionId, msgId, info.baseKey)) {
|
|
1145
|
-
logger.warn({ participant, retryCount }, 'base key collision on retry, forcing fresh session');
|
|
1146
|
-
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1147
|
-
}
|
|
1148
|
-
messageRetryManager.deleteBaseKey(sessionId, msgId);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
764
|
+
// Check if we should recreate session for this retry
|
|
1152
765
|
let shouldRecreateSession = false;
|
|
1153
766
|
let recreateReason = '';
|
|
1154
|
-
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1
|
|
767
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
1155
768
|
try {
|
|
769
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
1156
770
|
const hasSession = await signalRepository.validateSession(participant);
|
|
1157
771
|
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
1158
772
|
shouldRecreateSession = result.recreate;
|
|
@@ -1166,13 +780,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1166
780
|
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
1167
781
|
}
|
|
1168
782
|
}
|
|
1169
|
-
|
|
1170
|
-
await assertSessions([participant], true);
|
|
1171
|
-
}
|
|
783
|
+
await assertSessions([participant], true);
|
|
1172
784
|
if (isJidGroup(remoteJid)) {
|
|
1173
785
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
1174
786
|
}
|
|
1175
|
-
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason
|
|
787
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
|
|
1176
788
|
for (const [i, msg] of msgs.entries()) {
|
|
1177
789
|
if (!ids[i])
|
|
1178
790
|
continue;
|
|
@@ -1207,6 +819,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1207
819
|
fromMe,
|
|
1208
820
|
participant: attrs.participant
|
|
1209
821
|
};
|
|
822
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
823
|
+
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
824
|
+
await sendMessageAck(node);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
1210
827
|
const ids = [attrs.id];
|
|
1211
828
|
if (Array.isArray(content)) {
|
|
1212
829
|
const items = getBinaryNodeChildren(content[0], 'item');
|
|
@@ -1248,7 +865,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1248
865
|
try {
|
|
1249
866
|
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
1250
867
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
1251
|
-
await sendMessagesAgain(key, ids, retryNode
|
|
868
|
+
await sendMessagesAgain(key, ids, retryNode);
|
|
1252
869
|
}
|
|
1253
870
|
catch (error) {
|
|
1254
871
|
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
@@ -1271,6 +888,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1271
888
|
};
|
|
1272
889
|
const handleNotification = async (node) => {
|
|
1273
890
|
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
|
+
}
|
|
1274
896
|
try {
|
|
1275
897
|
await Promise.all([
|
|
1276
898
|
notificationMutex.mutex(async () => {
|
|
@@ -1283,7 +905,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1283
905
|
fromMe,
|
|
1284
906
|
participant: node.attrs.participant,
|
|
1285
907
|
participantAlt,
|
|
1286
|
-
participantUsername: node.attrs.participant_username,
|
|
1287
908
|
addressingMode,
|
|
1288
909
|
id: node.attrs.id,
|
|
1289
910
|
...(msg.key || {})
|
|
@@ -1301,6 +922,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1301
922
|
}
|
|
1302
923
|
};
|
|
1303
924
|
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
|
+
}
|
|
1304
930
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1305
931
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
1306
932
|
if (encNode?.attrs.type === 'msmsg') {
|
|
@@ -1404,14 +1030,29 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1404
1030
|
return sendMessageAck(node);
|
|
1405
1031
|
}
|
|
1406
1032
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1033
|
+
const errorMessage = msg?.messageStubParameters?.[0] || '';
|
|
1034
|
+
const isPreKeyError = errorMessage.includes('PreKey');
|
|
1035
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
1036
|
+
// Handle both pre-key and normal retries in single mutex
|
|
1409
1037
|
await retryMutex.mutex(async () => {
|
|
1410
1038
|
try {
|
|
1411
1039
|
if (!ws.isOpen) {
|
|
1412
1040
|
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
1413
1041
|
return;
|
|
1414
1042
|
}
|
|
1043
|
+
// Handle pre-key errors with upload and delay
|
|
1044
|
+
if (isPreKeyError) {
|
|
1045
|
+
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
1046
|
+
try {
|
|
1047
|
+
logger.debug('Uploading pre-keys for error recovery');
|
|
1048
|
+
await uploadPreKeys(5);
|
|
1049
|
+
logger.debug('Waiting for server to process new pre-keys');
|
|
1050
|
+
await delay(1000);
|
|
1051
|
+
}
|
|
1052
|
+
catch (uploadErr) {
|
|
1053
|
+
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1415
1056
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1416
1057
|
await sendRetryRequest(node, !encNode);
|
|
1417
1058
|
if (retryRequestDelayMs) {
|
|
@@ -1419,7 +1060,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1419
1060
|
}
|
|
1420
1061
|
}
|
|
1421
1062
|
catch (err) {
|
|
1422
|
-
logger.error({ err }, 'Failed to
|
|
1063
|
+
logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
|
|
1064
|
+
// Still attempt retry even if pre-key upload failed
|
|
1065
|
+
try {
|
|
1066
|
+
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1067
|
+
await sendRetryRequest(node, !encNode);
|
|
1068
|
+
}
|
|
1069
|
+
catch (retryErr) {
|
|
1070
|
+
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
1071
|
+
}
|
|
1423
1072
|
}
|
|
1424
1073
|
acked = true;
|
|
1425
1074
|
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
@@ -1495,13 +1144,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1495
1144
|
offline: !!attrs.offline,
|
|
1496
1145
|
status
|
|
1497
1146
|
};
|
|
1498
|
-
if (status === 'relaylatency') {
|
|
1499
|
-
const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
|
|
1500
|
-
const latencyMs = latencyValue ? Number(latencyValue) : undefined;
|
|
1501
|
-
if (Number.isFinite(latencyMs)) {
|
|
1502
|
-
call.latencyMs = latencyMs;
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
1147
|
if (status === 'offer') {
|
|
1506
1148
|
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1507
1149
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
@@ -1546,61 +1188,29 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1546
1188
|
// error in acknowledgement,
|
|
1547
1189
|
// device could not display the message
|
|
1548
1190
|
if (attrs.error) {
|
|
1549
|
-
|
|
1550
|
-
if (attrs.error === SERVER_ERROR_CODES.MessageAccountRestriction) {
|
|
1551
|
-
// 463 = 1:1 message missing privacy token (tctoken). Usually means the
|
|
1552
|
-
// account is restricted: WhatsApp blocks starting new chats but preserves
|
|
1553
|
-
// existing ones, since established chats already carry a tctoken.
|
|
1554
|
-
// WA Web prevents this client-side (disables the compose bar).
|
|
1555
|
-
// No retry — retrying counts as another "reach out" and worsens the restriction.
|
|
1556
|
-
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
1557
|
-
const ackFrom = attrs.from;
|
|
1558
|
-
if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
|
|
1559
|
-
inFlight463Recoveries.add(ackFrom);
|
|
1560
|
-
void (async () => {
|
|
1561
|
-
try {
|
|
1562
|
-
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
1563
|
-
const tcStorageJid = await resolveTcTokenJid(ackFrom, getLIDForPN);
|
|
1564
|
-
const issueJid = await resolveIssuanceJid(ackFrom, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
1565
|
-
const result = await issuePrivacyTokens([issueJid], unixTimestampSeconds());
|
|
1566
|
-
await storeTcTokensFromIqResult({
|
|
1567
|
-
result,
|
|
1568
|
-
fallbackJid: tcStorageJid,
|
|
1569
|
-
keys: authState.keys,
|
|
1570
|
-
getLIDForPN,
|
|
1571
|
-
onNewJidStored: trackTcTokenJid
|
|
1572
|
-
});
|
|
1573
|
-
logger.debug({ from: ackFrom }, 'completed 463 token recovery issuance');
|
|
1574
|
-
}
|
|
1575
|
-
catch (err) {
|
|
1576
|
-
logger.debug({ from: ackFrom, err: err?.message }, 'failed 463 token recovery issuance');
|
|
1577
|
-
}
|
|
1578
|
-
finally {
|
|
1579
|
-
inFlight463Recoveries.delete(ackFrom);
|
|
1580
|
-
}
|
|
1581
|
-
})();
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
|
|
1585
|
-
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
1586
|
-
}
|
|
1587
|
-
else if (isReachoutTimelocked) {
|
|
1588
|
-
// user is temporarily restricted, fetch current restriction details
|
|
1589
|
-
await fetchAccountReachoutTimelock().catch(err => logger.warn({ err }, 'failed to fetch reachout timelock'));
|
|
1590
|
-
logger.warn({ attrs }, 'received error in ack');
|
|
1591
|
-
}
|
|
1592
|
-
else {
|
|
1593
|
-
logger.warn({ attrs }, 'received error in ack');
|
|
1594
|
-
}
|
|
1191
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1595
1192
|
ev.emit('messages.update', [
|
|
1596
1193
|
{
|
|
1597
1194
|
key,
|
|
1598
1195
|
update: {
|
|
1599
1196
|
status: WAMessageStatus.ERROR,
|
|
1600
|
-
messageStubParameters:
|
|
1197
|
+
messageStubParameters: [attrs.error]
|
|
1601
1198
|
}
|
|
1602
1199
|
}
|
|
1603
1200
|
]);
|
|
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
|
+
// }
|
|
1604
1214
|
}
|
|
1605
1215
|
};
|
|
1606
1216
|
/// processes a node with the given function
|
|
@@ -1624,19 +1234,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1624
1234
|
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1625
1235
|
});
|
|
1626
1236
|
const processNode = async (type, node, identifier, exec) => {
|
|
1627
|
-
// Fast path: ack and drop ignored JIDs before entering the buffer/queue
|
|
1628
|
-
const from = node.attrs.from;
|
|
1629
|
-
let ignoreJid = from;
|
|
1630
|
-
if (type === 'receipt' && from) {
|
|
1631
|
-
const attrs = node.attrs;
|
|
1632
|
-
const isLid = attrs.from.includes('lid');
|
|
1633
|
-
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1634
|
-
ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
1635
|
-
}
|
|
1636
|
-
if (ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
|
|
1637
|
-
await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
1237
|
const isOffline = !!node.attrs.offline;
|
|
1641
1238
|
if (isOffline) {
|
|
1642
1239
|
offlineNodeProcessor.enqueue(type, node);
|
|
@@ -1692,110 +1289,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1692
1289
|
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1693
1290
|
}
|
|
1694
1291
|
});
|
|
1695
|
-
|
|
1696
|
-
let lastTcTokenPruneTs = 0;
|
|
1697
|
-
/** dedupe in-flight 463 recovery token issuance by target JID */
|
|
1698
|
-
const inFlight463Recoveries = new Set();
|
|
1699
|
-
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1292
|
+
ev.on('connection.update', ({ isOnline }) => {
|
|
1700
1293
|
if (typeof isOnline !== 'undefined') {
|
|
1701
1294
|
sendActiveReceipts = isOnline;
|
|
1702
1295
|
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1703
1296
|
}
|
|
1704
|
-
// Flush pending tctoken index save on disconnect to avoid writing after close
|
|
1705
|
-
if (connection === 'close' && tcTokenIndexTimer) {
|
|
1706
|
-
clearTimeout(tcTokenIndexTimer);
|
|
1707
|
-
tcTokenIndexTimer = undefined;
|
|
1708
|
-
// Best-effort flush — may fail if store is already closed
|
|
1709
|
-
try {
|
|
1710
|
-
void Promise.resolve(flushTcTokenIndex()).catch(() => { });
|
|
1711
|
-
}
|
|
1712
|
-
catch {
|
|
1713
|
-
/* ignore sync errors */
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
// Prune expired tctokens when coming online, at most once per 24 hours
|
|
1717
|
-
// Matches WA Web's CLEAN_TC_TOKENS task
|
|
1718
|
-
// Note: don't gate on tcTokenKnownJids.size — the index may still be loading
|
|
1719
|
-
if (isOnline) {
|
|
1720
|
-
const now = Date.now();
|
|
1721
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
1722
|
-
if (now - lastTcTokenPruneTs >= DAY_MS) {
|
|
1723
|
-
lastTcTokenPruneTs = now;
|
|
1724
|
-
void pruneExpiredTcTokens();
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
1297
|
});
|
|
1728
|
-
registerSocketEndHandler(() => {
|
|
1729
|
-
if (!config.msgRetryCounterCache && msgRetryCache.close) {
|
|
1730
|
-
msgRetryCache.close();
|
|
1731
|
-
}
|
|
1732
|
-
if (!config.callOfferCache && callOfferCache.close) {
|
|
1733
|
-
callOfferCache.close();
|
|
1734
|
-
}
|
|
1735
|
-
identityAssertDebounce.close();
|
|
1736
|
-
sendActiveReceipts = false;
|
|
1737
|
-
});
|
|
1738
|
-
async function pruneExpiredTcTokens() {
|
|
1739
|
-
try {
|
|
1740
|
-
await tcTokenIndexLoaded;
|
|
1741
|
-
// Union with the persisted index picks up JIDs added by other layers
|
|
1742
|
-
// (history sync) without needing inter-module wiring.
|
|
1743
|
-
const persisted = await readTcTokenIndex(authState.keys);
|
|
1744
|
-
const allJids = new Set(tcTokenKnownJids);
|
|
1745
|
-
for (const jid of persisted)
|
|
1746
|
-
allJids.add(jid);
|
|
1747
|
-
if (!allJids.size)
|
|
1748
|
-
return;
|
|
1749
|
-
const jids = [...allJids];
|
|
1750
|
-
const allTokens = await authState.keys.get('tctoken', jids);
|
|
1751
|
-
const writes = {};
|
|
1752
|
-
const survivors = new Set();
|
|
1753
|
-
let mutated = 0;
|
|
1754
|
-
for (const jid of jids) {
|
|
1755
|
-
const entry = allTokens[jid];
|
|
1756
|
-
if (!entry) {
|
|
1757
|
-
// Tracked but nothing in store — drop from index.
|
|
1758
|
-
mutated++;
|
|
1759
|
-
continue;
|
|
1760
|
-
}
|
|
1761
|
-
const hasPeerToken = !!entry.token?.length;
|
|
1762
|
-
const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
|
|
1763
|
-
const hasSenderTs = entry.senderTimestamp !== undefined;
|
|
1764
|
-
const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
|
|
1765
|
-
const keepPeerToken = hasPeerToken && !peerTokenExpired;
|
|
1766
|
-
const keepSenderTs = hasSenderTs && !senderTsExpired;
|
|
1767
|
-
if (!keepPeerToken && !keepSenderTs) {
|
|
1768
|
-
writes[jid] = null;
|
|
1769
|
-
mutated++;
|
|
1770
|
-
}
|
|
1771
|
-
else if (peerTokenExpired && keepSenderTs) {
|
|
1772
|
-
writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
|
|
1773
|
-
survivors.add(jid);
|
|
1774
|
-
mutated++;
|
|
1775
|
-
}
|
|
1776
|
-
else {
|
|
1777
|
-
survivors.add(jid);
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
if (mutated === 0)
|
|
1781
|
-
return;
|
|
1782
|
-
await authState.keys.set({
|
|
1783
|
-
tctoken: {
|
|
1784
|
-
...writes,
|
|
1785
|
-
[TC_TOKEN_INDEX_KEY]: {
|
|
1786
|
-
token: Buffer.from(JSON.stringify([...survivors]))
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
});
|
|
1790
|
-
tcTokenKnownJids.clear();
|
|
1791
|
-
for (const jid of survivors)
|
|
1792
|
-
tcTokenKnownJids.add(jid);
|
|
1793
|
-
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
|
|
1794
|
-
}
|
|
1795
|
-
catch (err) {
|
|
1796
|
-
logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
1298
|
return {
|
|
1800
1299
|
...sock,
|
|
1801
1300
|
sendMessageAck,
|