@ubedsockets/baileys 1.6.6 → 1.6.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/WAProto/GenerateStatics.sh +4 -3
- package/WAProto/WAProto.proto +511 -1215
- package/WAProto/index.js +131318 -59331
- package/lib/Defaults/index.js +92 -76
- package/lib/Signal/Group/ciphertext-message.js +5 -2
- package/lib/Signal/Group/group-session-builder.js +41 -7
- package/lib/Signal/Group/group_cipher.js +51 -37
- package/lib/Signal/Group/index.js +57 -12
- package/lib/Signal/Group/keyhelper.js +44 -7
- package/lib/Signal/Group/sender-chain-key.js +15 -7
- package/lib/Signal/Group/sender-key-distribution-message.js +11 -8
- package/lib/Signal/Group/sender-key-message.js +12 -9
- package/lib/Signal/Group/sender-key-name.js +5 -2
- package/lib/Signal/Group/sender-key-record.js +21 -9
- package/lib/Signal/Group/sender-key-state.js +42 -27
- package/lib/Signal/Group/sender-message-key.js +7 -4
- package/lib/Signal/libsignal.js +90 -347
- package/lib/Socket/Client/index.js +18 -3
- package/lib/Socket/Client/types.js +6 -4
- package/lib/Socket/Client/websocket.js +78 -21
- package/lib/Socket/business.js +43 -162
- package/lib/Socket/chats.js +249 -571
- package/lib/Socket/groups.js +99 -114
- package/lib/Socket/index.js +9 -11
- package/lib/Socket/messages-recv.js +559 -1277
- package/lib/Socket/messages-send.js +669 -674
- package/lib/Socket/newsletter.js +286 -149
- package/lib/Socket/socket.js +235 -548
- package/lib/Types/Auth.js +2 -2
- package/lib/Types/Call.js +2 -2
- package/lib/Types/Chat.js +4 -8
- package/lib/Types/Contact.js +2 -2
- package/lib/Types/Events.js +2 -2
- package/lib/Types/GroupMetadata.js +2 -2
- package/lib/Types/Label.js +5 -3
- package/lib/Types/LabelAssociation.js +5 -3
- package/lib/Types/Message.js +7 -11
- package/lib/Types/Product.js +2 -2
- package/lib/Types/Signal.js +2 -2
- package/lib/Types/Socket.js +2 -3
- package/lib/Types/State.js +2 -56
- package/lib/Types/USync.js +2 -2
- package/lib/Types/index.js +31 -15
- package/lib/Utils/auth-utils.js +136 -239
- package/lib/Utils/business.js +69 -66
- package/lib/Utils/chat-utils.js +238 -380
- package/lib/Utils/crypto.js +127 -56
- package/lib/Utils/decode-wa-message.js +63 -173
- package/lib/Utils/event-buffer.js +70 -174
- package/lib/Utils/generics.js +207 -159
- package/lib/Utils/history.js +53 -93
- package/lib/Utils/index.js +33 -22
- package/lib/Utils/link-preview.js +58 -17
- package/lib/Utils/logger.js +7 -3
- package/lib/Utils/lt-hash.js +46 -3
- package/lib/Utils/make-mutex.js +34 -24
- package/lib/Utils/messages-media.js +744 -483
- package/lib/Utils/messages.js +621 -411
- package/lib/Utils/noise-handler.js +99 -149
- package/lib/Utils/process-message.js +150 -459
- package/lib/Utils/signal.js +73 -112
- package/lib/Utils/use-multi-file-auth-state.js +27 -23
- package/lib/Utils/validate-connection.js +104 -74
- package/lib/WABinary/constants.js +11 -9
- package/lib/WABinary/decode.js +52 -49
- package/lib/WABinary/encode.js +49 -19
- package/lib/WABinary/generic-utils.js +43 -46
- package/lib/WABinary/index.js +21 -6
- package/lib/WABinary/jid-utils.js +45 -72
- package/lib/WABinary/types.js +2 -2
- package/lib/WAM/BinaryInfo.js +5 -2
- package/lib/WAM/constants.js +11958 -19461
- package/lib/WAM/encode.js +22 -17
- package/lib/WAM/index.js +19 -4
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +13 -33
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +14 -11
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +11 -8
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +13 -9
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +22 -20
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +8 -13
- package/lib/WAUSync/Protocols/index.js +20 -6
- package/lib/WAUSync/USyncQuery.js +35 -44
- package/lib/WAUSync/USyncUser.js +5 -10
- package/lib/WAUSync/index.js +19 -4
- package/lib/index.js +70 -11
- package/package.json +60 -100
- package/engine-requirements.js +0 -10
|
@@ -1,465 +1,163 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
const sock = makeMessagesSocket(config);
|
|
23
|
-
const { userDevicesCache, devicesMutex, ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, registerSocketEndHandler, issuePrivacyTokens, fetchAccountReachoutTimelock, placeholderResendCache } = sock;
|
|
24
|
-
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.makeMessagesRecvSocket = void 0;
|
|
7
|
+
const boom_1 = require("@hapi/boom");
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
|
|
10
|
+
const WAProto_1 = require("../../WAProto");
|
|
11
|
+
const Defaults_1 = require("../Defaults");
|
|
12
|
+
const Types_1 = require("../Types");
|
|
13
|
+
const Utils_1 = require("../Utils");
|
|
14
|
+
const make_mutex_1 = require("../Utils/make-mutex");
|
|
15
|
+
const WABinary_1 = require("../WABinary");
|
|
16
|
+
const groups_1 = require("./groups");
|
|
17
|
+
const messages_send_1 = require("./messages-send");
|
|
18
|
+
const makeMessagesRecvSocket = (config) => {
|
|
19
|
+
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
|
|
20
|
+
const sock = (0, messages_send_1.makeMessagesSocket)(config);
|
|
21
|
+
const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, groupMetadata, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, createParticipantNodes, getUSyncDevices, sendPeerDataOperationMessage, } = sock;
|
|
25
22
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
26
|
-
const retryMutex = makeMutex();
|
|
27
|
-
const msgRetryCache = config.msgRetryCounterCache ||
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
const retryMutex = (0, make_mutex_1.makeMutex)();
|
|
24
|
+
const msgRetryCache = config.msgRetryCounterCache || new node_cache_1.default({
|
|
25
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
26
|
+
useClones: false
|
|
27
|
+
});
|
|
28
|
+
const callOfferCache = config.callOfferCache || new node_cache_1.default({
|
|
29
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
30
|
+
useClones: false
|
|
31
|
+
});
|
|
32
|
+
const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
|
|
33
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
34
|
+
useClones: false
|
|
35
|
+
});
|
|
39
36
|
let sendActiveReceipts = false;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
oldestMsgId: oldestMsgKey.id,
|
|
49
|
-
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
50
|
-
onDemandMsgCount: count
|
|
51
|
-
},
|
|
52
|
-
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
37
|
+
const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
|
|
38
|
+
const stanza = {
|
|
39
|
+
tag: 'ack',
|
|
40
|
+
attrs: {
|
|
41
|
+
id: attrs.id,
|
|
42
|
+
to: attrs.from,
|
|
43
|
+
class: tag
|
|
44
|
+
}
|
|
53
45
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const requestPlaceholderResend = async (messageKey, msgData) => {
|
|
57
|
-
if (!authState.creds.me?.id) {
|
|
58
|
-
throw new Boom('Not authenticated');
|
|
46
|
+
if (!!errorCode) {
|
|
47
|
+
stanza.attrs.error = errorCode.toString();
|
|
59
48
|
}
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
return;
|
|
49
|
+
if (!!attrs.participant) {
|
|
50
|
+
stanza.attrs.participant = attrs.participant;
|
|
63
51
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// metadata (LID details, timestamps, etc.) that the phone may omit
|
|
67
|
-
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
52
|
+
if (!!attrs.recipient) {
|
|
53
|
+
stanza.attrs.recipient = attrs.recipient;
|
|
68
54
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logger.debug({ messageKey }, 'message received while resend requested');
|
|
72
|
-
return 'RESOLVED';
|
|
55
|
+
if (!!attrs.type && (tag !== 'message' || (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
|
|
56
|
+
stanza.attrs.type = attrs.type;
|
|
73
57
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
messageKey
|
|
78
|
-
}
|
|
79
|
-
],
|
|
80
|
-
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
81
|
-
};
|
|
82
|
-
setTimeout(async () => {
|
|
83
|
-
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
84
|
-
logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
|
|
85
|
-
await placeholderResendCache.del(messageKey?.id);
|
|
86
|
-
}
|
|
87
|
-
}, 8000);
|
|
88
|
-
return sendPeerDataOperationMessage(pdoMessage);
|
|
89
|
-
};
|
|
90
|
-
const handleMexNotification = async (node) => {
|
|
91
|
-
const updateNode = getBinaryNodeChild(node, 'update');
|
|
92
|
-
if (updateNode) {
|
|
93
|
-
const opName = updateNode.attrs?.op_name;
|
|
94
|
-
if (!opName) {
|
|
95
|
-
logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name, fallback to legacy');
|
|
96
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
let mexResponse;
|
|
100
|
-
try {
|
|
101
|
-
mexResponse = JSON.parse(updateNode.content.toString());
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (mexResponse.errors?.length) {
|
|
108
|
-
logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const data = mexResponse.data;
|
|
112
|
-
if (!data) {
|
|
113
|
-
logger.warn({ opName }, 'mex notification has null data');
|
|
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;
|
|
58
|
+
if (tag === 'message' && (0, WABinary_1.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable')) {
|
|
59
|
+
stanza.attrs.from = authState.creds.me.id;
|
|
148
60
|
}
|
|
149
|
-
|
|
61
|
+
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
|
|
62
|
+
await sendNode(stanza);
|
|
150
63
|
};
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
64
|
+
const offerCall = async (toJid, isVideo = false) => {
|
|
65
|
+
const callId = (0, crypto_1.randomBytes)(16).toString('hex').toUpperCase().substring(0, 64);
|
|
66
|
+
const offerContent = [];
|
|
67
|
+
offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined });
|
|
68
|
+
offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined });
|
|
69
|
+
if (isVideo) {
|
|
70
|
+
offerContent.push({
|
|
71
|
+
tag: 'video',
|
|
72
|
+
attrs: {
|
|
73
|
+
orientation: '0',
|
|
74
|
+
'screen_width': '1920',
|
|
75
|
+
'screen_height': '1080',
|
|
76
|
+
'device_orientation': '0',
|
|
77
|
+
enc: 'vp8',
|
|
78
|
+
dec: 'vp8',
|
|
163
79
|
}
|
|
164
80
|
});
|
|
165
|
-
return;
|
|
166
81
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
reachoutTimeLock: {
|
|
177
|
-
isActive: true,
|
|
178
|
-
timeEnforcementEnds,
|
|
179
|
-
enforcementType
|
|
82
|
+
offerContent.push({ tag: 'net', attrs: { medium: '3' }, content: undefined });
|
|
83
|
+
offerContent.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) });
|
|
84
|
+
offerContent.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
|
|
85
|
+
const encKey = (0, crypto_1.randomBytes)(32);
|
|
86
|
+
const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => (0, WABinary_1.jidEncode)(user, 's.whatsapp.net', device));
|
|
87
|
+
await assertSessions(devices, true);
|
|
88
|
+
const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
|
|
89
|
+
call: {
|
|
90
|
+
callKey: encKey
|
|
180
91
|
}
|
|
181
92
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (!updates || !operation) {
|
|
223
|
-
logger.warn({ data }, 'invalid mex newsletter notification content');
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
227
|
-
switch (operation) {
|
|
228
|
-
case 'NotificationNewsletterUpdate':
|
|
229
|
-
for (const update of updates) {
|
|
230
|
-
if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
|
|
231
|
-
ev.emit('newsletter-settings.update', {
|
|
232
|
-
id: update.jid,
|
|
233
|
-
update: update.settings
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
break;
|
|
238
|
-
case 'NotificationNewsletterAdminPromote':
|
|
239
|
-
for (const update of updates) {
|
|
240
|
-
if (update.jid && update.user) {
|
|
241
|
-
ev.emit('newsletter-participants.update', {
|
|
242
|
-
id: update.jid,
|
|
243
|
-
author: node.attrs.from,
|
|
244
|
-
user: update.user,
|
|
245
|
-
new_role: 'ADMIN',
|
|
246
|
-
action: 'promote'
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
break;
|
|
251
|
-
case 'NotificationLinkedProfilesUpdates':
|
|
252
|
-
for (const update of updates) {
|
|
253
|
-
const lid = update?.jid;
|
|
254
|
-
const addedProfiles = Array.isArray(update?.added_profiles) ? update.added_profiles : [];
|
|
255
|
-
const mappings = [];
|
|
256
|
-
for (const profile of addedProfiles) {
|
|
257
|
-
const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
|
|
258
|
-
if (lid && pn) {
|
|
259
|
-
const mapping = { lid, pn };
|
|
260
|
-
ev.emit('lid-mapping.update', mapping);
|
|
261
|
-
mappings.push(mapping);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
await signalRepository.lidMapping.storeLIDPNMappings(mappings);
|
|
265
|
-
}
|
|
266
|
-
break;
|
|
267
|
-
default:
|
|
268
|
-
logger.info({ operation, data }, 'unhandled mex newsletter notification');
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
// Handles newsletter notifications
|
|
273
|
-
const handleNewsletterNotification = async (node) => {
|
|
274
|
-
const from = node.attrs.from;
|
|
275
|
-
const children = getAllBinaryNodeChildren(node);
|
|
276
|
-
const author = node.attrs.participant;
|
|
277
|
-
for (const child of children) {
|
|
278
|
-
logger.debug({ from, child }, 'got newsletter notification');
|
|
279
|
-
switch (child.tag) {
|
|
280
|
-
case 'reaction': {
|
|
281
|
-
const reactionUpdate = {
|
|
282
|
-
id: from,
|
|
283
|
-
server_id: child.attrs.message_id,
|
|
284
|
-
reaction: {
|
|
285
|
-
code: getBinaryNodeChildString(child, 'reaction'),
|
|
286
|
-
count: 1
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
ev.emit('newsletter.reaction', reactionUpdate);
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
case 'view': {
|
|
293
|
-
const viewUpdate = {
|
|
294
|
-
id: from,
|
|
295
|
-
server_id: child.attrs.message_id,
|
|
296
|
-
count: parseInt(child.content?.toString() || '0', 10)
|
|
297
|
-
};
|
|
298
|
-
ev.emit('newsletter.view', viewUpdate);
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
case 'participant': {
|
|
302
|
-
const participantUpdate = {
|
|
303
|
-
id: from,
|
|
304
|
-
author,
|
|
305
|
-
user: child.attrs.jid,
|
|
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;
|
|
358
|
-
}
|
|
93
|
+
offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
|
|
94
|
+
if (shouldIncludeDeviceIdentity) {
|
|
95
|
+
offerContent.push({
|
|
96
|
+
tag: 'device-identity',
|
|
97
|
+
attrs: {},
|
|
98
|
+
content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
|
|
99
|
+
});
|
|
359
100
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
101
|
+
const stanza = ({
|
|
102
|
+
tag: 'call',
|
|
103
|
+
attrs: {
|
|
104
|
+
to: toJid,
|
|
105
|
+
},
|
|
106
|
+
content: [{
|
|
107
|
+
tag: 'offer',
|
|
108
|
+
attrs: {
|
|
109
|
+
'call-id': callId,
|
|
110
|
+
'call-creator': authState.creds.me.id,
|
|
111
|
+
},
|
|
112
|
+
content: offerContent,
|
|
113
|
+
}],
|
|
114
|
+
});
|
|
115
|
+
await query(stanza);
|
|
116
|
+
return {
|
|
117
|
+
callId,
|
|
118
|
+
toJid,
|
|
119
|
+
isVideo,
|
|
120
|
+
};
|
|
365
121
|
};
|
|
366
122
|
const rejectCall = async (callId, callFrom) => {
|
|
367
|
-
const stanza = {
|
|
123
|
+
const stanza = ({
|
|
368
124
|
tag: 'call',
|
|
369
125
|
attrs: {
|
|
370
126
|
from: authState.creds.me.id,
|
|
371
|
-
to: callFrom
|
|
127
|
+
to: callFrom,
|
|
372
128
|
},
|
|
373
|
-
content: [
|
|
374
|
-
{
|
|
129
|
+
content: [{
|
|
375
130
|
tag: 'reject',
|
|
376
131
|
attrs: {
|
|
377
132
|
'call-id': callId,
|
|
378
133
|
'call-creator': callFrom,
|
|
379
|
-
count: '0'
|
|
134
|
+
count: '0',
|
|
380
135
|
},
|
|
381
|
-
content: undefined
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
};
|
|
136
|
+
content: undefined,
|
|
137
|
+
}],
|
|
138
|
+
});
|
|
385
139
|
await query(stanza);
|
|
386
140
|
};
|
|
387
141
|
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
388
|
-
const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
142
|
+
const { fullMessage } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
389
143
|
const { key: msgKey } = fullMessage;
|
|
390
144
|
const msgId = msgKey.id;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
// Increment retry count using new system
|
|
399
|
-
const retryCount = messageRetryManager.incrementRetryCount(msgId);
|
|
400
|
-
// Use the new retry count for the rest of the logic
|
|
401
|
-
const key = `${msgId}:${msgKey?.participant}`;
|
|
402
|
-
await msgRetryCache.set(key, retryCount);
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
// Fallback to old system
|
|
406
|
-
const key = `${msgId}:${msgKey?.participant}`;
|
|
407
|
-
let retryCount = (await msgRetryCache.get(key)) || 0;
|
|
408
|
-
if (retryCount >= maxMsgRetryCount) {
|
|
409
|
-
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
|
|
410
|
-
await msgRetryCache.del(key);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
retryCount += 1;
|
|
414
|
-
await msgRetryCache.set(key, retryCount);
|
|
145
|
+
const key = `${msgId}:${msgKey === null || msgKey === void 0 ? void 0 : msgKey.participant}`;
|
|
146
|
+
let retryCount = msgRetryCache.get(key) || 0;
|
|
147
|
+
if (retryCount >= maxMsgRetryCount) {
|
|
148
|
+
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
|
|
149
|
+
msgRetryCache.del(key);
|
|
150
|
+
return;
|
|
415
151
|
}
|
|
416
|
-
|
|
417
|
-
|
|
152
|
+
retryCount += 1;
|
|
153
|
+
msgRetryCache.set(key, retryCount);
|
|
418
154
|
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
424
|
-
try {
|
|
425
|
-
// 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
|
-
shouldRecreateSession = result.recreate;
|
|
430
|
-
recreateReason = result.reason;
|
|
431
|
-
if (shouldRecreateSession) {
|
|
432
|
-
logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
|
|
433
|
-
// Delete existing session to force recreation
|
|
434
|
-
await authState.keys.set({ session: { [sessionId]: null } });
|
|
435
|
-
forceIncludeKeys = true;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
catch (error) {
|
|
439
|
-
logger.warn({ error, fromJid }, 'failed to check session recreation');
|
|
440
|
-
}
|
|
155
|
+
if (retryCount === 1) {
|
|
156
|
+
//request a resend via phone
|
|
157
|
+
const msgId = await requestPlaceholderResend(msgKey);
|
|
158
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
|
|
441
159
|
}
|
|
442
|
-
|
|
443
|
-
// Use new retry manager for phone requests if available
|
|
444
|
-
if (messageRetryManager) {
|
|
445
|
-
// Schedule phone request with delay (like whatsmeow)
|
|
446
|
-
messageRetryManager.schedulePhoneRequest(msgId, async () => {
|
|
447
|
-
try {
|
|
448
|
-
const requestId = await requestPlaceholderResend(msgKey);
|
|
449
|
-
logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
|
|
450
|
-
}
|
|
451
|
-
catch (error) {
|
|
452
|
-
logger.warn({ error, msgId }, 'failed to send scheduled phone request');
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
// Fallback to immediate request
|
|
458
|
-
const msgId = await requestPlaceholderResend(msgKey);
|
|
459
|
-
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
const deviceIdentity = encodeSignedDeviceIdentity(account, true);
|
|
160
|
+
const deviceIdentity = (0, Utils_1.encodeSignedDeviceIdentity)(account, true);
|
|
463
161
|
await authState.keys.transaction(async () => {
|
|
464
162
|
const receipt = {
|
|
465
163
|
tag: 'receipt',
|
|
@@ -475,15 +173,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
475
173
|
count: retryCount.toString(),
|
|
476
174
|
id: node.attrs.id,
|
|
477
175
|
t: node.attrs.t,
|
|
478
|
-
v: '1'
|
|
479
|
-
// ADD ERROR FIELD
|
|
480
|
-
error: '0'
|
|
176
|
+
v: '1'
|
|
481
177
|
}
|
|
482
178
|
},
|
|
483
179
|
{
|
|
484
180
|
tag: 'registration',
|
|
485
181
|
attrs: {},
|
|
486
|
-
content: encodeBigEndian(authState.creds.registrationId)
|
|
182
|
+
content: (0, Utils_1.encodeBigEndian)(authState.creds.registrationId)
|
|
487
183
|
}
|
|
488
184
|
]
|
|
489
185
|
};
|
|
@@ -493,8 +189,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
493
189
|
if (node.attrs.participant) {
|
|
494
190
|
receipt.attrs.participant = node.attrs.participant;
|
|
495
191
|
}
|
|
496
|
-
if (retryCount > 1 || forceIncludeKeys
|
|
497
|
-
const { update, preKeys } = await getNextPreKeys(authState, 1);
|
|
192
|
+
if (retryCount > 1 || forceIncludeKeys) {
|
|
193
|
+
const { update, preKeys } = await (0, Utils_1.getNextPreKeys)(authState, 1);
|
|
498
194
|
const [keyId] = Object.keys(preKeys);
|
|
499
195
|
const key = preKeys[+keyId];
|
|
500
196
|
const content = receipt.content;
|
|
@@ -502,10 +198,10 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
502
198
|
tag: 'keys',
|
|
503
199
|
attrs: {},
|
|
504
200
|
content: [
|
|
505
|
-
{ tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
|
|
201
|
+
{ tag: 'type', attrs: {}, content: Buffer.from(Defaults_1.KEY_BUNDLE_TYPE) },
|
|
506
202
|
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
|
507
|
-
xmppPreKey(key, +keyId),
|
|
508
|
-
xmppSignedPreKey(signedPreKey),
|
|
203
|
+
(0, Utils_1.xmppPreKey)(key, +keyId),
|
|
204
|
+
(0, Utils_1.xmppSignedPreKey)(signedPreKey),
|
|
509
205
|
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
|
510
206
|
]
|
|
511
207
|
});
|
|
@@ -513,119 +209,63 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
513
209
|
}
|
|
514
210
|
await sendNode(receipt);
|
|
515
211
|
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;
|
|
533
|
-
}
|
|
534
|
-
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
|
|
535
|
-
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
536
|
-
const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
537
|
-
const result = await issuePrivacyTokens([issueJid], senderTs);
|
|
538
|
-
await storeTcTokensFromIqResult({
|
|
539
|
-
result,
|
|
540
|
-
fallbackJid: tcJid,
|
|
541
|
-
keys: authState.keys,
|
|
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
212
|
});
|
|
548
213
|
};
|
|
549
214
|
const handleEncryptNotification = async (node) => {
|
|
550
215
|
const from = node.attrs.from;
|
|
551
|
-
if (from === S_WHATSAPP_NET) {
|
|
552
|
-
const
|
|
553
|
-
if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
const countChild = getBinaryNodeChild(node, 'count');
|
|
216
|
+
if (from === WABinary_1.S_WHATSAPP_NET) {
|
|
217
|
+
const countChild = (0, WABinary_1.getBinaryNodeChild)(node, 'count');
|
|
557
218
|
const count = +countChild.attrs.value;
|
|
558
|
-
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
|
|
219
|
+
const shouldUploadMorePreKeys = count < Defaults_1.MIN_PREKEY_COUNT;
|
|
559
220
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
560
221
|
if (shouldUploadMorePreKeys) {
|
|
561
|
-
|
|
562
|
-
inFlightPreKeyLow.add(stanzaId);
|
|
563
|
-
try {
|
|
564
|
-
await uploadPreKeys();
|
|
565
|
-
}
|
|
566
|
-
finally {
|
|
567
|
-
if (stanzaId)
|
|
568
|
-
inFlightPreKeyLow.delete(stanzaId);
|
|
569
|
-
}
|
|
222
|
+
await uploadPreKeys();
|
|
570
223
|
}
|
|
571
224
|
}
|
|
572
225
|
else {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
581
|
-
});
|
|
582
|
-
if (result.action === 'no_identity_node') {
|
|
226
|
+
const identityNode = (0, WABinary_1.getBinaryNodeChild)(node, 'identity');
|
|
227
|
+
if (identityNode) {
|
|
228
|
+
logger.info({ jid: from }, 'identity changed');
|
|
229
|
+
// not handling right now
|
|
230
|
+
// signal will override new identity anyway
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
583
233
|
logger.info({ node }, 'unknown encrypt notification');
|
|
584
234
|
}
|
|
585
235
|
}
|
|
586
236
|
};
|
|
587
|
-
const handleGroupNotification = (
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
592
|
-
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
593
|
-
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
594
|
-
switch (child?.tag) {
|
|
237
|
+
const handleGroupNotification = (participant, child, msg) => {
|
|
238
|
+
var _a, _b, _c, _d;
|
|
239
|
+
const participantJid = ((_b = (_a = (0, WABinary_1.getBinaryNodeChild)(child, 'participant')) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.jid) || participant;
|
|
240
|
+
switch (child === null || child === void 0 ? void 0 : child.tag) {
|
|
595
241
|
case 'create':
|
|
596
|
-
const metadata = extractGroupMetadata(child);
|
|
597
|
-
msg.messageStubType = WAMessageStubType.GROUP_CREATE;
|
|
242
|
+
const metadata = (0, groups_1.extractGroupMetadata)(child);
|
|
243
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE;
|
|
598
244
|
msg.messageStubParameters = [metadata.subject];
|
|
599
|
-
msg.key = { participant: metadata.owner
|
|
600
|
-
ev.emit('chats.upsert', [
|
|
601
|
-
{
|
|
245
|
+
msg.key = { participant: metadata.owner };
|
|
246
|
+
ev.emit('chats.upsert', [{
|
|
602
247
|
id: metadata.id,
|
|
603
248
|
name: metadata.subject,
|
|
604
|
-
conversationTimestamp: metadata.creation
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
ev.emit('groups.upsert', [
|
|
608
|
-
{
|
|
249
|
+
conversationTimestamp: metadata.creation,
|
|
250
|
+
}]);
|
|
251
|
+
ev.emit('groups.upsert', [{
|
|
609
252
|
...metadata,
|
|
610
|
-
author:
|
|
611
|
-
|
|
612
|
-
authorUsername: actingParticipantUsername
|
|
613
|
-
}
|
|
614
|
-
]);
|
|
253
|
+
author: participant
|
|
254
|
+
}]);
|
|
615
255
|
break;
|
|
616
256
|
case 'ephemeral':
|
|
617
257
|
case 'not_ephemeral':
|
|
618
258
|
msg.message = {
|
|
619
259
|
protocolMessage: {
|
|
620
|
-
type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
260
|
+
type: WAProto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
621
261
|
ephemeralExpiration: +(child.attrs.expiration || 0)
|
|
622
262
|
}
|
|
623
263
|
};
|
|
624
264
|
break;
|
|
625
265
|
case 'modify':
|
|
626
|
-
const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid);
|
|
266
|
+
const oldNumber = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
|
|
627
267
|
msg.messageStubParameters = oldNumber || [];
|
|
628
|
-
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
|
|
268
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
|
|
629
269
|
break;
|
|
630
270
|
case 'promote':
|
|
631
271
|
case 'demote':
|
|
@@ -633,224 +273,179 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
633
273
|
case 'add':
|
|
634
274
|
case 'leave':
|
|
635
275
|
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
636
|
-
msg.messageStubType = WAMessageStubType[stubType];
|
|
637
|
-
const participants = getBinaryNodeChildren(child, 'participant').map(
|
|
638
|
-
// TODO: Store LID MAPPINGS
|
|
639
|
-
return {
|
|
640
|
-
id: attrs.jid,
|
|
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
|
-
});
|
|
276
|
+
msg.messageStubType = Types_1.WAMessageStubType[stubType];
|
|
277
|
+
const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
|
|
647
278
|
if (participants.length === 1 &&
|
|
648
279
|
// if recv. "remove" message and sender removed themselves
|
|
649
280
|
// mark as left
|
|
650
|
-
(areJidsSameUser(participants[0]
|
|
651
|
-
areJidsSameUser(participants[0].id, actingParticipantPn)) &&
|
|
281
|
+
(0, WABinary_1.areJidsSameUser)(participants[0], participant) &&
|
|
652
282
|
child.tag === 'remove') {
|
|
653
|
-
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
|
|
283
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
|
|
654
284
|
}
|
|
655
|
-
msg.messageStubParameters = participants
|
|
285
|
+
msg.messageStubParameters = participants;
|
|
656
286
|
break;
|
|
657
287
|
case 'subject':
|
|
658
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
|
|
288
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_SUBJECT;
|
|
659
289
|
msg.messageStubParameters = [child.attrs.subject];
|
|
660
290
|
break;
|
|
661
291
|
case 'description':
|
|
662
|
-
const description = getBinaryNodeChild(child, 'body')
|
|
663
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
|
|
292
|
+
const description = (_d = (_c = (0, WABinary_1.getBinaryNodeChild)(child, 'body')) === null || _c === void 0 ? void 0 : _c.content) === null || _d === void 0 ? void 0 : _d.toString();
|
|
293
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
|
|
664
294
|
msg.messageStubParameters = description ? [description] : undefined;
|
|
665
295
|
break;
|
|
666
296
|
case 'announcement':
|
|
667
297
|
case 'not_announcement':
|
|
668
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
|
|
669
|
-
msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
|
|
298
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
|
|
299
|
+
msg.messageStubParameters = [(child.tag === 'announcement') ? 'on' : 'off'];
|
|
670
300
|
break;
|
|
671
301
|
case 'locked':
|
|
672
302
|
case 'unlocked':
|
|
673
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT;
|
|
674
|
-
msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
|
|
303
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_RESTRICT;
|
|
304
|
+
msg.messageStubParameters = [(child.tag === 'locked') ? 'on' : 'off'];
|
|
675
305
|
break;
|
|
676
306
|
case 'invite':
|
|
677
|
-
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
|
|
307
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
|
|
678
308
|
msg.messageStubParameters = [child.attrs.code];
|
|
679
309
|
break;
|
|
680
310
|
case 'member_add_mode':
|
|
681
311
|
const addMode = child.content;
|
|
682
312
|
if (addMode) {
|
|
683
|
-
msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE;
|
|
313
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBER_ADD_MODE;
|
|
684
314
|
msg.messageStubParameters = [addMode.toString()];
|
|
685
315
|
}
|
|
686
316
|
break;
|
|
687
317
|
case 'membership_approval_mode':
|
|
688
|
-
const approvalMode = getBinaryNodeChild(child, 'group_join');
|
|
318
|
+
const approvalMode = (0, WABinary_1.getBinaryNodeChild)(child, 'group_join');
|
|
689
319
|
if (approvalMode) {
|
|
690
|
-
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
|
|
320
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
|
|
691
321
|
msg.messageStubParameters = [approvalMode.attrs.state];
|
|
692
322
|
}
|
|
693
323
|
break;
|
|
694
324
|
case 'created_membership_requests':
|
|
695
|
-
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
696
|
-
msg.messageStubParameters = [
|
|
697
|
-
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
698
|
-
'created',
|
|
699
|
-
child.attrs.request_method
|
|
700
|
-
];
|
|
325
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
326
|
+
msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method];
|
|
701
327
|
break;
|
|
702
328
|
case 'revoked_membership_requests':
|
|
703
|
-
const isDenied = areJidsSameUser(
|
|
704
|
-
|
|
705
|
-
msg.
|
|
706
|
-
|
|
707
|
-
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
708
|
-
isDenied ? 'revoked' : 'rejected'
|
|
709
|
-
];
|
|
329
|
+
const isDenied = (0, WABinary_1.areJidsSameUser)(participantJid, participant);
|
|
330
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
331
|
+
msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'];
|
|
332
|
+
break;
|
|
710
333
|
break;
|
|
334
|
+
default:
|
|
335
|
+
// console.log("BAILEYS-DEBUG:", JSON.stringify({ ...child, content: Buffer.isBuffer(child.content) ? child.content.toString() : child.content, participant }, null, 2))
|
|
711
336
|
}
|
|
712
337
|
};
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (areJidsSameUser(from, authState.creds.me.id) || areJidsSameUser(from, authState.creds.me.lid)) {
|
|
724
|
-
const deviceJids = devices.map(d => d.attrs.jid);
|
|
725
|
-
logger.info({ deviceJids }, 'got my own devices');
|
|
726
|
-
}
|
|
727
|
-
if (!devices.length) {
|
|
728
|
-
logger.debug({ from, tag }, 'no devices in notification, skipping');
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
const decoded = [];
|
|
732
|
-
for (const d of devices) {
|
|
733
|
-
const jid = d.attrs.jid;
|
|
734
|
-
if (!jid)
|
|
735
|
-
continue;
|
|
736
|
-
const parts = jidDecode(jid);
|
|
737
|
-
if (!parts) {
|
|
738
|
-
logger.debug({ jid }, 'failed to decode device jid, skipping');
|
|
739
|
-
continue;
|
|
338
|
+
const handleNewsletterNotification = (id, node) => {
|
|
339
|
+
const messages = (0, WABinary_1.getBinaryNodeChild)(node, 'messages');
|
|
340
|
+
const message = (0, WABinary_1.getBinaryNodeChild)(messages, 'message');
|
|
341
|
+
const serverId = message.attrs.server_id;
|
|
342
|
+
const reactionsList = (0, WABinary_1.getBinaryNodeChild)(message, 'reactions');
|
|
343
|
+
const viewsList = (0, WABinary_1.getBinaryNodeChildren)(message, 'views_count');
|
|
344
|
+
if (reactionsList) {
|
|
345
|
+
const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsList, 'reaction');
|
|
346
|
+
if (reactions.length === 0) {
|
|
347
|
+
ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { removed: true } });
|
|
740
348
|
}
|
|
741
|
-
|
|
349
|
+
reactions.forEach(item => {
|
|
350
|
+
var _a, _b;
|
|
351
|
+
ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { code: (_a = item.attrs) === null || _a === void 0 ? void 0 : _a.code, count: +((_b = item.attrs) === null || _b === void 0 ? void 0 : _b.count) } });
|
|
352
|
+
});
|
|
742
353
|
}
|
|
743
|
-
if (
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
354
|
+
if (viewsList.length) {
|
|
355
|
+
viewsList.forEach(item => {
|
|
356
|
+
ev.emit('newsletter.view', { id, 'server_id': serverId, count: +item.attrs.count });
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const handleMexNewsletterNotification = (id, node) => {
|
|
361
|
+
var _a;
|
|
362
|
+
const operation = node === null || node === void 0 ? void 0 : node.attrs.op_name;
|
|
363
|
+
const content = JSON.parse((_a = node === null || node === void 0 ? void 0 : node.content) === null || _a === void 0 ? void 0 : _a.toString());
|
|
364
|
+
let contentPath;
|
|
365
|
+
if (operation === Types_1.MexOperations.PROMOTE || operation === Types_1.MexOperations.DEMOTE) {
|
|
366
|
+
let action;
|
|
367
|
+
if (operation === Types_1.MexOperations.PROMOTE) {
|
|
368
|
+
action = 'promote';
|
|
369
|
+
contentPath = content.data[Types_1.XWAPaths.PROMOTE];
|
|
751
370
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
await userDevicesCache?.del(user);
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
if (tag === 'remove') {
|
|
759
|
-
await signalRepository.deleteSession(entries.map(e => e.jid));
|
|
760
|
-
}
|
|
761
|
-
const existingCache = (await userDevicesCache?.get(user)) || [];
|
|
762
|
-
if (!existingCache.length) {
|
|
763
|
-
// No baseline yet; skip applying the delta so getUSyncDevices can
|
|
764
|
-
// later fetch the full device list. Caching just the notification
|
|
765
|
-
// entries would make a partial list look authoritative.
|
|
766
|
-
logger.debug({ user, tag }, 'device list not cached, deferring to USync refresh');
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
const affected = new Set(entries.map(e => e.device));
|
|
770
|
-
let updatedDevices;
|
|
771
|
-
switch (tag) {
|
|
772
|
-
case 'add':
|
|
773
|
-
logger.info({ deviceHash, count: entries.length }, 'devices added');
|
|
774
|
-
updatedDevices = [
|
|
775
|
-
...existingCache.filter(d => !affected.has(d.device)),
|
|
776
|
-
...entries.map(e => ({ user: e.user, server: e.server, device: e.device }))
|
|
777
|
-
];
|
|
778
|
-
break;
|
|
779
|
-
case 'remove':
|
|
780
|
-
logger.info({ deviceHash, count: entries.length }, 'devices removed');
|
|
781
|
-
updatedDevices = existingCache.filter(d => !affected.has(d.device));
|
|
782
|
-
break;
|
|
783
|
-
default:
|
|
784
|
-
logger.debug({ tag }, 'Unknown device list change tag');
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
if (updatedDevices.length === 0) {
|
|
788
|
-
await userDevicesCache?.del(user);
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
await userDevicesCache?.set(user, updatedDevices);
|
|
792
|
-
}
|
|
371
|
+
if (operation === Types_1.MexOperations.DEMOTE) {
|
|
372
|
+
action = 'demote';
|
|
373
|
+
contentPath = content.data[Types_1.XWAPaths.DEMOTE];
|
|
793
374
|
}
|
|
794
|
-
|
|
375
|
+
ev.emit('newsletter-participants.update', { id, author: contentPath.actor.pn, user: contentPath.user.pn, new_role: contentPath.user_new_role, action });
|
|
376
|
+
}
|
|
377
|
+
if (operation === Types_1.MexOperations.UPDATE) {
|
|
378
|
+
contentPath = content.data[Types_1.XWAPaths.METADATA_UPDATE];
|
|
379
|
+
ev.emit('newsletter-settings.update', { id, update: contentPath.thread_metadata.settings });
|
|
380
|
+
}
|
|
795
381
|
};
|
|
796
382
|
const processNotification = async (node) => {
|
|
383
|
+
var _a, _b;
|
|
797
384
|
const result = {};
|
|
798
|
-
const [child] = getAllBinaryNodeChildren(node);
|
|
385
|
+
const [child] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
|
|
799
386
|
const nodeType = node.attrs.type;
|
|
800
|
-
const from = jidNormalizedUser(node.attrs.from);
|
|
387
|
+
const from = (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
|
|
801
388
|
switch (nodeType) {
|
|
389
|
+
case 'privacy_token':
|
|
390
|
+
const tokenList = (0, WABinary_1.getBinaryNodeChildren)(child, 'token');
|
|
391
|
+
for (const { attrs, content } of tokenList) {
|
|
392
|
+
const jid = attrs.jid;
|
|
393
|
+
ev.emit('chats.update', [
|
|
394
|
+
{
|
|
395
|
+
id: jid,
|
|
396
|
+
tcToken: content
|
|
397
|
+
}
|
|
398
|
+
]);
|
|
399
|
+
logger.debug({ jid }, 'got privacy token update');
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
802
402
|
case 'newsletter':
|
|
803
|
-
|
|
403
|
+
handleNewsletterNotification(node.attrs.from, child);
|
|
804
404
|
break;
|
|
805
405
|
case 'mex':
|
|
806
|
-
|
|
406
|
+
handleMexNewsletterNotification(node.attrs.from, child);
|
|
807
407
|
break;
|
|
808
408
|
case 'w:gp2':
|
|
809
|
-
|
|
810
|
-
handleGroupNotification(node, child, result);
|
|
409
|
+
handleGroupNotification(node.attrs.participant, child, result);
|
|
811
410
|
break;
|
|
812
411
|
case 'mediaretry':
|
|
813
|
-
const event = decodeMediaRetryNode(node);
|
|
412
|
+
const event = (0, Utils_1.decodeMediaRetryNode)(node);
|
|
814
413
|
ev.emit('messages.media-update', [event]);
|
|
815
414
|
break;
|
|
816
415
|
case 'encrypt':
|
|
817
416
|
await handleEncryptNotification(node);
|
|
818
417
|
break;
|
|
819
418
|
case 'devices':
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
logger.error({ error, node }, 'failed to handle devices notification');
|
|
419
|
+
const devices = (0, WABinary_1.getBinaryNodeChildren)(child, 'device');
|
|
420
|
+
if ((0, WABinary_1.areJidsSameUser)(child.attrs.jid, authState.creds.me.id)) {
|
|
421
|
+
const deviceJids = devices.map(d => d.attrs.jid);
|
|
422
|
+
logger.info({ deviceJids }, 'got my own devices');
|
|
825
423
|
}
|
|
826
424
|
break;
|
|
827
425
|
case 'server_sync':
|
|
828
|
-
const update = getBinaryNodeChild(node, 'collection');
|
|
426
|
+
const update = (0, WABinary_1.getBinaryNodeChild)(node, 'collection');
|
|
829
427
|
if (update) {
|
|
830
428
|
const name = update.attrs.name;
|
|
831
429
|
await resyncAppState([name], false);
|
|
832
430
|
}
|
|
833
431
|
break;
|
|
834
432
|
case 'picture':
|
|
835
|
-
const setPicture = getBinaryNodeChild(node, 'set');
|
|
836
|
-
const delPicture = getBinaryNodeChild(node, 'delete');
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
{
|
|
840
|
-
id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
|
433
|
+
const setPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'set');
|
|
434
|
+
const delPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'delete');
|
|
435
|
+
ev.emit('contacts.update', [{
|
|
436
|
+
id: from || ((_b = (_a = (setPicture || delPicture)) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.hash) || '',
|
|
841
437
|
imgUrl: setPicture ? 'changed' : 'removed'
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
if (isJidGroup(from)) {
|
|
438
|
+
}]);
|
|
439
|
+
if ((0, WABinary_1.isJidGroup)(from)) {
|
|
845
440
|
const node = setPicture || delPicture;
|
|
846
|
-
result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
|
|
441
|
+
result.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ICON;
|
|
847
442
|
if (setPicture) {
|
|
848
443
|
result.messageStubParameters = [setPicture.attrs.id];
|
|
849
444
|
}
|
|
850
|
-
result.participant = node
|
|
445
|
+
result.participant = node === null || node === void 0 ? void 0 : node.attrs.author;
|
|
851
446
|
result.key = {
|
|
852
|
-
...
|
|
853
|
-
participant: setPicture
|
|
447
|
+
...result.key || {},
|
|
448
|
+
participant: setPicture === null || setPicture === void 0 ? void 0 : setPicture.attrs.author
|
|
854
449
|
};
|
|
855
450
|
}
|
|
856
451
|
break;
|
|
@@ -864,48 +459,44 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
864
459
|
...authState.creds.accountSettings,
|
|
865
460
|
defaultDisappearingMode: {
|
|
866
461
|
ephemeralExpiration: newDuration,
|
|
867
|
-
ephemeralSettingTimestamp: timestamp
|
|
868
|
-
}
|
|
462
|
+
ephemeralSettingTimestamp: timestamp,
|
|
463
|
+
},
|
|
869
464
|
}
|
|
870
465
|
});
|
|
871
466
|
}
|
|
872
467
|
else if (child.tag === 'blocklist') {
|
|
873
|
-
const blocklists = getBinaryNodeChildren(child, 'item');
|
|
468
|
+
const blocklists = (0, WABinary_1.getBinaryNodeChildren)(child, 'item');
|
|
874
469
|
for (const { attrs } of blocklists) {
|
|
875
470
|
const blocklist = [attrs.jid];
|
|
876
|
-
const type = attrs.action === 'block' ? 'add' : 'remove';
|
|
471
|
+
const type = (attrs.action === 'block') ? 'add' : 'remove';
|
|
877
472
|
ev.emit('blocklist.update', { blocklist, type });
|
|
878
473
|
}
|
|
879
474
|
}
|
|
880
475
|
break;
|
|
881
476
|
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'));
|
|
477
|
+
const linkCodeCompanionReg = (0, WABinary_1.getBinaryNodeChild)(node, 'link_code_companion_reg');
|
|
478
|
+
const ref = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_ref'));
|
|
479
|
+
const primaryIdentityPublicKey = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'primary_identity_pub'));
|
|
480
|
+
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer((0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
|
|
886
481
|
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
|
-
const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
|
|
482
|
+
const companionSharedKey = Utils_1.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
|
|
483
|
+
const random = (0, crypto_1.randomBytes)(32);
|
|
484
|
+
const linkCodeSalt = (0, crypto_1.randomBytes)(32);
|
|
485
|
+
const linkCodePairingExpanded = await (0, Utils_1.hkdf)(companionSharedKey, 32, {
|
|
891
486
|
salt: linkCodeSalt,
|
|
892
487
|
info: 'link_code_pairing_key_bundle_encryption_key'
|
|
893
488
|
});
|
|
894
|
-
const encryptPayload = Buffer.concat([
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
random
|
|
898
|
-
]);
|
|
899
|
-
const encryptIv = randomBytes(12);
|
|
900
|
-
const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
|
|
489
|
+
const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random]);
|
|
490
|
+
const encryptIv = (0, crypto_1.randomBytes)(12);
|
|
491
|
+
const encrypted = (0, Utils_1.aesEncryptGCM)(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
|
|
901
492
|
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
|
|
902
|
-
const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
|
|
493
|
+
const identitySharedKey = Utils_1.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
|
|
903
494
|
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
|
|
904
|
-
authState.creds.advSecretKey =
|
|
495
|
+
authState.creds.advSecretKey = (await (0, Utils_1.hkdf)(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
|
|
905
496
|
await query({
|
|
906
497
|
tag: 'iq',
|
|
907
498
|
attrs: {
|
|
908
|
-
to: S_WHATSAPP_NET,
|
|
499
|
+
to: WABinary_1.S_WHATSAPP_NET,
|
|
909
500
|
type: 'set',
|
|
910
501
|
id: sock.generateMessageTag(),
|
|
911
502
|
xmlns: 'md'
|
|
@@ -915,7 +506,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
915
506
|
tag: 'link_code_companion_reg',
|
|
916
507
|
attrs: {
|
|
917
508
|
jid: authState.creds.me.id,
|
|
918
|
-
stage: 'companion_finish'
|
|
509
|
+
stage: 'companion_finish',
|
|
919
510
|
},
|
|
920
511
|
content: [
|
|
921
512
|
{
|
|
@@ -939,208 +530,53 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
939
530
|
});
|
|
940
531
|
authState.creds.registered = true;
|
|
941
532
|
ev.emit('creds.update', authState.creds);
|
|
942
|
-
break;
|
|
943
|
-
case 'privacy_token':
|
|
944
|
-
await handlePrivacyTokenNotification(node);
|
|
945
|
-
break;
|
|
946
533
|
}
|
|
947
534
|
if (Object.keys(result).length) {
|
|
948
535
|
return result;
|
|
949
536
|
}
|
|
950
537
|
};
|
|
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;
|
|
973
|
-
}
|
|
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
|
-
}
|
|
979
|
-
function scheduleTcTokenIndexSave() {
|
|
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
|
-
};
|
|
1016
538
|
async function decipherLinkPublicKey(data) {
|
|
1017
539
|
const buffer = toRequiredBuffer(data);
|
|
1018
540
|
const salt = buffer.slice(0, 32);
|
|
1019
|
-
const secretKey = await derivePairingCodeKey(authState.creds.pairingCode, salt);
|
|
541
|
+
const secretKey = await (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
|
|
1020
542
|
const iv = buffer.slice(32, 48);
|
|
1021
543
|
const payload = buffer.slice(48, 80);
|
|
1022
|
-
return aesDecryptCTR(payload, secretKey, iv);
|
|
544
|
+
return (0, Utils_1.aesDecryptCTR)(payload, secretKey, iv);
|
|
1023
545
|
}
|
|
1024
546
|
function toRequiredBuffer(data) {
|
|
1025
547
|
if (data === undefined) {
|
|
1026
|
-
throw new Boom('Invalid buffer', { statusCode: 400 });
|
|
548
|
+
throw new boom_1.Boom('Invalid buffer', { statusCode: 400 });
|
|
1027
549
|
}
|
|
1028
550
|
return data instanceof Buffer ? data : Buffer.from(data);
|
|
1029
551
|
}
|
|
1030
|
-
const willSendMessageAgain =
|
|
552
|
+
const willSendMessageAgain = (id, participant) => {
|
|
1031
553
|
const key = `${id}:${participant}`;
|
|
1032
|
-
const retryCount =
|
|
554
|
+
const retryCount = msgRetryCache.get(key) || 0;
|
|
1033
555
|
return retryCount < maxMsgRetryCount;
|
|
1034
556
|
};
|
|
1035
|
-
const updateSendMessageAgainCount =
|
|
557
|
+
const updateSendMessageAgainCount = (id, participant) => {
|
|
1036
558
|
const key = `${id}:${participant}`;
|
|
1037
|
-
const newValue = (
|
|
1038
|
-
|
|
559
|
+
const newValue = (msgRetryCache.get(key) || 0) + 1;
|
|
560
|
+
msgRetryCache.set(key, newValue);
|
|
1039
561
|
};
|
|
1040
|
-
const sendMessagesAgain = async (key, ids, retryNode
|
|
562
|
+
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
563
|
+
var _a;
|
|
564
|
+
// todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
|
|
565
|
+
const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
|
|
1041
566
|
const remoteJid = key.remoteJid;
|
|
1042
567
|
const participant = key.participant || remoteJid;
|
|
1043
|
-
const retryCount = +retryNode.attrs.count || 1;
|
|
1044
|
-
const msgId = ids[0];
|
|
1045
|
-
// Try to get messages from cache first, then fallback to getMessage
|
|
1046
|
-
const msgs = [];
|
|
1047
|
-
for (const id of ids) {
|
|
1048
|
-
let msg;
|
|
1049
|
-
// Try to get from retry cache first if enabled
|
|
1050
|
-
if (messageRetryManager) {
|
|
1051
|
-
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
|
|
1052
|
-
if (cachedMsg) {
|
|
1053
|
-
msg = cachedMsg.message;
|
|
1054
|
-
logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
|
|
1055
|
-
// Mark retry as successful since we found the message
|
|
1056
|
-
messageRetryManager.markRetrySuccess(id);
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
// Fallback to getMessage if not found in cache
|
|
1060
|
-
if (!msg) {
|
|
1061
|
-
msg = await getMessage({ ...key, id });
|
|
1062
|
-
if (msg) {
|
|
1063
|
-
logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
|
|
1064
|
-
// Also mark as successful if found via getMessage
|
|
1065
|
-
if (messageRetryManager) {
|
|
1066
|
-
messageRetryManager.markRetrySuccess(id);
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
msgs.push(msg);
|
|
1071
|
-
}
|
|
1072
568
|
// if it's the primary jid sending the request
|
|
1073
569
|
// just re-send the message to everyone
|
|
1074
570
|
// prevents the first message decryption failure
|
|
1075
|
-
const sendToAll = !jidDecode(participant)
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const bundle = extractE2ESessionFromRetryReceipt(receiptNode);
|
|
1079
|
-
if (bundle) {
|
|
1080
|
-
try {
|
|
1081
|
-
await signalRepository.injectE2ESession({ jid: participant, session: bundle });
|
|
1082
|
-
injectedFromBundle = true;
|
|
1083
|
-
logger.debug({ participant, retryCount }, 'injected session from retry receipt key bundle');
|
|
1084
|
-
}
|
|
1085
|
-
catch (error) {
|
|
1086
|
-
logger.warn({ error, participant }, 'failed to inject session from retry receipt');
|
|
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;
|
|
1123
|
-
if (shouldRecreateSession) {
|
|
1124
|
-
logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
|
|
1125
|
-
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
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
|
-
}
|
|
1135
|
-
if (isJidGroup(remoteJid)) {
|
|
571
|
+
const sendToAll = !((_a = (0, WABinary_1.jidDecode)(participant)) === null || _a === void 0 ? void 0 : _a.device);
|
|
572
|
+
await assertSessions([participant], true);
|
|
573
|
+
if ((0, WABinary_1.isJidGroup)(remoteJid)) {
|
|
1136
574
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
1137
575
|
}
|
|
1138
|
-
logger.debug({ participant, sendToAll
|
|
576
|
+
logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
|
|
1139
577
|
for (const [i, msg] of msgs.entries()) {
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1142
|
-
if (msg && (await willSendMessageAgain(ids[i], participant))) {
|
|
1143
|
-
await updateSendMessageAgainCount(ids[i], participant);
|
|
578
|
+
if (msg) {
|
|
579
|
+
updateSendMessageAgainCount(ids[i], participant);
|
|
1144
580
|
const msgRelayOpts = { messageId: ids[i] };
|
|
1145
581
|
if (sendToAll) {
|
|
1146
582
|
msgRelayOpts.useUserDevicesCache = false;
|
|
@@ -1159,10 +595,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1159
595
|
}
|
|
1160
596
|
};
|
|
1161
597
|
const handleReceipt = async (node) => {
|
|
598
|
+
var _a, _b;
|
|
1162
599
|
const { attrs, content } = node;
|
|
1163
600
|
const isLid = attrs.from.includes('lid');
|
|
1164
|
-
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me
|
|
1165
|
-
const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
601
|
+
const isNodeFromMe = (0, WABinary_1.areJidsSameUser)(attrs.participant || attrs.from, isLid ? (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.lid : (_b = authState.creds.me) === null || _b === void 0 ? void 0 : _b.id);
|
|
602
|
+
const remoteJid = !isNodeFromMe || (0, WABinary_1.isJidGroup)(attrs.from) ? attrs.from : attrs.recipient;
|
|
1166
603
|
const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
|
|
1167
604
|
const key = {
|
|
1168
605
|
remoteJid,
|
|
@@ -1170,26 +607,33 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1170
607
|
fromMe,
|
|
1171
608
|
participant: attrs.participant
|
|
1172
609
|
};
|
|
610
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
|
|
611
|
+
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
612
|
+
await sendMessageAck(node);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
1173
615
|
const ids = [attrs.id];
|
|
1174
616
|
if (Array.isArray(content)) {
|
|
1175
|
-
const items = getBinaryNodeChildren(content[0], 'item');
|
|
617
|
+
const items = (0, WABinary_1.getBinaryNodeChildren)(content[0], 'item');
|
|
1176
618
|
ids.push(...items.map(i => i.attrs.id));
|
|
1177
619
|
}
|
|
1178
620
|
try {
|
|
1179
621
|
await Promise.all([
|
|
1180
|
-
|
|
1181
|
-
const status = getStatusFromReceiptType(attrs.type);
|
|
622
|
+
processingMutex.mutex(async () => {
|
|
623
|
+
const status = (0, Utils_1.getStatusFromReceiptType)(attrs.type);
|
|
1182
624
|
if (typeof status !== 'undefined' &&
|
|
625
|
+
(
|
|
1183
626
|
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
|
1184
627
|
// or another device of ours has read some messages
|
|
1185
|
-
|
|
1186
|
-
|
|
628
|
+
status >= WAProto_1.proto.WebMessageInfo.Status.SERVER_ACK ||
|
|
629
|
+
!isNodeFromMe)) {
|
|
630
|
+
if ((0, WABinary_1.isJidGroup)(remoteJid) || (0, WABinary_1.isJidStatusBroadcast)(remoteJid)) {
|
|
1187
631
|
if (attrs.participant) {
|
|
1188
|
-
const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
|
|
632
|
+
const updateKey = status === WAProto_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
|
|
1189
633
|
ev.emit('message-receipt.update', ids.map(id => ({
|
|
1190
634
|
key: { ...key, id },
|
|
1191
635
|
receipt: {
|
|
1192
|
-
userJid: jidNormalizedUser(attrs.participant),
|
|
636
|
+
userJid: (0, WABinary_1.jidNormalizedUser)(attrs.participant),
|
|
1193
637
|
[updateKey]: +attrs.t
|
|
1194
638
|
}
|
|
1195
639
|
})));
|
|
@@ -1198,23 +642,22 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1198
642
|
else {
|
|
1199
643
|
ev.emit('messages.update', ids.map(id => ({
|
|
1200
644
|
key: { ...key, id },
|
|
1201
|
-
update: { status
|
|
645
|
+
update: { status }
|
|
1202
646
|
})));
|
|
1203
647
|
}
|
|
1204
648
|
}
|
|
1205
649
|
if (attrs.type === 'retry') {
|
|
1206
650
|
// correctly set who is asking for the retry
|
|
1207
651
|
key.participant = key.participant || attrs.from;
|
|
1208
|
-
const retryNode = getBinaryNodeChild(node, 'retry');
|
|
1209
|
-
if (
|
|
652
|
+
const retryNode = (0, WABinary_1.getBinaryNodeChild)(node, 'retry');
|
|
653
|
+
if (willSendMessageAgain(ids[0], key.participant)) {
|
|
1210
654
|
if (key.fromMe) {
|
|
1211
655
|
try {
|
|
1212
|
-
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
1213
656
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
1214
|
-
await sendMessagesAgain(key, ids, retryNode
|
|
657
|
+
await sendMessagesAgain(key, ids, retryNode);
|
|
1215
658
|
}
|
|
1216
659
|
catch (error) {
|
|
1217
|
-
logger.error({ key, ids, trace: error
|
|
660
|
+
logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
|
|
1218
661
|
}
|
|
1219
662
|
}
|
|
1220
663
|
else {
|
|
@@ -1229,338 +672,269 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1229
672
|
]);
|
|
1230
673
|
}
|
|
1231
674
|
finally {
|
|
1232
|
-
await sendMessageAck(node)
|
|
675
|
+
await sendMessageAck(node);
|
|
1233
676
|
}
|
|
1234
677
|
};
|
|
1235
678
|
const handleNotification = async (node) => {
|
|
1236
679
|
const remoteJid = node.attrs.from;
|
|
680
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
|
|
681
|
+
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
682
|
+
await sendMessageAck(node);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
1237
685
|
try {
|
|
1238
686
|
await Promise.all([
|
|
1239
|
-
|
|
687
|
+
processingMutex.mutex(async () => {
|
|
688
|
+
var _a;
|
|
1240
689
|
const msg = await processNotification(node);
|
|
1241
690
|
if (msg) {
|
|
1242
|
-
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
|
|
1243
|
-
const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
|
|
691
|
+
const fromMe = (0, WABinary_1.areJidsSameUser)(node.attrs.participant || remoteJid, authState.creds.me.id);
|
|
1244
692
|
msg.key = {
|
|
1245
693
|
remoteJid,
|
|
1246
694
|
fromMe,
|
|
1247
695
|
participant: node.attrs.participant,
|
|
1248
|
-
participantAlt,
|
|
1249
|
-
participantUsername: node.attrs.participant_username,
|
|
1250
|
-
addressingMode,
|
|
1251
696
|
id: node.attrs.id,
|
|
1252
697
|
...(msg.key || {})
|
|
1253
698
|
};
|
|
1254
|
-
msg.participant
|
|
699
|
+
(_a = msg.participant) !== null && _a !== void 0 ? _a : (msg.participant = node.attrs.participant);
|
|
1255
700
|
msg.messageTimestamp = +node.attrs.t;
|
|
1256
|
-
const fullMsg = proto.WebMessageInfo.fromObject(msg);
|
|
701
|
+
const fullMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
|
|
1257
702
|
await upsertMessage(fullMsg, 'append');
|
|
1258
703
|
}
|
|
1259
704
|
})
|
|
1260
705
|
]);
|
|
1261
706
|
}
|
|
1262
707
|
finally {
|
|
1263
|
-
await sendMessageAck(node)
|
|
708
|
+
await sendMessageAck(node);
|
|
1264
709
|
}
|
|
1265
710
|
};
|
|
1266
711
|
const handleMessage = async (node) => {
|
|
1267
|
-
|
|
712
|
+
var _a, _b, _c;
|
|
713
|
+
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
|
|
714
|
+
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
715
|
+
await sendMessageAck(node);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
|
|
1268
719
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
1269
|
-
if (encNode
|
|
720
|
+
if (encNode && encNode.attrs.type === 'msmsg') {
|
|
1270
721
|
logger.debug({ key: node.attrs.key }, 'ignored msmsg');
|
|
1271
|
-
await sendMessageAck(node
|
|
722
|
+
await sendMessageAck(node);
|
|
1272
723
|
return;
|
|
1273
724
|
}
|
|
1274
|
-
let
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
if (
|
|
1280
|
-
|
|
1281
|
-
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1282
|
-
if (altServer === 'lid') {
|
|
1283
|
-
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1284
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1285
|
-
await signalRepository.migrateSession(primaryJid, alt);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
else {
|
|
1289
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1290
|
-
await signalRepository.migrateSession(alt, primaryJid);
|
|
1291
|
-
}
|
|
725
|
+
let response;
|
|
726
|
+
if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable') && !encNode) {
|
|
727
|
+
await sendMessageAck(node);
|
|
728
|
+
const { key } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage;
|
|
729
|
+
response = await requestPlaceholderResend(key);
|
|
730
|
+
if (response === 'RESOLVED') {
|
|
731
|
+
return;
|
|
1292
732
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1317
|
-
if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1318
|
-
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
|
|
1319
|
-
acked = true;
|
|
1320
|
-
return sendMessageAck(node);
|
|
1321
|
-
}
|
|
1322
|
-
// Request the real content from the phone via placeholder resend PDO.
|
|
1323
|
-
// Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
|
|
1324
|
-
// and store the requestId in stubParameters[1] so users can correlate
|
|
1325
|
-
// with the incoming PDO response event.
|
|
1326
|
-
const cleanKey = {
|
|
1327
|
-
remoteJid: msg.key.remoteJid,
|
|
1328
|
-
fromMe: msg.key.fromMe,
|
|
1329
|
-
id: msg.key.id,
|
|
1330
|
-
participant: msg.key.participant
|
|
1331
|
-
};
|
|
1332
|
-
// Cache the original message metadata so the PDO response handler
|
|
1333
|
-
// can preserve key fields (LID details etc.) that the phone may omit
|
|
1334
|
-
const msgData = {
|
|
1335
|
-
key: msg.key,
|
|
1336
|
-
messageTimestamp: msg.messageTimestamp,
|
|
1337
|
-
pushName: msg.pushName,
|
|
1338
|
-
participant: msg.participant,
|
|
1339
|
-
verifiedBizName: msg.verifiedBizName
|
|
1340
|
-
};
|
|
1341
|
-
requestPlaceholderResend(cleanKey, msgData)
|
|
1342
|
-
.then(requestId => {
|
|
1343
|
-
if (requestId && requestId !== 'RESOLVED') {
|
|
1344
|
-
logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
|
|
1345
|
-
ev.emit('messages.update', [
|
|
1346
|
-
{
|
|
1347
|
-
key: msg.key,
|
|
1348
|
-
update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
|
|
1349
|
-
}
|
|
1350
|
-
]);
|
|
1351
|
-
}
|
|
1352
|
-
})
|
|
1353
|
-
.catch(err => {
|
|
1354
|
-
logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
|
|
1355
|
-
});
|
|
1356
|
-
acked = true;
|
|
1357
|
-
await sendMessageAck(node);
|
|
1358
|
-
// Don't return — fall through to upsertMessage so the stub is emitted
|
|
1359
|
-
}
|
|
1360
|
-
else {
|
|
1361
|
-
// Skip retry for expired status messages (>24h old)
|
|
1362
|
-
if (isJidStatusBroadcast(msg.key.remoteJid)) {
|
|
1363
|
-
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1364
|
-
if (messageAge > STATUS_EXPIRY_SECONDS) {
|
|
1365
|
-
logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
|
|
1366
|
-
acked = true;
|
|
1367
|
-
return sendMessageAck(node);
|
|
1368
|
-
}
|
|
733
|
+
logger.debug('received unavailable message, acked and requested resend from phone');
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
if (placeholderResendCache.get(node.attrs.id)) {
|
|
737
|
+
placeholderResendCache.del(node.attrs.id);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const { fullMessage: msg, category, author, decrypt } = (0, Utils_1.decryptMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
741
|
+
if (response && ((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
742
|
+
msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response];
|
|
743
|
+
}
|
|
744
|
+
if (((_c = (_b = msg.message) === null || _b === void 0 ? void 0 : _b.protocolMessage) === null || _c === void 0 ? void 0 : _c.type) === WAProto_1.proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) {
|
|
745
|
+
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
await Promise.all([
|
|
749
|
+
processingMutex.mutex(async () => {
|
|
750
|
+
var _a, _b, _c, _d, _e, _f;
|
|
751
|
+
await decrypt();
|
|
752
|
+
// message failed to decrypt
|
|
753
|
+
if (msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT) {
|
|
754
|
+
if (((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.MISSING_KEYS_ERROR_TEXT) {
|
|
755
|
+
return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError);
|
|
1369
756
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
try {
|
|
1374
|
-
if (!ws.isOpen) {
|
|
1375
|
-
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
757
|
+
retryMutex.mutex(async () => {
|
|
758
|
+
if (ws.isOpen) {
|
|
759
|
+
if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable')) {
|
|
1376
760
|
return;
|
|
1377
761
|
}
|
|
1378
|
-
const encNode = getBinaryNodeChild(node, 'enc');
|
|
762
|
+
const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
|
|
1379
763
|
await sendRetryRequest(node, !encNode);
|
|
1380
764
|
if (retryRequestDelayMs) {
|
|
1381
|
-
await delay(retryRequestDelayMs);
|
|
765
|
+
await (0, Utils_1.delay)(retryRequestDelayMs);
|
|
1382
766
|
}
|
|
1383
767
|
}
|
|
1384
|
-
|
|
1385
|
-
logger.
|
|
768
|
+
else {
|
|
769
|
+
logger.debug({ node }, 'connection closed, ignoring retry req');
|
|
1386
770
|
}
|
|
1387
|
-
acked = true;
|
|
1388
|
-
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1389
771
|
});
|
|
1390
772
|
}
|
|
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) {
|
|
773
|
+
else {
|
|
1398
774
|
// no type in the receipt => message delivered
|
|
1399
775
|
let type = undefined;
|
|
776
|
+
if ((_b = msg.key.participant) === null || _b === void 0 ? void 0 : _b.endsWith('@lid')) {
|
|
777
|
+
msg.key.participant = node.attrs.participant_pn || authState.creds.me.id;
|
|
778
|
+
}
|
|
779
|
+
if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid) && ((_f = (_e = (_d = (_c = msg.message) === null || _c === void 0 ? void 0 : _c.extendedTextMessage) === null || _d === void 0 ? void 0 : _d.contextInfo) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.endsWith('@lid'))) {
|
|
780
|
+
if (msg.message.extendedTextMessage.contextInfo) {
|
|
781
|
+
const metadata = await groupMetadata(msg.key.remoteJid);
|
|
782
|
+
const sender = msg.message.extendedTextMessage.contextInfo.participant;
|
|
783
|
+
const found = metadata.participants.find(p => p.id === sender);
|
|
784
|
+
msg.message.extendedTextMessage.contextInfo.participant = (found === null || found === void 0 ? void 0 : found.jid) || sender;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (!(0, WABinary_1.isJidGroup)(msg.key.remoteJid) && (0, WABinary_1.isLidUser)(msg.key.remoteJid)) {
|
|
788
|
+
msg.key.remoteJid = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
|
|
789
|
+
}
|
|
1400
790
|
let participant = msg.key.participant;
|
|
1401
|
-
if (category === 'peer') {
|
|
1402
|
-
// special peer message
|
|
791
|
+
if (category === 'peer') { // special peer message
|
|
1403
792
|
type = 'peer_msg';
|
|
1404
793
|
}
|
|
1405
|
-
else if (msg.key.fromMe) {
|
|
1406
|
-
// message was sent by us from a different device
|
|
794
|
+
else if (msg.key.fromMe) { // message was sent by us from a different device
|
|
1407
795
|
type = 'sender';
|
|
1408
796
|
// need to specially handle this case
|
|
1409
|
-
if (
|
|
1410
|
-
participant = author;
|
|
797
|
+
if ((0, WABinary_1.isJidUser)(msg.key.remoteJid)) {
|
|
798
|
+
participant = author;
|
|
1411
799
|
}
|
|
1412
800
|
}
|
|
1413
801
|
else if (!sendActiveReceipts) {
|
|
1414
802
|
type = 'inactive';
|
|
1415
803
|
}
|
|
1416
|
-
acked = true;
|
|
1417
804
|
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
|
|
1418
805
|
// send ack for history message
|
|
1419
|
-
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
806
|
+
const isAnyHistoryMsg = (0, Utils_1.getHistoryMsg)(msg.message);
|
|
1420
807
|
if (isAnyHistoryMsg) {
|
|
1421
|
-
const jid = jidNormalizedUser(msg.key.remoteJid);
|
|
1422
|
-
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
808
|
+
const jid = (0, WABinary_1.jidNormalizedUser)(msg.key.remoteJid);
|
|
809
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1423
810
|
}
|
|
1424
811
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
}
|
|
1431
|
-
cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
1432
|
-
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1433
|
-
});
|
|
812
|
+
(0, Utils_1.cleanMessage)(msg, authState.creds.me.id);
|
|
813
|
+
await sendMessageAck(node);
|
|
814
|
+
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
815
|
+
})
|
|
816
|
+
]);
|
|
1434
817
|
}
|
|
1435
818
|
catch (error) {
|
|
1436
|
-
logger.error({ error, node
|
|
1437
|
-
if (!acked) {
|
|
1438
|
-
await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
|
|
1439
|
-
}
|
|
819
|
+
logger.error({ error, node }, 'error in handling message');
|
|
1440
820
|
}
|
|
1441
821
|
};
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
if (!infoChild) {
|
|
1447
|
-
throw new Boom('Missing call info in call node');
|
|
1448
|
-
}
|
|
1449
|
-
const status = getCallStatusFromNode(infoChild);
|
|
1450
|
-
const callId = infoChild.attrs['call-id'];
|
|
1451
|
-
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
1452
|
-
const call = {
|
|
1453
|
-
chatId: attrs.from,
|
|
1454
|
-
from,
|
|
1455
|
-
callerPn: infoChild.attrs['caller_pn'],
|
|
1456
|
-
id: callId,
|
|
1457
|
-
date: new Date(+attrs.t * 1000),
|
|
1458
|
-
offline: !!attrs.offline,
|
|
1459
|
-
status
|
|
1460
|
-
};
|
|
1461
|
-
if (status === 'relaylatency') {
|
|
1462
|
-
const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
|
|
1463
|
-
const latencyMs = latencyValue ? Number(latencyValue) : undefined;
|
|
1464
|
-
if (Number.isFinite(latencyMs)) {
|
|
1465
|
-
call.latencyMs = latencyMs;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
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]);
|
|
822
|
+
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
823
|
+
var _a;
|
|
824
|
+
if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
825
|
+
throw new boom_1.Boom('Not authenticated');
|
|
1486
826
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
827
|
+
const pdoMessage = {
|
|
828
|
+
historySyncOnDemandRequest: {
|
|
829
|
+
chatJid: oldestMsgKey.remoteJid,
|
|
830
|
+
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
831
|
+
oldestMsgId: oldestMsgKey.id,
|
|
832
|
+
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
833
|
+
onDemandMsgCount: count
|
|
834
|
+
},
|
|
835
|
+
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
836
|
+
};
|
|
837
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
838
|
+
};
|
|
839
|
+
const requestPlaceholderResend = async (messageKey) => {
|
|
840
|
+
var _a;
|
|
841
|
+
if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
842
|
+
throw new boom_1.Boom('Not authenticated');
|
|
1489
843
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
844
|
+
if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
|
|
845
|
+
logger.debug({ messageKey }, 'already requested resend');
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
placeholderResendCache.set(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id, true);
|
|
1492
850
|
}
|
|
851
|
+
await (0, Utils_1.delay)(5000);
|
|
852
|
+
if (!placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
|
|
853
|
+
logger.debug({ messageKey }, 'message received while resend requested');
|
|
854
|
+
return 'RESOLVED';
|
|
855
|
+
}
|
|
856
|
+
const pdoMessage = {
|
|
857
|
+
placeholderMessageResendRequest: [{
|
|
858
|
+
messageKey
|
|
859
|
+
}],
|
|
860
|
+
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
861
|
+
};
|
|
862
|
+
setTimeout(() => {
|
|
863
|
+
if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
|
|
864
|
+
logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
|
|
865
|
+
placeholderResendCache.del(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id);
|
|
866
|
+
}
|
|
867
|
+
}, 15000);
|
|
868
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
869
|
+
};
|
|
870
|
+
const handleCall = async (node) => {
|
|
871
|
+
const { attrs } = node;
|
|
872
|
+
const [infoChild] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
|
|
873
|
+
const callId = infoChild.attrs['call-id'];
|
|
874
|
+
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
875
|
+
const status = (0, Utils_1.getCallStatusFromNode)(infoChild);
|
|
876
|
+
const call = {
|
|
877
|
+
chatId: attrs.from,
|
|
878
|
+
from,
|
|
879
|
+
id: callId,
|
|
880
|
+
date: new Date(+attrs.t * 1000),
|
|
881
|
+
offline: !!attrs.offline,
|
|
882
|
+
status,
|
|
883
|
+
};
|
|
884
|
+
if (status === 'offer') {
|
|
885
|
+
call.isVideo = !!(0, WABinary_1.getBinaryNodeChild)(infoChild, 'video');
|
|
886
|
+
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
887
|
+
call.groupJid = infoChild.attrs['group-jid'];
|
|
888
|
+
callOfferCache.set(call.id, call);
|
|
889
|
+
}
|
|
890
|
+
const existingCall = callOfferCache.get(call.id);
|
|
891
|
+
// use existing call info to populate this event
|
|
892
|
+
if (existingCall) {
|
|
893
|
+
call.isVideo = existingCall.isVideo;
|
|
894
|
+
call.isGroup = existingCall.isGroup;
|
|
895
|
+
}
|
|
896
|
+
// delete data once call has ended
|
|
897
|
+
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
898
|
+
callOfferCache.del(call.id);
|
|
899
|
+
}
|
|
900
|
+
ev.emit('call', [call]);
|
|
901
|
+
await sendMessageAck(node);
|
|
1493
902
|
};
|
|
1494
903
|
const handleBadAck = async ({ attrs }) => {
|
|
1495
|
-
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
1496
|
-
//
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
// logger.warn({ attrs }, 'could not send message again, as it was not found')
|
|
1507
|
-
// }
|
|
1508
|
-
// }
|
|
1509
|
-
// error in acknowledgement,
|
|
1510
|
-
// device could not display the message
|
|
1511
|
-
if (attrs.error) {
|
|
1512
|
-
const isReachoutTimelocked = attrs.error === String(NACK_REASONS.SenderReachoutTimelocked);
|
|
1513
|
-
if (attrs.error === SERVER_ERROR_CODES.MessageAccountRestriction) {
|
|
1514
|
-
// 463 = 1:1 message missing privacy token (tctoken). Usually means the
|
|
1515
|
-
// account is restricted: WhatsApp blocks starting new chats but preserves
|
|
1516
|
-
// existing ones, since established chats already carry a tctoken.
|
|
1517
|
-
// WA Web prevents this client-side (disables the compose bar).
|
|
1518
|
-
// No retry — retrying counts as another "reach out" and worsens the restriction.
|
|
1519
|
-
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
1520
|
-
const ackFrom = attrs.from;
|
|
1521
|
-
if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
|
|
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');
|
|
904
|
+
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id, 'server_id': attrs === null || attrs === void 0 ? void 0 : attrs.server_id };
|
|
905
|
+
// current hypothesis is that if pash is sent in the ack
|
|
906
|
+
// it means -- the message hasn't reached all devices yet
|
|
907
|
+
// we'll retry sending the message here
|
|
908
|
+
if (attrs.phash) {
|
|
909
|
+
logger.info({ attrs }, 'received phash in ack, resending message...');
|
|
910
|
+
const cacheKey = `${key.remoteJid}:${key.id}`;
|
|
911
|
+
if ((msgRetryCache.get(cacheKey) || 0) >= maxMsgRetryCount) {
|
|
912
|
+
logger.warn({ attrs }, 'reached max retry count, not sending message again');
|
|
913
|
+
msgRetryCache.del(cacheKey);
|
|
914
|
+
return;
|
|
1549
915
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
916
|
+
const retryCount = msgRetryCache.get(cacheKey) || 0;
|
|
917
|
+
const msg = await getMessage(key);
|
|
918
|
+
if (msg) {
|
|
919
|
+
await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false });
|
|
920
|
+
msgRetryCache.set(cacheKey, retryCount + 1);
|
|
1554
921
|
}
|
|
1555
922
|
else {
|
|
1556
|
-
logger.warn({ attrs }, '
|
|
923
|
+
logger.warn({ attrs }, 'could not send message again, as it was not found');
|
|
1557
924
|
}
|
|
925
|
+
}
|
|
926
|
+
// error in acknowledgement,
|
|
927
|
+
// device could not display the message
|
|
928
|
+
if (attrs.error) {
|
|
929
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1558
930
|
ev.emit('messages.update', [
|
|
1559
931
|
{
|
|
1560
932
|
key,
|
|
1561
933
|
update: {
|
|
1562
|
-
status: WAMessageStatus.ERROR,
|
|
1563
|
-
messageStubParameters:
|
|
934
|
+
status: Types_1.WAMessageStatus.ERROR,
|
|
935
|
+
messageStubParameters: [
|
|
936
|
+
attrs.error
|
|
937
|
+
]
|
|
1564
938
|
}
|
|
1565
939
|
}
|
|
1566
940
|
]);
|
|
@@ -1573,61 +947,69 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1573
947
|
await execTask();
|
|
1574
948
|
ev.flush();
|
|
1575
949
|
function execTask() {
|
|
1576
|
-
return exec(node, false)
|
|
950
|
+
return exec(node, false)
|
|
951
|
+
.catch(err => onUnexpectedError(err, identifier));
|
|
1577
952
|
}
|
|
1578
953
|
};
|
|
1579
|
-
const
|
|
1580
|
-
[
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
954
|
+
const makeOfflineNodeProcessor = () => {
|
|
955
|
+
const nodeProcessorMap = new Map([
|
|
956
|
+
['message', handleMessage],
|
|
957
|
+
['call', handleCall],
|
|
958
|
+
['receipt', handleReceipt],
|
|
959
|
+
['notification', handleNotification]
|
|
960
|
+
]);
|
|
961
|
+
const nodes = [];
|
|
962
|
+
let isProcessing = false;
|
|
963
|
+
const enqueue = (type, node) => {
|
|
964
|
+
nodes.push({ type, node });
|
|
965
|
+
if (isProcessing) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
isProcessing = true;
|
|
969
|
+
const promise = async () => {
|
|
970
|
+
while (nodes.length && ws.isOpen) {
|
|
971
|
+
const { type, node } = nodes.shift();
|
|
972
|
+
const nodeProcessor = nodeProcessorMap.get(type);
|
|
973
|
+
if (!nodeProcessor) {
|
|
974
|
+
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
await nodeProcessor(node);
|
|
978
|
+
}
|
|
979
|
+
isProcessing = false;
|
|
980
|
+
};
|
|
981
|
+
promise().catch(error => onUnexpectedError(error, 'processing offline nodes'));
|
|
982
|
+
};
|
|
983
|
+
return { enqueue };
|
|
984
|
+
};
|
|
985
|
+
const offlineNodeProcessor = makeOfflineNodeProcessor();
|
|
986
|
+
const processNode = (type, node, identifier, exec) => {
|
|
1603
987
|
const isOffline = !!node.attrs.offline;
|
|
1604
988
|
if (isOffline) {
|
|
1605
989
|
offlineNodeProcessor.enqueue(type, node);
|
|
1606
990
|
}
|
|
1607
991
|
else {
|
|
1608
|
-
|
|
992
|
+
processNodeWithBuffer(node, identifier, exec);
|
|
1609
993
|
}
|
|
1610
994
|
};
|
|
1611
995
|
// recv a message
|
|
1612
|
-
ws.on('CB:message',
|
|
1613
|
-
|
|
996
|
+
ws.on('CB:message', (node) => {
|
|
997
|
+
processNode('message', node, 'processing message', handleMessage);
|
|
1614
998
|
});
|
|
1615
999
|
ws.on('CB:call', async (node) => {
|
|
1616
|
-
|
|
1000
|
+
processNode('call', node, 'handling call', handleCall);
|
|
1617
1001
|
});
|
|
1618
|
-
ws.on('CB:receipt',
|
|
1619
|
-
|
|
1002
|
+
ws.on('CB:receipt', node => {
|
|
1003
|
+
processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1620
1004
|
});
|
|
1621
1005
|
ws.on('CB:notification', async (node) => {
|
|
1622
|
-
|
|
1006
|
+
processNode('notification', node, 'handling notification', handleNotification);
|
|
1623
1007
|
});
|
|
1624
1008
|
ws.on('CB:ack,class:message', (node) => {
|
|
1625
|
-
handleBadAck(node)
|
|
1009
|
+
handleBadAck(node)
|
|
1010
|
+
.catch(error => onUnexpectedError(error, 'handling bad ack'));
|
|
1626
1011
|
});
|
|
1627
|
-
ev.on('call',
|
|
1628
|
-
if (!call) {
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1012
|
+
ev.on('call', ([call]) => {
|
|
1631
1013
|
// missed call + group call notification message generation
|
|
1632
1014
|
if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
|
|
1633
1015
|
const msg = {
|
|
@@ -1636,137 +1018,37 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1636
1018
|
id: call.id,
|
|
1637
1019
|
fromMe: false
|
|
1638
1020
|
},
|
|
1639
|
-
messageTimestamp: unixTimestampSeconds(call.date)
|
|
1021
|
+
messageTimestamp: (0, Utils_1.unixTimestampSeconds)(call.date),
|
|
1640
1022
|
};
|
|
1641
1023
|
if (call.status === 'timeout') {
|
|
1642
1024
|
if (call.isGroup) {
|
|
1643
|
-
msg.messageStubType = call.isVideo
|
|
1644
|
-
? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
|
|
1645
|
-
: WAMessageStubType.CALL_MISSED_GROUP_VOICE;
|
|
1025
|
+
msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_GROUP_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_GROUP_VOICE;
|
|
1646
1026
|
}
|
|
1647
1027
|
else {
|
|
1648
|
-
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE;
|
|
1028
|
+
msg.messageStubType = call.isVideo ? Types_1.WAMessageStubType.CALL_MISSED_VIDEO : Types_1.WAMessageStubType.CALL_MISSED_VOICE;
|
|
1649
1029
|
}
|
|
1650
1030
|
}
|
|
1651
1031
|
else {
|
|
1652
1032
|
msg.message = { call: { callKey: Buffer.from(call.id) } };
|
|
1653
1033
|
}
|
|
1654
|
-
const protoMsg = proto.WebMessageInfo.fromObject(msg);
|
|
1655
|
-
|
|
1034
|
+
const protoMsg = WAProto_1.proto.WebMessageInfo.fromObject(msg);
|
|
1035
|
+
upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1656
1036
|
}
|
|
1657
1037
|
});
|
|
1658
|
-
|
|
1659
|
-
let lastTcTokenPruneTs = 0;
|
|
1660
|
-
/** dedupe in-flight 463 recovery token issuance by target JID */
|
|
1661
|
-
const inFlight463Recoveries = new Set();
|
|
1662
|
-
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1038
|
+
ev.on('connection.update', ({ isOnline }) => {
|
|
1663
1039
|
if (typeof isOnline !== 'undefined') {
|
|
1664
1040
|
sendActiveReceipts = isOnline;
|
|
1665
1041
|
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1666
1042
|
}
|
|
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
1043
|
});
|
|
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');
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
1044
|
return {
|
|
1763
1045
|
...sock,
|
|
1764
1046
|
sendMessageAck,
|
|
1765
1047
|
sendRetryRequest,
|
|
1766
1048
|
rejectCall,
|
|
1049
|
+
offerCall,
|
|
1767
1050
|
fetchMessageHistory,
|
|
1768
1051
|
requestPlaceholderResend,
|
|
1769
|
-
messageRetryManager
|
|
1770
1052
|
};
|
|
1771
1053
|
};
|
|
1772
|
-
|
|
1054
|
+
exports.makeMessagesRecvSocket = makeMessagesRecvSocket;
|