@crysnovax/baileys 2.5.5 → 2.5.6
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/index.js +3578 -14789
- package/lib/Defaults/index.js +11 -19
- package/lib/Signal/libsignal.js +18 -42
- package/lib/Socket/chats.js +91 -246
- package/lib/Socket/messages-recv.js +322 -618
- package/lib/Socket/messages-send.js +74 -174
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/socket.js +20 -12
- package/lib/Types/index.js +0 -1
- package/lib/Utils/index.js +0 -1
- package/lib/Utils/messages.js +114 -148
- package/lib/WABinary/constants.js +5 -99
- package/package.json +9 -43
- package/lib/Socket/Client//342/234/230 +0 -1
- package/lib/Socket//342/230/201/357/270/216 +0 -1
- package/lib/Types/Mex.js +0 -39
- package/lib/Types//342/234/206 +0 -1
- package/lib/Utils/use-sqlite-auth-state.js +0 -109
- package/lib/Utils//342/232/211 +0 -1
- package/lib/WABinary//342/216/231 +0 -1
|
@@ -4,26 +4,21 @@ import { randomBytes } from 'crypto';
|
|
|
4
4
|
import Long from 'long';
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
+
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
10
|
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
11
|
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
12
|
-
import {
|
|
13
|
-
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
12
|
+
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
14
13
|
import { extractGroupMetadata } from './groups.js';
|
|
15
14
|
import { makeMessagesSocket } from './messages-send.js';
|
|
16
|
-
const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
|
|
17
|
-
function isValidEnforcementType(value) {
|
|
18
|
-
return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
|
|
19
|
-
}
|
|
20
15
|
export const makeMessagesRecvSocket = (config) => {
|
|
21
16
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
22
17
|
const sock = makeMessagesSocket(config);
|
|
23
|
-
const {
|
|
24
|
-
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
18
|
+
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, generateMessageTag, messageRetryManager, registerSocketEndHandler } = sock;
|
|
25
19
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
26
20
|
const retryMutex = makeMutex();
|
|
21
|
+
const devicesMutex = makeMutex();
|
|
27
22
|
const msgRetryCache = config.msgRetryCounterCache ||
|
|
28
23
|
new NodeCache({
|
|
29
24
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
@@ -34,6 +29,16 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
34
29
|
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
35
30
|
useClones: false
|
|
36
31
|
});
|
|
32
|
+
const placeholderResendCache = config.placeholderResendCache ||
|
|
33
|
+
new NodeCache({
|
|
34
|
+
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
35
|
+
useClones: false
|
|
36
|
+
});
|
|
37
|
+
const userDevicesCache = config.userDevicesCache ??=
|
|
38
|
+
new NodeCache({
|
|
39
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
40
|
+
useClones: false
|
|
41
|
+
});
|
|
37
42
|
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
38
43
|
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
|
|
39
44
|
let sendActiveReceipts = false;
|
|
@@ -87,140 +92,25 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
87
92
|
}, 8000);
|
|
88
93
|
return sendPeerDataOperationMessage(pdoMessage);
|
|
89
94
|
};
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
|
148
|
-
}
|
|
149
|
-
await handleLegacyMexNewsletterNotification(node);
|
|
150
|
-
};
|
|
151
|
-
const handleReachoutTimelockNotification = (data) => {
|
|
152
|
-
const payload = data.xwa2_notify_account_reachout_timelock;
|
|
153
|
-
if (!payload) {
|
|
154
|
-
logger.warn('reachout timelock notification missing payload');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (!payload.is_active) {
|
|
158
|
-
logger.info('reachout timelock restriction lifted');
|
|
159
|
-
ev.emit('connection.update', {
|
|
160
|
-
reachoutTimeLock: {
|
|
161
|
-
isActive: false,
|
|
162
|
-
enforcementType: ReachoutTimelockEnforcementType.DEFAULT
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
// WA Web defaults to now+60s when the server omits the expiry
|
|
168
|
-
const timeEnforcementEnds = payload.time_enforcement_ends
|
|
169
|
-
? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
|
|
170
|
-
: new Date(Date.now() + 60000);
|
|
171
|
-
const enforcementType = isValidEnforcementType(payload.enforcement_type)
|
|
172
|
-
? payload.enforcement_type
|
|
173
|
-
: ReachoutTimelockEnforcementType.DEFAULT;
|
|
174
|
-
logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
|
|
175
|
-
ev.emit('connection.update', {
|
|
176
|
-
reachoutTimeLock: {
|
|
177
|
-
isActive: true,
|
|
178
|
-
timeEnforcementEnds,
|
|
179
|
-
enforcementType
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
|
-
const handleMessageCappingNotification = (data) => {
|
|
184
|
-
const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
|
|
185
|
-
if (!payload) {
|
|
186
|
-
logger.warn('message capping notification missing payload');
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
logger.info({ payload }, 'received message capping update');
|
|
190
|
-
ev.emit('message-capping.update', payload);
|
|
191
|
-
};
|
|
192
|
-
const handleLegacyMexNewsletterNotification = async (node) => {
|
|
95
|
+
// Handles mex newsletter notifications
|
|
96
|
+
const handleMexNewsletterNotification = async (node) => {
|
|
193
97
|
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!payloadNode?.content) {
|
|
197
|
-
logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
|
|
98
|
+
if (!mexNode?.content) {
|
|
99
|
+
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
198
100
|
return;
|
|
199
101
|
}
|
|
200
102
|
let data;
|
|
201
103
|
try {
|
|
202
|
-
|
|
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());
|
|
104
|
+
data = JSON.parse(mexNode.content.toString());
|
|
209
105
|
}
|
|
210
106
|
catch (error) {
|
|
211
|
-
logger.error({ err: error, node
|
|
107
|
+
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
212
108
|
return;
|
|
213
109
|
}
|
|
214
|
-
const operation = data?.operation
|
|
215
|
-
|
|
216
|
-
if (!updates) {
|
|
217
|
-
const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
|
|
218
|
-
if (linkedProfiles) {
|
|
219
|
-
updates = [linkedProfiles];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
110
|
+
const operation = data?.operation;
|
|
111
|
+
const updates = data?.updates;
|
|
222
112
|
if (!updates || !operation) {
|
|
223
|
-
logger.warn({ data }, '
|
|
113
|
+
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
224
114
|
return;
|
|
225
115
|
}
|
|
226
116
|
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
@@ -248,114 +138,91 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
248
138
|
}
|
|
249
139
|
}
|
|
250
140
|
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
141
|
default:
|
|
268
|
-
logger.info({ operation, data }, '
|
|
142
|
+
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
269
143
|
break;
|
|
270
144
|
}
|
|
271
145
|
};
|
|
272
146
|
// Handles newsletter notifications
|
|
273
147
|
const handleNewsletterNotification = async (node) => {
|
|
274
148
|
const from = node.attrs.from;
|
|
275
|
-
const
|
|
149
|
+
const child = getAllBinaryNodeChildren(node)[0];
|
|
276
150
|
const author = node.attrs.participant;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
151
|
+
logger.info({ from, child }, 'got newsletter notification');
|
|
152
|
+
switch (child.tag) {
|
|
153
|
+
case 'reaction':
|
|
154
|
+
const reactionUpdate = {
|
|
155
|
+
id: from,
|
|
156
|
+
server_id: child.attrs.message_id,
|
|
157
|
+
reaction: {
|
|
158
|
+
code: getBinaryNodeChildString(child, 'reaction'),
|
|
159
|
+
count: 1
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
ev.emit('newsletter.reaction', reactionUpdate);
|
|
163
|
+
break;
|
|
164
|
+
case 'view':
|
|
165
|
+
const viewUpdate = {
|
|
166
|
+
id: from,
|
|
167
|
+
server_id: child.attrs.message_id,
|
|
168
|
+
count: parseInt(child.content?.toString() || '0', 10)
|
|
169
|
+
};
|
|
170
|
+
ev.emit('newsletter.view', viewUpdate);
|
|
171
|
+
break;
|
|
172
|
+
case 'participant':
|
|
173
|
+
const participantUpdate = {
|
|
174
|
+
id: from,
|
|
175
|
+
author,
|
|
176
|
+
user: child.attrs.jid,
|
|
177
|
+
action: child.attrs.action,
|
|
178
|
+
new_role: child.attrs.role
|
|
179
|
+
};
|
|
180
|
+
ev.emit('newsletter-participants.update', participantUpdate);
|
|
181
|
+
break;
|
|
182
|
+
case 'update':
|
|
183
|
+
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
184
|
+
if (settingsNode) {
|
|
185
|
+
const update = {};
|
|
186
|
+
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
187
|
+
if (nameNode?.content)
|
|
188
|
+
update.name = nameNode.content.toString();
|
|
189
|
+
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
190
|
+
if (descriptionNode?.content)
|
|
191
|
+
update.description = descriptionNode.content.toString();
|
|
192
|
+
ev.emit('newsletter-settings.update', {
|
|
303
193
|
id: from,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
action: child.attrs.action,
|
|
307
|
-
new_role: child.attrs.role
|
|
308
|
-
};
|
|
309
|
-
ev.emit('newsletter-participants.update', participantUpdate);
|
|
310
|
-
break;
|
|
194
|
+
update
|
|
195
|
+
});
|
|
311
196
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
197
|
+
break;
|
|
198
|
+
case 'message':
|
|
199
|
+
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
200
|
+
if (plaintextNode?.content) {
|
|
201
|
+
try {
|
|
202
|
+
const contentBuf = typeof plaintextNode.content === 'string'
|
|
203
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
204
|
+
: Buffer.from(plaintextNode.content);
|
|
205
|
+
const messageProto = proto.Message.decode(contentBuf).toJSON();
|
|
206
|
+
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
207
|
+
key: {
|
|
208
|
+
remoteJid: from,
|
|
209
|
+
id: child.attrs.message_id || child.attrs.server_id,
|
|
210
|
+
fromMe: false // TODO: is this really true though
|
|
211
|
+
},
|
|
212
|
+
message: messageProto,
|
|
213
|
+
messageTimestamp: +child.attrs.t
|
|
214
|
+
}).toJSON();
|
|
215
|
+
await upsertMessage(fullMessage, 'append');
|
|
216
|
+
logger.info('Processed plaintext newsletter message');
|
|
326
217
|
}
|
|
327
|
-
|
|
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
|
-
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
352
220
|
}
|
|
353
|
-
break;
|
|
354
221
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
222
|
+
break;
|
|
223
|
+
default:
|
|
224
|
+
logger.warn({ node }, 'Unknown newsletter notification');
|
|
225
|
+
break;
|
|
359
226
|
}
|
|
360
227
|
};
|
|
361
228
|
const sendMessageAck = async (node, errorCode) => {
|
|
@@ -384,6 +251,97 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
384
251
|
};
|
|
385
252
|
await query(stanza);
|
|
386
253
|
};
|
|
254
|
+
// Lia@Note 01-03-26 --- Source: https://github.com/koptereli/Baileys/commit/575cd41e6f01a9b3e1d7e2708c2292fa93de91f2
|
|
255
|
+
const initiateCall = async (jid, options = {}) => {
|
|
256
|
+
const meId = authState.creds.me?.id;
|
|
257
|
+
if (!meId) {
|
|
258
|
+
throw new Boom('Not authenticated');
|
|
259
|
+
}
|
|
260
|
+
const callId = randomBytes(8).toString('hex');
|
|
261
|
+
const isVideo = !!options.isVideo;
|
|
262
|
+
const isGroup = isJidGroup(jid);
|
|
263
|
+
const stanza = {
|
|
264
|
+
tag: 'call',
|
|
265
|
+
attrs: {
|
|
266
|
+
id: generateMessageTag(),
|
|
267
|
+
from: meId,
|
|
268
|
+
to: jid,
|
|
269
|
+
t: String(unixTimestampSeconds()),
|
|
270
|
+
...(authState.creds.me?.name ? { notify: authState.creds.me.name } : {})
|
|
271
|
+
},
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
tag: 'offer',
|
|
275
|
+
attrs: {
|
|
276
|
+
'call-id': callId,
|
|
277
|
+
'call-creator': meId,
|
|
278
|
+
count: '0'
|
|
279
|
+
},
|
|
280
|
+
content: [
|
|
281
|
+
{
|
|
282
|
+
tag: isVideo ? 'video' : 'audio',
|
|
283
|
+
attrs: {}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
tag: 'net',
|
|
287
|
+
attrs: {}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
tag: 'encopt',
|
|
291
|
+
attrs: { key: randomBytes(2).toString('hex') }
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
tag: 'relaylatency',
|
|
295
|
+
attrs: {}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
tag: 'te',
|
|
299
|
+
attrs: {}
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
};
|
|
305
|
+
await query(stanza);
|
|
306
|
+
await callOfferCache.set(callId, {
|
|
307
|
+
chatId: jid,
|
|
308
|
+
from: meId,
|
|
309
|
+
id: callId,
|
|
310
|
+
date: new Date(),
|
|
311
|
+
offline: false,
|
|
312
|
+
status: 'offer',
|
|
313
|
+
isVideo,
|
|
314
|
+
isGroup,
|
|
315
|
+
groupJid: isGroup ? jid : undefined
|
|
316
|
+
});
|
|
317
|
+
// TODO: implement ICE/DTLS-SRTP call media setup once full signaling requirements are mapped.
|
|
318
|
+
return { callId, to: jid, isVideo };
|
|
319
|
+
};
|
|
320
|
+
const cancelCall = async (callId, callTo) => {
|
|
321
|
+
const meId = authState.creds.me?.id;
|
|
322
|
+
if (!meId) {
|
|
323
|
+
throw new Boom('Not authenticated');
|
|
324
|
+
}
|
|
325
|
+
const stanza = {
|
|
326
|
+
tag: 'call',
|
|
327
|
+
attrs: {
|
|
328
|
+
from: meId,
|
|
329
|
+
to: callTo
|
|
330
|
+
},
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
tag: 'terminate',
|
|
334
|
+
attrs: {
|
|
335
|
+
'call-id': callId,
|
|
336
|
+
'call-creator': meId,
|
|
337
|
+
count: '0'
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
};
|
|
342
|
+
await query(stanza);
|
|
343
|
+
await callOfferCache.del(callId);
|
|
344
|
+
};
|
|
387
345
|
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
388
346
|
const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
389
347
|
const { key: msgKey } = fullMessage;
|
|
@@ -515,58 +473,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
515
473
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
516
474
|
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
517
475
|
};
|
|
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
|
-
});
|
|
548
|
-
};
|
|
549
476
|
const handleEncryptNotification = async (node) => {
|
|
550
477
|
const from = node.attrs.from;
|
|
551
478
|
if (from === S_WHATSAPP_NET) {
|
|
552
|
-
const stanzaId = node.attrs.id;
|
|
553
|
-
if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
479
|
const countChild = getBinaryNodeChild(node, 'count');
|
|
557
480
|
const count = +countChild.attrs.value;
|
|
558
481
|
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
|
|
559
482
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
560
483
|
if (shouldUploadMorePreKeys) {
|
|
561
|
-
|
|
562
|
-
inFlightPreKeyLow.add(stanzaId);
|
|
563
|
-
try {
|
|
564
|
-
await uploadPreKeys();
|
|
565
|
-
}
|
|
566
|
-
finally {
|
|
567
|
-
if (stanzaId)
|
|
568
|
-
inFlightPreKeyLow.delete(stanzaId);
|
|
569
|
-
}
|
|
484
|
+
await uploadPreKeys();
|
|
570
485
|
}
|
|
571
486
|
}
|
|
572
487
|
else {
|
|
@@ -576,8 +491,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
576
491
|
validateSession: signalRepository.validateSession,
|
|
577
492
|
assertSessions,
|
|
578
493
|
debounceCache: identityAssertDebounce,
|
|
579
|
-
logger
|
|
580
|
-
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
494
|
+
logger
|
|
581
495
|
});
|
|
582
496
|
if (result.action === 'no_identity_node') {
|
|
583
497
|
logger.info({ node }, 'unknown encrypt notification');
|
|
@@ -588,7 +502,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
588
502
|
// TODO: Support PN/LID (Here is only LID now)
|
|
589
503
|
const actingParticipantLid = fullNode.attrs.participant;
|
|
590
504
|
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
591
|
-
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
592
505
|
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
593
506
|
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
594
507
|
switch (child?.tag) {
|
|
@@ -608,8 +521,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
608
521
|
{
|
|
609
522
|
...metadata,
|
|
610
523
|
author: actingParticipantLid,
|
|
611
|
-
authorPn: actingParticipantPn
|
|
612
|
-
authorUsername: actingParticipantUsername
|
|
524
|
+
authorPn: actingParticipantPn
|
|
613
525
|
}
|
|
614
526
|
]);
|
|
615
527
|
break;
|
|
@@ -640,7 +552,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
640
552
|
id: attrs.jid,
|
|
641
553
|
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
642
554
|
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
643
|
-
username: attrs.participant_username || attrs.username || undefined,
|
|
644
555
|
admin: (attrs.type || null)
|
|
645
556
|
};
|
|
646
557
|
});
|
|
@@ -713,83 +624,58 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
713
624
|
const handleDevicesNotification = async (node) => {
|
|
714
625
|
const [child] = getAllBinaryNodeChildren(node);
|
|
715
626
|
const from = jidNormalizedUser(node.attrs.from);
|
|
716
|
-
if (!child) {
|
|
717
|
-
logger.debug({ from }, 'devices notification missing child, skipping');
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
const tag = child.tag;
|
|
721
|
-
const deviceHash = child.attrs.device_hash;
|
|
722
627
|
const devices = getBinaryNodeChildren(child, 'device');
|
|
723
|
-
if (areJidsSameUser(from, authState.creds.me.id) ||
|
|
628
|
+
if (areJidsSameUser(from, authState.creds.me.id) ||
|
|
629
|
+
areJidsSameUser(from, authState.creds.me.lid)) {
|
|
724
630
|
const deviceJids = devices.map(d => d.attrs.jid);
|
|
725
631
|
logger.info({ deviceJids }, 'got my own devices');
|
|
726
632
|
}
|
|
727
|
-
if (!devices.length) {
|
|
728
|
-
logger.debug({ from
|
|
633
|
+
if (!devices || !devices.length || !devices[0]) {
|
|
634
|
+
logger.debug({ from }, 'no devices in notification, skipping');
|
|
729
635
|
return;
|
|
730
636
|
}
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
logger.debug({ jid }, 'failed to decode device jid, skipping');
|
|
739
|
-
continue;
|
|
740
|
-
}
|
|
741
|
-
decoded.push({ jid, user: parts.user, server: parts.server, device: parts.device });
|
|
742
|
-
}
|
|
743
|
-
if (!decoded.length)
|
|
637
|
+
const deviceJid = devices[0].attrs.jid;
|
|
638
|
+
const decoded = jidDecode(deviceJid);
|
|
639
|
+
if (!decoded) return;
|
|
640
|
+
const { user, device } = decoded;
|
|
641
|
+
const tag = child.tag;
|
|
642
|
+
if (!deviceJid) {
|
|
643
|
+
logger.debug({ tag }, 'no device jid in notification, skipping');
|
|
744
644
|
return;
|
|
645
|
+
}
|
|
745
646
|
await devicesMutex.mutex(async () => {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
byUser.set(d.user, list);
|
|
751
|
-
}
|
|
752
|
-
for (const [user, entries] of byUser) {
|
|
753
|
-
if (tag === 'update') {
|
|
754
|
-
logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
|
|
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);
|
|
647
|
+
if (tag === 'update') {
|
|
648
|
+
logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
|
|
649
|
+
if (userDevicesCache) {
|
|
650
|
+
await userDevicesCache.del(user);
|
|
792
651
|
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const existingCache = (await (userDevicesCache?.get(user))) || [];
|
|
655
|
+
if (!existingCache.length) {
|
|
656
|
+
logger.debug({ user, tag }, 'device list not cached, skipping cache update')
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const deviceHash = child.attrs.device_hash;
|
|
660
|
+
let updatedDevices = [];
|
|
661
|
+
switch (tag) {
|
|
662
|
+
case 'add':
|
|
663
|
+
logger.info({ deviceHash }, 'device added');
|
|
664
|
+
updatedDevices = [
|
|
665
|
+
...existingCache.filter(d => d.device !== device),
|
|
666
|
+
{ user, device }
|
|
667
|
+
];
|
|
668
|
+
break;
|
|
669
|
+
case 'remove':
|
|
670
|
+
logger.info({ deviceHash }, 'device removed');
|
|
671
|
+
updatedDevices = existingCache.filter(d => d.device !== device);
|
|
672
|
+
break;
|
|
673
|
+
default:
|
|
674
|
+
logger.debug({ tag }, 'Unknown device list change tag');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (updatedDevices.length > 0 && userDevicesCache) {
|
|
678
|
+
await userDevicesCache.set(user, updatedDevices);
|
|
793
679
|
}
|
|
794
680
|
});
|
|
795
681
|
};
|
|
@@ -803,7 +689,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
803
689
|
await handleNewsletterNotification(node);
|
|
804
690
|
break;
|
|
805
691
|
case 'mex':
|
|
806
|
-
await
|
|
692
|
+
await handleMexNewsletterNotification(node);
|
|
807
693
|
break;
|
|
808
694
|
case 'w:gp2':
|
|
809
695
|
// TODO: HANDLE PARTICIPANT_PN
|
|
@@ -821,7 +707,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
821
707
|
await handleDevicesNotification(node);
|
|
822
708
|
}
|
|
823
709
|
catch (error) {
|
|
824
|
-
|
|
710
|
+
logger.error({ error, node }, 'failed to handle devices notification');
|
|
825
711
|
}
|
|
826
712
|
break;
|
|
827
713
|
case 'server_sync':
|
|
@@ -948,70 +834,27 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
948
834
|
return result;
|
|
949
835
|
}
|
|
950
836
|
};
|
|
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
837
|
const handlePrivacyTokenNotification = async (node) => {
|
|
997
838
|
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
839
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
998
840
|
if (!tokensNode)
|
|
999
841
|
return;
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
842
|
+
const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
|
|
843
|
+
for (const tokenNode of tokenNodes) {
|
|
844
|
+
const { attrs, content } = tokenNode;
|
|
845
|
+
const type = attrs.type;
|
|
846
|
+
const timestamp = attrs.t;
|
|
847
|
+
if (type === 'trusted_contact' && content instanceof Buffer) {
|
|
848
|
+
logger.debug({
|
|
849
|
+
from,
|
|
850
|
+
timestamp,
|
|
851
|
+
tcToken: content
|
|
852
|
+
}, 'received trusted contact token');
|
|
853
|
+
await authState.keys.set({
|
|
854
|
+
tctoken: { [from]: { token: content, timestamp } }
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
}
|
|
1015
858
|
};
|
|
1016
859
|
async function decipherLinkPublicKey(data) {
|
|
1017
860
|
const buffer = toRequiredBuffer(data);
|
|
@@ -1037,11 +880,10 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1037
880
|
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
1038
881
|
await msgRetryCache.set(key, newValue);
|
|
1039
882
|
};
|
|
1040
|
-
const sendMessagesAgain = async (key, ids, retryNode
|
|
883
|
+
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
1041
884
|
const remoteJid = key.remoteJid;
|
|
1042
885
|
const participant = key.participant || remoteJid;
|
|
1043
886
|
const retryCount = +retryNode.attrs.count || 1;
|
|
1044
|
-
const msgId = ids[0];
|
|
1045
887
|
// Try to get messages from cache first, then fallback to getMessage
|
|
1046
888
|
const msgs = [];
|
|
1047
889
|
for (const id of ids) {
|
|
@@ -1073,49 +915,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1073
915
|
// just re-send the message to everyone
|
|
1074
916
|
// prevents the first message decryption failure
|
|
1075
917
|
const sendToAll = !jidDecode(participant)?.device;
|
|
1076
|
-
|
|
1077
|
-
let injectedFromBundle = false;
|
|
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
|
-
}
|
|
918
|
+
// Check if we should recreate session for this retry
|
|
1115
919
|
let shouldRecreateSession = false;
|
|
1116
920
|
let recreateReason = '';
|
|
1117
|
-
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1
|
|
921
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
1118
922
|
try {
|
|
923
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
1119
924
|
const hasSession = await signalRepository.validateSession(participant);
|
|
1120
925
|
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
1121
926
|
shouldRecreateSession = result.recreate;
|
|
@@ -1129,13 +934,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1129
934
|
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
1130
935
|
}
|
|
1131
936
|
}
|
|
1132
|
-
|
|
1133
|
-
await assertSessions([participant], true);
|
|
1134
|
-
}
|
|
937
|
+
await assertSessions([participant], true);
|
|
1135
938
|
if (isJidGroup(remoteJid)) {
|
|
1136
939
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
1137
940
|
}
|
|
1138
|
-
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason
|
|
941
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
|
|
1139
942
|
for (const [i, msg] of msgs.entries()) {
|
|
1140
943
|
if (!ids[i])
|
|
1141
944
|
continue;
|
|
@@ -1170,6 +973,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1170
973
|
fromMe,
|
|
1171
974
|
participant: attrs.participant
|
|
1172
975
|
};
|
|
976
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
977
|
+
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
978
|
+
await sendMessageAck(node);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
1173
981
|
const ids = [attrs.id];
|
|
1174
982
|
if (Array.isArray(content)) {
|
|
1175
983
|
const items = getBinaryNodeChildren(content[0], 'item');
|
|
@@ -1211,7 +1019,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1211
1019
|
try {
|
|
1212
1020
|
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
1213
1021
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
1214
|
-
await sendMessagesAgain(key, ids, retryNode
|
|
1022
|
+
await sendMessagesAgain(key, ids, retryNode);
|
|
1215
1023
|
}
|
|
1216
1024
|
catch (error) {
|
|
1217
1025
|
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
@@ -1234,6 +1042,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1234
1042
|
};
|
|
1235
1043
|
const handleNotification = async (node) => {
|
|
1236
1044
|
const remoteJid = node.attrs.from;
|
|
1045
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
1046
|
+
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
1047
|
+
await sendMessageAck(node);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1237
1050
|
try {
|
|
1238
1051
|
await Promise.all([
|
|
1239
1052
|
notificationMutex.mutex(async () => {
|
|
@@ -1246,7 +1059,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1246
1059
|
fromMe,
|
|
1247
1060
|
participant: node.attrs.participant,
|
|
1248
1061
|
participantAlt,
|
|
1249
|
-
participantUsername: node.attrs.participant_username,
|
|
1250
1062
|
addressingMode,
|
|
1251
1063
|
id: node.attrs.id,
|
|
1252
1064
|
...(msg.key || {})
|
|
@@ -1264,6 +1076,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1264
1076
|
}
|
|
1265
1077
|
};
|
|
1266
1078
|
const handleMessage = async (node) => {
|
|
1079
|
+
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== S_WHATSAPP_NET) {
|
|
1080
|
+
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
1081
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1267
1084
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1268
1085
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
1269
1086
|
if (encNode?.attrs.type === 'msmsg') {
|
|
@@ -1367,14 +1184,29 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1367
1184
|
return sendMessageAck(node);
|
|
1368
1185
|
}
|
|
1369
1186
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1187
|
+
const errorMessage = msg?.messageStubParameters?.[0] || '';
|
|
1188
|
+
const isPreKeyError = errorMessage.includes('PreKey');
|
|
1189
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
1190
|
+
// Handle both pre-key and normal retries in single mutex
|
|
1372
1191
|
await retryMutex.mutex(async () => {
|
|
1373
1192
|
try {
|
|
1374
1193
|
if (!ws.isOpen) {
|
|
1375
1194
|
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
1376
1195
|
return;
|
|
1377
1196
|
}
|
|
1197
|
+
// Handle pre-key errors with upload and delay
|
|
1198
|
+
if (isPreKeyError) {
|
|
1199
|
+
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
1200
|
+
try {
|
|
1201
|
+
logger.debug('Uploading pre-keys for error recovery');
|
|
1202
|
+
await uploadPreKeys(5);
|
|
1203
|
+
logger.debug('Waiting for server to process new pre-keys');
|
|
1204
|
+
await delay(1000);
|
|
1205
|
+
}
|
|
1206
|
+
catch (uploadErr) {
|
|
1207
|
+
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1378
1210
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1379
1211
|
await sendRetryRequest(node, !encNode);
|
|
1380
1212
|
if (retryRequestDelayMs) {
|
|
@@ -1382,7 +1214,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1382
1214
|
}
|
|
1383
1215
|
}
|
|
1384
1216
|
catch (err) {
|
|
1385
|
-
logger.error({ err }, 'Failed to
|
|
1217
|
+
logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
|
|
1218
|
+
// Still attempt retry even if pre-key upload failed
|
|
1219
|
+
try {
|
|
1220
|
+
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1221
|
+
await sendRetryRequest(node, !encNode);
|
|
1222
|
+
}
|
|
1223
|
+
catch (retryErr) {
|
|
1224
|
+
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
1225
|
+
}
|
|
1386
1226
|
}
|
|
1387
1227
|
acked = true;
|
|
1388
1228
|
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
@@ -1458,13 +1298,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1458
1298
|
offline: !!attrs.offline,
|
|
1459
1299
|
status
|
|
1460
1300
|
};
|
|
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
1301
|
if (status === 'offer') {
|
|
1469
1302
|
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1470
1303
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
@@ -1509,61 +1342,29 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1509
1342
|
// error in acknowledgement,
|
|
1510
1343
|
// device could not display the message
|
|
1511
1344
|
if (attrs.error) {
|
|
1512
|
-
|
|
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');
|
|
1549
|
-
}
|
|
1550
|
-
else if (isReachoutTimelocked) {
|
|
1551
|
-
// user is temporarily restricted, fetch current restriction details
|
|
1552
|
-
await fetchAccountReachoutTimelock().catch(err => logger.warn({ err }, 'failed to fetch reachout timelock'));
|
|
1553
|
-
logger.warn({ attrs }, 'received error in ack');
|
|
1554
|
-
}
|
|
1555
|
-
else {
|
|
1556
|
-
logger.warn({ attrs }, 'received error in ack');
|
|
1557
|
-
}
|
|
1345
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1558
1346
|
ev.emit('messages.update', [
|
|
1559
1347
|
{
|
|
1560
1348
|
key,
|
|
1561
1349
|
update: {
|
|
1562
1350
|
status: WAMessageStatus.ERROR,
|
|
1563
|
-
messageStubParameters:
|
|
1351
|
+
messageStubParameters: [attrs.error]
|
|
1564
1352
|
}
|
|
1565
1353
|
}
|
|
1566
1354
|
]);
|
|
1355
|
+
// resend the message with device_fanout=false, use at your own risk
|
|
1356
|
+
// if (attrs.error === '475') {
|
|
1357
|
+
// const msg = await getMessage(key)
|
|
1358
|
+
// if (msg) {
|
|
1359
|
+
// await relayMessage(key.remoteJid!, msg, {
|
|
1360
|
+
// messageId: key.id!,
|
|
1361
|
+
// useUserDevicesCache: false,
|
|
1362
|
+
// additionalAttributes: {
|
|
1363
|
+
// device_fanout: 'false'
|
|
1364
|
+
// }
|
|
1365
|
+
// })
|
|
1366
|
+
// }
|
|
1367
|
+
// }
|
|
1567
1368
|
}
|
|
1568
1369
|
};
|
|
1569
1370
|
/// processes a node with the given function
|
|
@@ -1587,19 +1388,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1587
1388
|
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1588
1389
|
});
|
|
1589
1390
|
const processNode = async (type, node, identifier, exec) => {
|
|
1590
|
-
// Fast path: ack and drop ignored JIDs before entering the buffer/queue
|
|
1591
|
-
const from = node.attrs.from;
|
|
1592
|
-
let ignoreJid = from;
|
|
1593
|
-
if (type === 'receipt' && from) {
|
|
1594
|
-
const attrs = node.attrs;
|
|
1595
|
-
const isLid = attrs.from.includes('lid');
|
|
1596
|
-
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1597
|
-
ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
1598
|
-
}
|
|
1599
|
-
if (ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
|
|
1600
|
-
await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
1391
|
const isOffline = !!node.attrs.offline;
|
|
1604
1392
|
if (isOffline) {
|
|
1605
1393
|
offlineNodeProcessor.enqueue(type, node);
|
|
@@ -1655,38 +1443,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1655
1443
|
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1656
1444
|
}
|
|
1657
1445
|
});
|
|
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 }) => {
|
|
1446
|
+
ev.on('connection.update', ({ isOnline }) => {
|
|
1663
1447
|
if (typeof isOnline !== 'undefined') {
|
|
1664
1448
|
sendActiveReceipts = isOnline;
|
|
1665
1449
|
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1666
1450
|
}
|
|
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
1451
|
});
|
|
1691
1452
|
registerSocketEndHandler(() => {
|
|
1692
1453
|
if (!config.msgRetryCounterCache && msgRetryCache.close) {
|
|
@@ -1695,78 +1456,21 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1695
1456
|
if (!config.callOfferCache && callOfferCache.close) {
|
|
1696
1457
|
callOfferCache.close();
|
|
1697
1458
|
}
|
|
1459
|
+
if (!config.placeholderResendCache && placeholderResendCache.close) {
|
|
1460
|
+
placeholderResendCache.close();
|
|
1461
|
+
}
|
|
1698
1462
|
identityAssertDebounce.close();
|
|
1699
1463
|
sendActiveReceipts = false;
|
|
1700
1464
|
});
|
|
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
1465
|
return {
|
|
1763
1466
|
...sock,
|
|
1764
1467
|
sendMessageAck,
|
|
1765
1468
|
sendRetryRequest,
|
|
1766
1469
|
rejectCall,
|
|
1470
|
+
initiateCall,
|
|
1471
|
+
cancelCall,
|
|
1767
1472
|
fetchMessageHistory,
|
|
1768
1473
|
requestPlaceholderResend,
|
|
1769
1474
|
messageRetryManager
|
|
1770
1475
|
};
|
|
1771
|
-
};
|
|
1772
|
-
//# sourceMappingURL=messages-recv.js.map
|
|
1476
|
+
};
|