@pontalabs/baileys 1.0.6 → 1.0.7
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/LICENSE +1 -1
- package/README.md +839 -267
- package/WAProto/GenerateStatics.sh +4 -3
- package/WAProto/WAProto.proto +511 -1215
- package/WAProto/index.d.ts +55 -14017
- package/WAProto/index.js +29 -97689
- package/lib/Defaults/index.d.ts +74 -72
- package/lib/Defaults/index.js +108 -94
- package/lib/Signal/Group/ciphertext-message.d.ts +7 -8
- package/lib/Signal/Group/ciphertext-message.js +16 -9
- package/lib/Signal/Group/group-session-builder.d.ts +13 -11
- package/lib/Signal/Group/group-session-builder.js +61 -19
- package/lib/Signal/Group/group_cipher.d.ts +16 -14
- package/lib/Signal/Group/group_cipher.js +70 -41
- package/lib/Signal/Group/index.d.ts +11 -12
- package/lib/Signal/Group/index.js +61 -12
- package/lib/Signal/Group/keyhelper.d.ts +14 -9
- package/lib/Signal/Group/keyhelper.js +58 -10
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
- package/lib/Signal/Group/sender-chain-key.js +34 -13
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
- package/lib/Signal/Group/sender-key-distribution-message.js +36 -28
- package/lib/Signal/Group/sender-key-message.d.ts +18 -18
- package/lib/Signal/Group/sender-key-message.js +45 -38
- package/lib/Signal/Group/sender-key-name.d.ts +15 -14
- package/lib/Signal/Group/sender-key-name.js +31 -20
- package/lib/Signal/Group/sender-key-record.d.ts +25 -24
- package/lib/Signal/Group/sender-key-record.js +35 -18
- package/lib/Signal/Group/sender-key-state.d.ts +34 -29
- package/lib/Signal/Group/sender-key-state.js +100 -37
- package/lib/Signal/Group/sender-message-key.d.ts +10 -11
- package/lib/Signal/Group/sender-message-key.js +22 -15
- package/lib/Signal/libsignal.d.ts +8 -5
- package/lib/Signal/libsignal.js +142 -288
- package/lib/Signal/lid-mapping.d.ts +28 -23
- package/lib/Signal/lid-mapping.js +184 -277
- package/lib/Socket/Client/index.d.ts +2 -3
- package/lib/Socket/Client/index.js +22 -3
- package/lib/Socket/Client/types.d.ts +15 -15
- package/lib/Socket/Client/types.js +15 -8
- package/lib/Socket/Client/websocket.d.ts +12 -12
- package/lib/Socket/Client/websocket.js +37 -29
- package/lib/Socket/business.d.ts +178 -208
- package/lib/Socket/business.js +127 -91
- package/lib/Socket/chats.d.ts +93 -120
- package/lib/Socket/chats.js +652 -727
- package/lib/Socket/community.js +10 -2
- package/lib/Socket/groups.d.ts +111 -143
- package/lib/Socket/groups.js +186 -165
- package/lib/Socket/index.d.ts +183 -252
- package/lib/Socket/index.js +18 -38
- package/lib/Socket/messages-recv.d.ts +167 -196
- package/lib/Socket/messages-recv.js +1567 -1443
- package/lib/Socket/messages-send.d.ts +158 -192
- package/lib/Socket/messages-send.js +1208 -1102
- package/lib/Socket/newsletter.d.ts +138 -163
- package/lib/Socket/newsletter.js +249 -153
- package/lib/Socket/socket.d.ts +36 -50
- package/lib/Socket/socket.js +820 -749
- package/lib/Store/index.d.ts +4 -4
- package/lib/Store/index.js +24 -4
- package/lib/Store/make-in-memory-store.d.ts +104 -44
- package/lib/Store/make-in-memory-store.js +213 -199
- package/lib/Store/make-ordered-dictionary.d.ts +12 -13
- package/lib/Store/make-ordered-dictionary.js +45 -38
- package/lib/Store/object-repository.d.ts +10 -11
- package/lib/Store/object-repository.js +16 -9
- package/lib/Types/Auth.d.ts +97 -93
- package/lib/Types/Auth.js +3 -2
- package/lib/Types/Bussines.js +3 -2
- package/lib/Types/Call.d.ts +13 -14
- package/lib/Types/Call.js +3 -2
- package/lib/Types/Chat.d.ts +97 -78
- package/lib/Types/Chat.js +9 -8
- package/lib/Types/Contact.d.ts +9 -12
- package/lib/Types/Contact.js +3 -2
- package/lib/Types/Events.d.ts +176 -206
- package/lib/Types/Events.js +3 -2
- package/lib/Types/GroupMetadata.d.ts +48 -53
- package/lib/Types/GroupMetadata.js +3 -2
- package/lib/Types/Label.d.ts +14 -13
- package/lib/Types/Label.js +30 -24
- package/lib/Types/LabelAssociation.d.ts +20 -15
- package/lib/Types/LabelAssociation.js +12 -6
- package/lib/Types/Message.d.ts +412 -248
- package/lib/Types/Message.js +19 -17
- package/lib/Types/Product.d.ts +71 -58
- package/lib/Types/Product.js +3 -2
- package/lib/Types/Signal.d.ts +82 -71
- package/lib/Types/Signal.js +3 -2
- package/lib/Types/Socket.d.ts +81 -76
- package/lib/Types/Socket.js +3 -3
- package/lib/Types/State.d.ts +19 -75
- package/lib/Types/State.js +14 -56
- package/lib/Types/USync.d.ts +8 -8
- package/lib/Types/USync.js +3 -2
- package/lib/Types/index.d.ts +62 -47
- package/lib/Types/index.js +50 -26
- package/lib/Utils/auth-utils.d.ts +10 -13
- package/lib/Utils/auth-utils.js +472 -213
- package/lib/Utils/business.d.ts +20 -14
- package/lib/Utils/business.js +134 -110
- package/lib/Utils/chat-utils.d.ts +70 -88
- package/lib/Utils/chat-utils.js +403 -466
- package/lib/Utils/crypto.d.ts +46 -27
- package/lib/Utils/crypto.js +188 -117
- package/lib/Utils/decode-wa-message.d.ts +45 -58
- package/lib/Utils/decode-wa-message.js +311 -212
- package/lib/Utils/event-buffer.d.ts +17 -14
- package/lib/Utils/event-buffer.js +296 -321
- package/lib/Utils/generics.d.ts +92 -66
- package/lib/Utils/generics.js +436 -270
- package/lib/Utils/history.d.ts +23 -24
- package/lib/Utils/history.js +76 -106
- package/lib/Utils/index.d.ts +21 -24
- package/lib/Utils/index.js +41 -24
- package/lib/Utils/link-preview.d.ts +14 -12
- package/lib/Utils/link-preview.js +75 -40
- package/lib/Utils/logger.d.ts +11 -10
- package/lib/Utils/logger.js +7 -3
- package/lib/Utils/lt-hash.d.ts +14 -8
- package/lib/Utils/lt-hash.js +53 -3
- package/lib/Utils/make-mutex.d.ts +7 -7
- package/lib/Utils/make-mutex.js +44 -28
- package/lib/Utils/message-retry-manager.d.ts +88 -115
- package/lib/Utils/message-retry-manager.js +160 -265
- package/lib/Utils/messages-media.d.ts +107 -105
- package/lib/Utils/messages-media.js +619 -548
- package/lib/Utils/messages.d.ts +66 -52
- package/lib/Utils/messages.js +1558 -1730
- package/lib/Utils/noise-handler.d.ts +18 -18
- package/lib/Utils/noise-handler.js +130 -176
- package/lib/Utils/process-message.d.ts +32 -43
- package/lib/Utils/process-message.js +282 -484
- package/lib/Utils/rich-message-utils.js +724 -1220
- package/lib/Utils/signal.d.ts +32 -37
- package/lib/Utils/signal.js +105 -140
- package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
- package/lib/Utils/use-multi-file-auth-state.js +120 -70
- package/lib/Utils/use-single-file-auth-state.d.ts +13 -11
- package/lib/Utils/use-single-file-auth-state.js +66 -92
- package/lib/Utils/validate-connection.d.ts +13 -11
- package/lib/Utils/validate-connection.js +113 -128
- package/lib/WABinary/constants.d.ts +27 -25
- package/lib/WABinary/constants.js +1292 -1451
- package/lib/WABinary/decode.d.ts +9 -7
- package/lib/WABinary/decode.js +189 -163
- package/lib/WABinary/encode.d.ts +3 -3
- package/lib/WABinary/encode.js +155 -110
- package/lib/WABinary/generic-utils.d.ts +27 -14
- package/lib/WABinary/generic-utils.js +105 -177
- package/lib/WABinary/index.d.ts +5 -6
- package/lib/WABinary/index.js +25 -6
- package/lib/WABinary/jid-utils.d.ts +54 -41
- package/lib/WABinary/jid-utils.js +152 -83
- package/lib/WABinary/types.d.ts +13 -10
- package/lib/WABinary/types.js +3 -2
- package/lib/WAM/BinaryInfo.d.ts +15 -8
- package/lib/WAM/BinaryInfo.js +14 -7
- package/lib/WAM/constants.d.ts +37 -30
- package/lib/WAM/constants.js +11983 -19465
- package/lib/WAM/encode.d.ts +3 -3
- package/lib/WAM/encode.js +109 -95
- package/lib/WAM/index.d.ts +3 -4
- package/lib/WAM/index.js +23 -4
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +21 -37
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -19
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +26 -18
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -11
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -11
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +24 -16
- package/lib/WAUSync/Protocols/index.d.ts +6 -6
- package/lib/WAUSync/Protocols/index.js +26 -6
- package/lib/WAUSync/USyncQuery.d.ts +28 -27
- package/lib/WAUSync/USyncQuery.js +62 -68
- package/lib/WAUSync/USyncUser.d.ts +10 -15
- package/lib/WAUSync/USyncUser.js +19 -20
- package/lib/WAUSync/index.d.ts +3 -4
- package/lib/WAUSync/index.js +23 -4
- package/lib/index.d.ts +12 -11
- package/lib/index.js +39 -12
- package/package.json +42 -86
- package/engine-requirements.js +0 -10
|
@@ -1,465 +1,388 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
-
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, 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
|
-
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
|
-
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
|
-
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
12
|
-
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
|
|
13
|
-
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
14
|
-
import { extractGroupMetadata } from './groups.js';
|
|
15
|
-
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);
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function(mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : {
|
|
5
|
+
"default": mod
|
|
6
|
+
}
|
|
19
7
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
logger.debug({ opName }, 'processing mex notification');
|
|
117
|
-
switch (opName) {
|
|
118
|
-
case 'NotificationUserReachoutTimelockUpdate':
|
|
119
|
-
handleReachoutTimelockNotification(data);
|
|
120
|
-
break;
|
|
121
|
-
case 'MessageCappingInfoNotification':
|
|
122
|
-
handleMessageCappingNotification(data);
|
|
123
|
-
break;
|
|
124
|
-
// newsletter ops still use the legacy <mex> child structure
|
|
125
|
-
case 'NotificationNewsletterUpdate':
|
|
126
|
-
case 'NotificationLinkedProfilesUpdates':
|
|
127
|
-
case 'NotificationNewsletterAdminPromote':
|
|
128
|
-
case 'NotificationNewsletterAdminDemote':
|
|
129
|
-
case 'NotificationNewsletterUserSettingChange':
|
|
130
|
-
case 'NotificationNewsletterJoin':
|
|
131
|
-
case 'NotificationNewsletterLeave':
|
|
132
|
-
case 'NotificationNewsletterStateChange':
|
|
133
|
-
case 'NotificationNewsletterAdminMetadataUpdate':
|
|
134
|
-
case 'NotificationNewsletterOwnerUpdate':
|
|
135
|
-
case 'NotificationNewsletterAdminInviteRevoke':
|
|
136
|
-
case 'NotificationNewsletterWamoSubStatusChange':
|
|
137
|
-
case 'NotificationNewsletterBlockUser':
|
|
138
|
-
case 'NotificationNewsletterPaidPartnership':
|
|
139
|
-
case 'NotificationNewsletterMilestone':
|
|
140
|
-
case 'NewsletterResponseStateUpdate':
|
|
141
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
142
|
-
break;
|
|
143
|
-
default:
|
|
144
|
-
logger.debug({ opName }, 'unhandled mex notification');
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
150
|
-
};
|
|
151
|
-
const handleReachoutTimelockNotification = (data) => {
|
|
152
|
-
const payload = data.xwa2_notify_account_reachout_timelock;
|
|
153
|
-
if (!payload) {
|
|
154
|
-
logger.warn('reachout timelock notification missing payload');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (!payload.is_active) {
|
|
158
|
-
logger.info('reachout timelock restriction lifted');
|
|
159
|
-
ev.emit('connection.update', {
|
|
160
|
-
reachoutTimeLock: {
|
|
161
|
-
isActive: false,
|
|
162
|
-
enforcementType: ReachoutTimelockEnforcementType.DEFAULT
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
// WA Web defaults to now+60s when the server omits the expiry
|
|
168
|
-
const timeEnforcementEnds = payload.time_enforcement_ends
|
|
169
|
-
? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
|
|
170
|
-
: new Date(Date.now() + 60000);
|
|
171
|
-
const enforcementType = isValidEnforcementType(payload.enforcement_type)
|
|
172
|
-
? payload.enforcement_type
|
|
173
|
-
: ReachoutTimelockEnforcementType.DEFAULT;
|
|
174
|
-
logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
|
|
175
|
-
ev.emit('connection.update', {
|
|
176
|
-
reachoutTimeLock: {
|
|
177
|
-
isActive: true,
|
|
178
|
-
timeEnforcementEnds,
|
|
179
|
-
enforcementType
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
|
-
const handleMessageCappingNotification = (data) => {
|
|
184
|
-
const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
|
|
185
|
-
if (!payload) {
|
|
186
|
-
logger.warn('message capping notification missing payload');
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
logger.info({ payload }, 'received message capping update');
|
|
190
|
-
ev.emit('message-capping.update', payload);
|
|
191
|
-
};
|
|
192
|
-
const handleLegacyMexNewsletterNotification = async (node) => {
|
|
193
|
-
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
194
|
-
const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
|
|
195
|
-
const payloadNode = mexNode?.content ? mexNode : updateNode;
|
|
196
|
-
if (!payloadNode?.content) {
|
|
197
|
-
logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
let data;
|
|
201
|
-
try {
|
|
202
|
-
const payloadContent = payloadNode.content;
|
|
203
|
-
if (Array.isArray(payloadContent)) {
|
|
204
|
-
logger.warn({ payloadNode }, 'invalid mex newsletter notification payload format');
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
|
|
208
|
-
data = JSON.parse(contentBuf.toString());
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
const operation = data?.operation ?? payloadNode?.attrs?.op_name;
|
|
215
|
-
let updates = data?.updates;
|
|
216
|
-
if (!updates) {
|
|
217
|
-
const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
|
|
218
|
-
if (linkedProfiles) {
|
|
219
|
-
updates = [linkedProfiles];
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(exports, "__esModule", {
|
|
10
|
+
value: true
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const node_cache_1 = __importDefault(require("@cacheable/node-cache"))
|
|
14
|
+
const boom_1 = require("@hapi/boom")
|
|
15
|
+
const crypto_1 = require("crypto")
|
|
16
|
+
const WAProto_1 = require("../../WAProto")
|
|
17
|
+
const Defaults_1 = require("../Defaults")
|
|
18
|
+
const Types_1 = require("../Types")
|
|
19
|
+
const Utils_1 = require("../Utils")
|
|
20
|
+
const WABinary_1 = require("../WABinary")
|
|
21
|
+
const groups_1 = require("./groups")
|
|
22
|
+
const make_mutex_1 = require("../Utils/make-mutex")
|
|
23
|
+
const messages_send_1 = require("./messages-send")
|
|
24
|
+
|
|
25
|
+
const makeMessagesRecvSocket = (config) => {
|
|
26
|
+
const {
|
|
27
|
+
logger,
|
|
28
|
+
retryRequestDelayMs,
|
|
29
|
+
maxMsgRetryCount,
|
|
30
|
+
getMessage,
|
|
31
|
+
shouldIgnoreJid,
|
|
32
|
+
enableAutoSessionRecreation
|
|
33
|
+
} = config
|
|
34
|
+
const sock = messages_send_1.makeMessagesSocket(config)
|
|
35
|
+
const {
|
|
36
|
+
ev,
|
|
37
|
+
authState,
|
|
38
|
+
ws,
|
|
39
|
+
processingMutex,
|
|
40
|
+
signalRepository,
|
|
41
|
+
query,
|
|
42
|
+
upsertMessage,
|
|
43
|
+
resyncAppState,
|
|
44
|
+
onUnexpectedError,
|
|
45
|
+
assertSessions,
|
|
46
|
+
sendNode,
|
|
47
|
+
relayMessage,
|
|
48
|
+
sendReceipt,
|
|
49
|
+
uploadPreKeys,
|
|
50
|
+
groupMetadata,
|
|
51
|
+
getUSyncDevices,
|
|
52
|
+
createParticipantNodes,
|
|
53
|
+
messageRetryManager,
|
|
54
|
+
sendPeerDataOperationMessage
|
|
55
|
+
} = sock
|
|
56
|
+
|
|
57
|
+
const retryMutex = make_mutex_1.makeMutex()
|
|
58
|
+
// Mutex terpisah dari processingMutex (dipakai chats.js/messages-send.js) supaya
|
|
59
|
+
// handleMessage/handleReceipt/handleNotification tidak saling antre di satu antrean
|
|
60
|
+
// yang sama. Kalau satu pesan lambat diproses (nunggu plugin/group metadata), receipt
|
|
61
|
+
// & notification lain tetap bisa jalan tanpa nunggu giliran.
|
|
62
|
+
const messageMutex = make_mutex_1.makeMutex()
|
|
63
|
+
const receiptMutex = make_mutex_1.makeMutex()
|
|
64
|
+
const notificationMutex = make_mutex_1.makeMutex()
|
|
65
|
+
|
|
66
|
+
const groupDataCache = new Map()
|
|
67
|
+
global.groupMetadataCache = async (jid) => {
|
|
68
|
+
if (!groupDataCache.has(jid)) {
|
|
69
|
+
groupDataCache.set(jid, groupMetadata(jid))
|
|
70
|
+
}
|
|
71
|
+
return await groupDataCache.get(jid)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const msgRetryCache = config.msgRetryCounterCache || new node_cache_1.default({
|
|
75
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
76
|
+
useClones: false
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const callOfferCache = config.callOfferCache || new node_cache_1.default({
|
|
80
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER,
|
|
81
|
+
useClones: false
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
|
|
85
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
86
|
+
useClones: false
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
let sendActiveReceipts = false
|
|
90
|
+
|
|
91
|
+
const sendMessageAck = async ({
|
|
92
|
+
tag,
|
|
93
|
+
attrs,
|
|
94
|
+
content
|
|
95
|
+
}, errorCode) => {
|
|
96
|
+
const stanza = {
|
|
97
|
+
tag: 'ack',
|
|
98
|
+
attrs: {
|
|
99
|
+
id: attrs.id,
|
|
100
|
+
to: attrs.from,
|
|
101
|
+
class: tag
|
|
220
102
|
}
|
|
221
103
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
104
|
+
|
|
105
|
+
if (!!errorCode) {
|
|
106
|
+
stanza.attrs.error = errorCode.toString()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!!attrs.participant) {
|
|
110
|
+
stanza.attrs.participant = attrs.participant
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!!attrs.recipient) {
|
|
114
|
+
stanza.attrs.recipient = attrs.recipient
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!!attrs.type && (tag !== 'message' || WABinary_1.getBinaryNodeChild({
|
|
118
|
+
tag,
|
|
119
|
+
attrs,
|
|
120
|
+
content
|
|
121
|
+
}, 'unavailable') || errorCode !== 0)) {
|
|
122
|
+
stanza.attrs.type = attrs.type
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (tag === 'message' && WABinary_1.getBinaryNodeChild({
|
|
126
|
+
tag,
|
|
127
|
+
attrs,
|
|
128
|
+
content
|
|
129
|
+
}, 'unavailable')) {
|
|
130
|
+
stanza.attrs.from = authState.creds.me.id
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
logger.debug({
|
|
134
|
+
recv: {
|
|
135
|
+
tag,
|
|
136
|
+
attrs
|
|
137
|
+
},
|
|
138
|
+
sent: stanza.attrs
|
|
139
|
+
}, 'sent ack')
|
|
140
|
+
await sendNode(stanza)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const offerCall = async (toJid, isVideo = false) => {
|
|
144
|
+
const callId = crypto_1.randomBytes(16).toString('hex').toUpperCase().substring(0, 64)
|
|
145
|
+
const offerContent = []
|
|
146
|
+
offerContent.push({
|
|
147
|
+
tag: 'audio',
|
|
148
|
+
attrs: {
|
|
149
|
+
enc: 'opus',
|
|
150
|
+
rate: '16000'
|
|
151
|
+
},
|
|
152
|
+
content: undefined
|
|
153
|
+
})
|
|
154
|
+
offerContent.push({
|
|
155
|
+
tag: 'audio',
|
|
156
|
+
attrs: {
|
|
157
|
+
enc: 'opus',
|
|
158
|
+
rate: '8000'
|
|
159
|
+
},
|
|
160
|
+
content: undefined
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
if (isVideo) {
|
|
164
|
+
offerContent.push({
|
|
165
|
+
tag: 'video',
|
|
166
|
+
attrs: {
|
|
167
|
+
enc: 'vp8',
|
|
168
|
+
dec: 'vp8',
|
|
169
|
+
orientation: '0',
|
|
170
|
+
'screen_width': '1920',
|
|
171
|
+
'screen_height': '1080',
|
|
172
|
+
'device_orientation': '0'
|
|
173
|
+
},
|
|
174
|
+
content: undefined
|
|
175
|
+
})
|
|
270
176
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
action: child.attrs.action,
|
|
307
|
-
new_role: child.attrs.role
|
|
308
|
-
};
|
|
309
|
-
ev.emit('newsletter-participants.update', participantUpdate);
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
case 'update': {
|
|
313
|
-
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
314
|
-
if (settingsNode) {
|
|
315
|
-
const update = {};
|
|
316
|
-
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
317
|
-
if (nameNode?.content)
|
|
318
|
-
update.name = nameNode.content.toString();
|
|
319
|
-
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
320
|
-
if (descriptionNode?.content)
|
|
321
|
-
update.description = descriptionNode.content.toString();
|
|
322
|
-
ev.emit('newsletter-settings.update', {
|
|
323
|
-
id: from,
|
|
324
|
-
update
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
case 'message': {
|
|
330
|
-
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
331
|
-
if (plaintextNode?.content) {
|
|
332
|
-
try {
|
|
333
|
-
const contentBuf = typeof plaintextNode.content === 'string'
|
|
334
|
-
? Buffer.from(plaintextNode.content, 'binary')
|
|
335
|
-
: Buffer.from(plaintextNode.content);
|
|
336
|
-
const messageProto = proto.Message.decode(contentBuf).toJSON();
|
|
337
|
-
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
338
|
-
key: {
|
|
339
|
-
remoteJid: from,
|
|
340
|
-
id: child.attrs.message_id || child.attrs.server_id,
|
|
341
|
-
fromMe: false // TODO: is this really true though
|
|
342
|
-
},
|
|
343
|
-
message: messageProto,
|
|
344
|
-
messageTimestamp: +child.attrs.t
|
|
345
|
-
}).toJSON();
|
|
346
|
-
await upsertMessage(fullMessage, 'append');
|
|
347
|
-
logger.debug('Processed plaintext newsletter message');
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
default:
|
|
356
|
-
logger.warn({ node, child }, 'Unknown newsletter notification child');
|
|
357
|
-
break;
|
|
177
|
+
offerContent.push({
|
|
178
|
+
tag: 'net',
|
|
179
|
+
attrs: {
|
|
180
|
+
medium: '3'
|
|
181
|
+
},
|
|
182
|
+
content: undefined
|
|
183
|
+
})
|
|
184
|
+
offerContent.push({
|
|
185
|
+
tag: 'capability',
|
|
186
|
+
attrs: {
|
|
187
|
+
ver: '1'
|
|
188
|
+
},
|
|
189
|
+
content: new Uint8Array([1, 4, 255, 131, 207, 4])
|
|
190
|
+
})
|
|
191
|
+
offerContent.push({
|
|
192
|
+
tag: 'encopt',
|
|
193
|
+
attrs: {
|
|
194
|
+
keygen: '2'
|
|
195
|
+
},
|
|
196
|
+
content: undefined
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const encKey = crypto_1.randomBytes(32)
|
|
200
|
+
const devices = (await getUSyncDevices([toJid], true, false)).map(({
|
|
201
|
+
user,
|
|
202
|
+
device
|
|
203
|
+
}) => WABinary_1.jidEncode(user, 's.whatsapp.net', device))
|
|
204
|
+
await assertSessions(devices, true)
|
|
205
|
+
|
|
206
|
+
const {
|
|
207
|
+
nodes: destinations,
|
|
208
|
+
shouldIncludeDeviceIdentity
|
|
209
|
+
} = await createParticipantNodes(devices, {
|
|
210
|
+
call: {
|
|
211
|
+
callKey: new Uint8Array(encKey)
|
|
358
212
|
}
|
|
213
|
+
}, {
|
|
214
|
+
count: '0'
|
|
215
|
+
})
|
|
216
|
+
offerContent.push({
|
|
217
|
+
tag: 'destination',
|
|
218
|
+
attrs: {},
|
|
219
|
+
content: destinations
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
if (shouldIncludeDeviceIdentity) {
|
|
223
|
+
offerContent.push({
|
|
224
|
+
tag: 'device-identity',
|
|
225
|
+
attrs: {},
|
|
226
|
+
content: Utils_1.encodeSignedDeviceIdentity(authState.creds.account, true)
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const stanza = ({
|
|
231
|
+
tag: 'call',
|
|
232
|
+
attrs: {
|
|
233
|
+
id: Utils_1.generateMessageID(),
|
|
234
|
+
to: toJid,
|
|
235
|
+
},
|
|
236
|
+
content: [{
|
|
237
|
+
tag: 'offer',
|
|
238
|
+
attrs: {
|
|
239
|
+
'call-id': callId,
|
|
240
|
+
'call-creator': authState.creds.me.id,
|
|
241
|
+
},
|
|
242
|
+
content: offerContent,
|
|
243
|
+
}],
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
await query(stanza)
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
id: callId,
|
|
250
|
+
to: toJid
|
|
359
251
|
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const stanza = buildAckStanza(node, errorCode, authState.creds.me.id);
|
|
363
|
-
logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack');
|
|
364
|
-
await sendNode(stanza);
|
|
365
|
-
};
|
|
252
|
+
}
|
|
253
|
+
|
|
366
254
|
const rejectCall = async (callId, callFrom) => {
|
|
367
|
-
const stanza = {
|
|
255
|
+
const stanza = ({
|
|
368
256
|
tag: 'call',
|
|
369
257
|
attrs: {
|
|
370
258
|
from: authState.creds.me.id,
|
|
371
|
-
to: callFrom
|
|
259
|
+
to: callFrom,
|
|
372
260
|
},
|
|
373
|
-
content: [
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
261
|
+
content: [{
|
|
262
|
+
tag: 'reject',
|
|
263
|
+
attrs: {
|
|
264
|
+
'call-id': callId,
|
|
265
|
+
'call-creator': callFrom,
|
|
266
|
+
count: '0',
|
|
267
|
+
},
|
|
268
|
+
content: undefined,
|
|
269
|
+
}],
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
await query(stanza)
|
|
273
|
+
}
|
|
274
|
+
|
|
387
275
|
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
388
|
-
const {
|
|
389
|
-
|
|
390
|
-
|
|
276
|
+
const {
|
|
277
|
+
fullMessage
|
|
278
|
+
} = Utils_1.decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '')
|
|
279
|
+
const {
|
|
280
|
+
key: msgKey
|
|
281
|
+
} = fullMessage
|
|
282
|
+
const msgId = msgKey.id
|
|
283
|
+
|
|
391
284
|
if (messageRetryManager) {
|
|
392
285
|
// Check if we've exceeded max retries using the new system
|
|
393
286
|
if (messageRetryManager.hasExceededMaxRetries(msgId)) {
|
|
394
|
-
logger.debug({
|
|
395
|
-
|
|
396
|
-
|
|
287
|
+
logger.debug({
|
|
288
|
+
msgId
|
|
289
|
+
}, 'reached retry limit with new retry manager, clearing')
|
|
290
|
+
messageRetryManager.markRetryFailed(msgId)
|
|
291
|
+
return
|
|
397
292
|
}
|
|
293
|
+
|
|
398
294
|
// Increment retry count using new system
|
|
399
|
-
const retryCount = messageRetryManager.incrementRetryCount(msgId)
|
|
295
|
+
const retryCount = messageRetryManager.incrementRetryCount(msgId)
|
|
296
|
+
|
|
400
297
|
// Use the new retry count for the rest of the logic
|
|
401
|
-
const key = `${msgId}:${msgKey?.participant}
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
298
|
+
const key = `${msgId}:${msgKey?.participant}`
|
|
299
|
+
msgRetryCache.set(key, retryCount)
|
|
300
|
+
} else {
|
|
405
301
|
// Fallback to old system
|
|
406
|
-
const key = `${msgId}:${msgKey?.participant}
|
|
407
|
-
let retryCount = (await msgRetryCache.get(key)) || 0
|
|
302
|
+
const key = `${msgId}:${msgKey?.participant}`
|
|
303
|
+
let retryCount = (await msgRetryCache.get(key)) || 0
|
|
304
|
+
|
|
408
305
|
if (retryCount >= maxMsgRetryCount) {
|
|
409
|
-
logger.debug({
|
|
410
|
-
|
|
411
|
-
|
|
306
|
+
logger.debug({
|
|
307
|
+
retryCount,
|
|
308
|
+
msgId
|
|
309
|
+
}, 'reached retry limit, clearing')
|
|
310
|
+
msgRetryCache.del(key)
|
|
311
|
+
return
|
|
412
312
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
const
|
|
313
|
+
|
|
314
|
+
retryCount += 1
|
|
315
|
+
await msgRetryCache.set(key, retryCount)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const key = `${msgId}:${msgKey?.participant}`
|
|
319
|
+
const retryCount = (await msgRetryCache.get(key)) || 1
|
|
320
|
+
const {
|
|
321
|
+
account,
|
|
322
|
+
signedPreKey,
|
|
323
|
+
signedIdentityKey: identityKey
|
|
324
|
+
} = authState.creds
|
|
325
|
+
const fromJid = node.attrs.from
|
|
326
|
+
|
|
420
327
|
// Check if we should recreate the session
|
|
421
|
-
let shouldRecreateSession = false
|
|
422
|
-
let recreateReason = ''
|
|
423
|
-
|
|
328
|
+
let shouldRecreateSession = false
|
|
329
|
+
let recreateReason = ''
|
|
330
|
+
|
|
331
|
+
if (enableAutoSessionRecreation && messageRetryManager) {
|
|
424
332
|
try {
|
|
425
333
|
// Check if we have a session with this JID
|
|
426
|
-
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
|
|
427
|
-
const hasSession = await signalRepository.validateSession(fromJid)
|
|
428
|
-
const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists)
|
|
429
|
-
|
|
430
|
-
|
|
334
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
|
|
335
|
+
const hasSession = await signalRepository.validateSession(fromJid)
|
|
336
|
+
const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists)
|
|
337
|
+
|
|
338
|
+
shouldRecreateSession = result.recreate
|
|
339
|
+
recreateReason = result.reason
|
|
340
|
+
|
|
431
341
|
if (shouldRecreateSession) {
|
|
432
|
-
logger.
|
|
342
|
+
logger.info({
|
|
343
|
+
fromJid,
|
|
344
|
+
retryCount,
|
|
345
|
+
reason: recreateReason
|
|
346
|
+
}, 'recreating session for retry')
|
|
433
347
|
// Delete existing session to force recreation
|
|
434
|
-
await authState.keys.set({
|
|
435
|
-
|
|
348
|
+
await authState.keys.set({
|
|
349
|
+
session: {
|
|
350
|
+
[sessionId]: null
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
forceIncludeKeys = true
|
|
436
354
|
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
355
|
+
} catch (error) {
|
|
356
|
+
logger.warn({
|
|
357
|
+
error,
|
|
358
|
+
fromJid
|
|
359
|
+
}, 'failed to check session recreation')
|
|
440
360
|
}
|
|
441
361
|
}
|
|
362
|
+
|
|
442
363
|
if (retryCount <= 2) {
|
|
443
364
|
// Use new retry manager for phone requests if available
|
|
444
365
|
if (messageRetryManager) {
|
|
445
366
|
// Schedule phone request with delay (like whatsmeow)
|
|
446
367
|
messageRetryManager.schedulePhoneRequest(msgId, async () => {
|
|
447
368
|
try {
|
|
448
|
-
const
|
|
449
|
-
logger.debug(`sendRetryRequest: requested placeholder resend
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
369
|
+
const msgId = await requestPlaceholderResend(msgKey)
|
|
370
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId} (scheduled)`)
|
|
371
|
+
} catch (error) {
|
|
372
|
+
logger.warn({
|
|
373
|
+
error,
|
|
374
|
+
msgId
|
|
375
|
+
}, 'failed to send scheduled phone request');
|
|
453
376
|
}
|
|
454
|
-
})
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
377
|
+
})
|
|
378
|
+
} else {
|
|
457
379
|
// Fallback to immediate request
|
|
458
|
-
const msgId = await requestPlaceholderResend(msgKey)
|
|
459
|
-
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`)
|
|
380
|
+
const msgId = await requestPlaceholderResend(msgKey)
|
|
381
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`)
|
|
460
382
|
}
|
|
461
383
|
}
|
|
462
|
-
|
|
384
|
+
|
|
385
|
+
const deviceIdentity = Utils_1.encodeSignedDeviceIdentity(account, true)
|
|
463
386
|
await authState.keys.transaction(async () => {
|
|
464
387
|
const receipt = {
|
|
465
388
|
tag: 'receipt',
|
|
@@ -468,1031 +391,1315 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
468
391
|
type: 'retry',
|
|
469
392
|
to: node.attrs.from
|
|
470
393
|
},
|
|
471
|
-
content: [
|
|
472
|
-
{
|
|
394
|
+
content: [{
|
|
473
395
|
tag: 'retry',
|
|
474
396
|
attrs: {
|
|
475
397
|
count: retryCount.toString(),
|
|
476
398
|
id: node.attrs.id,
|
|
477
399
|
t: node.attrs.t,
|
|
478
|
-
v: '1'
|
|
479
|
-
// ADD ERROR FIELD
|
|
480
|
-
error: '0'
|
|
400
|
+
v: '1'
|
|
481
401
|
}
|
|
482
402
|
},
|
|
483
403
|
{
|
|
484
404
|
tag: 'registration',
|
|
485
405
|
attrs: {},
|
|
486
|
-
content: encodeBigEndian(authState.creds.registrationId)
|
|
406
|
+
content: Utils_1.encodeBigEndian(authState.creds.registrationId)
|
|
487
407
|
}
|
|
488
408
|
]
|
|
489
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
490
411
|
if (node.attrs.recipient) {
|
|
491
|
-
receipt.attrs.recipient = node.attrs.recipient
|
|
412
|
+
receipt.attrs.recipient = node.attrs.recipient
|
|
492
413
|
}
|
|
414
|
+
|
|
493
415
|
if (node.attrs.participant) {
|
|
494
|
-
receipt.attrs.participant = node.attrs.participant
|
|
416
|
+
receipt.attrs.participant = node.attrs.participant
|
|
495
417
|
}
|
|
418
|
+
|
|
496
419
|
if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
|
|
497
|
-
const {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
420
|
+
const {
|
|
421
|
+
update,
|
|
422
|
+
preKeys
|
|
423
|
+
} = await Utils_1.getNextPreKeys(authState, 1)
|
|
424
|
+
const [keyId] = Object.keys(preKeys)
|
|
425
|
+
const key = preKeys[+keyId]
|
|
426
|
+
const content = receipt.content
|
|
427
|
+
|
|
501
428
|
content.push({
|
|
502
429
|
tag: 'keys',
|
|
503
430
|
attrs: {},
|
|
504
|
-
content: [
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
{
|
|
431
|
+
content: [{
|
|
432
|
+
tag: 'type',
|
|
433
|
+
attrs: {},
|
|
434
|
+
content: Buffer.from(Defaults_1.KEY_BUNDLE_TYPE)
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
tag: 'identity',
|
|
438
|
+
attrs: {},
|
|
439
|
+
content: identityKey.public
|
|
440
|
+
},
|
|
441
|
+
Utils_1.xmppPreKey(key, +keyId),
|
|
442
|
+
Utils_1.xmppSignedPreKey(signedPreKey),
|
|
443
|
+
{
|
|
444
|
+
tag: 'device-identity',
|
|
445
|
+
attrs: {},
|
|
446
|
+
content: deviceIdentity
|
|
447
|
+
}
|
|
510
448
|
]
|
|
511
449
|
});
|
|
512
|
-
ev.emit('creds.update', update)
|
|
513
|
-
}
|
|
514
|
-
await sendNode(receipt);
|
|
515
|
-
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
516
|
-
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
517
|
-
};
|
|
518
|
-
// Mirrors WAWeb/Handle/PreKeyLow.js: skip a re-issued notification with the same stanza id.
|
|
519
|
-
const inFlightPreKeyLow = new Set();
|
|
520
|
-
/**
|
|
521
|
-
* Fire-and-forget tctoken re-issuance after a peer's device identity changed.
|
|
522
|
-
* Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
|
|
523
|
-
* the session refresh (not after it).
|
|
524
|
-
*/
|
|
525
|
-
const reissueTcTokenAfterIdentityChange = (from) => {
|
|
526
|
-
void (async () => {
|
|
527
|
-
const normalizedJid = jidNormalizedUser(from);
|
|
528
|
-
const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
|
|
529
|
-
const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
|
|
530
|
-
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
|
|
531
|
-
if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
|
|
532
|
-
return;
|
|
450
|
+
ev.emit('creds.update', update)
|
|
533
451
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
getLIDForPN,
|
|
543
|
-
onNewJidStored: trackTcTokenJid
|
|
544
|
-
});
|
|
545
|
-
})().catch(err => {
|
|
546
|
-
logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
|
|
547
|
-
});
|
|
548
|
-
};
|
|
452
|
+
await sendNode(receipt)
|
|
453
|
+
logger.info({
|
|
454
|
+
msgAttrs: node.attrs,
|
|
455
|
+
retryCount
|
|
456
|
+
}, 'sent retry receipt')
|
|
457
|
+
}, authState?.creds?.me?.id || 'sendRetryRequest')
|
|
458
|
+
}
|
|
459
|
+
|
|
549
460
|
const handleEncryptNotification = async (node) => {
|
|
550
|
-
const from = node.attrs.from
|
|
551
|
-
if (from === S_WHATSAPP_NET) {
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
461
|
+
const from = node.attrs.from
|
|
462
|
+
if (from === WABinary_1.S_WHATSAPP_NET) {
|
|
463
|
+
const countChild = WABinary_1.getBinaryNodeChild(node, 'count')
|
|
464
|
+
const count = +countChild.attrs.value
|
|
465
|
+
const shouldUploadMorePreKeys = count < Defaults_1.MIN_PREKEY_COUNT
|
|
466
|
+
logger.debug({
|
|
467
|
+
count,
|
|
468
|
+
shouldUploadMorePreKeys
|
|
469
|
+
}, 'recv pre-key count')
|
|
470
|
+
|
|
560
471
|
if (shouldUploadMorePreKeys) {
|
|
561
|
-
|
|
562
|
-
inFlightPreKeyLow.add(stanzaId);
|
|
563
|
-
try {
|
|
564
|
-
await uploadPreKeys();
|
|
565
|
-
}
|
|
566
|
-
finally {
|
|
567
|
-
if (stanzaId)
|
|
568
|
-
inFlightPreKeyLow.delete(stanzaId);
|
|
569
|
-
}
|
|
472
|
+
await uploadPreKeys()
|
|
570
473
|
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
logger.info({ node }, 'unknown encrypt notification');
|
|
474
|
+
} else {
|
|
475
|
+
const identityNode = WABinary_1.getBinaryNodeChild(node, 'identity')
|
|
476
|
+
if (identityNode) {
|
|
477
|
+
logger.info({
|
|
478
|
+
jid: from
|
|
479
|
+
}, 'identity changed')
|
|
480
|
+
// not handling right now
|
|
481
|
+
// signal will override new identity anyway
|
|
482
|
+
} else {
|
|
483
|
+
logger.info({
|
|
484
|
+
node
|
|
485
|
+
}, 'unknown encrypt notification')
|
|
584
486
|
}
|
|
585
487
|
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
594
|
-
switch (child?.tag) {
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const handleGroupNotification = (participant, child, msg, mode) => {
|
|
491
|
+
let participantJid = mode === 'lid' ? WABinary_1.getBinaryNodeChild(child, 'participant')?.attrs?.phone_number : WABinary_1.getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant
|
|
492
|
+
|
|
493
|
+
// TODO: Add participant LID
|
|
494
|
+
switch (child.tag) {
|
|
595
495
|
case 'create':
|
|
596
|
-
const metadata = extractGroupMetadata(child)
|
|
597
|
-
msg.messageStubType = WAMessageStubType.GROUP_CREATE
|
|
598
|
-
msg.messageStubParameters = [metadata.subject]
|
|
599
|
-
msg.key = {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
])
|
|
607
|
-
ev.emit('groups.upsert', [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
]
|
|
615
|
-
break
|
|
496
|
+
const metadata = groups_1.extractGroupMetadata(child)
|
|
497
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE
|
|
498
|
+
msg.messageStubParameters = [metadata.subject]
|
|
499
|
+
msg.key = {
|
|
500
|
+
participant: metadata.owner
|
|
501
|
+
}
|
|
502
|
+
ev.emit('chats.upsert', [{
|
|
503
|
+
id: metadata.id,
|
|
504
|
+
name: metadata.subject,
|
|
505
|
+
conversationTimestamp: metadata.creation,
|
|
506
|
+
}])
|
|
507
|
+
ev.emit('groups.upsert', [{
|
|
508
|
+
...metadata,
|
|
509
|
+
author: participant
|
|
510
|
+
}])
|
|
511
|
+
break
|
|
512
|
+
case 'delete':
|
|
513
|
+
msg.messageStubType = Types_1.WAMessageStubType.COMMUNITY_PARENT_GROUP_DELETED
|
|
514
|
+
msg.messageStubParameters = [participantJid, 'delete']
|
|
515
|
+
break
|
|
616
516
|
case 'ephemeral':
|
|
617
517
|
case 'not_ephemeral':
|
|
618
518
|
msg.message = {
|
|
619
519
|
protocolMessage: {
|
|
620
|
-
type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
520
|
+
type: WAProto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
621
521
|
ephemeralExpiration: +(child.attrs.expiration || 0)
|
|
622
522
|
}
|
|
623
|
-
}
|
|
624
|
-
break
|
|
523
|
+
}
|
|
524
|
+
break
|
|
625
525
|
case 'modify':
|
|
626
|
-
const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
|
627
|
-
msg.messageStubParameters = oldNumber || []
|
|
628
|
-
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER
|
|
629
|
-
break
|
|
526
|
+
const oldNumber = mode === 'lid' ? WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.phone_number) : WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
|
527
|
+
msg.messageStubParameters = oldNumber || []
|
|
528
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER
|
|
529
|
+
break
|
|
630
530
|
case 'promote':
|
|
631
531
|
case 'demote':
|
|
632
532
|
case 'remove':
|
|
633
533
|
case 'add':
|
|
634
534
|
case 'leave':
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
642
|
-
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
643
|
-
username: attrs.participant_username || attrs.username || undefined,
|
|
644
|
-
admin: (attrs.type || null)
|
|
645
|
-
};
|
|
646
|
-
});
|
|
535
|
+
let stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`
|
|
536
|
+
if (child.attrs?.reason === 'linked_group_join') {
|
|
537
|
+
stubType = GROUP_PARTICIPANT_LINKED_GROUP_JOIN
|
|
538
|
+
}
|
|
539
|
+
msg.messageStubType = Types_1.WAMessageStubType[stubType]
|
|
540
|
+
const participants = mode === 'lid' ? WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.phone_number) : WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
|
647
541
|
if (participants.length === 1 &&
|
|
648
542
|
// if recv. "remove" message and sender removed themselves
|
|
649
543
|
// mark as left
|
|
650
|
-
|
|
651
|
-
areJidsSameUser(participants[0].id, actingParticipantPn)) &&
|
|
544
|
+
WABinary_1.areJidsSameUser(participants[0], participant) &&
|
|
652
545
|
child.tag === 'remove') {
|
|
653
|
-
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
|
546
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
|
654
547
|
}
|
|
655
|
-
msg.messageStubParameters = participants
|
|
656
|
-
break
|
|
548
|
+
msg.messageStubParameters = participants
|
|
549
|
+
break
|
|
657
550
|
case 'subject':
|
|
658
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
|
|
659
|
-
msg.messageStubParameters = [child.attrs.subject]
|
|
660
|
-
break
|
|
551
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_SUBJECT
|
|
552
|
+
msg.messageStubParameters = [participantJid, child.attrs.subject]
|
|
553
|
+
break
|
|
661
554
|
case 'description':
|
|
662
|
-
const description = getBinaryNodeChild(child, 'body')?.content?.toString()
|
|
663
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION
|
|
664
|
-
msg.messageStubParameters = description ? [description] : undefined
|
|
665
|
-
break
|
|
555
|
+
const description = WABinary_1.getBinaryNodeChild(child, 'body')?.content?.toString()
|
|
556
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_DESCRIPTION
|
|
557
|
+
msg.messageStubParameters = description ? [description] : undefined
|
|
558
|
+
break
|
|
666
559
|
case 'announcement':
|
|
667
560
|
case 'not_announcement':
|
|
668
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
|
|
669
|
-
msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off']
|
|
670
|
-
break
|
|
561
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ANNOUNCE
|
|
562
|
+
msg.messageStubParameters = [(child.tag === 'announcement') ? 'on' : 'off']
|
|
563
|
+
break
|
|
671
564
|
case 'locked':
|
|
672
565
|
case 'unlocked':
|
|
673
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
|
|
674
|
-
msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off']
|
|
675
|
-
break
|
|
566
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_RESTRICT
|
|
567
|
+
msg.messageStubParameters = [(child.tag === 'locked') ? 'on' : 'off']
|
|
568
|
+
break
|
|
676
569
|
case 'invite':
|
|
677
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
|
|
678
|
-
msg.messageStubParameters = [child.attrs.code]
|
|
679
|
-
break
|
|
570
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_INVITE_LINK
|
|
571
|
+
msg.messageStubParameters = [child.attrs.code]
|
|
572
|
+
break
|
|
680
573
|
case 'member_add_mode':
|
|
681
|
-
const addMode = child.content
|
|
574
|
+
const addMode = child.content
|
|
682
575
|
if (addMode) {
|
|
683
|
-
msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE
|
|
684
|
-
msg.messageStubParameters = [addMode.toString()]
|
|
576
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBER_ADD_MODE
|
|
577
|
+
msg.messageStubParameters = [addMode.toString()]
|
|
685
578
|
}
|
|
686
|
-
break
|
|
579
|
+
break
|
|
687
580
|
case 'membership_approval_mode':
|
|
688
|
-
const approvalMode = getBinaryNodeChild(child, 'group_join')
|
|
581
|
+
const approvalMode = WABinary_1.getBinaryNodeChild(child, 'group_join')
|
|
689
582
|
if (approvalMode) {
|
|
690
|
-
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE
|
|
691
|
-
msg.messageStubParameters = [approvalMode.attrs.state]
|
|
583
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE
|
|
584
|
+
msg.messageStubParameters = [approvalMode.attrs.state]
|
|
692
585
|
}
|
|
693
|
-
break
|
|
586
|
+
break
|
|
694
587
|
case 'created_membership_requests':
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
];
|
|
701
|
-
break;
|
|
588
|
+
participantJid = mode === 'lid' ? WABinary_1.getBinaryNodeChild(child, 'requested_user')?.attrs?.phone_number : WABinary_1.getBinaryNodeChild(child, 'requested_user')?.attrs?.jid || participant
|
|
589
|
+
|
|
590
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
|
|
591
|
+
msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method]
|
|
592
|
+
break
|
|
702
593
|
case 'revoked_membership_requests':
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
msg.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
594
|
+
participantJid = mode === 'lid' ? WABinary_1.getBinaryNodeChild(child, 'requested_user')?.attrs?.phone_number : WABinary_1.getBinaryNodeChild(child, 'requested_user')?.attrs?.jid || participant
|
|
595
|
+
|
|
596
|
+
const isDenied = WABinary_1.areJidsSameUser(participantJid, participant)
|
|
597
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
|
|
598
|
+
msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected']
|
|
599
|
+
break
|
|
600
|
+
case 'link':
|
|
601
|
+
case 'unlink':
|
|
602
|
+
const type = child.attrs?.unlink_type || child.attrs?.link_type
|
|
603
|
+
const stubMap = {
|
|
604
|
+
parent_group: Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_PARENT_GROUP`],
|
|
605
|
+
sibling_group: Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_SIBLING_GROUP`],
|
|
606
|
+
sub_group: Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_SUB_GROUP`]
|
|
607
|
+
}
|
|
608
|
+
const groups = WABinary_1.getBinaryNodeChildren(child, 'group')
|
|
609
|
+
.map(g => g.attrs?.jid || g.attrs?.subject || '')
|
|
610
|
+
.filter(x => x)
|
|
611
|
+
msg.messageStubType = stubMap?.[type] || Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_PARENT_GROUP`]
|
|
612
|
+
msg.messageStubParameters = [participantJid, child.tag, groups]
|
|
613
|
+
break
|
|
614
|
+
case 'linked_group_promote':
|
|
615
|
+
case 'linked_group_demote':
|
|
616
|
+
const stubtype = `COMMUNITY_PARTICIPANT_${child.tag.split('_')[2].toUpperCase()}`
|
|
617
|
+
const participantS = mode === 'lid' ? WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.phone_number) : WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
|
618
|
+
msg.messageStubType = Types_1.WAMessageStubType[stubtype]
|
|
619
|
+
msg.messageStubParameters = participantS
|
|
620
|
+
break
|
|
621
|
+
case 'created_sub_group_suggestion':
|
|
622
|
+
msg.messageStubType = Types_1.WAMessageStubType.SUGGESTED_SUBGROUP_ANNOUNCE
|
|
623
|
+
msg.messageStubParameters = [participantJid, 'add']
|
|
624
|
+
break
|
|
625
|
+
case 'revoked_sub_group_suggestions':
|
|
626
|
+
const res = WABinary_1.getBinaryNodeChildren(child, 'sub_group_suggestions')
|
|
627
|
+
const reason = res.attrs?.reason
|
|
628
|
+
if (reason === 'approved') msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE
|
|
629
|
+
else msg.messageStubType = Types_1.WAMessageStubType.GENERIC_NOTIFICATION
|
|
630
|
+
msg.messageStubParameters = [participantJid, reason]
|
|
631
|
+
break
|
|
632
|
+
default:
|
|
633
|
+
logger.warn(child.tag, 'Unhandled group node')
|
|
634
|
+
break
|
|
730
635
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const handleNewsletterNotification = (id, node) => {
|
|
639
|
+
const messages = WABinary_1.getBinaryNodeChild(node, 'messages')
|
|
640
|
+
const message = WABinary_1.getBinaryNodeChild(node, 'message')
|
|
641
|
+
const serverId = node.attrs.server_id
|
|
642
|
+
|
|
643
|
+
const reactionsList = WABinary_1.getBinaryNodeChild(node, 'reactions')
|
|
644
|
+
const viewsList = WABinary_1.getBinaryNodeChild(node, 'views_count')
|
|
645
|
+
|
|
646
|
+
if (reactionsList) {
|
|
647
|
+
const reactions = WABinary_1.getBinaryNodeChild(reactionsList, 'reaction')
|
|
648
|
+
|
|
649
|
+
if (reactions.length === 0) {
|
|
650
|
+
ev.emit('newsletter.reaction', {
|
|
651
|
+
id,
|
|
652
|
+
newsletter_server_id: serverId,
|
|
653
|
+
reaction: {
|
|
654
|
+
removed: true
|
|
655
|
+
}
|
|
656
|
+
})
|
|
740
657
|
}
|
|
741
|
-
|
|
658
|
+
|
|
659
|
+
reactions.forEach(item => {
|
|
660
|
+
ev.emit('newsletter.reaction', {
|
|
661
|
+
id,
|
|
662
|
+
newsletter_server_id: serverId,
|
|
663
|
+
reaction: {
|
|
664
|
+
code: item.attrs?.code,
|
|
665
|
+
count: +item.attrs.count
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
})
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (viewsList.length) {
|
|
672
|
+
viewsList.forEach(item => {
|
|
673
|
+
ev.emit('newsletter.view', {
|
|
674
|
+
id,
|
|
675
|
+
newsletter_server_id: serverId,
|
|
676
|
+
count: +item.attrs.count
|
|
677
|
+
})
|
|
678
|
+
})
|
|
742
679
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (updatedDevices.length === 0) {
|
|
788
|
-
await userDevicesCache?.del(user);
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
await userDevicesCache?.set(user, updatedDevices);
|
|
792
|
-
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const handleMexNotification = (id, node) => {
|
|
683
|
+
const operation = node?.attrs?.op_name
|
|
684
|
+
const content = JSON.parse(node?.content)
|
|
685
|
+
|
|
686
|
+
let contentPath
|
|
687
|
+
let action
|
|
688
|
+
|
|
689
|
+
if (operation === Types_1.MexOperations.UPDATE) {
|
|
690
|
+
contentPath = content.data[Types_1.XWAPaths.METADATA_UPDATE]
|
|
691
|
+
|
|
692
|
+
ev.emit('newsletter-settings.update', {
|
|
693
|
+
id,
|
|
694
|
+
update: contentPath.thread_metadata.settings
|
|
695
|
+
})
|
|
696
|
+
} else if (operation === Types_1.MexUpdatesOperations.GROUP_LIMIT_SHARING) {
|
|
697
|
+
contentPath = content.data[Types_1.XWAPathsMexUpdates.GROUP_SHARING_CHANGE]
|
|
698
|
+
|
|
699
|
+
ev.emit('limit-sharing.update', {
|
|
700
|
+
id,
|
|
701
|
+
author: contentPath.updated_by?.pn ? contentPath.updated_by.pn : contentPath.updated_by.id,
|
|
702
|
+
action: `${contentPath.properties.limit_sharing.limit_sharing_enabled ? 'on' : 'off'}`,
|
|
703
|
+
trigger: contentPath.properties.limit_sharing.limit_sharing_trigger,
|
|
704
|
+
update_time: contentPath.update_time
|
|
705
|
+
})
|
|
706
|
+
} else if (operation === Types_1.MexUpdatesOperations.OWNER_COMMUNITY) {
|
|
707
|
+
contentPath = content.data[Types_1.XWAPathsMexUpdates.COMMUNITY_OWNER_CHANGE]
|
|
708
|
+
|
|
709
|
+
ev.emit('community-owner.update', {
|
|
710
|
+
id,
|
|
711
|
+
author: contentPath.updated_by?.pn ? contentPath.updated_by.pn : contentPath.updated_by.id,
|
|
712
|
+
user: contentPath.role_updates[0].user?.pn ? contentPath.role_updates[0].user.pn : contentPath.role_updates[0].user.jid,
|
|
713
|
+
new_role: contentPath.role_updates[0].new_role,
|
|
714
|
+
update_time: contentPath.update_time
|
|
715
|
+
})
|
|
716
|
+
} else {
|
|
717
|
+
|
|
718
|
+
if (operation === Types_1.MexOperations.PROMOTE) {
|
|
719
|
+
action = 'promote'
|
|
720
|
+
contentPath = content.data[Types_1.XWAPaths.PROMOTE]
|
|
721
|
+
} else {
|
|
722
|
+
action = 'demote'
|
|
723
|
+
contentPath = content.data[Types_1.XWAPaths.DEMOTE]
|
|
793
724
|
}
|
|
794
|
-
|
|
795
|
-
|
|
725
|
+
|
|
726
|
+
ev.emit('newsletter-participants.update', {
|
|
727
|
+
id,
|
|
728
|
+
author: contentPath.actor.pn,
|
|
729
|
+
user: contentPath.user.pn,
|
|
730
|
+
new_role: contentPath.user_new_role,
|
|
731
|
+
action
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
796
736
|
const processNotification = async (node) => {
|
|
797
|
-
const result = {}
|
|
798
|
-
const [child] = getAllBinaryNodeChildren(node)
|
|
799
|
-
const nodeType = node.attrs.type
|
|
800
|
-
const from = jidNormalizedUser(node.attrs.from)
|
|
737
|
+
const result = {}
|
|
738
|
+
const [child] = WABinary_1.getAllBinaryNodeChildren(node)
|
|
739
|
+
const nodeType = node.attrs.type
|
|
740
|
+
const from = WABinary_1.jidNormalizedUser(node.attrs.from)
|
|
741
|
+
|
|
801
742
|
switch (nodeType) {
|
|
743
|
+
case 'privacy_token':
|
|
744
|
+
const tokenList = WABinary_1.getBinaryNodeChildren(child, 'token')
|
|
745
|
+
for (const {
|
|
746
|
+
attrs,
|
|
747
|
+
content
|
|
748
|
+
}
|
|
749
|
+
of tokenList) {
|
|
750
|
+
const jid = attrs.jid
|
|
751
|
+
ev.emit('chats.update', [{
|
|
752
|
+
id: jid,
|
|
753
|
+
tcToken: content
|
|
754
|
+
}])
|
|
755
|
+
logger.debug({
|
|
756
|
+
jid
|
|
757
|
+
}, 'got privacy token update')
|
|
758
|
+
}
|
|
759
|
+
break
|
|
760
|
+
case 'w:gp2':
|
|
761
|
+
const mode = node.attrs.addressing_mode
|
|
762
|
+
handleGroupNotification(mode === 'lid' ? node.attrs.participant_pn : node.attrs.participant, child, result, mode)
|
|
763
|
+
break
|
|
802
764
|
case 'newsletter':
|
|
803
|
-
|
|
804
|
-
break
|
|
765
|
+
handleNewsletterNotification(node.attrs.from, child)
|
|
766
|
+
break
|
|
805
767
|
case 'mex':
|
|
806
|
-
|
|
807
|
-
break
|
|
808
|
-
case 'w:gp2':
|
|
809
|
-
// TODO: HANDLE PARTICIPANT_PN
|
|
810
|
-
handleGroupNotification(node, child, result);
|
|
811
|
-
break;
|
|
768
|
+
handleMexNotification(node.attrs.from, child)
|
|
769
|
+
break
|
|
812
770
|
case 'mediaretry':
|
|
813
|
-
const event = decodeMediaRetryNode(node)
|
|
814
|
-
ev.emit('messages.media-update', [event])
|
|
815
|
-
break
|
|
771
|
+
const event = Utils_1.decodeMediaRetryNode(node)
|
|
772
|
+
ev.emit('messages.media-update', [event])
|
|
773
|
+
break
|
|
816
774
|
case 'encrypt':
|
|
817
|
-
await handleEncryptNotification(node)
|
|
818
|
-
break
|
|
775
|
+
await handleEncryptNotification(node)
|
|
776
|
+
break
|
|
819
777
|
case 'devices':
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
778
|
+
const devices = WABinary_1.getBinaryNodeChildren(child, 'device')
|
|
779
|
+
if (WABinary_1.areJidsSameUser(child.attrs.jid, authState.creds.me.id) ||
|
|
780
|
+
WABinary_1.areJidsSameUser(child.attrs.lid, authState.creds.me.lid)) {
|
|
781
|
+
const deviceData = devices.map(d => ({
|
|
782
|
+
id: d.attrs.jid,
|
|
783
|
+
lid: d.attrs.lid
|
|
784
|
+
}))
|
|
785
|
+
logger.info({
|
|
786
|
+
deviceData
|
|
787
|
+
}, 'my own devices changed')
|
|
825
788
|
}
|
|
826
|
-
|
|
789
|
+
//TODO: drop a new event, add hashes
|
|
790
|
+
break
|
|
827
791
|
case 'server_sync':
|
|
828
|
-
const update = getBinaryNodeChild(node, 'collection')
|
|
792
|
+
const update = WABinary_1.getBinaryNodeChild(node, 'collection')
|
|
829
793
|
if (update) {
|
|
830
|
-
const name = update.attrs.name
|
|
831
|
-
await resyncAppState([name], false)
|
|
794
|
+
const name = update.attrs.name
|
|
795
|
+
await resyncAppState([name], false)
|
|
832
796
|
}
|
|
833
|
-
break
|
|
797
|
+
break
|
|
834
798
|
case 'picture':
|
|
835
|
-
const setPicture = getBinaryNodeChild(node, 'set')
|
|
836
|
-
const delPicture = getBinaryNodeChild(node, 'delete')
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
if (isJidGroup(from)) {
|
|
845
|
-
const node = setPicture || delPicture;
|
|
846
|
-
result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
|
|
799
|
+
const setPicture = WABinary_1.getBinaryNodeChild(node, 'set')
|
|
800
|
+
const delPicture = WABinary_1.getBinaryNodeChild(node, 'delete')
|
|
801
|
+
ev.emit('contacts.update', [{
|
|
802
|
+
id: WABinary_1.jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
|
803
|
+
imgUrl: setPicture ? 'changed' : 'removed'
|
|
804
|
+
}])
|
|
805
|
+
if (WABinary_1.isJidGroup(from)) {
|
|
806
|
+
const node = setPicture || delPicture
|
|
807
|
+
result.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ICON
|
|
847
808
|
if (setPicture) {
|
|
848
|
-
result.messageStubParameters = [setPicture.attrs.id]
|
|
809
|
+
result.messageStubParameters = [setPicture.attrs.id]
|
|
849
810
|
}
|
|
850
|
-
result.participant = node?.attrs
|
|
811
|
+
result.participant = node?.attrs?.author
|
|
851
812
|
result.key = {
|
|
852
|
-
...
|
|
853
|
-
participant: setPicture?.attrs
|
|
854
|
-
}
|
|
813
|
+
...result.key || {},
|
|
814
|
+
participant: setPicture?.attrs?.author
|
|
815
|
+
}
|
|
855
816
|
}
|
|
856
|
-
break
|
|
817
|
+
break
|
|
857
818
|
case 'account_sync':
|
|
858
819
|
if (child.tag === 'disappearing_mode') {
|
|
859
|
-
const newDuration = +child.attrs.duration
|
|
860
|
-
const timestamp = +child.attrs.t
|
|
861
|
-
logger.info({
|
|
820
|
+
const newDuration = +child.attrs.duration
|
|
821
|
+
const timestamp = +child.attrs.t
|
|
822
|
+
logger.info({
|
|
823
|
+
newDuration
|
|
824
|
+
}, 'updated account disappearing mode')
|
|
862
825
|
ev.emit('creds.update', {
|
|
863
826
|
accountSettings: {
|
|
864
827
|
...authState.creds.accountSettings,
|
|
865
828
|
defaultDisappearingMode: {
|
|
866
829
|
ephemeralExpiration: newDuration,
|
|
867
|
-
ephemeralSettingTimestamp: timestamp
|
|
868
|
-
}
|
|
830
|
+
ephemeralSettingTimestamp: timestamp,
|
|
831
|
+
},
|
|
869
832
|
}
|
|
870
|
-
})
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
833
|
+
})
|
|
834
|
+
} else if (child.tag === 'blocklist') {
|
|
835
|
+
const blocklists = WABinary_1.getBinaryNodeChildren(child, 'item')
|
|
836
|
+
for (const {
|
|
837
|
+
attrs
|
|
838
|
+
}
|
|
839
|
+
of blocklists) {
|
|
840
|
+
const blocklist = [attrs.jid]
|
|
841
|
+
const type = (attrs.action === 'block') ? 'add' : 'remove'
|
|
842
|
+
ev.emit('blocklist.update', {
|
|
843
|
+
blocklist,
|
|
844
|
+
type
|
|
845
|
+
})
|
|
878
846
|
}
|
|
879
847
|
}
|
|
880
|
-
break
|
|
848
|
+
break
|
|
881
849
|
case 'link_code_companion_reg':
|
|
882
|
-
const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg')
|
|
883
|
-
const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'))
|
|
884
|
-
const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'))
|
|
885
|
-
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'))
|
|
886
|
-
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
|
|
887
|
-
const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey)
|
|
888
|
-
const random = randomBytes(32)
|
|
889
|
-
const linkCodeSalt = randomBytes(32)
|
|
890
|
-
|
|
850
|
+
const linkCodeCompanionReg = WABinary_1.getBinaryNodeChild(node, 'link_code_companion_reg')
|
|
851
|
+
const ref = toRequiredBuffer(WABinary_1.getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'))
|
|
852
|
+
const primaryIdentityPublicKey = toRequiredBuffer(WABinary_1.getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'))
|
|
853
|
+
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(WABinary_1.getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'))
|
|
854
|
+
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
|
|
855
|
+
const companionSharedKey = Utils_1.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey)
|
|
856
|
+
const random = crypto_1.randomBytes(32)
|
|
857
|
+
const linkCodeSalt = crypto_1.randomBytes(32)
|
|
858
|
+
|
|
859
|
+
const linkCodePairingExpanded = await Utils_1.hkdf(companionSharedKey, 32, {
|
|
891
860
|
salt: linkCodeSalt,
|
|
892
861
|
info: 'link_code_pairing_key_bundle_encryption_key'
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
])
|
|
899
|
-
const
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random])
|
|
865
|
+
const encryptIv = crypto_1.randomBytes(12)
|
|
866
|
+
const encrypted = Utils_1.aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0))
|
|
867
|
+
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted])
|
|
868
|
+
const identitySharedKey = Utils_1.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey)
|
|
869
|
+
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random])
|
|
870
|
+
|
|
871
|
+
authState.creds.advSecretKey = (await Utils_1.hkdf(identityPayload, 32, {
|
|
872
|
+
info: 'adv_secret'
|
|
873
|
+
})).toString('base64')
|
|
874
|
+
|
|
905
875
|
await query({
|
|
906
876
|
tag: 'iq',
|
|
907
877
|
attrs: {
|
|
908
|
-
to: S_WHATSAPP_NET,
|
|
878
|
+
to: WABinary_1.S_WHATSAPP_NET,
|
|
909
879
|
type: 'set',
|
|
910
880
|
id: sock.generateMessageTag(),
|
|
911
881
|
xmlns: 'md'
|
|
912
882
|
},
|
|
913
|
-
content: [
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
883
|
+
content: [{
|
|
884
|
+
tag: 'link_code_companion_reg',
|
|
885
|
+
attrs: {
|
|
886
|
+
jid: authState.creds.me.id,
|
|
887
|
+
stage: 'companion_finish',
|
|
888
|
+
},
|
|
889
|
+
content: [{
|
|
890
|
+
tag: 'link_code_pairing_wrapped_key_bundle',
|
|
891
|
+
attrs: {},
|
|
892
|
+
content: encryptedPayload
|
|
919
893
|
},
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
]
|
|
937
|
-
}
|
|
938
|
-
]
|
|
939
|
-
});
|
|
940
|
-
authState.creds.registered = true;
|
|
941
|
-
ev.emit('creds.update', authState.creds);
|
|
942
|
-
break;
|
|
943
|
-
case 'privacy_token':
|
|
944
|
-
await handlePrivacyTokenNotification(node);
|
|
945
|
-
break;
|
|
894
|
+
{
|
|
895
|
+
tag: 'companion_identity_public',
|
|
896
|
+
attrs: {},
|
|
897
|
+
content: authState.creds.signedIdentityKey.public
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
tag: 'link_code_pairing_ref',
|
|
901
|
+
attrs: {},
|
|
902
|
+
content: ref
|
|
903
|
+
}
|
|
904
|
+
]
|
|
905
|
+
}]
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
authState.creds.registered = true
|
|
909
|
+
ev.emit('creds.update', authState.creds)
|
|
946
910
|
}
|
|
911
|
+
|
|
947
912
|
if (Object.keys(result).length) {
|
|
948
|
-
return result
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
/**
|
|
952
|
-
* In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
|
|
953
|
-
* Used to coalesce writes during a session; pruning always re-reads the persisted index
|
|
954
|
-
* to cover writes made by other layers (e.g. history sync).
|
|
955
|
-
*/
|
|
956
|
-
const tcTokenKnownJids = new Set();
|
|
957
|
-
const tcTokenIndexLoaded = (async () => {
|
|
958
|
-
try {
|
|
959
|
-
const jids = await readTcTokenIndex(authState.keys);
|
|
960
|
-
for (const jid of jids)
|
|
961
|
-
tcTokenKnownJids.add(jid);
|
|
962
|
-
logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
|
|
963
|
-
}
|
|
964
|
-
catch (err) {
|
|
965
|
-
logger.warn({ err: err?.message }, 'failed to load tctoken index');
|
|
966
|
-
}
|
|
967
|
-
})();
|
|
968
|
-
let tcTokenIndexTimer;
|
|
969
|
-
async function flushTcTokenIndex() {
|
|
970
|
-
if (tcTokenIndexTimer) {
|
|
971
|
-
clearTimeout(tcTokenIndexTimer);
|
|
972
|
-
tcTokenIndexTimer = undefined;
|
|
913
|
+
return result
|
|
973
914
|
}
|
|
974
|
-
// Merge with whatever is already persisted so we don't clobber writes from other
|
|
975
|
-
// paths (history sync, concurrent sessions on the same store).
|
|
976
|
-
const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
|
|
977
|
-
return authState.keys.set({ tctoken: write });
|
|
978
915
|
}
|
|
979
|
-
|
|
980
|
-
if (tcTokenIndexTimer) {
|
|
981
|
-
clearTimeout(tcTokenIndexTimer);
|
|
982
|
-
}
|
|
983
|
-
tcTokenIndexTimer = setTimeout(() => {
|
|
984
|
-
tcTokenIndexTimer = undefined;
|
|
985
|
-
flushTcTokenIndex().catch(err => {
|
|
986
|
-
logger.warn({ err: err?.message }, 'failed to save tctoken index');
|
|
987
|
-
});
|
|
988
|
-
}, 5000);
|
|
989
|
-
}
|
|
990
|
-
function trackTcTokenJid(jid) {
|
|
991
|
-
if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
992
|
-
tcTokenKnownJids.add(jid);
|
|
993
|
-
scheduleTcTokenIndexSave();
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
const handlePrivacyTokenNotification = async (node) => {
|
|
997
|
-
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
998
|
-
if (!tokensNode)
|
|
999
|
-
return;
|
|
1000
|
-
const from = jidNormalizedUser(node.attrs.from);
|
|
1001
|
-
// WA Web uses: senderLid ?? toLid(from) for the storage key
|
|
1002
|
-
// The sender_lid attribute provides the LID directly when available
|
|
1003
|
-
const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
|
|
1004
|
-
? jidNormalizedUser(node.attrs.sender_lid)
|
|
1005
|
-
: undefined;
|
|
1006
|
-
const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
|
|
1007
|
-
logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
|
|
1008
|
-
await storeTcTokensFromIqResult({
|
|
1009
|
-
result: node,
|
|
1010
|
-
fallbackJid,
|
|
1011
|
-
keys: authState.keys,
|
|
1012
|
-
getLIDForPN,
|
|
1013
|
-
onNewJidStored: trackTcTokenJid
|
|
1014
|
-
});
|
|
1015
|
-
};
|
|
916
|
+
|
|
1016
917
|
async function decipherLinkPublicKey(data) {
|
|
1017
|
-
const buffer = toRequiredBuffer(data)
|
|
1018
|
-
const salt = buffer.slice(0, 32)
|
|
1019
|
-
const secretKey = await derivePairingCodeKey(authState.creds.pairingCode, salt)
|
|
1020
|
-
const iv = buffer.slice(32, 48)
|
|
1021
|
-
const payload = buffer.slice(48, 80)
|
|
1022
|
-
return aesDecryptCTR(payload, secretKey, iv)
|
|
918
|
+
const buffer = toRequiredBuffer(data)
|
|
919
|
+
const salt = buffer.slice(0, 32)
|
|
920
|
+
const secretKey = await Utils_1.derivePairingCodeKey(authState.creds.pairingCode, salt)
|
|
921
|
+
const iv = buffer.slice(32, 48)
|
|
922
|
+
const payload = buffer.slice(48, 80)
|
|
923
|
+
return Utils_1.aesDecryptCTR(payload, secretKey, iv)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function getTypeMessage(message) {
|
|
927
|
+
const type = Object.keys(message)
|
|
928
|
+
const restype = (!['senderKeyDistributionMessage', 'messageContextInfo'].includes(type[0]) && type[0]) || (type.length >= 3 && type[1] !== 'messageContextInfo' && type[1]) || type[type.length - 1] || Object.keys(message)[0]
|
|
929
|
+
return restype
|
|
1023
930
|
}
|
|
931
|
+
|
|
1024
932
|
function toRequiredBuffer(data) {
|
|
1025
933
|
if (data === undefined) {
|
|
1026
|
-
throw new Boom('Invalid buffer', {
|
|
934
|
+
throw new boom_1.Boom('Invalid buffer', {
|
|
935
|
+
statusCode: 400
|
|
936
|
+
})
|
|
1027
937
|
}
|
|
1028
|
-
return data instanceof Buffer ? data : Buffer.from(data)
|
|
938
|
+
return data instanceof Buffer ? data : Buffer.from(data)
|
|
1029
939
|
}
|
|
940
|
+
|
|
1030
941
|
const willSendMessageAgain = async (id, participant) => {
|
|
1031
|
-
const key = `${id}:${participant}
|
|
1032
|
-
const retryCount = (await msgRetryCache.get(key)) || 0
|
|
1033
|
-
return retryCount < maxMsgRetryCount
|
|
1034
|
-
}
|
|
942
|
+
const key = `${id}:${participant}`
|
|
943
|
+
const retryCount = (await msgRetryCache.get(key)) || 0
|
|
944
|
+
return retryCount < maxMsgRetryCount
|
|
945
|
+
}
|
|
946
|
+
|
|
1035
947
|
const updateSendMessageAgainCount = async (id, participant) => {
|
|
1036
|
-
const key = `${id}:${participant}
|
|
1037
|
-
const newValue = ((await msgRetryCache.get(key)) || 0) + 1
|
|
1038
|
-
await msgRetryCache.set(key, newValue)
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
const
|
|
1044
|
-
const
|
|
948
|
+
const key = `${id}:${participant}`
|
|
949
|
+
const newValue = ((await msgRetryCache.get(key)) || 0) + 1
|
|
950
|
+
await msgRetryCache.set(key, newValue)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
954
|
+
const remoteJid = key.remoteJid
|
|
955
|
+
const participant = key.participant || remoteJid
|
|
956
|
+
const retryCount = +retryNode.attrs.count || 1
|
|
957
|
+
|
|
1045
958
|
// Try to get messages from cache first, then fallback to getMessage
|
|
1046
|
-
const msgs = []
|
|
959
|
+
const msgs = []
|
|
960
|
+
|
|
1047
961
|
for (const id of ids) {
|
|
1048
|
-
let msg
|
|
962
|
+
let msg
|
|
963
|
+
|
|
1049
964
|
// Try to get from retry cache first if enabled
|
|
1050
965
|
if (messageRetryManager) {
|
|
1051
|
-
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id)
|
|
966
|
+
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id)
|
|
1052
967
|
if (cachedMsg) {
|
|
1053
|
-
msg = cachedMsg.message
|
|
1054
|
-
logger.debug({
|
|
968
|
+
msg = cachedMsg.message
|
|
969
|
+
logger.debug({
|
|
970
|
+
jid: remoteJid,
|
|
971
|
+
id
|
|
972
|
+
}, 'found message in retry cache')
|
|
973
|
+
|
|
1055
974
|
// Mark retry as successful since we found the message
|
|
1056
|
-
messageRetryManager.markRetrySuccess(id)
|
|
975
|
+
messageRetryManager.markRetrySuccess(id)
|
|
1057
976
|
}
|
|
1058
977
|
}
|
|
978
|
+
|
|
1059
979
|
// Fallback to getMessage if not found in cache
|
|
1060
980
|
if (!msg) {
|
|
1061
|
-
msg = await getMessage({
|
|
981
|
+
msg = await getMessage({
|
|
982
|
+
...key,
|
|
983
|
+
id
|
|
984
|
+
})
|
|
1062
985
|
if (msg) {
|
|
1063
|
-
logger.debug({
|
|
986
|
+
logger.debug({
|
|
987
|
+
jid: remoteJid,
|
|
988
|
+
id
|
|
989
|
+
}, 'found message via getMessage')
|
|
990
|
+
|
|
1064
991
|
// Also mark as successful if found via getMessage
|
|
1065
992
|
if (messageRetryManager) {
|
|
1066
993
|
messageRetryManager.markRetrySuccess(id);
|
|
1067
994
|
}
|
|
1068
995
|
}
|
|
1069
996
|
}
|
|
1070
|
-
msgs.push(msg)
|
|
997
|
+
msgs.push(msg)
|
|
1071
998
|
}
|
|
999
|
+
|
|
1072
1000
|
// if it's the primary jid sending the request
|
|
1073
1001
|
// just re-send the message to everyone
|
|
1074
1002
|
// prevents the first message decryption failure
|
|
1075
|
-
const sendToAll = !jidDecode(participant)?.device
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1003
|
+
const sendToAll = !WABinary_1.jidDecode(participant)?.device
|
|
1004
|
+
|
|
1005
|
+
// Check if we should recreate session for this retry
|
|
1006
|
+
let shouldRecreateSession = false
|
|
1007
|
+
let recreateReason = ''
|
|
1008
|
+
|
|
1009
|
+
if (enableAutoSessionRecreation && messageRetryManager) {
|
|
1080
1010
|
try {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
if (!injectedFromBundle) {
|
|
1090
|
-
const receivedRegId = getBinaryNodeChildUInt(receiptNode, 'registration', 4);
|
|
1091
|
-
if (typeof receivedRegId === 'number' && Number.isInteger(receivedRegId)) {
|
|
1092
|
-
const info = await signalRepository.getSessionInfo(participant);
|
|
1093
|
-
if (info && info.registrationId !== 0 && info.registrationId !== receivedRegId) {
|
|
1094
|
-
logger.info({ participant, stored: info.registrationId, received: receivedRegId }, 'reg id mismatch on retry without bundle, deleting session');
|
|
1095
|
-
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
const BASE_KEY_CHECK_RETRY = 2;
|
|
1100
|
-
if (msgId && messageRetryManager) {
|
|
1101
|
-
const info = await signalRepository.getSessionInfo(participant);
|
|
1102
|
-
if (info) {
|
|
1103
|
-
if (retryCount === BASE_KEY_CHECK_RETRY) {
|
|
1104
|
-
messageRetryManager.saveBaseKey(sessionId, msgId, info.baseKey);
|
|
1105
|
-
}
|
|
1106
|
-
else if (retryCount > BASE_KEY_CHECK_RETRY) {
|
|
1107
|
-
if (messageRetryManager.hasSameBaseKey(sessionId, msgId, info.baseKey)) {
|
|
1108
|
-
logger.warn({ participant, retryCount }, 'base key collision on retry, forcing fresh session');
|
|
1109
|
-
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1110
|
-
}
|
|
1111
|
-
messageRetryManager.deleteBaseKey(sessionId, msgId);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
let shouldRecreateSession = false;
|
|
1116
|
-
let recreateReason = '';
|
|
1117
|
-
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1 && !injectedFromBundle) {
|
|
1118
|
-
try {
|
|
1119
|
-
const hasSession = await signalRepository.validateSession(participant);
|
|
1120
|
-
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
1121
|
-
shouldRecreateSession = result.recreate;
|
|
1122
|
-
recreateReason = result.reason;
|
|
1011
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant)
|
|
1012
|
+
const hasSession = await signalRepository.validateSession(participant)
|
|
1013
|
+
const result = messageRetryManager.shouldRecreateSession(participant, retryCount, hasSession.exists)
|
|
1014
|
+
|
|
1015
|
+
shouldRecreateSession = result.recreate
|
|
1016
|
+
recreateReason = result.reason
|
|
1017
|
+
|
|
1123
1018
|
if (shouldRecreateSession) {
|
|
1124
|
-
logger.
|
|
1125
|
-
|
|
1019
|
+
logger.info({
|
|
1020
|
+
participant,
|
|
1021
|
+
retryCount,
|
|
1022
|
+
reason: recreateReason
|
|
1023
|
+
}, 'recreating session for outgoing retry')
|
|
1024
|
+
await authState.keys.set({
|
|
1025
|
+
session: {
|
|
1026
|
+
[sessionId]: null
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1126
1029
|
}
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
logger.warn({
|
|
1032
|
+
error,
|
|
1033
|
+
participant
|
|
1034
|
+
}, 'failed to check session recreation for outgoing retry')
|
|
1127
1035
|
}
|
|
1128
|
-
catch (error) {
|
|
1129
|
-
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
if (!injectedFromBundle) {
|
|
1133
|
-
await assertSessions([participant], true);
|
|
1134
1036
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1037
|
+
|
|
1038
|
+
await assertSessions([participant], shouldRecreateSession)
|
|
1039
|
+
|
|
1040
|
+
if (WABinary_1.isJidGroup(remoteJid)) {
|
|
1041
|
+
await authState.keys.set({
|
|
1042
|
+
'sender-key-memory': {
|
|
1043
|
+
[remoteJid]: null
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1137
1046
|
}
|
|
1138
|
-
logger.debug({
|
|
1047
|
+
logger.debug({
|
|
1048
|
+
participant,
|
|
1049
|
+
sendToAll,
|
|
1050
|
+
shouldRecreateSession,
|
|
1051
|
+
recreateReason
|
|
1052
|
+
}, 'forced new session for retry recp')
|
|
1053
|
+
|
|
1139
1054
|
for (const [i, msg] of msgs.entries()) {
|
|
1140
1055
|
if (!ids[i])
|
|
1141
|
-
continue
|
|
1056
|
+
continue
|
|
1057
|
+
|
|
1142
1058
|
if (msg && (await willSendMessageAgain(ids[i], participant))) {
|
|
1143
|
-
|
|
1144
|
-
const msgRelayOpts = {
|
|
1145
|
-
|
|
1146
|
-
msgRelayOpts.useUserDevicesCache = false;
|
|
1059
|
+
updateSendMessageAgainCount(ids[i], participant)
|
|
1060
|
+
const msgRelayOpts = {
|
|
1061
|
+
messageId: ids[i]
|
|
1147
1062
|
}
|
|
1148
|
-
|
|
1063
|
+
|
|
1064
|
+
if (sendToAll) {
|
|
1065
|
+
msgRelayOpts.useUserDevicesCache = false
|
|
1066
|
+
} else {
|
|
1149
1067
|
msgRelayOpts.participant = {
|
|
1150
1068
|
jid: participant,
|
|
1151
1069
|
count: +retryNode.attrs.count
|
|
1152
|
-
}
|
|
1070
|
+
}
|
|
1153
1071
|
}
|
|
1154
|
-
await relayMessage(key.remoteJid, msg, msgRelayOpts)
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1072
|
+
await relayMessage(key.remoteJid, msg, msgRelayOpts)
|
|
1073
|
+
} else {
|
|
1074
|
+
logger.debug({
|
|
1075
|
+
jid: key.remoteJid,
|
|
1076
|
+
id: ids[i]
|
|
1077
|
+
}, 'recv retry request, but message not available')
|
|
1158
1078
|
}
|
|
1159
1079
|
}
|
|
1160
|
-
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1161
1082
|
const handleReceipt = async (node) => {
|
|
1162
|
-
const {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
const
|
|
1083
|
+
const {
|
|
1084
|
+
attrs,
|
|
1085
|
+
content
|
|
1086
|
+
} = node
|
|
1087
|
+
const isLid = attrs.from.includes('lid')
|
|
1088
|
+
const isNodeFromMe = WABinary_1.areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id)
|
|
1089
|
+
const remoteJid = !isNodeFromMe || WABinary_1.isJidGroup(attrs.from) ? attrs.from : attrs.recipient
|
|
1090
|
+
const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe)
|
|
1091
|
+
|
|
1167
1092
|
const key = {
|
|
1168
1093
|
remoteJid,
|
|
1169
1094
|
id: '',
|
|
1170
1095
|
fromMe,
|
|
1171
1096
|
participant: attrs.participant
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
|
|
1100
|
+
logger.debug({
|
|
1101
|
+
remoteJid
|
|
1102
|
+
}, 'ignoring receipt from jid')
|
|
1103
|
+
await sendMessageAck(node)
|
|
1104
|
+
return
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const ids = [attrs.id]
|
|
1174
1108
|
if (Array.isArray(content)) {
|
|
1175
|
-
const items = getBinaryNodeChildren(content[0], 'item')
|
|
1176
|
-
ids.push(...items.map(i => i.attrs.id))
|
|
1109
|
+
const items = WABinary_1.getBinaryNodeChildren(content[0], 'item')
|
|
1110
|
+
ids.push(...items.map(i => i.attrs.id))
|
|
1177
1111
|
}
|
|
1112
|
+
|
|
1178
1113
|
try {
|
|
1179
1114
|
await Promise.all([
|
|
1180
1115
|
receiptMutex.mutex(async () => {
|
|
1181
|
-
const status = getStatusFromReceiptType(attrs.type)
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1116
|
+
const status = Utils_1.getStatusFromReceiptType(attrs.type)
|
|
1117
|
+
|
|
1118
|
+
if (typeof status !== 'undefined' && (
|
|
1119
|
+
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
|
1120
|
+
// or another device of ours has read some messages
|
|
1121
|
+
status >= WAProto_1.proto.WebMessageInfo.Status.SERVER_ACK ||
|
|
1122
|
+
!isNodeFromMe)) {
|
|
1123
|
+
if (WABinary_1.isJidGroup(remoteJid) || WABinary_1.isJidStatusBroadcast(remoteJid)) {
|
|
1187
1124
|
if (attrs.participant) {
|
|
1188
|
-
const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp'
|
|
1125
|
+
const updateKey = status === WAProto_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp'
|
|
1189
1126
|
ev.emit('message-receipt.update', ids.map(id => ({
|
|
1190
|
-
key: {
|
|
1127
|
+
key: {
|
|
1128
|
+
...key,
|
|
1129
|
+
id
|
|
1130
|
+
},
|
|
1191
1131
|
receipt: {
|
|
1192
|
-
userJid: jidNormalizedUser(attrs.participant),
|
|
1132
|
+
userJid: WABinary_1.jidNormalizedUser(attrs.participant),
|
|
1193
1133
|
[updateKey]: +attrs.t
|
|
1194
1134
|
}
|
|
1195
|
-
})))
|
|
1135
|
+
})))
|
|
1196
1136
|
}
|
|
1197
|
-
}
|
|
1198
|
-
else {
|
|
1137
|
+
} else {
|
|
1199
1138
|
ev.emit('messages.update', ids.map(id => ({
|
|
1200
|
-
key: {
|
|
1201
|
-
|
|
1202
|
-
|
|
1139
|
+
key: {
|
|
1140
|
+
...key,
|
|
1141
|
+
id
|
|
1142
|
+
},
|
|
1143
|
+
update: {
|
|
1144
|
+
status
|
|
1145
|
+
}
|
|
1146
|
+
})))
|
|
1203
1147
|
}
|
|
1204
1148
|
}
|
|
1149
|
+
|
|
1205
1150
|
if (attrs.type === 'retry') {
|
|
1206
1151
|
// correctly set who is asking for the retry
|
|
1207
|
-
key.participant = key.participant || attrs.from
|
|
1208
|
-
const retryNode = getBinaryNodeChild(node, 'retry')
|
|
1152
|
+
key.participant = key.participant || attrs.from
|
|
1153
|
+
const retryNode = WABinary_1.getBinaryNodeChild(node, 'retry')
|
|
1154
|
+
|
|
1209
1155
|
if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
|
|
1210
1156
|
if (key.fromMe) {
|
|
1211
1157
|
try {
|
|
1212
|
-
|
|
1213
|
-
logger.debug({
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1158
|
+
updateSendMessageAgainCount(ids[0], key.participant)
|
|
1159
|
+
logger.debug({
|
|
1160
|
+
attrs,
|
|
1161
|
+
key
|
|
1162
|
+
}, 'recv retry request')
|
|
1163
|
+
await sendMessagesAgain(key, ids, retryNode)
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
logger.error({
|
|
1166
|
+
key,
|
|
1167
|
+
ids,
|
|
1168
|
+
trace: error instanceof Error ? error.stack : 'Unknown error'
|
|
1169
|
+
}, 'error in sending message again')
|
|
1218
1170
|
}
|
|
1171
|
+
} else {
|
|
1172
|
+
logger.info({
|
|
1173
|
+
attrs,
|
|
1174
|
+
key
|
|
1175
|
+
}, 'recv retry for not fromMe message')
|
|
1219
1176
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
logger.info({ attrs, key }, 'will not send message again, as sent too many times');
|
|
1177
|
+
} else {
|
|
1178
|
+
logger.info({
|
|
1179
|
+
attrs,
|
|
1180
|
+
key
|
|
1181
|
+
}, 'will not send message again, as sent too many times')
|
|
1226
1182
|
}
|
|
1227
1183
|
}
|
|
1228
1184
|
})
|
|
1229
|
-
])
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack receipt'));
|
|
1185
|
+
])
|
|
1186
|
+
} finally {
|
|
1187
|
+
await sendMessageAck(node)
|
|
1233
1188
|
}
|
|
1234
|
-
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1235
1191
|
const handleNotification = async (node) => {
|
|
1236
|
-
const remoteJid = node.attrs.from
|
|
1192
|
+
const remoteJid = node.attrs.from
|
|
1193
|
+
|
|
1194
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
|
|
1195
|
+
logger.debug({
|
|
1196
|
+
remoteJid,
|
|
1197
|
+
id: node.attrs.id
|
|
1198
|
+
}, 'ignored notification')
|
|
1199
|
+
await sendMessageAck(node)
|
|
1200
|
+
return
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1237
1203
|
try {
|
|
1238
1204
|
await Promise.all([
|
|
1239
1205
|
notificationMutex.mutex(async () => {
|
|
1240
|
-
const msg = await processNotification(node)
|
|
1206
|
+
const msg = await processNotification(node)
|
|
1207
|
+
|
|
1241
1208
|
if (msg) {
|
|
1242
|
-
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id)
|
|
1243
|
-
const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
|
|
1209
|
+
const fromMe = WABinary_1.areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id)
|
|
1244
1210
|
msg.key = {
|
|
1245
1211
|
remoteJid,
|
|
1246
1212
|
fromMe,
|
|
1247
1213
|
participant: node.attrs.participant,
|
|
1248
|
-
participantAlt,
|
|
1249
|
-
participantUsername: node.attrs.participant_username,
|
|
1250
|
-
addressingMode,
|
|
1251
1214
|
id: node.attrs.id,
|
|
1252
1215
|
...(msg.key || {})
|
|
1253
|
-
}
|
|
1254
|
-
msg.participant
|
|
1255
|
-
msg.messageTimestamp = +node.attrs.t
|
|
1256
|
-
const fullMsg = proto.WebMessageInfo.fromObject(msg)
|
|
1257
|
-
await upsertMessage(fullMsg, 'append')
|
|
1216
|
+
}
|
|
1217
|
+
msg.participant = msg.participant ? msg.participant : node.attrs.participant
|
|
1218
|
+
msg.messageTimestamp = +node.attrs.t
|
|
1219
|
+
const fullMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg)
|
|
1220
|
+
await upsertMessage(fullMsg, 'append')
|
|
1258
1221
|
}
|
|
1259
1222
|
})
|
|
1260
|
-
])
|
|
1223
|
+
])
|
|
1224
|
+
} finally {
|
|
1225
|
+
await sendMessageAck(node)
|
|
1261
1226
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1266
1229
|
const handleMessage = async (node) => {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
await sendMessageAck(node
|
|
1272
|
-
return
|
|
1230
|
+
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
|
|
1231
|
+
logger.debug({
|
|
1232
|
+
key: node.attrs.key
|
|
1233
|
+
}, 'ignored message')
|
|
1234
|
+
await sendMessageAck(node)
|
|
1235
|
+
return
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
let response
|
|
1239
|
+
|
|
1240
|
+
const encNode = WABinary_1.getBinaryNodeChild(node, 'enc')
|
|
1241
|
+
if (encNode && encNode.attrs.type === 'msmsg') {
|
|
1242
|
+
logger.debug({
|
|
1243
|
+
key: node.attrs.key
|
|
1244
|
+
}, 'ignored msmsg')
|
|
1245
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.MissingMessageSecret)
|
|
1246
|
+
return
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (WABinary_1.getBinaryNodeChild(node, 'unavailable') && !encNode) {
|
|
1250
|
+
await sendMessageAck(node)
|
|
1251
|
+
let key
|
|
1252
|
+
try {
|
|
1253
|
+
key = Utils_1.decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage.key
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
logger.error({ error, node }, 'failed to decode unavailable message node, skipping placeholder resend')
|
|
1256
|
+
return
|
|
1257
|
+
}
|
|
1258
|
+
const unavailableNode = WABinary_1.getBinaryNodeChild(node, 'unavailable')
|
|
1259
|
+
const unavailableType = unavailableNode?.attrs?.type
|
|
1260
|
+
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1261
|
+
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1262
|
+
unavailableType === 'view_once_unavailable_fanout') {
|
|
1263
|
+
logger.debug({ msgId: node.attrs.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type')
|
|
1264
|
+
return
|
|
1265
|
+
}
|
|
1266
|
+
const messageAge = Utils_1.unixTimestampSeconds() - Utils_1.toNumber(node.attrs.t)
|
|
1267
|
+
if (messageAge > Defaults_1.PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1268
|
+
logger.debug({ msgId: node.attrs.id, messageAge }, 'skipping placeholder resend for message too old')
|
|
1269
|
+
return
|
|
1270
|
+
}
|
|
1271
|
+
response = await requestPlaceholderResend(key);
|
|
1272
|
+
if (response === 'RESOLVED') {
|
|
1273
|
+
return
|
|
1274
|
+
}
|
|
1275
|
+
logger.debug('received unavailable message, acked and requested resend from phone');
|
|
1276
|
+
} else {
|
|
1277
|
+
if (placeholderResendCache.get(node.attrs.id)) {
|
|
1278
|
+
await placeholderResendCache.del(node.attrs.id)
|
|
1279
|
+
}
|
|
1273
1280
|
}
|
|
1274
|
-
|
|
1281
|
+
|
|
1282
|
+
let decodeResult
|
|
1275
1283
|
try {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
//
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1288
|
-
else {
|
|
1289
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1290
|
-
await signalRepository.migrateSession(alt, primaryJid);
|
|
1291
|
-
}
|
|
1284
|
+
// decryptMessageNode bisa throw sebelum sempat masuk try/catch utama di bawah
|
|
1285
|
+
// (contoh: bug upstream baileys "No participant in group message" pada node
|
|
1286
|
+
// yang malformed/gak lengkap). Kalau gak ditangkep di sini, exception ini lolos
|
|
1287
|
+
// sebagai unhandled rejection dari handleMessage - baik pas diproses langsung
|
|
1288
|
+
// (live message) maupun lewat offline queue.
|
|
1289
|
+
decodeResult = Utils_1.decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger)
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
logger.error({ error, node }, 'failed to decrypt/decode message node, dropping')
|
|
1292
|
+
try {
|
|
1293
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError)
|
|
1294
|
+
} catch (ackError) {
|
|
1295
|
+
logger.error({ ackError }, 'failed to ack undecodable message node')
|
|
1292
1296
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const {
|
|
1301
|
+
fullMessage: msg,
|
|
1302
|
+
category,
|
|
1303
|
+
author,
|
|
1304
|
+
decrypt
|
|
1305
|
+
} = decodeResult
|
|
1306
|
+
if (response && msg?.messageStubParameters?.[0] === Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1307
|
+
msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response]
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
if (msg.message?.protocolMessage?.type === WAProto_1.proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) {
|
|
1311
|
+
const lid = WABinary_1.jidNormalizedUser(node.attrs.from), pn = WABinary_1.jidNormalizedUser(node.attrs.sender_pn)
|
|
1312
|
+
ev.emit('lid-mapping.update', {
|
|
1313
|
+
lid,
|
|
1314
|
+
pn
|
|
1315
|
+
})
|
|
1316
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{
|
|
1317
|
+
lid,
|
|
1318
|
+
pn
|
|
1319
|
+
}])
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
try {
|
|
1323
|
+
await Promise.all([
|
|
1324
|
+
messageMutex.mutex(async () => {
|
|
1325
|
+
await decrypt()
|
|
1326
|
+
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
|
|
1327
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message)
|
|
1328
|
+
logger.debug({
|
|
1329
|
+
jid: msg.key.remoteJid,
|
|
1330
|
+
id: msg.key.id
|
|
1331
|
+
}, 'Added message to recent cache for retry receipts')
|
|
1303
1332
|
}
|
|
1304
|
-
if (msg.
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
const unavailableNode = getBinaryNodeChild(node, 'unavailable');
|
|
1308
|
-
const unavailableType = unavailableNode?.attrs?.type;
|
|
1309
|
-
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1310
|
-
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1311
|
-
unavailableType === 'view_once_unavailable_fanout') {
|
|
1312
|
-
logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
|
|
1313
|
-
acked = true;
|
|
1314
|
-
return sendMessageAck(node);
|
|
1333
|
+
if (msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT) {
|
|
1334
|
+
if (msg?.messageStubParameters?.[0] === Utils_1.MISSING_KEYS_ERROR_TEXT) {
|
|
1335
|
+
return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError)
|
|
1315
1336
|
}
|
|
1316
|
-
|
|
1317
|
-
if (
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1337
|
+
|
|
1338
|
+
if (WABinary_1.isJidStatusBroadcast(msg.key.remoteJid)) {
|
|
1339
|
+
const messageAge = Utils_1.unixTimestampSeconds() - Utils_1.toNumber(msg.messageTimestamp)
|
|
1340
|
+
if (messageAge > Defaults_1.STATUS_EXPIRY_SECONDS) {
|
|
1341
|
+
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping retry for expired status message')
|
|
1342
|
+
return sendMessageAck(node)
|
|
1343
|
+
}
|
|
1321
1344
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
//
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1345
|
+
|
|
1346
|
+
const errorMessage = msg?.messageStubParameters?.[0] || ''
|
|
1347
|
+
const isPreKeyError = errorMessage.includes('PreKey')
|
|
1348
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`)
|
|
1349
|
+
|
|
1350
|
+
// .catch() wajib ada: call ini sengaja tidak di-await (biar retry
|
|
1351
|
+
// request gak ngeblok proses pesan lain), tapi kalau dibiarkan tanpa
|
|
1352
|
+
// .catch() dan mutex-nya reject, itu jadi unhandled promise rejection
|
|
1353
|
+
// yang bisa nge-crash seluruh proses Node (default Node behavior sejak
|
|
1354
|
+
// v15). Ini yang paling mungkin jadi penyebab bot "tiba-tiba disconnect
|
|
1355
|
+
// dan gak recover" setelah jalan beberapa jam.
|
|
1356
|
+
retryMutex.mutex(async () => {
|
|
1357
|
+
try {
|
|
1358
|
+
if (!ws.isOpen) {
|
|
1359
|
+
logger.debug({
|
|
1360
|
+
node
|
|
1361
|
+
}, 'Connection closed, skipping retry')
|
|
1362
|
+
return
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (WABinary_1.getBinaryNodeChild(node, 'unavailable')) {
|
|
1366
|
+
logger.debug('Message unavailable, skipping retry')
|
|
1367
|
+
return
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (isPreKeyError) {
|
|
1371
|
+
logger.info({
|
|
1372
|
+
error: errorMessage
|
|
1373
|
+
}, 'PreKey error detected, uploading and retrying')
|
|
1374
|
+
try {
|
|
1375
|
+
logger.debug('Uploading pre-keys for error recovery')
|
|
1376
|
+
await uploadPreKeys(5)
|
|
1377
|
+
logger.debug('Waiting for server to process new pre-keys')
|
|
1378
|
+
await Utils_1.delay(1000)
|
|
1379
|
+
} catch (uploadErr) {
|
|
1380
|
+
logger.error({
|
|
1381
|
+
uploadErr
|
|
1382
|
+
}, 'Pre-key upload failed, proceeding with retry anyway')
|
|
1349
1383
|
}
|
|
1350
|
-
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
const encNode = WABinary_1.getBinaryNodeChild(node, 'enc')
|
|
1387
|
+
await sendRetryRequest(node, !encNode)
|
|
1388
|
+
if (retryRequestDelayMs) {
|
|
1389
|
+
await Utils_1.delay(retryRequestDelayMs)
|
|
1390
|
+
}
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
logger.error({
|
|
1393
|
+
err,
|
|
1394
|
+
isPreKeyError
|
|
1395
|
+
}, 'Failed to handle retry, attempting basic retry')
|
|
1396
|
+
try {
|
|
1397
|
+
const encNode = WABinary_1.getBinaryNodeChild(node, 'enc')
|
|
1398
|
+
await sendRetryRequest(node, !encNode)
|
|
1399
|
+
} catch (retryErr) {
|
|
1400
|
+
logger.error({
|
|
1401
|
+
retryErr
|
|
1402
|
+
}, 'Failed to send retry after error handling')
|
|
1403
|
+
}
|
|
1351
1404
|
}
|
|
1405
|
+
}).catch((mutexErr) => {
|
|
1406
|
+
logger.error({ mutexErr }, 'retryMutex task failed unexpectedly')
|
|
1352
1407
|
})
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1408
|
+
} else {
|
|
1409
|
+
let type = undefined
|
|
1410
|
+
// FIX: Jangan fallback ke me.id saat LID tidak bisa di-resolve.
|
|
1411
|
+
// Fallback ke me.id menyebabkan semua pesan LID private chat
|
|
1412
|
+
// punya participant = JID bot sendiri, sehingga owner check lolos
|
|
1413
|
+
// untuk semua user yang menggunakan LID addressing.
|
|
1414
|
+
if (msg.key.participant && msg.key.participant.endsWith('@lid')) {
|
|
1415
|
+
if (node.attrs.participant_pn) {
|
|
1416
|
+
// Gunakan nomor telepon asli jika tersedia
|
|
1417
|
+
msg.key.participant = WABinary_1.jidNormalizedUser(node.attrs.participant_pn);
|
|
1418
|
+
} else {
|
|
1419
|
+
// Coba resolve dari lidMapping, jika gagal pakai remoteJid
|
|
1420
|
+
// (lebih aman daripada me.id yang menyebabkan false owner match)
|
|
1421
|
+
try {
|
|
1422
|
+
const resolvedPn = await signalRepository.lidMapping.getPNForLID(msg.key.participant);
|
|
1423
|
+
msg.key.participant = resolvedPn
|
|
1424
|
+
? WABinary_1.jidNormalizedUser(resolvedPn)
|
|
1425
|
+
: WABinary_1.jidNormalizedUser(msg.key.remoteJid);
|
|
1426
|
+
} catch (_) {
|
|
1427
|
+
msg.key.participant = WABinary_1.jidNormalizedUser(msg.key.remoteJid);
|
|
1428
|
+
}
|
|
1368
1429
|
}
|
|
1369
1430
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1431
|
+
if (WABinary_1.isJidGroup(msg.key.remoteJid)) {
|
|
1432
|
+
const mtype = getTypeMessage(msg.message)
|
|
1433
|
+
const message = msg.message?.[mtype]
|
|
1434
|
+
const contextInfo = message?.contextInfo
|
|
1435
|
+
const participant = contextInfo?.participant
|
|
1436
|
+
const mentionedJid = contextInfo?.mentionedJid
|
|
1437
|
+
if (typeof participant === "string" && participant.endsWith('@lid')) {
|
|
1438
|
+
if (msg.message?.[mtype]?.contextInfo) {
|
|
1439
|
+
const metadata = await global.groupMetadataCache(msg.key.remoteJid)
|
|
1440
|
+
const sender_lid = msg.message[mtype].contextInfo.participant
|
|
1441
|
+
const found = metadata.participants.find(p => p.lid === sender_lid)
|
|
1442
|
+
if (found) {
|
|
1443
|
+
msg.message[mtype].contextInfo.participant = found.id
|
|
1444
|
+
} else {
|
|
1445
|
+
const lid = WABinary_1.jidNormalizedUser(sender_lid)
|
|
1446
|
+
const sender_jid = await signalRepository.lidMapping.getPNForLID(sender_lid)
|
|
1447
|
+
if (sender_jid) {
|
|
1448
|
+
const pn = WABinary_1.jidNormalizedUser(sender_jid)
|
|
1449
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }])
|
|
1450
|
+
msg.message[mtype].contextInfo.participant = pn
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1377
1453
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
if (
|
|
1381
|
-
await
|
|
1454
|
+
}
|
|
1455
|
+
if (Array.isArray(mentionedJid) && mentionedJid.length > 0) {
|
|
1456
|
+
if (msg.message?.[mtype]?.contextInfo) {
|
|
1457
|
+
const metadata = await global.groupMetadataCache(msg.key.remoteJid)
|
|
1458
|
+
const updatedMentionedJid = []
|
|
1459
|
+
let needsUpdate = false
|
|
1460
|
+
for (const mentionedLid of mentionedJid) {
|
|
1461
|
+
if (typeof mentionedLid === "string" && mentionedLid.endsWith('@lid')) {
|
|
1462
|
+
const found = metadata.participants.find(p => p.lid === mentionedLid)
|
|
1463
|
+
if (found) {
|
|
1464
|
+
updatedMentionedJid.push(found.id)
|
|
1465
|
+
if (msg.message[mtype]?.text) {
|
|
1466
|
+
msg.message[mtype].text = msg.message[mtype].text.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${found.id.split("@")[0]}`);
|
|
1467
|
+
} else if (msg.message[mtype]?.caption) {
|
|
1468
|
+
msg.message[mtype].caption = msg.message[mtype].caption.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${found.id.split("@")[0]}`);
|
|
1469
|
+
}
|
|
1470
|
+
} else {
|
|
1471
|
+
const lid = WABinary_1.jidNormalizedUser(mentionedLid)
|
|
1472
|
+
const mentioned_jid = await signalRepository.lidMapping.getPNForLID(mentionedLid)
|
|
1473
|
+
if (mentioned_jid) {
|
|
1474
|
+
const pn = WABinary_1.jidNormalizedUser(mentioned_jid)
|
|
1475
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }])
|
|
1476
|
+
updatedMentionedJid.push(pn)
|
|
1477
|
+
if (msg.message[mtype]?.text) {
|
|
1478
|
+
msg.message[mtype].text = msg.message[mtype].text.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${pn.split("@")[0]}`);
|
|
1479
|
+
} else if (msg.message[mtype]?.caption) {
|
|
1480
|
+
msg.message[mtype].caption = msg.message[mtype].caption.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${pn.split("@")[0]}`);
|
|
1481
|
+
}
|
|
1482
|
+
} else {
|
|
1483
|
+
updatedMentionedJid.push(mentionedLid)
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
needsUpdate = true
|
|
1487
|
+
} else {
|
|
1488
|
+
updatedMentionedJid.push(mentionedLid)
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (needsUpdate) {
|
|
1492
|
+
msg.message[mtype].contextInfo.mentionedJid = updatedMentionedJid
|
|
1493
|
+
}
|
|
1382
1494
|
}
|
|
1383
1495
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1496
|
+
}
|
|
1497
|
+
if (!WABinary_1.isJidGroup(msg.key.remoteJid) && WABinary_1.isLidUser(msg.key.remoteJid)) {
|
|
1498
|
+
// FIX: private chat - resolve remoteJid dari LID ke JID,
|
|
1499
|
+
// simpen bentuk LID aslinya ke remoteJidAlt biar gak ilang
|
|
1500
|
+
const originalLidRemote = msg.key.remoteJid;
|
|
1501
|
+
const resolvedRemote = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
|
|
1502
|
+
if (resolvedRemote) {
|
|
1503
|
+
msg.key.remoteJid = WABinary_1.jidNormalizedUser(resolvedRemote);
|
|
1504
|
+
msg.key.remoteJidAlt = msg.key.remoteJidAlt || originalLidRemote;
|
|
1505
|
+
} else {
|
|
1506
|
+
// Fallback: coba resolve dari lidMapping
|
|
1507
|
+
try {
|
|
1508
|
+
const resolvedPn = await signalRepository.lidMapping.getPNForLID(msg.key.remoteJid);
|
|
1509
|
+
if (resolvedPn) {
|
|
1510
|
+
msg.key.remoteJid = WABinary_1.jidNormalizedUser(resolvedPn);
|
|
1511
|
+
msg.key.remoteJidAlt = msg.key.remoteJidAlt || originalLidRemote;
|
|
1512
|
+
}
|
|
1513
|
+
} catch (_) {}
|
|
1386
1514
|
}
|
|
1387
|
-
acked = true;
|
|
1388
|
-
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
else {
|
|
1393
|
-
if (messageRetryManager && msg.key.id) {
|
|
1394
|
-
messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
|
|
1395
|
-
}
|
|
1396
|
-
const isNewsletter = isJidNewsletter(msg.key.remoteJid);
|
|
1397
|
-
if (!isNewsletter) {
|
|
1398
|
-
// no type in the receipt => message delivered
|
|
1399
|
-
let type = undefined;
|
|
1400
|
-
let participant = msg.key.participant;
|
|
1401
|
-
if (category === 'peer') {
|
|
1402
|
-
// special peer message
|
|
1403
|
-
type = 'peer_msg';
|
|
1404
1515
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1516
|
+
// FIX: private chat - jika author masih LID, resolve ke JID
|
|
1517
|
+
if (!WABinary_1.isJidGroup(msg.key.remoteJid) && author && WABinary_1.isLidUser(author)) {
|
|
1518
|
+
const resolvedAuthor = node.attrs.sender_pn
|
|
1519
|
+
|| node.attrs.participant_pn
|
|
1520
|
+
|| node.attrs.peer_recipient_pn;
|
|
1521
|
+
if (resolvedAuthor) {
|
|
1522
|
+
author = WABinary_1.jidNormalizedUser(resolvedAuthor);
|
|
1523
|
+
} else {
|
|
1524
|
+
try {
|
|
1525
|
+
const resolvedPn = await signalRepository.lidMapping.getPNForLID(author);
|
|
1526
|
+
if (resolvedPn) author = WABinary_1.jidNormalizedUser(resolvedPn);
|
|
1527
|
+
} catch (_) {}
|
|
1411
1528
|
}
|
|
1412
1529
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1530
|
+
let participant = msg.key.participant
|
|
1531
|
+
if (category === 'peer') {
|
|
1532
|
+
type = 'peer_msg'
|
|
1533
|
+
} else if (msg.key.fromMe) {
|
|
1534
|
+
type = 'sender'
|
|
1535
|
+
if (WABinary_1.isLidUser(msg.key.remoteJid) || WABinary_1.isLidUser(msg.key.participantAlt)) {
|
|
1536
|
+
participant = author
|
|
1537
|
+
}
|
|
1538
|
+
} else if (!sendActiveReceipts) {
|
|
1539
|
+
type = 'inactive'
|
|
1415
1540
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
1541
|
+
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type)
|
|
1542
|
+
|
|
1543
|
+
const isAnyHistoryMsg = Utils_1.getHistoryMsg(msg.message)
|
|
1420
1544
|
if (isAnyHistoryMsg) {
|
|
1421
|
-
const jid = jidNormalizedUser(msg.key.remoteJid)
|
|
1422
|
-
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync')
|
|
1545
|
+
const jid = WABinary_1.jidNormalizedUser(msg.key.remoteJid)
|
|
1546
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync')
|
|
1423
1547
|
}
|
|
1424
1548
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1549
|
+
Utils_1.cleanMessage(msg, authState.creds.me.id)
|
|
1550
|
+
await sendMessageAck(node)
|
|
1551
|
+
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify')
|
|
1552
|
+
})
|
|
1553
|
+
])
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
logger.error({
|
|
1556
|
+
error,
|
|
1557
|
+
node
|
|
1558
|
+
}, 'error in handling message')
|
|
1559
|
+
// Kalau exception terjadi sebelum sempat ack (misal error di plugin/group
|
|
1560
|
+
// metadata sebelum baris sendMessageAck), stanza ini wajib tetap di-ack.
|
|
1561
|
+
// Tanpa ini, server WA bisa terus retry/redeliver pesan yang sama berulang-ulang.
|
|
1562
|
+
try {
|
|
1563
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.UnhandledError)
|
|
1564
|
+
} catch (ackError) {
|
|
1565
|
+
logger.error({ ackError }, 'failed to ack message after error')
|
|
1439
1566
|
}
|
|
1440
1567
|
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
1571
|
+
if (!authState.creds.me?.id) {
|
|
1572
|
+
throw new boom_1.Boom('Not authenticated')
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const pdoMessage = {
|
|
1576
|
+
historySyncOnDemandRequest: {
|
|
1577
|
+
chatJid: oldestMsgKey.remoteJid,
|
|
1578
|
+
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
1579
|
+
oldestMsgId: oldestMsgKey.id,
|
|
1580
|
+
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
1581
|
+
onDemandMsgCount: count
|
|
1582
|
+
},
|
|
1583
|
+
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
return sendPeerDataOperationMessage(pdoMessage)
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
const requestPlaceholderResend = async (messageKey) => {
|
|
1590
|
+
if (!authState.creds.me?.id) {
|
|
1591
|
+
throw new boom_1.Boom('Not authenticated')
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
if (placeholderResendCache.get(messageKey?.id)) {
|
|
1595
|
+
logger.debug({
|
|
1596
|
+
messageKey
|
|
1597
|
+
}, 'already requested resend')
|
|
1598
|
+
return
|
|
1599
|
+
} else {
|
|
1600
|
+
placeholderResendCache.set(messageKey?.id, true)
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
await Utils_1.delay(5000)
|
|
1604
|
+
|
|
1605
|
+
if (!placeholderResendCache.get(messageKey?.id)) {
|
|
1606
|
+
logger.debug({
|
|
1607
|
+
messageKey
|
|
1608
|
+
}, 'message received while resend requested')
|
|
1609
|
+
return 'RESOLVED'
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const pdoMessage = {
|
|
1613
|
+
placeholderMessageResendRequest: [{
|
|
1614
|
+
messageKey
|
|
1615
|
+
}],
|
|
1616
|
+
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
setTimeout(() => {
|
|
1620
|
+
if (placeholderResendCache.get(messageKey?.id)) {
|
|
1621
|
+
logger.debug({
|
|
1622
|
+
messageKey
|
|
1623
|
+
}, 'PDO message without response after 15 seconds. Phone possibly offline')
|
|
1624
|
+
placeholderResendCache.del(messageKey?.id)
|
|
1448
1625
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1626
|
+
}, 15000)
|
|
1627
|
+
|
|
1628
|
+
return sendPeerDataOperationMessage(pdoMessage)
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const handleCall = async (node) => {
|
|
1632
|
+
let status
|
|
1633
|
+
|
|
1634
|
+
const {
|
|
1635
|
+
attrs
|
|
1636
|
+
} = node
|
|
1637
|
+
const [infoChild] = WABinary_1.getAllBinaryNodeChildren(node)
|
|
1638
|
+
const callId = infoChild.attrs['call-id']
|
|
1639
|
+
const from = infoChild.attrs.from || infoChild.attrs['call-creator']
|
|
1640
|
+
status = Utils_1.getCallStatusFromNode(infoChild)
|
|
1641
|
+
|
|
1642
|
+
if (WABinary_1.isLidUser(from) && infoChild.tag === 'relaylatency') {
|
|
1643
|
+
const verify = await callOfferCache.get(callId)
|
|
1644
|
+
|
|
1645
|
+
if (!verify) {
|
|
1646
|
+
status = 'offer'
|
|
1647
|
+
|
|
1648
|
+
const callLid = {
|
|
1649
|
+
chatId: attrs.from,
|
|
1650
|
+
from,
|
|
1651
|
+
id: callId,
|
|
1652
|
+
date: new Date(+attrs.t * 1000),
|
|
1653
|
+
offline: !!attrs.offline,
|
|
1654
|
+
status
|
|
1466
1655
|
}
|
|
1656
|
+
await callOfferCache.set(callId, callLid)
|
|
1467
1657
|
}
|
|
1468
|
-
if (status === 'offer') {
|
|
1469
|
-
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1470
|
-
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
1471
|
-
call.groupJid = infoChild.attrs['group-jid'];
|
|
1472
|
-
await callOfferCache.set(call.id, call);
|
|
1473
|
-
}
|
|
1474
|
-
const existingCall = await callOfferCache.get(call.id);
|
|
1475
|
-
// use existing call info to populate this event
|
|
1476
|
-
if (existingCall) {
|
|
1477
|
-
call.isVideo = existingCall.isVideo;
|
|
1478
|
-
call.isGroup = existingCall.isGroup;
|
|
1479
|
-
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
1480
|
-
}
|
|
1481
|
-
// delete data once call has ended
|
|
1482
|
-
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
1483
|
-
await callOfferCache.del(call.id);
|
|
1484
|
-
}
|
|
1485
|
-
ev.emit('call', [call]);
|
|
1486
|
-
}
|
|
1487
|
-
catch (error) {
|
|
1488
|
-
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling call');
|
|
1489
1658
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1659
|
+
|
|
1660
|
+
const call = {
|
|
1661
|
+
chatId: attrs.from,
|
|
1662
|
+
from,
|
|
1663
|
+
id: callId,
|
|
1664
|
+
date: new Date(+attrs.t * 1000),
|
|
1665
|
+
offline: !!attrs.offline,
|
|
1666
|
+
status,
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (status === 'offer') {
|
|
1670
|
+
call.isVideo = !!WABinary_1.getBinaryNodeChild(infoChild, 'video')
|
|
1671
|
+
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid']
|
|
1672
|
+
call.groupJid = infoChild.attrs['group-jid']
|
|
1673
|
+
await callOfferCache.set(call.id, call)
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
const existingCall = await callOfferCache.get(call.id)
|
|
1677
|
+
|
|
1678
|
+
// use existing call info to populate this event
|
|
1679
|
+
if (existingCall) {
|
|
1680
|
+
call.isVideo = existingCall.isVideo
|
|
1681
|
+
call.isGroup = existingCall.isGroup
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// delete data once call has ended
|
|
1685
|
+
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
1686
|
+
await callOfferCache.del(call.id)
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
ev.emit('call', [call])
|
|
1690
|
+
|
|
1691
|
+
await sendMessageAck(node)
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
const handleBadAck = async ({
|
|
1695
|
+
attrs
|
|
1696
|
+
}) => {
|
|
1697
|
+
const key = {
|
|
1698
|
+
remoteJid: attrs.from,
|
|
1699
|
+
fromMe: true,
|
|
1700
|
+
id: attrs.id,
|
|
1701
|
+
newsletter_server_id: attrs?.server_id
|
|
1492
1702
|
}
|
|
1493
|
-
};
|
|
1494
|
-
const handleBadAck = async ({ attrs }) => {
|
|
1495
|
-
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
1496
1703
|
// WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
|
|
1497
1704
|
// // current hypothesis is that if pash is sent in the ack
|
|
1498
1705
|
// // it means -- the message hasn't reached all devices yet
|
|
@@ -1509,125 +1716,130 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1509
1716
|
// error in acknowledgement,
|
|
1510
1717
|
// device could not display the message
|
|
1511
1718
|
if (attrs.error) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
inFlight463Recoveries.add(ackFrom);
|
|
1523
|
-
void (async () => {
|
|
1524
|
-
try {
|
|
1525
|
-
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
1526
|
-
const tcStorageJid = await resolveTcTokenJid(ackFrom, getLIDForPN);
|
|
1527
|
-
const issueJid = await resolveIssuanceJid(ackFrom, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
1528
|
-
const result = await issuePrivacyTokens([issueJid], unixTimestampSeconds());
|
|
1529
|
-
await storeTcTokensFromIqResult({
|
|
1530
|
-
result,
|
|
1531
|
-
fallbackJid: tcStorageJid,
|
|
1532
|
-
keys: authState.keys,
|
|
1533
|
-
getLIDForPN,
|
|
1534
|
-
onNewJidStored: trackTcTokenJid
|
|
1535
|
-
});
|
|
1536
|
-
logger.debug({ from: ackFrom }, 'completed 463 token recovery issuance');
|
|
1537
|
-
}
|
|
1538
|
-
catch (err) {
|
|
1539
|
-
logger.debug({ from: ackFrom, err: err?.message }, 'failed 463 token recovery issuance');
|
|
1540
|
-
}
|
|
1541
|
-
finally {
|
|
1542
|
-
inFlight463Recoveries.delete(ackFrom);
|
|
1543
|
-
}
|
|
1544
|
-
})();
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
|
|
1548
|
-
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
1549
|
-
}
|
|
1550
|
-
else if (isReachoutTimelocked) {
|
|
1551
|
-
// user is temporarily restricted, fetch current restriction details
|
|
1552
|
-
await fetchAccountReachoutTimelock().catch(err => logger.warn({ err }, 'failed to fetch reachout timelock'));
|
|
1553
|
-
logger.warn({ attrs }, 'received error in ack');
|
|
1554
|
-
}
|
|
1555
|
-
else {
|
|
1556
|
-
logger.warn({ attrs }, 'received error in ack');
|
|
1557
|
-
}
|
|
1558
|
-
ev.emit('messages.update', [
|
|
1559
|
-
{
|
|
1560
|
-
key,
|
|
1561
|
-
update: {
|
|
1562
|
-
status: WAMessageStatus.ERROR,
|
|
1563
|
-
messageStubParameters: isReachoutTimelocked ? [attrs.error, ACCOUNT_RESTRICTED_TEXT] : [attrs.error]
|
|
1564
|
-
}
|
|
1719
|
+
logger.warn({
|
|
1720
|
+
attrs
|
|
1721
|
+
}, 'received error in ack')
|
|
1722
|
+
ev.emit('messages.update', [{
|
|
1723
|
+
key,
|
|
1724
|
+
update: {
|
|
1725
|
+
status: Types_1.WAMessageStatus.ERROR,
|
|
1726
|
+
messageStubParameters: [
|
|
1727
|
+
attrs.error
|
|
1728
|
+
]
|
|
1565
1729
|
}
|
|
1566
|
-
])
|
|
1730
|
+
}])
|
|
1567
1731
|
}
|
|
1568
|
-
}
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1569
1734
|
/// processes a node with the given function
|
|
1570
1735
|
/// and adds the task to the existing buffer if we're buffering events
|
|
1571
1736
|
const processNodeWithBuffer = async (node, identifier, exec) => {
|
|
1572
|
-
ev.buffer()
|
|
1573
|
-
await execTask()
|
|
1574
|
-
ev.flush()
|
|
1737
|
+
ev.buffer()
|
|
1738
|
+
await execTask()
|
|
1739
|
+
ev.flush()
|
|
1740
|
+
|
|
1575
1741
|
function execTask() {
|
|
1576
|
-
return exec(node, false)
|
|
1742
|
+
return exec(node, false)
|
|
1743
|
+
.catch(err => onUnexpectedError(err, identifier))
|
|
1577
1744
|
}
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
[
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
const makeOfflineNodeProcessor = () => {
|
|
1748
|
+
const nodeProcessorMap = new Map([
|
|
1749
|
+
['message', handleMessage],
|
|
1750
|
+
['call', handleCall],
|
|
1751
|
+
['receipt', handleReceipt],
|
|
1752
|
+
['notification', handleNotification]
|
|
1753
|
+
])
|
|
1754
|
+
|
|
1755
|
+
const nodes = []
|
|
1756
|
+
let isProcessing = false
|
|
1757
|
+
|
|
1758
|
+
const enqueue = (type, node) => {
|
|
1759
|
+
nodes.push({
|
|
1760
|
+
type,
|
|
1761
|
+
node
|
|
1762
|
+
})
|
|
1763
|
+
|
|
1764
|
+
if (isProcessing) {
|
|
1765
|
+
return
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
isProcessing = true
|
|
1769
|
+
|
|
1770
|
+
const promise = async () => {
|
|
1771
|
+
// PENTING: sebelumnya kalau nodeProcessor(node) throw (misal bug upstream
|
|
1772
|
+
// baileys "No participant in group message" pas decrypt node offline yang
|
|
1773
|
+
// malformed), exception itu keluar dari while-loop ini SEBELUM sempat
|
|
1774
|
+
// `isProcessing = false`. Akibatnya isProcessing kepentok true SELAMANYA,
|
|
1775
|
+
// dan enqueue() di bawah selalu langsung `return` tanpa pernah mulai
|
|
1776
|
+
// proses lagi -> semua pesan sesudahnya cuma numpuk di array `nodes` dan
|
|
1777
|
+
// gak pernah diproses lagi sampai bot di-restart total. Ini kemungkinan
|
|
1778
|
+
// besar penyebab "reconnect tapi gak pernah nerima pesan lagi".
|
|
1779
|
+
try {
|
|
1780
|
+
while (nodes.length && ws.isOpen) {
|
|
1781
|
+
const {
|
|
1782
|
+
type,
|
|
1783
|
+
node
|
|
1784
|
+
} = nodes.shift()
|
|
1785
|
+
const nodeProcessor = nodeProcessorMap.get(type)
|
|
1786
|
+
if (!nodeProcessor) {
|
|
1787
|
+
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node')
|
|
1788
|
+
continue
|
|
1789
|
+
}
|
|
1790
|
+
try {
|
|
1791
|
+
await nodeProcessor(node)
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
// Satu node gagal (misal pesan corrupt/participant kosong) TIDAK
|
|
1794
|
+
// boleh ngeblok node-node lain di belakangnya dalam antrian.
|
|
1795
|
+
onUnexpectedError(error, 'processing offline node')
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
} finally {
|
|
1799
|
+
isProcessing = false
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
promise().catch(error => onUnexpectedError(error, 'processing offline nodes'))
|
|
1598
1803
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1804
|
+
|
|
1805
|
+
return {
|
|
1806
|
+
enqueue
|
|
1602
1807
|
}
|
|
1603
|
-
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
const offlineNodeProcessor = makeOfflineNodeProcessor()
|
|
1811
|
+
|
|
1812
|
+
const processNode = async (type, node, identifier, exec) => {
|
|
1813
|
+
const isOffline = !!node.attrs.offline
|
|
1814
|
+
|
|
1604
1815
|
if (isOffline) {
|
|
1605
|
-
offlineNodeProcessor.enqueue(type, node)
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
await processNodeWithBuffer(node, identifier, exec);
|
|
1816
|
+
offlineNodeProcessor.enqueue(type, node)
|
|
1817
|
+
} else {
|
|
1818
|
+
await processNodeWithBuffer(node, identifier, exec)
|
|
1609
1819
|
}
|
|
1610
|
-
}
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1611
1822
|
// recv a message
|
|
1823
|
+
// Semua handler di-await supaya event 'CB:message'/'CB:call'/'CB:receipt'/'CB:notification'
|
|
1824
|
+
// yang datang beruntun tidak diproses bersamaan tanpa urutan (race condition di state
|
|
1825
|
+
// bersama seperti session & cache).
|
|
1612
1826
|
ws.on('CB:message', async (node) => {
|
|
1613
|
-
await processNode('message', node, 'processing message', handleMessage)
|
|
1614
|
-
})
|
|
1827
|
+
await processNode('message', node, 'processing message', handleMessage)
|
|
1828
|
+
})
|
|
1615
1829
|
ws.on('CB:call', async (node) => {
|
|
1616
|
-
await processNode('call', node, 'handling call', handleCall)
|
|
1617
|
-
})
|
|
1830
|
+
await processNode('call', node, 'handling call', handleCall)
|
|
1831
|
+
})
|
|
1618
1832
|
ws.on('CB:receipt', async (node) => {
|
|
1619
|
-
await processNode('receipt', node, 'handling receipt', handleReceipt)
|
|
1620
|
-
})
|
|
1833
|
+
await processNode('receipt', node, 'handling receipt', handleReceipt)
|
|
1834
|
+
})
|
|
1621
1835
|
ws.on('CB:notification', async (node) => {
|
|
1622
|
-
await processNode('notification', node, 'handling notification', handleNotification)
|
|
1623
|
-
})
|
|
1836
|
+
await processNode('notification', node, 'handling notification', handleNotification)
|
|
1837
|
+
})
|
|
1624
1838
|
ws.on('CB:ack,class:message', (node) => {
|
|
1625
|
-
handleBadAck(node)
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1839
|
+
handleBadAck(node)
|
|
1840
|
+
.catch(error => onUnexpectedError(error, 'handling bad ack'))
|
|
1841
|
+
})
|
|
1842
|
+
ev.on('call', ([call]) => {
|
|
1631
1843
|
// missed call + group call notification message generation
|
|
1632
1844
|
if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
|
|
1633
1845
|
const msg = {
|
|
@@ -1636,137 +1848,49 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1636
1848
|
id: call.id,
|
|
1637
1849
|
fromMe: false
|
|
1638
1850
|
},
|
|
1639
|
-
messageTimestamp: unixTimestampSeconds(call.date)
|
|
1640
|
-
}
|
|
1851
|
+
messageTimestamp: Utils_1.unixTimestampSeconds(call.date),
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1641
1854
|
if (call.status === 'timeout') {
|
|
1642
1855
|
if (call.isGroup) {
|
|
1643
|
-
msg.messageStubType = call.isVideo
|
|
1644
|
-
|
|
1645
|
-
|
|
1856
|
+
msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_GROUP_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_GROUP_VOICE
|
|
1857
|
+
} else {
|
|
1858
|
+
msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_VOICE
|
|
1646
1859
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1860
|
+
} else {
|
|
1861
|
+
msg.message = {
|
|
1862
|
+
call: {
|
|
1863
|
+
callKey: Buffer.from(call.id)
|
|
1864
|
+
}
|
|
1649
1865
|
}
|
|
1650
1866
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
/** dedupe in-flight 463 recovery token issuance by target JID */
|
|
1661
|
-
const inFlight463Recoveries = new Set();
|
|
1662
|
-
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1867
|
+
|
|
1868
|
+
const protoMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg)
|
|
1869
|
+
upsertMessage(protoMsg, call.offline ? 'append' : 'notify')
|
|
1870
|
+
}
|
|
1871
|
+
})
|
|
1872
|
+
|
|
1873
|
+
ev.on('connection.update', ({
|
|
1874
|
+
isOnline
|
|
1875
|
+
}) => {
|
|
1663
1876
|
if (typeof isOnline !== 'undefined') {
|
|
1664
|
-
sendActiveReceipts = isOnline
|
|
1665
|
-
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`)
|
|
1666
|
-
}
|
|
1667
|
-
// Flush pending tctoken index save on disconnect to avoid writing after close
|
|
1668
|
-
if (connection === 'close' && tcTokenIndexTimer) {
|
|
1669
|
-
clearTimeout(tcTokenIndexTimer);
|
|
1670
|
-
tcTokenIndexTimer = undefined;
|
|
1671
|
-
// Best-effort flush — may fail if store is already closed
|
|
1672
|
-
try {
|
|
1673
|
-
void Promise.resolve(flushTcTokenIndex()).catch(() => { });
|
|
1674
|
-
}
|
|
1675
|
-
catch {
|
|
1676
|
-
/* ignore sync errors */
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
// Prune expired tctokens when coming online, at most once per 24 hours
|
|
1680
|
-
// Matches WA Web's CLEAN_TC_TOKENS task
|
|
1681
|
-
// Note: don't gate on tcTokenKnownJids.size — the index may still be loading
|
|
1682
|
-
if (isOnline) {
|
|
1683
|
-
const now = Date.now();
|
|
1684
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
1685
|
-
if (now - lastTcTokenPruneTs >= DAY_MS) {
|
|
1686
|
-
lastTcTokenPruneTs = now;
|
|
1687
|
-
void pruneExpiredTcTokens();
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
});
|
|
1691
|
-
registerSocketEndHandler(() => {
|
|
1692
|
-
if (!config.msgRetryCounterCache && msgRetryCache.close) {
|
|
1693
|
-
msgRetryCache.close();
|
|
1694
|
-
}
|
|
1695
|
-
if (!config.callOfferCache && callOfferCache.close) {
|
|
1696
|
-
callOfferCache.close();
|
|
1697
|
-
}
|
|
1698
|
-
identityAssertDebounce.close();
|
|
1699
|
-
sendActiveReceipts = false;
|
|
1700
|
-
});
|
|
1701
|
-
async function pruneExpiredTcTokens() {
|
|
1702
|
-
try {
|
|
1703
|
-
await tcTokenIndexLoaded;
|
|
1704
|
-
// Union with the persisted index picks up JIDs added by other layers
|
|
1705
|
-
// (history sync) without needing inter-module wiring.
|
|
1706
|
-
const persisted = await readTcTokenIndex(authState.keys);
|
|
1707
|
-
const allJids = new Set(tcTokenKnownJids);
|
|
1708
|
-
for (const jid of persisted)
|
|
1709
|
-
allJids.add(jid);
|
|
1710
|
-
if (!allJids.size)
|
|
1711
|
-
return;
|
|
1712
|
-
const jids = [...allJids];
|
|
1713
|
-
const allTokens = await authState.keys.get('tctoken', jids);
|
|
1714
|
-
const writes = {};
|
|
1715
|
-
const survivors = new Set();
|
|
1716
|
-
let mutated = 0;
|
|
1717
|
-
for (const jid of jids) {
|
|
1718
|
-
const entry = allTokens[jid];
|
|
1719
|
-
if (!entry) {
|
|
1720
|
-
// Tracked but nothing in store — drop from index.
|
|
1721
|
-
mutated++;
|
|
1722
|
-
continue;
|
|
1723
|
-
}
|
|
1724
|
-
const hasPeerToken = !!entry.token?.length;
|
|
1725
|
-
const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
|
|
1726
|
-
const hasSenderTs = entry.senderTimestamp !== undefined;
|
|
1727
|
-
const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
|
|
1728
|
-
const keepPeerToken = hasPeerToken && !peerTokenExpired;
|
|
1729
|
-
const keepSenderTs = hasSenderTs && !senderTsExpired;
|
|
1730
|
-
if (!keepPeerToken && !keepSenderTs) {
|
|
1731
|
-
writes[jid] = null;
|
|
1732
|
-
mutated++;
|
|
1733
|
-
}
|
|
1734
|
-
else if (peerTokenExpired && keepSenderTs) {
|
|
1735
|
-
writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
|
|
1736
|
-
survivors.add(jid);
|
|
1737
|
-
mutated++;
|
|
1738
|
-
}
|
|
1739
|
-
else {
|
|
1740
|
-
survivors.add(jid);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
if (mutated === 0)
|
|
1744
|
-
return;
|
|
1745
|
-
await authState.keys.set({
|
|
1746
|
-
tctoken: {
|
|
1747
|
-
...writes,
|
|
1748
|
-
[TC_TOKEN_INDEX_KEY]: {
|
|
1749
|
-
token: Buffer.from(JSON.stringify([...survivors]))
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
});
|
|
1753
|
-
tcTokenKnownJids.clear();
|
|
1754
|
-
for (const jid of survivors)
|
|
1755
|
-
tcTokenKnownJids.add(jid);
|
|
1756
|
-
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
|
|
1757
|
-
}
|
|
1758
|
-
catch (err) {
|
|
1759
|
-
logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
|
|
1877
|
+
sendActiveReceipts = isOnline
|
|
1878
|
+
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`)
|
|
1760
1879
|
}
|
|
1761
|
-
}
|
|
1880
|
+
})
|
|
1881
|
+
|
|
1762
1882
|
return {
|
|
1763
1883
|
...sock,
|
|
1764
1884
|
sendMessageAck,
|
|
1765
1885
|
sendRetryRequest,
|
|
1886
|
+
offerCall,
|
|
1766
1887
|
rejectCall,
|
|
1767
1888
|
fetchMessageHistory,
|
|
1768
1889
|
requestPlaceholderResend,
|
|
1769
1890
|
messageRetryManager
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
module.exports = {
|
|
1895
|
+
makeMessagesRecvSocket
|
|
1896
|
+
}
|