@itsliaaa/baileys 0.3.0-rc.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -29
- package/lib/Defaults/index.d.ts +6 -7
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +7 -8
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/libsignal.d.ts +13 -0
- package/lib/Signal/libsignal.d.ts.map +1 -1
- package/lib/Signal/libsignal.js +45 -17
- package/lib/Signal/libsignal.js.map +1 -1
- package/lib/Signal/lid-mapping.d.ts +4 -0
- package/lib/Signal/lid-mapping.d.ts.map +1 -1
- package/lib/Signal/lid-mapping.js +6 -0
- package/lib/Signal/lid-mapping.js.map +1 -1
- package/lib/Socket/business.d.ts +9 -1
- package/lib/Socket/business.d.ts.map +1 -1
- package/lib/Socket/chats.d.ts +4 -1
- package/lib/Socket/chats.d.ts.map +1 -1
- package/lib/Socket/chats.js +14 -5
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +9 -1
- package/lib/Socket/communities.d.ts.map +1 -1
- package/lib/Socket/groups.d.ts +4 -1
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/index.d.ts +9 -1
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/messages-recv.d.ts +9 -1
- package/lib/Socket/messages-recv.d.ts.map +1 -1
- package/lib/Socket/messages-recv.js +392 -133
- package/lib/Socket/messages-recv.js.map +1 -1
- package/lib/Socket/messages-send.d.ts +9 -1
- package/lib/Socket/messages-send.d.ts.map +1 -1
- package/lib/Socket/messages-send.js +68 -37
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/newsletter.d.ts +4 -1
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/socket.d.ts +3 -1
- package/lib/Socket/socket.d.ts.map +1 -1
- package/lib/Socket/socket.js +24 -19
- package/lib/Socket/socket.js.map +1 -1
- package/lib/Utils/chat-utils.d.ts +1 -1
- package/lib/Utils/chat-utils.d.ts.map +1 -1
- package/lib/Utils/chat-utils.js +46 -12
- package/lib/Utils/chat-utils.js.map +1 -1
- package/lib/Utils/decode-wa-message.d.ts +1 -1
- package/lib/Utils/decode-wa-message.d.ts.map +1 -1
- package/lib/Utils/decode-wa-message.js +6 -2
- package/lib/Utils/decode-wa-message.js.map +1 -1
- package/lib/Utils/event-buffer.d.ts +1 -0
- package/lib/Utils/event-buffer.d.ts.map +1 -1
- package/lib/Utils/event-buffer.js +47 -1
- package/lib/Utils/event-buffer.js.map +1 -1
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +4 -4
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/history.d.ts +2 -0
- package/lib/Utils/history.d.ts.map +1 -1
- package/lib/Utils/history.js +1 -0
- package/lib/Utils/history.js.map +1 -1
- package/lib/Utils/link-preview.js +2 -2
- package/lib/Utils/link-preview.js.map +1 -1
- package/lib/Utils/message-retry-manager.d.ts +5 -0
- package/lib/Utils/message-retry-manager.d.ts.map +1 -1
- package/lib/Utils/message-retry-manager.js +40 -0
- package/lib/Utils/message-retry-manager.js.map +1 -1
- package/lib/Utils/messages-media.d.ts +2 -1
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +16 -4
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.js +1 -1
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/signal.d.ts +13 -0
- package/lib/Utils/signal.d.ts.map +1 -1
- package/lib/Utils/signal.js +42 -0
- package/lib/Utils/signal.js.map +1 -1
- package/lib/Utils/validate-connection.d.ts.map +1 -1
- package/lib/Utils/validate-connection.js +3 -0
- package/lib/Utils/validate-connection.js.map +1 -1
- package/lib/WAUSync/USyncQuery.js +1 -1
- package/lib/WAUSync/USyncQuery.js.map +1 -1
- package/package.json +33 -4
|
@@ -4,19 +4,23 @@ 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 { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
-
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
7
|
+
import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
+
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
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
12
|
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
|
|
13
|
-
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
13
|
+
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
14
14
|
import { extractGroupMetadata } from './groups.js';
|
|
15
15
|
import { makeMessagesSocket } from './messages-send.js';
|
|
16
|
+
const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
|
|
17
|
+
function isValidEnforcementType(value) {
|
|
18
|
+
return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
|
|
19
|
+
}
|
|
16
20
|
export const makeMessagesRecvSocket = (config) => {
|
|
17
21
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
18
22
|
const sock = makeMessagesSocket(config);
|
|
19
|
-
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, issuePrivacyTokens, fetchAccountReachoutTimelock } = sock;
|
|
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;
|
|
20
24
|
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
21
25
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
22
26
|
const retryMutex = makeMutex();
|
|
@@ -30,11 +34,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
30
34
|
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
31
35
|
useClones: false
|
|
32
36
|
});
|
|
33
|
-
const placeholderResendCache = config.placeholderResendCache ||
|
|
34
|
-
new NodeCache({
|
|
35
|
-
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
36
|
-
useClones: false
|
|
37
|
-
});
|
|
38
37
|
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
39
38
|
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
|
|
40
39
|
let sendActiveReceipts = false;
|
|
@@ -88,27 +87,128 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
88
87
|
}, 8000);
|
|
89
88
|
return sendPeerDataOperationMessage(pdoMessage);
|
|
90
89
|
};
|
|
91
|
-
|
|
92
|
-
|
|
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;
|
|
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) => {
|
|
93
193
|
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
94
194
|
const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
|
|
95
195
|
const payloadNode = mexNode?.content ? mexNode : updateNode;
|
|
96
196
|
if (!payloadNode?.content) {
|
|
97
|
-
logger.warn({ node }, '
|
|
197
|
+
logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
|
|
98
198
|
return;
|
|
99
199
|
}
|
|
100
200
|
let data;
|
|
101
201
|
try {
|
|
102
202
|
const payloadContent = payloadNode.content;
|
|
103
203
|
if (Array.isArray(payloadContent)) {
|
|
104
|
-
logger.warn({ payloadNode }, '
|
|
204
|
+
logger.warn({ payloadNode }, 'invalid mex newsletter notification payload format');
|
|
105
205
|
return;
|
|
106
206
|
}
|
|
107
207
|
const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
|
|
108
208
|
data = JSON.parse(contentBuf.toString());
|
|
109
209
|
}
|
|
110
210
|
catch (error) {
|
|
111
|
-
logger.error({ err: error, node }, '
|
|
211
|
+
logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
|
|
112
212
|
return;
|
|
113
213
|
}
|
|
114
214
|
const operation = data?.operation ?? payloadNode?.attrs?.op_name;
|
|
@@ -120,7 +220,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
120
220
|
}
|
|
121
221
|
}
|
|
122
222
|
if (!updates || !operation) {
|
|
123
|
-
logger.warn({ data }, '
|
|
223
|
+
logger.warn({ data }, 'invalid mex newsletter notification content');
|
|
124
224
|
return;
|
|
125
225
|
}
|
|
126
226
|
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
@@ -165,90 +265,97 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
165
265
|
}
|
|
166
266
|
break;
|
|
167
267
|
default:
|
|
168
|
-
logger.info({ operation, data }, '
|
|
268
|
+
logger.info({ operation, data }, 'unhandled mex newsletter notification');
|
|
169
269
|
break;
|
|
170
270
|
}
|
|
171
271
|
};
|
|
172
272
|
// Handles newsletter notifications
|
|
173
273
|
const handleNewsletterNotification = async (node) => {
|
|
174
274
|
const from = node.attrs.from;
|
|
175
|
-
const
|
|
275
|
+
const children = getAllBinaryNodeChildren(node);
|
|
176
276
|
const author = node.attrs.participant;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
server_id: child.attrs.message_id,
|
|
183
|
-
reaction: {
|
|
184
|
-
code: getBinaryNodeChildString(child, 'reaction'),
|
|
185
|
-
count: 1
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
ev.emit('newsletter.reaction', reactionUpdate);
|
|
189
|
-
break;
|
|
190
|
-
case 'view':
|
|
191
|
-
const viewUpdate = {
|
|
192
|
-
id: from,
|
|
193
|
-
server_id: child.attrs.message_id,
|
|
194
|
-
count: parseInt(child.content?.toString() || '0', 10)
|
|
195
|
-
};
|
|
196
|
-
ev.emit('newsletter.view', viewUpdate);
|
|
197
|
-
break;
|
|
198
|
-
case 'participant':
|
|
199
|
-
const participantUpdate = {
|
|
200
|
-
id: from,
|
|
201
|
-
author,
|
|
202
|
-
user: child.attrs.jid,
|
|
203
|
-
action: child.attrs.action,
|
|
204
|
-
new_role: child.attrs.role
|
|
205
|
-
};
|
|
206
|
-
ev.emit('newsletter-participants.update', participantUpdate);
|
|
207
|
-
break;
|
|
208
|
-
case 'update':
|
|
209
|
-
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
210
|
-
if (settingsNode) {
|
|
211
|
-
const update = {};
|
|
212
|
-
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
213
|
-
if (nameNode?.content)
|
|
214
|
-
update.name = nameNode.content.toString();
|
|
215
|
-
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
216
|
-
if (descriptionNode?.content)
|
|
217
|
-
update.description = descriptionNode.content.toString();
|
|
218
|
-
ev.emit('newsletter-settings.update', {
|
|
277
|
+
for (const child of children) {
|
|
278
|
+
logger.debug({ from, child }, 'got newsletter notification');
|
|
279
|
+
switch (child.tag) {
|
|
280
|
+
case 'reaction': {
|
|
281
|
+
const reactionUpdate = {
|
|
219
282
|
id: from,
|
|
220
|
-
|
|
221
|
-
|
|
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;
|
|
222
291
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
+
});
|
|
243
326
|
}
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
}
|
|
246
352
|
}
|
|
353
|
+
break;
|
|
247
354
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
355
|
+
default:
|
|
356
|
+
logger.warn({ node, child }, 'Unknown newsletter notification child');
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
252
359
|
}
|
|
253
360
|
};
|
|
254
361
|
const sendMessageAck = async (node, errorCode) => {
|
|
@@ -408,6 +515,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
408
515
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
409
516
|
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
410
517
|
};
|
|
518
|
+
// Mirrors WAWeb/Handle/PreKeyLow.js: skip a re-issued notification with the same stanza id.
|
|
519
|
+
const inFlightPreKeyLow = new Set();
|
|
411
520
|
/**
|
|
412
521
|
* Fire-and-forget tctoken re-issuance after a peer's device identity changed.
|
|
413
522
|
* Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
|
|
@@ -440,12 +549,24 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
440
549
|
const handleEncryptNotification = async (node) => {
|
|
441
550
|
const from = node.attrs.from;
|
|
442
551
|
if (from === S_WHATSAPP_NET) {
|
|
552
|
+
const stanzaId = node.attrs.id;
|
|
553
|
+
if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
443
556
|
const countChild = getBinaryNodeChild(node, 'count');
|
|
444
557
|
const count = +countChild.attrs.value;
|
|
445
558
|
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
|
|
446
559
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
447
560
|
if (shouldUploadMorePreKeys) {
|
|
448
|
-
|
|
561
|
+
if (stanzaId)
|
|
562
|
+
inFlightPreKeyLow.add(stanzaId);
|
|
563
|
+
try {
|
|
564
|
+
await uploadPreKeys();
|
|
565
|
+
}
|
|
566
|
+
finally {
|
|
567
|
+
if (stanzaId)
|
|
568
|
+
inFlightPreKeyLow.delete(stanzaId);
|
|
569
|
+
}
|
|
449
570
|
}
|
|
450
571
|
}
|
|
451
572
|
else {
|
|
@@ -589,6 +710,89 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
589
710
|
break;
|
|
590
711
|
}
|
|
591
712
|
};
|
|
713
|
+
const handleDevicesNotification = async (node) => {
|
|
714
|
+
const [child] = getAllBinaryNodeChildren(node);
|
|
715
|
+
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
|
+
const devices = getBinaryNodeChildren(child, 'device');
|
|
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;
|
|
740
|
+
}
|
|
741
|
+
decoded.push({ jid, user: parts.user, server: parts.server, device: parts.device });
|
|
742
|
+
}
|
|
743
|
+
if (!decoded.length)
|
|
744
|
+
return;
|
|
745
|
+
await devicesMutex.mutex(async () => {
|
|
746
|
+
const byUser = new Map();
|
|
747
|
+
for (const d of decoded) {
|
|
748
|
+
const list = byUser.get(d.user) || [];
|
|
749
|
+
list.push(d);
|
|
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);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
};
|
|
592
796
|
const processNotification = async (node) => {
|
|
593
797
|
const result = {};
|
|
594
798
|
const [child] = getAllBinaryNodeChildren(node);
|
|
@@ -599,7 +803,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
599
803
|
await handleNewsletterNotification(node);
|
|
600
804
|
break;
|
|
601
805
|
case 'mex':
|
|
602
|
-
await
|
|
806
|
+
await handleMexNotification(node);
|
|
603
807
|
break;
|
|
604
808
|
case 'w:gp2':
|
|
605
809
|
// TODO: HANDLE PARTICIPANT_PN
|
|
@@ -613,13 +817,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
613
817
|
await handleEncryptNotification(node);
|
|
614
818
|
break;
|
|
615
819
|
case 'devices':
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
logger.
|
|
820
|
+
try {
|
|
821
|
+
await handleDevicesNotification(node);
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
logger.error({ error, node }, 'failed to handle devices notification');
|
|
621
825
|
}
|
|
622
|
-
//TODO: drop a new event, add hashes
|
|
623
826
|
break;
|
|
624
827
|
case 'server_sync':
|
|
625
828
|
const update = getBinaryNodeChild(node, 'collection');
|
|
@@ -834,10 +1037,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
834
1037
|
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
835
1038
|
await msgRetryCache.set(key, newValue);
|
|
836
1039
|
};
|
|
837
|
-
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
1040
|
+
const sendMessagesAgain = async (key, ids, retryNode, receiptNode) => {
|
|
838
1041
|
const remoteJid = key.remoteJid;
|
|
839
1042
|
const participant = key.participant || remoteJid;
|
|
840
1043
|
const retryCount = +retryNode.attrs.count || 1;
|
|
1044
|
+
const msgId = ids[0];
|
|
841
1045
|
// Try to get messages from cache first, then fallback to getMessage
|
|
842
1046
|
const msgs = [];
|
|
843
1047
|
for (const id of ids) {
|
|
@@ -869,12 +1073,49 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
869
1073
|
// just re-send the message to everyone
|
|
870
1074
|
// prevents the first message decryption failure
|
|
871
1075
|
const sendToAll = !jidDecode(participant)?.device;
|
|
872
|
-
|
|
1076
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
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
|
+
}
|
|
873
1115
|
let shouldRecreateSession = false;
|
|
874
1116
|
let recreateReason = '';
|
|
875
|
-
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
1117
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1 && !injectedFromBundle) {
|
|
876
1118
|
try {
|
|
877
|
-
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
878
1119
|
const hasSession = await signalRepository.validateSession(participant);
|
|
879
1120
|
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
880
1121
|
shouldRecreateSession = result.recreate;
|
|
@@ -888,11 +1129,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
888
1129
|
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
889
1130
|
}
|
|
890
1131
|
}
|
|
891
|
-
|
|
1132
|
+
if (!injectedFromBundle) {
|
|
1133
|
+
await assertSessions([participant], true);
|
|
1134
|
+
}
|
|
892
1135
|
if (isJidGroup(remoteJid)) {
|
|
893
1136
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
894
1137
|
}
|
|
895
|
-
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, '
|
|
1138
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason, injectedFromBundle }, 'prepared session for retry resend');
|
|
896
1139
|
for (const [i, msg] of msgs.entries()) {
|
|
897
1140
|
if (!ids[i])
|
|
898
1141
|
continue;
|
|
@@ -968,7 +1211,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
968
1211
|
try {
|
|
969
1212
|
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
970
1213
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
971
|
-
await sendMessagesAgain(key, ids, retryNode);
|
|
1214
|
+
await sendMessagesAgain(key, ids, retryNode, node);
|
|
972
1215
|
}
|
|
973
1216
|
catch (error) {
|
|
974
1217
|
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
@@ -1124,29 +1367,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1124
1367
|
return sendMessageAck(node);
|
|
1125
1368
|
}
|
|
1126
1369
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
1130
|
-
// Handle both pre-key and normal retries in single mutex
|
|
1370
|
+
logger.debug('[handleMessage] Attempting retry request for failed decryption');
|
|
1371
|
+
// WAWeb only retry-receipts here; server emits PreKeyLow if prekeys run low.
|
|
1131
1372
|
await retryMutex.mutex(async () => {
|
|
1132
1373
|
try {
|
|
1133
1374
|
if (!ws.isOpen) {
|
|
1134
1375
|
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
1135
1376
|
return;
|
|
1136
1377
|
}
|
|
1137
|
-
// Handle pre-key errors with upload and delay
|
|
1138
|
-
if (isPreKeyError) {
|
|
1139
|
-
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
1140
|
-
try {
|
|
1141
|
-
logger.debug('Uploading pre-keys for error recovery');
|
|
1142
|
-
await uploadPreKeys(5);
|
|
1143
|
-
logger.debug('Waiting for server to process new pre-keys');
|
|
1144
|
-
await delay(1000);
|
|
1145
|
-
}
|
|
1146
|
-
catch (uploadErr) {
|
|
1147
|
-
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
1378
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1151
1379
|
await sendRetryRequest(node, !encNode);
|
|
1152
1380
|
if (retryRequestDelayMs) {
|
|
@@ -1154,15 +1382,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1154
1382
|
}
|
|
1155
1383
|
}
|
|
1156
1384
|
catch (err) {
|
|
1157
|
-
logger.error({ err
|
|
1158
|
-
// Still attempt retry even if pre-key upload failed
|
|
1159
|
-
try {
|
|
1160
|
-
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1161
|
-
await sendRetryRequest(node, !encNode);
|
|
1162
|
-
}
|
|
1163
|
-
catch (retryErr) {
|
|
1164
|
-
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
1165
|
-
}
|
|
1385
|
+
logger.error({ err }, 'Failed to send retry');
|
|
1166
1386
|
}
|
|
1167
1387
|
acked = true;
|
|
1168
1388
|
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
@@ -1290,12 +1510,39 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1290
1510
|
// device could not display the message
|
|
1291
1511
|
if (attrs.error) {
|
|
1292
1512
|
const isReachoutTimelocked = attrs.error === String(NACK_REASONS.SenderReachoutTimelocked);
|
|
1293
|
-
if (attrs.error === SERVER_ERROR_CODES.
|
|
1294
|
-
// 463 =
|
|
1295
|
-
//
|
|
1296
|
-
//
|
|
1297
|
-
//
|
|
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.
|
|
1298
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
|
+
}
|
|
1299
1546
|
}
|
|
1300
1547
|
else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
|
|
1301
1548
|
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
@@ -1410,6 +1657,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1410
1657
|
});
|
|
1411
1658
|
/** timestamp of last tctoken prune run — throttles to once per 24h */
|
|
1412
1659
|
let lastTcTokenPruneTs = 0;
|
|
1660
|
+
/** dedupe in-flight 463 recovery token issuance by target JID */
|
|
1661
|
+
const inFlight463Recoveries = new Set();
|
|
1413
1662
|
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1414
1663
|
if (typeof isOnline !== 'undefined') {
|
|
1415
1664
|
sendActiveReceipts = isOnline;
|
|
@@ -1439,6 +1688,16 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1439
1688
|
}
|
|
1440
1689
|
}
|
|
1441
1690
|
});
|
|
1691
|
+
registerSocketEndHandler(() => {
|
|
1692
|
+
if (!config.msgRetryCounterCache && msgRetryCache.close) {
|
|
1693
|
+
msgRetryCache.close();
|
|
1694
|
+
}
|
|
1695
|
+
if (!config.callOfferCache && callOfferCache.close) {
|
|
1696
|
+
callOfferCache.close();
|
|
1697
|
+
}
|
|
1698
|
+
identityAssertDebounce.close();
|
|
1699
|
+
sendActiveReceipts = false;
|
|
1700
|
+
});
|
|
1442
1701
|
async function pruneExpiredTcTokens() {
|
|
1443
1702
|
try {
|
|
1444
1703
|
await tcTokenIndexLoaded;
|