@queenanya/baileys 9.2.4 → 9.4.1
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 +329 -1237
- package/WAProto/fix-imports.js +22 -18
- package/WAProto/index.js +22 -18
- package/lib/Defaults/index.d.ts +17 -0
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +29 -3
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/libsignal.d.ts.map +1 -1
- package/lib/Signal/libsignal.js +61 -2
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Signal/lid-mapping.d.ts +5 -9
- package/lib/Signal/lid-mapping.d.ts.map +1 -1
- package/lib/Signal/lid-mapping.js +170 -70
- package/lib/Signal/lid-mapping.js.map +1 -1
- package/lib/Socket/business.d.ts +112 -2
- package/lib/Socket/business.d.ts.map +1 -1
- package/lib/Socket/chats.d.ts +10 -0
- package/lib/Socket/chats.d.ts.map +1 -1
- package/lib/Socket/chats.js +243 -38
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +112 -2
- package/lib/Socket/communities.d.ts.map +1 -1
- package/lib/Socket/groups.d.ts +7 -0
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +6 -0
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +112 -2
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/index.js +0 -6
- package/lib/Socket/index.js.map +1 -1
- package/lib/Socket/messages-recv.d.ts +113 -3
- package/lib/Socket/messages-recv.d.ts.map +1 -1
- package/lib/Socket/messages-recv.js +739 -150
- package/lib/Socket/messages-recv.js.map +1 -1
- package/lib/Socket/messages-send.d.ts +116 -4
- package/lib/Socket/messages-send.d.ts.map +1 -1
- package/lib/Socket/messages-send.js +328 -86
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/newsletter.d.ts +7 -0
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/socket.d.ts +2 -0
- package/lib/Socket/socket.d.ts.map +1 -1
- package/lib/Socket/socket.js +142 -16
- 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 +10 -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 +21 -1
- 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 +530 -16
- package/lib/Types/Message.d.ts.map +1 -1
- package/lib/Types/Message.js.map +1 -1
- package/lib/Types/Newsletter.d.ts +33 -29
- package/lib/Types/Newsletter.d.ts.map +1 -1
- package/lib/Types/Newsletter.js +25 -23
- package/lib/Types/Newsletter.js.map +1 -1
- package/lib/Types/State.d.ts +54 -0
- package/lib/Types/State.d.ts.map +1 -1
- package/lib/Types/State.js +42 -0
- package/lib/Types/State.js.map +1 -1
- package/lib/Types/index.d.ts +9 -0
- package/lib/Types/index.d.ts.map +1 -1
- package/lib/Types/index.js.map +1 -1
- package/lib/Utils/browser-utils.d.ts +13 -0
- package/lib/Utils/browser-utils.d.ts.map +1 -1
- package/lib/Utils/browser-utils.js +90 -10
- package/lib/Utils/browser-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 +81 -52
- 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 +34 -0
- package/lib/Utils/companion-reg-client-utils.js.map +1 -0
- package/lib/Utils/crypto.d.ts +4 -8
- package/lib/Utils/crypto.d.ts.map +1 -1
- package/lib/Utils/crypto.js +2 -26
- package/lib/Utils/crypto.js.map +1 -1
- 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 +16 -0
- package/lib/Utils/decode-wa-message.js.map +1 -1
- package/lib/Utils/event-buffer.js +10 -1
- package/lib/Utils/event-buffer.js.map +1 -1
- package/lib/Utils/generics.d.ts +3 -1
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +16 -3
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/history.d.ts +5 -2
- package/lib/Utils/history.d.ts.map +1 -1
- package/lib/Utils/history.js +53 -17
- package/lib/Utils/history.js.map +1 -1
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.d.ts.map +1 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/identity-change-handler.js.map +1 -0
- package/lib/Utils/index.d.ts +6 -0
- package/lib/Utils/index.d.ts.map +1 -1
- package/lib/Utils/index.js +6 -0
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/interactive-message.d.ts +201 -0
- package/lib/Utils/interactive-message.d.ts.map +1 -0
- package/lib/Utils/interactive-message.js +256 -0
- package/lib/Utils/interactive-message.js.map +1 -0
- package/lib/Utils/lt-hash.d.ts +7 -12
- package/lib/Utils/lt-hash.d.ts.map +1 -1
- package/lib/Utils/lt-hash.js +2 -42
- package/lib/Utils/lt-hash.js.map +1 -1
- package/lib/Utils/message-composer.d.ts +5 -0
- package/lib/Utils/message-composer.d.ts.map +1 -0
- package/lib/Utils/message-composer.js +5 -0
- package/lib/Utils/message-composer.js.map +1 -0
- package/lib/Utils/message-retry-manager.d.ts +30 -2
- package/lib/Utils/message-retry-manager.d.ts.map +1 -1
- package/lib/Utils/message-retry-manager.js +59 -2
- package/lib/Utils/message-retry-manager.js.map +1 -1
- package/lib/Utils/messages-media.d.ts +19 -5
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +26 -17
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +433 -13
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/noise-handler.d.ts +2 -2
- package/lib/Utils/noise-handler.d.ts.map +1 -1
- package/lib/Utils/noise-handler.js +10 -10
- package/lib/Utils/noise-handler.js.map +1 -1
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.d.ts.map +1 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/offline-node-processor.js.map +1 -0
- package/lib/Utils/process-message.d.ts.map +1 -1
- package/lib/Utils/process-message.js +96 -16
- package/lib/Utils/process-message.js.map +1 -1
- package/lib/Utils/reporting-utils.js +2 -2
- package/lib/Utils/reporting-utils.js.map +1 -1
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.d.ts.map +1 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/stanza-ack.js.map +1 -0
- package/lib/Utils/sync-action-utils.d.ts.map +1 -1
- package/lib/Utils/sync-action-utils.js +2 -1
- 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/Utils/use-mongo-file-auth-state.d.ts +16 -0
- package/lib/Utils/use-mongo-file-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-mongo-file-auth-state.js +60 -0
- package/lib/Utils/use-mongo-file-auth-state.js.map +1 -0
- package/lib/Utils/use-multi-file-auth-state.js +1 -1
- package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
- package/lib/Utils/validate-connection.d.ts.map +1 -1
- package/lib/Utils/validate-connection.js +11 -1
- package/lib/Utils/validate-connection.js.map +1 -1
- package/lib/WABinary/generic-utils.d.ts +9 -0
- package/lib/WABinary/generic-utils.d.ts.map +1 -1
- package/lib/WABinary/generic-utils.js +23 -0
- package/lib/WABinary/generic-utils.js.map +1 -1
- package/lib/WABinary/jid-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/lib/addons/anti-delete.d.ts +72 -0
- package/lib/addons/anti-delete.d.ts.map +1 -0
- package/lib/addons/anti-delete.js +165 -0
- package/lib/addons/anti-delete.js.map +1 -0
- package/lib/addons/auto-reply.d.ts +67 -0
- package/lib/addons/auto-reply.d.ts.map +1 -0
- package/lib/addons/auto-reply.js +145 -0
- package/lib/addons/auto-reply.js.map +1 -0
- package/lib/addons/browser-presets.d.ts +16 -0
- package/lib/addons/browser-presets.d.ts.map +1 -0
- package/lib/addons/browser-presets.js +24 -0
- package/lib/addons/browser-presets.js.map +1 -0
- package/lib/addons/button-sender.d.ts +260 -0
- package/lib/addons/button-sender.d.ts.map +1 -0
- package/lib/addons/button-sender.js +771 -0
- package/lib/addons/button-sender.js.map +1 -0
- package/lib/addons/call-handler.d.ts +79 -0
- package/lib/addons/call-handler.d.ts.map +1 -0
- package/lib/addons/call-handler.js +342 -0
- package/lib/addons/call-handler.js.map +1 -0
- package/lib/addons/from-chats.d.ts +30 -0
- package/lib/addons/from-chats.d.ts.map +1 -0
- package/lib/addons/from-chats.js +38 -0
- package/lib/addons/from-chats.js.map +1 -0
- package/lib/addons/from-messages-recv.d.ts +59 -0
- package/lib/addons/from-messages-recv.d.ts.map +1 -0
- package/lib/addons/from-messages-recv.js +326 -0
- package/lib/addons/from-messages-recv.js.map +1 -0
- package/lib/addons/from-messages-send.d.ts +50 -0
- package/lib/addons/from-messages-send.d.ts.map +1 -0
- package/lib/addons/from-messages-send.js +148 -0
- package/lib/addons/from-messages-send.js.map +1 -0
- package/lib/addons/from-messages.d.ts +52 -0
- package/lib/addons/from-messages.d.ts.map +1 -0
- package/lib/addons/from-messages.js +304 -0
- package/lib/addons/from-messages.js.map +1 -0
- package/lib/addons/index.d.ts +67 -0
- package/lib/addons/index.d.ts.map +1 -0
- package/lib/addons/index.js +86 -0
- package/lib/addons/index.js.map +1 -0
- package/lib/addons/interactive-message.d.ts +201 -0
- package/lib/addons/interactive-message.d.ts.map +1 -0
- package/lib/addons/interactive-message.js +256 -0
- package/lib/addons/interactive-message.js.map +1 -0
- package/lib/addons/jid-plot.d.ts +49 -0
- package/lib/addons/jid-plot.d.ts.map +1 -0
- package/lib/addons/jid-plot.js +84 -0
- package/lib/addons/jid-plot.js.map +1 -0
- package/lib/addons/jid-plotting.d.ts +54 -0
- package/lib/addons/jid-plotting.d.ts.map +1 -0
- package/lib/addons/jid-plotting.js +150 -0
- package/lib/addons/jid-plotting.js.map +1 -0
- package/lib/addons/lid-support.d.ts +41 -0
- package/lib/addons/lid-support.d.ts.map +1 -0
- package/lib/addons/lid-support.js +42 -0
- package/lib/addons/lid-support.js.map +1 -0
- package/lib/addons/message-composer.d.ts +142 -0
- package/lib/addons/message-composer.d.ts.map +1 -0
- package/lib/addons/message-composer.js +377 -0
- package/lib/addons/message-composer.js.map +1 -0
- package/lib/addons/message-scheduler.d.ts +77 -0
- package/lib/addons/message-scheduler.d.ts.map +1 -0
- package/lib/addons/message-scheduler.js +108 -0
- package/lib/addons/message-scheduler.js.map +1 -0
- package/lib/addons/message-search.d.ts +51 -0
- package/lib/addons/message-search.d.ts.map +1 -0
- package/lib/addons/message-search.js +171 -0
- package/lib/addons/message-search.js.map +1 -0
- package/lib/addons/message-utils.d.ts +88 -0
- package/lib/addons/message-utils.d.ts.map +1 -0
- package/lib/addons/message-utils.js +292 -0
- package/lib/addons/message-utils.js.map +1 -0
- package/lib/addons/outgoing-calls.d.ts +64 -0
- package/lib/addons/outgoing-calls.d.ts.map +1 -0
- package/lib/addons/outgoing-calls.js +139 -0
- package/lib/addons/outgoing-calls.js.map +1 -0
- package/lib/addons/pairing-fix.d.ts +31 -0
- package/lib/addons/pairing-fix.d.ts.map +1 -0
- package/lib/addons/pairing-fix.js +74 -0
- package/lib/addons/pairing-fix.js.map +1 -0
- package/lib/addons/past-participants.d.ts +42 -0
- package/lib/addons/past-participants.d.ts.map +1 -0
- package/lib/addons/past-participants.js +41 -0
- package/lib/addons/past-participants.js.map +1 -0
- package/lib/addons/rich-response.d.ts +111 -0
- package/lib/addons/rich-response.d.ts.map +1 -0
- package/lib/addons/rich-response.js +152 -0
- package/lib/addons/rich-response.js.map +1 -0
- package/lib/addons/scheduling.d.ts +41 -0
- package/lib/addons/scheduling.d.ts.map +1 -0
- package/lib/addons/scheduling.js +110 -0
- package/lib/addons/scheduling.js.map +1 -0
- package/lib/addons/status-posting.d.ts +177 -0
- package/lib/addons/status-posting.d.ts.map +1 -0
- package/lib/addons/status-posting.js +240 -0
- package/lib/addons/status-posting.js.map +1 -0
- package/lib/addons/stickerpack.d.ts +37 -0
- package/lib/addons/stickerpack.d.ts.map +1 -0
- package/lib/addons/stickerpack.js +39 -0
- package/lib/addons/stickerpack.js.map +1 -0
- package/lib/addons/templates.d.ts +72 -0
- package/lib/addons/templates.d.ts.map +1 -0
- package/lib/addons/templates.js +145 -0
- package/lib/addons/templates.js.map +1 -0
- package/lib/addons/vcard.d.ts +59 -0
- package/lib/addons/vcard.d.ts.map +1 -0
- package/lib/addons/vcard.js +88 -0
- package/lib/addons/vcard.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
|
@@ -3,17 +3,19 @@ import { Boom } from '@hapi/boom';
|
|
|
3
3
|
import { randomBytes } from 'crypto';
|
|
4
4
|
import Long from 'long';
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
|
-
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
|
|
7
|
-
import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
-
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
6
|
+
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
|
|
7
|
+
import { ReachoutTimelockEnforcementType, 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, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
|
-
import {
|
|
10
|
+
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
|
|
11
|
+
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
12
|
import { extractGroupMetadata } from './groups.js';
|
|
12
13
|
import { makeMessagesSocket } from './messages-send.js';
|
|
13
14
|
export const makeMessagesRecvSocket = (config) => {
|
|
14
15
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
15
16
|
const sock = makeMessagesSocket(config);
|
|
16
|
-
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
|
|
17
|
+
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, generateMessageTag, getUSyncDevices, createParticipantNodes, messageRetryManager, issuePrivacyTokens } = sock;
|
|
18
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
17
19
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
18
20
|
const retryMutex = makeMutex();
|
|
19
21
|
const msgRetryCache = config.msgRetryCounterCache ||
|
|
@@ -50,7 +52,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
50
52
|
};
|
|
51
53
|
return sendPeerDataOperationMessage(pdoMessage);
|
|
52
54
|
};
|
|
53
|
-
const requestPlaceholderResend = async (messageKey) => {
|
|
55
|
+
const requestPlaceholderResend = async (messageKey, msgData) => {
|
|
54
56
|
if (!authState.creds.me?.id) {
|
|
55
57
|
throw new Boom('Not authenticated');
|
|
56
58
|
}
|
|
@@ -59,7 +61,9 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
else {
|
|
62
|
-
|
|
64
|
+
// Store original message data so PDO response handler can preserve
|
|
65
|
+
// metadata (LID details, timestamps, etc.) that the phone may omit
|
|
66
|
+
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
63
67
|
}
|
|
64
68
|
await delay(2000);
|
|
65
69
|
if (!(await placeholderResendCache.get(messageKey?.id))) {
|
|
@@ -82,25 +86,145 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
82
86
|
}, 8000);
|
|
83
87
|
return sendPeerDataOperationMessage(pdoMessage);
|
|
84
88
|
};
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
|
|
90
|
+
function isValidEnforcementType(value) {
|
|
91
|
+
return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
|
|
92
|
+
}
|
|
93
|
+
// ── Top-level mex dispatcher (PR: feat-mex-notification-dispatch) ─────────
|
|
94
|
+
const handleMexNotification = async (node) => {
|
|
95
|
+
const updateNode = getBinaryNodeChild(node, 'update');
|
|
96
|
+
if (updateNode) {
|
|
97
|
+
const opName = updateNode.attrs?.op_name;
|
|
98
|
+
if (!opName) {
|
|
99
|
+
logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
let mexResponse;
|
|
103
|
+
try {
|
|
104
|
+
mexResponse = JSON.parse(updateNode.content.toString());
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (mexResponse.errors?.length) {
|
|
111
|
+
logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const data = mexResponse.data;
|
|
115
|
+
if (!data) {
|
|
116
|
+
logger.warn({ opName }, 'mex notification has null data');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
logger.debug({ opName }, 'processing mex notification');
|
|
120
|
+
switch (opName) {
|
|
121
|
+
case 'NotificationUserReachoutTimelockUpdate':
|
|
122
|
+
handleReachoutTimelockNotification(data);
|
|
123
|
+
break;
|
|
124
|
+
case 'MessageCappingInfoNotification':
|
|
125
|
+
handleMessageCappingNotification(data);
|
|
126
|
+
break;
|
|
127
|
+
case 'NotificationLinkedProfilesUpdates': {
|
|
128
|
+
// PR: fix-mex-linked-profiles
|
|
129
|
+
const linkedProfiles = data.xwa2_notify_linked_profiles;
|
|
130
|
+
if (!linkedProfiles)
|
|
131
|
+
break;
|
|
132
|
+
const lid = linkedProfiles.jid;
|
|
133
|
+
for (const profile of linkedProfiles.added_profiles ?? []) {
|
|
134
|
+
const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
|
|
135
|
+
if (lid && pn)
|
|
136
|
+
ev.emit('lid-mapping.update', { lid, pn });
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
// newsletter ops still use the legacy <mex> child structure
|
|
141
|
+
case 'NotificationNewsletterUpdate':
|
|
142
|
+
case 'NotificationNewsletterAdminPromote':
|
|
143
|
+
case 'NotificationNewsletterAdminDemote':
|
|
144
|
+
case 'NotificationNewsletterUserSettingChange':
|
|
145
|
+
case 'NotificationNewsletterJoin':
|
|
146
|
+
case 'NotificationNewsletterLeave':
|
|
147
|
+
case 'NotificationNewsletterStateChange':
|
|
148
|
+
case 'NotificationNewsletterAdminMetadataUpdate':
|
|
149
|
+
case 'NotificationNewsletterOwnerUpdate':
|
|
150
|
+
case 'NotificationNewsletterAdminInviteRevoke':
|
|
151
|
+
case 'NotificationNewsletterWamoSubStatusChange':
|
|
152
|
+
case 'NotificationNewsletterBlockUser':
|
|
153
|
+
case 'NotificationNewsletterPaidPartnership':
|
|
154
|
+
case 'NotificationNewsletterMilestone':
|
|
155
|
+
case 'NewsletterResponseStateUpdate':
|
|
156
|
+
await handleLegacyMexNewsletterNotification(node);
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
logger.debug({ opName }, 'unhandled mex notification');
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await handleLegacyMexNewsletterNotification(node);
|
|
165
|
+
};
|
|
166
|
+
const handleReachoutTimelockNotification = (data) => {
|
|
167
|
+
const payload = data.xwa2_notify_account_reachout_timelock;
|
|
168
|
+
if (!payload) {
|
|
169
|
+
logger.warn('reachout timelock notification missing payload');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!payload.is_active) {
|
|
173
|
+
logger.info('reachout timelock restriction lifted');
|
|
174
|
+
ev.emit('connection.update', {
|
|
175
|
+
reachoutTimeLock: {
|
|
176
|
+
isActive: false,
|
|
177
|
+
enforcementType: ReachoutTimelockEnforcementType.DEFAULT
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// WA Web defaults to now+60s when the server omits the expiry
|
|
183
|
+
const timeEnforcementEnds = payload.time_enforcement_ends
|
|
184
|
+
? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
|
|
185
|
+
: new Date(Date.now() + 60000);
|
|
186
|
+
const enforcementType = isValidEnforcementType(payload.enforcement_type)
|
|
187
|
+
? payload.enforcement_type
|
|
188
|
+
: ReachoutTimelockEnforcementType.DEFAULT;
|
|
189
|
+
logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
|
|
190
|
+
ev.emit('connection.update', {
|
|
191
|
+
reachoutTimeLock: {
|
|
192
|
+
isActive: true,
|
|
193
|
+
timeEnforcementEnds,
|
|
194
|
+
enforcementType
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const handleMessageCappingNotification = (data) => {
|
|
199
|
+
const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
|
|
200
|
+
if (!payload) {
|
|
201
|
+
logger.warn('message capping notification missing payload');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
logger.info({ payload }, 'received message capping update');
|
|
205
|
+
ev.emit('message-capping.update', payload);
|
|
206
|
+
};
|
|
207
|
+
// ── Legacy mex newsletter notification handler ────────────────────────────
|
|
208
|
+
const handleLegacyMexNewsletterNotification = async (node) => {
|
|
87
209
|
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
88
210
|
if (!mexNode?.content) {
|
|
89
|
-
logger.warn({ node }, '
|
|
211
|
+
logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
|
|
90
212
|
return;
|
|
91
213
|
}
|
|
92
|
-
let
|
|
214
|
+
let parsed;
|
|
93
215
|
try {
|
|
94
|
-
|
|
216
|
+
// PR fix-mex-linked-profiles: handle binary-encoded content correctly
|
|
217
|
+
const payloadContent = mexNode.content;
|
|
218
|
+
const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
|
|
219
|
+
parsed = JSON.parse(contentBuf.toString());
|
|
95
220
|
}
|
|
96
221
|
catch (error) {
|
|
97
|
-
logger.error({ err: error, node }, '
|
|
222
|
+
logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
|
|
98
223
|
return;
|
|
99
224
|
}
|
|
100
|
-
const operation =
|
|
101
|
-
const updates = data?.updates;
|
|
225
|
+
const { operation, updates } = parsed;
|
|
102
226
|
if (!updates || !operation) {
|
|
103
|
-
logger.warn({
|
|
227
|
+
logger.warn({ parsed }, 'invalid mex newsletter notification content');
|
|
104
228
|
return;
|
|
105
229
|
}
|
|
106
230
|
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
@@ -129,7 +253,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
129
253
|
}
|
|
130
254
|
break;
|
|
131
255
|
default:
|
|
132
|
-
logger.info({ operation,
|
|
256
|
+
logger.info({ operation, parsed }, 'unhandled mex newsletter notification');
|
|
133
257
|
break;
|
|
134
258
|
}
|
|
135
259
|
};
|
|
@@ -243,26 +367,275 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
243
367
|
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
|
|
244
368
|
await sendNode(stanza);
|
|
245
369
|
};
|
|
370
|
+
// ── Call handlers ─────────────────────────────────────────────────
|
|
246
371
|
const rejectCall = async (callId, callFrom) => {
|
|
247
|
-
|
|
372
|
+
await query({
|
|
248
373
|
tag: 'call',
|
|
249
|
-
attrs: {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
374
|
+
attrs: { from: authState.creds.me.id, to: callFrom },
|
|
375
|
+
content: [
|
|
376
|
+
{ tag: 'reject', attrs: { 'call-id': callId, 'call-creator': callFrom, count: '0' }, content: undefined }
|
|
377
|
+
]
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
const offerCall = async (toJid, isVideo = false) => {
|
|
381
|
+
const callId = randomBytes(16).toString('hex').toUpperCase().substring(0, 64);
|
|
382
|
+
const offerContent = [];
|
|
383
|
+
if (isVideo) {
|
|
384
|
+
offerContent.push({
|
|
385
|
+
tag: 'video',
|
|
386
|
+
attrs: {
|
|
387
|
+
enc: 'vp8',
|
|
388
|
+
dec: 'vp8',
|
|
389
|
+
orientation: '0',
|
|
390
|
+
screen_width: '1920',
|
|
391
|
+
screen_height: '1080',
|
|
392
|
+
device_orientation: '0'
|
|
393
|
+
},
|
|
394
|
+
content: undefined
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined });
|
|
398
|
+
offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined });
|
|
399
|
+
offerContent.push({ tag: 'net', attrs: { medium: '3' }, content: undefined });
|
|
400
|
+
offerContent.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) });
|
|
401
|
+
offerContent.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
|
|
402
|
+
const encKey = randomBytes(32);
|
|
403
|
+
const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device));
|
|
404
|
+
await assertSessions(devices, true);
|
|
405
|
+
const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, { call: { callKey: new Uint8Array(encKey) } }, { count: '0' });
|
|
406
|
+
offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
|
|
407
|
+
if (shouldIncludeDeviceIdentity) {
|
|
408
|
+
offerContent.push({
|
|
409
|
+
tag: 'device-identity',
|
|
410
|
+
attrs: {},
|
|
411
|
+
content: encodeSignedDeviceIdentity(authState.creds.account, true)
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
await query({
|
|
415
|
+
tag: 'call',
|
|
416
|
+
attrs: { id: generateMessageTag(), to: toJid },
|
|
417
|
+
content: [
|
|
418
|
+
{ tag: 'offer', attrs: { 'call-id': callId, 'call-creator': authState.creds.me.id }, content: offerContent }
|
|
419
|
+
]
|
|
420
|
+
});
|
|
421
|
+
return { id: callId, to: toJid };
|
|
422
|
+
};
|
|
423
|
+
const initiateCall = async (jid, options = {}) => {
|
|
424
|
+
const meId = authState.creds.me?.id;
|
|
425
|
+
if (!meId)
|
|
426
|
+
throw new Boom('Not authenticated');
|
|
427
|
+
const isVideo = !!options.isVideo;
|
|
428
|
+
const isGroup = isJidGroup(jid);
|
|
429
|
+
const result = await offerCall(jid, isVideo);
|
|
430
|
+
const callId = result.id;
|
|
431
|
+
await callOfferCache.set(callId, {
|
|
432
|
+
chatId: jid,
|
|
433
|
+
from: meId,
|
|
434
|
+
id: callId,
|
|
435
|
+
date: new Date(),
|
|
436
|
+
offline: false,
|
|
437
|
+
status: 'offer',
|
|
438
|
+
isVideo,
|
|
439
|
+
isGroup,
|
|
440
|
+
groupJid: isGroup ? jid : undefined
|
|
441
|
+
});
|
|
442
|
+
return { callId, to: jid, isVideo };
|
|
443
|
+
};
|
|
444
|
+
const terminateCall = async (callId, callTo, callCreator, reason, duration) => {
|
|
445
|
+
const meId = authState.creds.me?.id;
|
|
446
|
+
if (!meId)
|
|
447
|
+
throw new Boom('Not authenticated', { statusCode: 401 });
|
|
448
|
+
const attrs = { 'call-id': callId, 'call-creator': callCreator || meId };
|
|
449
|
+
if (reason)
|
|
450
|
+
attrs.reason = reason;
|
|
451
|
+
if (typeof duration === 'number') {
|
|
452
|
+
attrs.duration = String(duration);
|
|
453
|
+
attrs.audio_duration = String(duration);
|
|
454
|
+
}
|
|
455
|
+
await query({
|
|
456
|
+
tag: 'call',
|
|
457
|
+
attrs: { to: callTo, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
458
|
+
content: [{ tag: 'terminate', attrs, content: undefined }]
|
|
459
|
+
});
|
|
460
|
+
await callOfferCache.del(callId);
|
|
461
|
+
};
|
|
462
|
+
const cancelCall = async (callId, callTo) => terminateCall(callId, callTo);
|
|
463
|
+
const acceptCall = async (callId, callFrom, isVideo) => {
|
|
464
|
+
const meId = authState.creds.me?.id;
|
|
465
|
+
if (!meId)
|
|
466
|
+
throw new Boom('Not authenticated', { statusCode: 401 });
|
|
467
|
+
const content = [{ tag: 'audio', attrs: { rate: '16000', enc: 'opus' }, content: undefined }];
|
|
468
|
+
if (isVideo)
|
|
469
|
+
content.push({ tag: 'video', attrs: { dec: 'H264,AV1', device_orientation: '1' }, content: undefined });
|
|
470
|
+
content.push({ tag: 'net', attrs: { medium: '2' }, content: undefined }, { tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
|
|
471
|
+
await query({
|
|
472
|
+
tag: 'call',
|
|
473
|
+
attrs: { from: meId, to: callFrom, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
474
|
+
content: [{ tag: 'accept', attrs: { 'call-id': callId, 'call-creator': callFrom }, content }]
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
const preacceptCall = async (callId, callCreator, isVideo) => {
|
|
478
|
+
const content = [{ tag: 'audio', attrs: { rate: '16000', enc: 'opus' }, content: undefined }];
|
|
479
|
+
if (isVideo) {
|
|
480
|
+
content.push({
|
|
481
|
+
tag: 'video',
|
|
482
|
+
attrs: { screen_width: '1080', screen_height: '2400', dec: 'H264,H265,AV1', device_orientation: '0' },
|
|
483
|
+
content: undefined
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
content.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined }, { tag: 'capability', attrs: { ver: '1' }, content: undefined });
|
|
487
|
+
await query({
|
|
488
|
+
tag: 'call',
|
|
489
|
+
attrs: { to: callCreator, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
490
|
+
content: [{ tag: 'preaccept', attrs: { 'call-id': callId, 'call-creator': callCreator }, content }]
|
|
491
|
+
});
|
|
492
|
+
};
|
|
493
|
+
const sendRelayLatency = async (callId, callCreator, relays, transactionId) => {
|
|
494
|
+
const attrs = { 'call-id': callId, 'call-creator': callCreator };
|
|
495
|
+
if (transactionId)
|
|
496
|
+
attrs['transaction-id'] = transactionId;
|
|
497
|
+
await sendNode({
|
|
498
|
+
tag: 'call',
|
|
499
|
+
attrs: { to: callCreator, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
253
500
|
content: [
|
|
254
501
|
{
|
|
255
|
-
tag: '
|
|
502
|
+
tag: 'relaylatency',
|
|
503
|
+
attrs,
|
|
504
|
+
content: relays.map(r => {
|
|
505
|
+
const a = {};
|
|
506
|
+
if (r.relayName)
|
|
507
|
+
a.relay_name = r.relayName;
|
|
508
|
+
a.latency = String(r.latency);
|
|
509
|
+
if (r.relayId)
|
|
510
|
+
a.relay_id = r.relayId;
|
|
511
|
+
if (r.dlBw !== undefined)
|
|
512
|
+
a.dl_bw = String(r.dlBw);
|
|
513
|
+
if (r.ulBw !== undefined)
|
|
514
|
+
a.ul_bw = String(r.ulBw);
|
|
515
|
+
return { tag: 'te', attrs: a, content: undefined };
|
|
516
|
+
})
|
|
517
|
+
}
|
|
518
|
+
]
|
|
519
|
+
});
|
|
520
|
+
};
|
|
521
|
+
const sendTransport = async (callId, callCreator, to, candidates, round) => {
|
|
522
|
+
const attrs = {
|
|
523
|
+
'call-id': callId,
|
|
524
|
+
'call-creator': callCreator,
|
|
525
|
+
'transport-message-type': '1'
|
|
526
|
+
};
|
|
527
|
+
if (round !== undefined)
|
|
528
|
+
attrs['p2p-cand-round'] = String(round);
|
|
529
|
+
await sendNode({
|
|
530
|
+
tag: 'call',
|
|
531
|
+
attrs: { to, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
532
|
+
content: [
|
|
533
|
+
{
|
|
534
|
+
tag: 'transport',
|
|
535
|
+
attrs,
|
|
536
|
+
content: candidates.map(c => ({ tag: 'te', attrs: { priority: c.priority }, content: c.data }))
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
});
|
|
540
|
+
};
|
|
541
|
+
const sendCallDuration = async (callId, callCreator, peer, audioDuration, callType = '1x1') => {
|
|
542
|
+
await sendNode({
|
|
543
|
+
tag: 'call',
|
|
544
|
+
attrs: { to: 'call', id: randomBytes(16).toString('hex').toUpperCase() },
|
|
545
|
+
content: [
|
|
546
|
+
{
|
|
547
|
+
tag: 'duration',
|
|
256
548
|
attrs: {
|
|
257
549
|
'call-id': callId,
|
|
258
|
-
'call-creator':
|
|
259
|
-
|
|
550
|
+
'call-creator': callCreator,
|
|
551
|
+
peer,
|
|
552
|
+
audio_duration: String(audioDuration),
|
|
553
|
+
type: callType
|
|
260
554
|
},
|
|
261
555
|
content: undefined
|
|
262
556
|
}
|
|
263
557
|
]
|
|
264
|
-
};
|
|
265
|
-
|
|
558
|
+
});
|
|
559
|
+
};
|
|
560
|
+
const muteCall = async (callId, callCreator, to, muted) => {
|
|
561
|
+
await sendNode({
|
|
562
|
+
tag: 'call',
|
|
563
|
+
attrs: { to, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
564
|
+
content: [
|
|
565
|
+
{
|
|
566
|
+
tag: 'mute_v2',
|
|
567
|
+
attrs: { 'mute-state': muted ? '1' : '0', 'call-id': callId, 'call-creator': callCreator },
|
|
568
|
+
content: undefined
|
|
569
|
+
}
|
|
570
|
+
]
|
|
571
|
+
});
|
|
572
|
+
};
|
|
573
|
+
const sendHeartbeat = async (callId, callCreator) => {
|
|
574
|
+
await sendNode({
|
|
575
|
+
tag: 'call',
|
|
576
|
+
attrs: { to: `${callId}@call`, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
577
|
+
content: [{ tag: 'heartbeat', attrs: { 'call-id': callId, 'call-creator': callCreator }, content: undefined }]
|
|
578
|
+
});
|
|
579
|
+
};
|
|
580
|
+
const sendEncRekey = async (callId, callCreator, to, transactionId) => {
|
|
581
|
+
await sendNode({
|
|
582
|
+
tag: 'call',
|
|
583
|
+
attrs: { to, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
584
|
+
content: [
|
|
585
|
+
{
|
|
586
|
+
tag: 'enc_rekey',
|
|
587
|
+
attrs: { 'transaction-id': transactionId, 'call-id': callId, 'call-creator': callCreator },
|
|
588
|
+
content: [
|
|
589
|
+
{ tag: 'encopt', attrs: { keygen: '2' }, content: undefined },
|
|
590
|
+
{ tag: 'enc', attrs: { v: '2', type: 'msg' }, content: undefined }
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
]
|
|
594
|
+
});
|
|
595
|
+
};
|
|
596
|
+
const sendVideoState = async (callId, callCreator, to, enabled, orientation = '1') => {
|
|
597
|
+
await sendNode({
|
|
598
|
+
tag: 'call',
|
|
599
|
+
attrs: { to, id: randomBytes(16).toString('hex').toUpperCase() },
|
|
600
|
+
content: [
|
|
601
|
+
{
|
|
602
|
+
tag: 'video',
|
|
603
|
+
attrs: {
|
|
604
|
+
'call-id': callId,
|
|
605
|
+
'call-creator': callCreator,
|
|
606
|
+
state: enabled ? '1' : '0',
|
|
607
|
+
device_orientation: orientation
|
|
608
|
+
},
|
|
609
|
+
content: undefined
|
|
610
|
+
}
|
|
611
|
+
]
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
const queryCallLink = async (token, media = 'video') => {
|
|
615
|
+
return await query({
|
|
616
|
+
tag: 'call',
|
|
617
|
+
attrs: { to: 'call', id: randomBytes(16).toString('hex').toUpperCase() },
|
|
618
|
+
content: [{ tag: 'link_query', attrs: { media, token }, content: undefined }]
|
|
619
|
+
});
|
|
620
|
+
};
|
|
621
|
+
const joinCallLink = async (token, media = 'video') => {
|
|
622
|
+
const content = [
|
|
623
|
+
{ tag: 'audio', attrs: { rate: '16000', enc: 'opus' }, content: undefined },
|
|
624
|
+
{ tag: 'net', attrs: { medium: '2' }, content: undefined },
|
|
625
|
+
{ tag: 'capability', attrs: { ver: '1' }, content: undefined }
|
|
626
|
+
];
|
|
627
|
+
if (media === 'video') {
|
|
628
|
+
content.splice(1, 0, {
|
|
629
|
+
tag: 'video',
|
|
630
|
+
attrs: { screen_width: '1080', screen_height: '2400', dec: 'H264,H265,AV1', device_orientation: '0' },
|
|
631
|
+
content: undefined
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
return await query({
|
|
635
|
+
tag: 'call',
|
|
636
|
+
attrs: { to: 'call', id: randomBytes(16).toString('hex').toUpperCase() },
|
|
637
|
+
content: [{ tag: 'link_join', attrs: { media, token }, content }]
|
|
638
|
+
});
|
|
266
639
|
};
|
|
267
640
|
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
268
641
|
const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
@@ -407,22 +780,16 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
407
780
|
}
|
|
408
781
|
}
|
|
409
782
|
else {
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
catch (error) {
|
|
422
|
-
logger.warn({ error, jid: from }, 'failed to assert sessions after identity change');
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
783
|
+
const result = await handleIdentityChange(node, {
|
|
784
|
+
meId: authState.creds.me?.id,
|
|
785
|
+
meLid: authState.creds.me?.lid,
|
|
786
|
+
validateSession: signalRepository.validateSession,
|
|
787
|
+
assertSessions,
|
|
788
|
+
debounceCache: identityAssertDebounce,
|
|
789
|
+
logger,
|
|
790
|
+
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
791
|
+
});
|
|
792
|
+
if (result.action === 'no_identity_node') {
|
|
426
793
|
logger.info({ node }, 'unknown encrypt notification');
|
|
427
794
|
}
|
|
428
795
|
}
|
|
@@ -431,6 +798,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
431
798
|
// TODO: Support PN/LID (Here is only LID now)
|
|
432
799
|
const actingParticipantLid = fullNode.attrs.participant;
|
|
433
800
|
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
801
|
+
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
434
802
|
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
435
803
|
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
436
804
|
switch (child?.tag) {
|
|
@@ -450,7 +818,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
450
818
|
{
|
|
451
819
|
...metadata,
|
|
452
820
|
author: actingParticipantLid,
|
|
453
|
-
authorPn: actingParticipantPn
|
|
821
|
+
authorPn: actingParticipantPn,
|
|
822
|
+
authorUsername: actingParticipantUsername
|
|
454
823
|
}
|
|
455
824
|
]);
|
|
456
825
|
break;
|
|
@@ -560,7 +929,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
560
929
|
await handleNewsletterNotification(node);
|
|
561
930
|
break;
|
|
562
931
|
case 'mex':
|
|
563
|
-
await
|
|
932
|
+
await handleMexNotification(node);
|
|
564
933
|
break;
|
|
565
934
|
case 'w:gp2':
|
|
566
935
|
// TODO: HANDLE PARTICIPANT_PN
|
|
@@ -592,6 +961,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
592
961
|
case 'picture':
|
|
593
962
|
const setPicture = getBinaryNodeChild(node, 'set');
|
|
594
963
|
const delPicture = getBinaryNodeChild(node, 'delete');
|
|
964
|
+
// TODO: WAJIDHASH stuff proper support inhouse
|
|
595
965
|
ev.emit('contacts.update', [
|
|
596
966
|
{
|
|
597
967
|
id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
|
@@ -644,7 +1014,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
644
1014
|
const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
|
|
645
1015
|
const random = randomBytes(32);
|
|
646
1016
|
const linkCodeSalt = randomBytes(32);
|
|
647
|
-
const linkCodePairingExpanded =
|
|
1017
|
+
const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
|
|
648
1018
|
salt: linkCodeSalt,
|
|
649
1019
|
info: 'link_code_pairing_key_bundle_encryption_key'
|
|
650
1020
|
});
|
|
@@ -658,7 +1028,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
658
1028
|
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
|
|
659
1029
|
const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
|
|
660
1030
|
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
|
|
661
|
-
authState.creds.advSecretKey = (
|
|
1031
|
+
authState.creds.advSecretKey = Buffer.from(hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
|
|
662
1032
|
await query({
|
|
663
1033
|
tag: 'iq',
|
|
664
1034
|
attrs: {
|
|
@@ -705,27 +1075,91 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
705
1075
|
return result;
|
|
706
1076
|
}
|
|
707
1077
|
};
|
|
1078
|
+
/**
|
|
1079
|
+
* In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
|
|
1080
|
+
*/
|
|
1081
|
+
const tcTokenKnownJids = new Set();
|
|
1082
|
+
const tcTokenIndexLoaded = (async () => {
|
|
1083
|
+
try {
|
|
1084
|
+
const jids = await readTcTokenIndex(authState.keys);
|
|
1085
|
+
for (const jid of jids)
|
|
1086
|
+
tcTokenKnownJids.add(jid);
|
|
1087
|
+
logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
|
|
1088
|
+
}
|
|
1089
|
+
catch (err) {
|
|
1090
|
+
logger.warn({ err: err?.message }, 'failed to load tctoken index');
|
|
1091
|
+
}
|
|
1092
|
+
})();
|
|
1093
|
+
let tcTokenIndexTimer;
|
|
1094
|
+
async function flushTcTokenIndex() {
|
|
1095
|
+
if (tcTokenIndexTimer) {
|
|
1096
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1097
|
+
tcTokenIndexTimer = undefined;
|
|
1098
|
+
}
|
|
1099
|
+
const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
|
|
1100
|
+
return authState.keys.set({ tctoken: write });
|
|
1101
|
+
}
|
|
1102
|
+
function scheduleTcTokenIndexSave() {
|
|
1103
|
+
if (tcTokenIndexTimer) {
|
|
1104
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1105
|
+
}
|
|
1106
|
+
tcTokenIndexTimer = setTimeout(() => {
|
|
1107
|
+
tcTokenIndexTimer = undefined;
|
|
1108
|
+
flushTcTokenIndex().catch(err => {
|
|
1109
|
+
logger.warn({ err: err?.message }, 'failed to save tctoken index');
|
|
1110
|
+
});
|
|
1111
|
+
}, 5000);
|
|
1112
|
+
}
|
|
1113
|
+
function trackTcTokenJid(jid) {
|
|
1114
|
+
if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
1115
|
+
tcTokenKnownJids.add(jid);
|
|
1116
|
+
scheduleTcTokenIndexSave();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
708
1119
|
const handlePrivacyTokenNotification = async (node) => {
|
|
709
1120
|
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
710
|
-
const from = jidNormalizedUser(node.attrs.from);
|
|
711
1121
|
if (!tokensNode)
|
|
712
1122
|
return;
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1123
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
1124
|
+
const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
|
|
1125
|
+
? jidNormalizedUser(node.attrs.sender_lid)
|
|
1126
|
+
: undefined;
|
|
1127
|
+
const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
|
|
1128
|
+
logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
|
|
1129
|
+
await storeTcTokensFromIqResult({
|
|
1130
|
+
result: node,
|
|
1131
|
+
fallbackJid,
|
|
1132
|
+
keys: authState.keys,
|
|
1133
|
+
getLIDForPN,
|
|
1134
|
+
onNewJidStored: trackTcTokenJid
|
|
1135
|
+
});
|
|
1136
|
+
};
|
|
1137
|
+
/**
|
|
1138
|
+
* Fire-and-forget tctoken re-issuance after a peer's device identity changed.
|
|
1139
|
+
*/
|
|
1140
|
+
const reissueTcTokenAfterIdentityChange = (from) => {
|
|
1141
|
+
void (async () => {
|
|
1142
|
+
const normalizedJid = jidNormalizedUser(from);
|
|
1143
|
+
const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
|
|
1144
|
+
const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
|
|
1145
|
+
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
|
|
1146
|
+
if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
|
|
1147
|
+
return;
|
|
727
1148
|
}
|
|
728
|
-
|
|
1149
|
+
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
|
|
1150
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
1151
|
+
const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
1152
|
+
const result = await issuePrivacyTokens([issueJid], senderTs);
|
|
1153
|
+
await storeTcTokensFromIqResult({
|
|
1154
|
+
result,
|
|
1155
|
+
fallbackJid: tcJid,
|
|
1156
|
+
keys: authState.keys,
|
|
1157
|
+
getLIDForPN,
|
|
1158
|
+
onNewJidStored: trackTcTokenJid
|
|
1159
|
+
});
|
|
1160
|
+
})().catch(err => {
|
|
1161
|
+
logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
|
|
1162
|
+
});
|
|
729
1163
|
};
|
|
730
1164
|
async function decipherLinkPublicKey(data) {
|
|
731
1165
|
const buffer = toRequiredBuffer(data);
|
|
@@ -959,81 +1393,148 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
959
1393
|
await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
|
|
960
1394
|
return;
|
|
961
1395
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
await signalRepository.
|
|
1396
|
+
let acked = false;
|
|
1397
|
+
try {
|
|
1398
|
+
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1399
|
+
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
1400
|
+
// store new mappings we didn't have before
|
|
1401
|
+
if (!!alt) {
|
|
1402
|
+
const altServer = jidDecode(alt)?.server;
|
|
1403
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1404
|
+
if (altServer === 'lid') {
|
|
1405
|
+
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1406
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1407
|
+
await signalRepository.migrateSession(primaryJid, alt);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1412
|
+
await signalRepository.migrateSession(alt, primaryJid);
|
|
972
1413
|
}
|
|
973
1414
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1415
|
+
// Cache for retry receipts BEFORE decrypt — so retry logic works even if decryption throws
|
|
1416
|
+
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
|
|
1417
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1418
|
+
logger.debug({ jid: msg.key.remoteJid, id: msg.key.id }, 'Added message to recent cache for retry receipts');
|
|
977
1419
|
}
|
|
978
|
-
}
|
|
979
|
-
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
|
|
980
|
-
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
981
|
-
logger.debug({
|
|
982
|
-
jid: msg.key.remoteJid,
|
|
983
|
-
id: msg.key.id
|
|
984
|
-
}, 'Added message to recent cache for retry receipts');
|
|
985
|
-
}
|
|
986
|
-
try {
|
|
987
1420
|
await messageMutex.mutex(async () => {
|
|
988
1421
|
await decrypt();
|
|
989
1422
|
// message failed to decrypt
|
|
990
1423
|
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
|
|
991
|
-
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT
|
|
992
|
-
|
|
993
|
-
return sendMessageAck(node);
|
|
1424
|
+
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
|
|
1425
|
+
acked = true;
|
|
1426
|
+
return sendMessageAck(node, NACK_REASONS.ParsingError);
|
|
994
1427
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1428
|
+
if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1429
|
+
// Message arrived without encryption (e.g. CTWA ads messages).
|
|
1430
|
+
// Check if this is eligible for placeholder resend (matching WA Web filters).
|
|
1431
|
+
const unavailableNode = getBinaryNodeChild(node, 'unavailable');
|
|
1432
|
+
const unavailableType = unavailableNode?.attrs?.type;
|
|
1433
|
+
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1434
|
+
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1435
|
+
unavailableType === 'view_once_unavailable_fanout') {
|
|
1436
|
+
logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
|
|
1437
|
+
acked = true;
|
|
1438
|
+
return sendMessageAck(node);
|
|
1439
|
+
}
|
|
1440
|
+
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1441
|
+
if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1442
|
+
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
|
|
1443
|
+
acked = true;
|
|
1444
|
+
return sendMessageAck(node);
|
|
1445
|
+
}
|
|
1446
|
+
// Request the real content from the phone via placeholder resend PDO.
|
|
1447
|
+
// Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
|
|
1448
|
+
// and store the requestId in stubParameters[1] so users can correlate
|
|
1449
|
+
// with the incoming PDO response event.
|
|
1450
|
+
const cleanKey = {
|
|
1451
|
+
remoteJid: msg.key.remoteJid,
|
|
1452
|
+
fromMe: msg.key.fromMe,
|
|
1453
|
+
id: msg.key.id,
|
|
1454
|
+
participant: msg.key.participant
|
|
1455
|
+
};
|
|
1456
|
+
// Cache the original message metadata so the PDO response handler
|
|
1457
|
+
// can preserve key fields (LID details etc.) that the phone may omit
|
|
1458
|
+
const msgData = {
|
|
1459
|
+
key: msg.key,
|
|
1460
|
+
messageTimestamp: msg.messageTimestamp,
|
|
1461
|
+
pushName: msg.pushName,
|
|
1462
|
+
participant: msg.participant,
|
|
1463
|
+
verifiedBizName: msg.verifiedBizName
|
|
1464
|
+
};
|
|
1465
|
+
requestPlaceholderResend(cleanKey, msgData)
|
|
1466
|
+
.then(requestId => {
|
|
1467
|
+
if (requestId && requestId !== 'RESOLVED') {
|
|
1468
|
+
logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
|
|
1469
|
+
ev.emit('messages.update', [
|
|
1470
|
+
{
|
|
1471
|
+
key: msg.key,
|
|
1472
|
+
update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
|
|
1473
|
+
}
|
|
1474
|
+
]);
|
|
1017
1475
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1476
|
+
})
|
|
1477
|
+
.catch(err => {
|
|
1478
|
+
logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
|
|
1479
|
+
});
|
|
1480
|
+
acked = true;
|
|
1481
|
+
await sendMessageAck(node);
|
|
1482
|
+
// Don't return — fall through to upsertMessage so the stub is emitted
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
// Skip retry for expired status messages (>24h old)
|
|
1486
|
+
if (isJidStatusBroadcast(msg.key.remoteJid)) {
|
|
1487
|
+
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1488
|
+
if (messageAge > STATUS_EXPIRY_SECONDS) {
|
|
1489
|
+
logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
|
|
1490
|
+
acked = true;
|
|
1491
|
+
return sendMessageAck(node);
|
|
1022
1492
|
}
|
|
1023
1493
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1494
|
+
const errorMessage = msg?.messageStubParameters?.[0] || '';
|
|
1495
|
+
const isPreKeyError = errorMessage.includes('PreKey');
|
|
1496
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
1497
|
+
// Handle both pre-key and normal retries in single mutex
|
|
1498
|
+
await retryMutex.mutex(async () => {
|
|
1027
1499
|
try {
|
|
1500
|
+
if (!ws.isOpen) {
|
|
1501
|
+
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
// Handle pre-key errors with upload and delay
|
|
1505
|
+
if (isPreKeyError) {
|
|
1506
|
+
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
1507
|
+
try {
|
|
1508
|
+
logger.debug('Uploading pre-keys for error recovery');
|
|
1509
|
+
await uploadPreKeys(5);
|
|
1510
|
+
logger.debug('Waiting for server to process new pre-keys');
|
|
1511
|
+
await delay(1000);
|
|
1512
|
+
}
|
|
1513
|
+
catch (uploadErr) {
|
|
1514
|
+
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1028
1517
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1029
1518
|
await sendRetryRequest(node, !encNode);
|
|
1519
|
+
if (retryRequestDelayMs) {
|
|
1520
|
+
await delay(retryRequestDelayMs);
|
|
1521
|
+
}
|
|
1030
1522
|
}
|
|
1031
|
-
catch (
|
|
1032
|
-
logger.error({
|
|
1523
|
+
catch (err) {
|
|
1524
|
+
logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
|
|
1525
|
+
// Still attempt retry even if pre-key upload failed
|
|
1526
|
+
try {
|
|
1527
|
+
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1528
|
+
await sendRetryRequest(node, !encNode);
|
|
1529
|
+
}
|
|
1530
|
+
catch (retryErr) {
|
|
1531
|
+
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
1532
|
+
}
|
|
1033
1533
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1534
|
+
acked = true;
|
|
1535
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1037
1538
|
}
|
|
1038
1539
|
else {
|
|
1039
1540
|
if (messageRetryManager && msg.key.id) {
|
|
@@ -1059,6 +1560,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1059
1560
|
else if (!sendActiveReceipts) {
|
|
1060
1561
|
type = 'inactive';
|
|
1061
1562
|
}
|
|
1563
|
+
acked = true;
|
|
1062
1564
|
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
|
|
1063
1565
|
// send ack for history message
|
|
1064
1566
|
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
@@ -1068,6 +1570,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1068
1570
|
}
|
|
1069
1571
|
}
|
|
1070
1572
|
else {
|
|
1573
|
+
acked = true;
|
|
1071
1574
|
await sendMessageAck(node);
|
|
1072
1575
|
logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
|
|
1073
1576
|
}
|
|
@@ -1078,6 +1581,9 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1078
1581
|
}
|
|
1079
1582
|
catch (error) {
|
|
1080
1583
|
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
|
|
1584
|
+
if (!acked) {
|
|
1585
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
|
|
1586
|
+
}
|
|
1081
1587
|
}
|
|
1082
1588
|
};
|
|
1083
1589
|
const handleCall = async (node) => {
|
|
@@ -1092,6 +1598,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1092
1598
|
const call = {
|
|
1093
1599
|
chatId: attrs.from,
|
|
1094
1600
|
from,
|
|
1601
|
+
callerPn: infoChild.attrs['caller_pn'],
|
|
1095
1602
|
id: callId,
|
|
1096
1603
|
date: new Date(+attrs.t * 1000),
|
|
1097
1604
|
offline: !!attrs.offline,
|
|
@@ -1108,6 +1615,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1108
1615
|
if (existingCall) {
|
|
1109
1616
|
call.isVideo = existingCall.isVideo;
|
|
1110
1617
|
call.isGroup = existingCall.isGroup;
|
|
1618
|
+
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
1111
1619
|
}
|
|
1112
1620
|
// delete data once call has ended
|
|
1113
1621
|
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
@@ -1118,23 +1626,22 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1118
1626
|
};
|
|
1119
1627
|
const handleBadAck = async ({ attrs }) => {
|
|
1120
1628
|
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
1121
|
-
// WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
|
|
1122
|
-
// // current hypothesis is that if pash is sent in the ack
|
|
1123
|
-
// // it means -- the message hasn't reached all devices yet
|
|
1124
|
-
// // we'll retry sending the message here
|
|
1125
|
-
// if(attrs.phash) {
|
|
1126
|
-
// logger.info({ attrs }, 'received phash in ack, resending message...')
|
|
1127
|
-
// const msg = await getMessage(key)
|
|
1128
|
-
// if(msg) {
|
|
1129
|
-
// await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
|
|
1130
|
-
// } else {
|
|
1131
|
-
// logger.warn({ attrs }, 'could not send message again, as it was not found')
|
|
1132
|
-
// }
|
|
1133
|
-
// }
|
|
1134
1629
|
// error in acknowledgement,
|
|
1135
1630
|
// device could not display the message
|
|
1136
1631
|
if (attrs.error) {
|
|
1137
|
-
|
|
1632
|
+
if (attrs.error === SERVER_ERROR_CODES.MissingTcToken) {
|
|
1633
|
+
// 463 = account restricted + no tctoken for this contact.
|
|
1634
|
+
// WA Web prevents this client-side (disables compose bar).
|
|
1635
|
+
// No retry — retrying worsens the restriction by counting
|
|
1636
|
+
// as another "reach out" to an unknown contact.
|
|
1637
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
1638
|
+
}
|
|
1639
|
+
else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
|
|
1640
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1644
|
+
}
|
|
1138
1645
|
ev.emit('messages.update', [
|
|
1139
1646
|
{
|
|
1140
1647
|
key,
|
|
@@ -1144,19 +1651,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1144
1651
|
}
|
|
1145
1652
|
}
|
|
1146
1653
|
]);
|
|
1147
|
-
// resend the message with device_fanout=false, use at your own risk
|
|
1148
|
-
// if (attrs.error === '475') {
|
|
1149
|
-
// const msg = await getMessage(key)
|
|
1150
|
-
// if (msg) {
|
|
1151
|
-
// await relayMessage(key.remoteJid!, msg, {
|
|
1152
|
-
// messageId: key.id!,
|
|
1153
|
-
// useUserDevicesCache: false,
|
|
1154
|
-
// additionalAttributes: {
|
|
1155
|
-
// device_fanout: 'false'
|
|
1156
|
-
// }
|
|
1157
|
-
// })
|
|
1158
|
-
// }
|
|
1159
|
-
// }
|
|
1160
1654
|
}
|
|
1161
1655
|
};
|
|
1162
1656
|
/// processes a node with the given function
|
|
@@ -1271,17 +1765,112 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1271
1765
|
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1272
1766
|
}
|
|
1273
1767
|
});
|
|
1274
|
-
|
|
1768
|
+
/** timestamp of last tctoken prune run — throttles to once per 24h */
|
|
1769
|
+
let lastTcTokenPruneTs = 0;
|
|
1770
|
+
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1275
1771
|
if (typeof isOnline !== 'undefined') {
|
|
1276
1772
|
sendActiveReceipts = isOnline;
|
|
1277
1773
|
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1278
1774
|
}
|
|
1775
|
+
// Flush pending tctoken index save on disconnect to avoid writing after close
|
|
1776
|
+
if (connection === 'close' && tcTokenIndexTimer) {
|
|
1777
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1778
|
+
tcTokenIndexTimer = undefined;
|
|
1779
|
+
try {
|
|
1780
|
+
void Promise.resolve(flushTcTokenIndex()).catch(() => { });
|
|
1781
|
+
}
|
|
1782
|
+
catch {
|
|
1783
|
+
/* ignore sync errors */
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
// Prune expired tctokens when coming online, at most once per 24 hours
|
|
1787
|
+
if (isOnline) {
|
|
1788
|
+
const now = Date.now();
|
|
1789
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
1790
|
+
if (now - lastTcTokenPruneTs >= DAY_MS) {
|
|
1791
|
+
lastTcTokenPruneTs = now;
|
|
1792
|
+
void pruneExpiredTcTokens();
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1279
1795
|
});
|
|
1796
|
+
async function pruneExpiredTcTokens() {
|
|
1797
|
+
try {
|
|
1798
|
+
await tcTokenIndexLoaded;
|
|
1799
|
+
const persisted = await readTcTokenIndex(authState.keys);
|
|
1800
|
+
const allJids = new Set(tcTokenKnownJids);
|
|
1801
|
+
for (const jid of persisted)
|
|
1802
|
+
allJids.add(jid);
|
|
1803
|
+
if (!allJids.size)
|
|
1804
|
+
return;
|
|
1805
|
+
const jids = [...allJids];
|
|
1806
|
+
const allTokens = await authState.keys.get('tctoken', jids);
|
|
1807
|
+
const writes = {};
|
|
1808
|
+
const survivors = new Set();
|
|
1809
|
+
let mutated = 0;
|
|
1810
|
+
for (const jid of jids) {
|
|
1811
|
+
const entry = allTokens[jid];
|
|
1812
|
+
if (!entry) {
|
|
1813
|
+
mutated++;
|
|
1814
|
+
continue;
|
|
1815
|
+
}
|
|
1816
|
+
const hasPeerToken = !!entry.token?.length;
|
|
1817
|
+
const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
|
|
1818
|
+
const hasSenderTs = entry.senderTimestamp !== undefined;
|
|
1819
|
+
const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
|
|
1820
|
+
const keepPeerToken = hasPeerToken && !peerTokenExpired;
|
|
1821
|
+
const keepSenderTs = hasSenderTs && !senderTsExpired;
|
|
1822
|
+
if (!keepPeerToken && !keepSenderTs) {
|
|
1823
|
+
writes[jid] = null;
|
|
1824
|
+
mutated++;
|
|
1825
|
+
}
|
|
1826
|
+
else if (peerTokenExpired && keepSenderTs) {
|
|
1827
|
+
writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
|
|
1828
|
+
survivors.add(jid);
|
|
1829
|
+
mutated++;
|
|
1830
|
+
}
|
|
1831
|
+
else {
|
|
1832
|
+
survivors.add(jid);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (mutated === 0)
|
|
1836
|
+
return;
|
|
1837
|
+
await authState.keys.set({
|
|
1838
|
+
tctoken: {
|
|
1839
|
+
...writes,
|
|
1840
|
+
[TC_TOKEN_INDEX_KEY]: {
|
|
1841
|
+
token: Buffer.from(JSON.stringify([...survivors]))
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
tcTokenKnownJids.clear();
|
|
1846
|
+
for (const jid of survivors)
|
|
1847
|
+
tcTokenKnownJids.add(jid);
|
|
1848
|
+
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
|
|
1849
|
+
}
|
|
1850
|
+
catch (err) {
|
|
1851
|
+
logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1280
1854
|
return {
|
|
1281
1855
|
...sock,
|
|
1282
1856
|
sendMessageAck,
|
|
1283
1857
|
sendRetryRequest,
|
|
1858
|
+
offerCall,
|
|
1859
|
+
initiateCall,
|
|
1860
|
+
cancelCall,
|
|
1284
1861
|
rejectCall,
|
|
1862
|
+
acceptCall,
|
|
1863
|
+
preacceptCall,
|
|
1864
|
+
terminateCall,
|
|
1865
|
+
sendRelayLatency,
|
|
1866
|
+
sendTransport,
|
|
1867
|
+
sendCallDuration,
|
|
1868
|
+
muteCall,
|
|
1869
|
+
sendHeartbeat,
|
|
1870
|
+
sendEncRekey,
|
|
1871
|
+
sendVideoState,
|
|
1872
|
+
queryCallLink,
|
|
1873
|
+
joinCallLink,
|
|
1285
1874
|
fetchMessageHistory,
|
|
1286
1875
|
requestPlaceholderResend,
|
|
1287
1876
|
messageRetryManager
|