whalibmob 5.5.70 → 5.5.72
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/lib/Client.js +56 -5
- package/lib/DeviceManager.js +9 -24
- package/lib/messages/MessageSender.js +18 -1
- package/package.json +1 -1
package/lib/Client.js
CHANGED
|
@@ -11,6 +11,11 @@ const { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, assert
|
|
|
11
11
|
const { getDeviceConfig } = require('./DeviceConfig');
|
|
12
12
|
const { createNewStore, saveStore, loadStore, toSixParts, fromSixParts } = require('./Store');
|
|
13
13
|
const { BinaryNode } = require('./BinaryNode');
|
|
14
|
+
|
|
15
|
+
// How many times a message may be re-sent in answer to retry receipts before
|
|
16
|
+
// the client stops. Each resend can itself draw retries, so this is what keeps
|
|
17
|
+
// an undecryptable message from turning into a flood.
|
|
18
|
+
const MAX_RETRY_RESENDS = 2;
|
|
14
19
|
const { NodeCache } = require('@cacheable/node-cache');
|
|
15
20
|
|
|
16
21
|
// Group metadata cache lifetime — matches DeviceManager's device cache.
|
|
@@ -877,9 +882,33 @@ class WhalibmobClient extends EventEmitter {
|
|
|
877
882
|
return;
|
|
878
883
|
}
|
|
879
884
|
|
|
880
|
-
|
|
881
|
-
//
|
|
882
|
-
|
|
885
|
+
// Consume the entry before doing anything else. Every device that failed to
|
|
886
|
+
// decrypt sends its own retry receipt for the same message, and a group
|
|
887
|
+
// resend already re-addresses all of them — without this, each receipt
|
|
888
|
+
// started its own resend.
|
|
889
|
+
this._sentMsgCache.delete(msgId);
|
|
890
|
+
|
|
891
|
+
// Bound the chain. A resend that still cannot be decrypted draws fresh
|
|
892
|
+
// retry receipts of its own, so an unbounded handler answers each one with
|
|
893
|
+
// another send and floods the chat. Give up after a few rounds and leave
|
|
894
|
+
// the message undelivered rather than keep spamming.
|
|
895
|
+
const depth = (cached.retryDepth || 0) + 1;
|
|
896
|
+
if (depth > MAX_RETRY_RESENDS) {
|
|
897
|
+
_whaDbg('[DBG] RETRY_RECV msgId=' + msgId + ' — giving up after ' +
|
|
898
|
+
cached.retryDepth + ' resends\n');
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const fromStr = attrs.from ? String(attrs.from) : '';
|
|
903
|
+
const isGroup = fromStr.endsWith('@g.us');
|
|
904
|
+
|
|
905
|
+
// On a group retry `from` is the group and `participant` is the device that
|
|
906
|
+
// failed to decrypt. Keying off `from` there would have targeted the group
|
|
907
|
+
// id as if it were a contact and cleared nothing.
|
|
908
|
+
const targetJid = isGroup && attrs.participant
|
|
909
|
+
? String(attrs.participant)
|
|
910
|
+
: fromStr;
|
|
911
|
+
const recipientPhone = targetJid.split('@')[0].split(':')[0].split('.')[0];
|
|
883
912
|
|
|
884
913
|
_whaDbg('[DBG] RETRY_RECV msgId=' + msgId + ' from=' + fromStr + ' — clearing session + resending\n');
|
|
885
914
|
|
|
@@ -900,9 +929,31 @@ class WhalibmobClient extends EventEmitter {
|
|
|
900
929
|
this._devMgr.clearCache([recipientPhone]);
|
|
901
930
|
}
|
|
902
931
|
|
|
903
|
-
// Re-send the message — will create fresh pre-key (pkmsg) sessions
|
|
932
|
+
// Re-send the message — will create fresh pre-key (pkmsg) sessions.
|
|
933
|
+
// A group resend must go back through the group path: it needs a fresh
|
|
934
|
+
// SenderKey distribution, which only _sendGroupMessage builds. Drop the
|
|
935
|
+
// "already distributed" record first, or the resend would carry the same
|
|
936
|
+
// bare skmsg the recipient just told us it could not read.
|
|
937
|
+
// Carry the round count into the resend so its own retry receipts continue
|
|
938
|
+
// the same chain instead of restarting it at zero.
|
|
939
|
+
const opts = Object.assign({}, cached.options, { _retryDepth: depth });
|
|
940
|
+
|
|
941
|
+
if (cached.isGroup) {
|
|
942
|
+
const resendId = generateMessageId();
|
|
943
|
+
if (this._signal && this._signal.senderKeyStore) {
|
|
944
|
+
this._signal.senderKeyStore.invalidateSKDM(cached.toJid);
|
|
945
|
+
}
|
|
946
|
+
_whaDbg('[DBG] RETRY_RESEND group round=' + depth + ' newId=' + resendId + '\n');
|
|
947
|
+
this._sender._sendGroupMessage(
|
|
948
|
+
cached.toJid, resendId, cached.plaintext, cached.mediaType, opts
|
|
949
|
+
)
|
|
950
|
+
.then(r => _whaDbg('[DBG] RETRY_RESEND group ok id=' + (r && r.id) + '\n'))
|
|
951
|
+
.catch(e => _whaDbg('[DBG] RETRY_RESEND_ERR: ' + e.message));
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
|
|
904
955
|
this._sender._sendDMMessage(
|
|
905
|
-
cached.toJid, cached.msgId, cached.plaintext, cached.mediaType,
|
|
956
|
+
cached.toJid, cached.msgId, cached.plaintext, cached.mediaType, opts
|
|
906
957
|
)
|
|
907
958
|
.then(r => _whaDbg('[DBG] RETRY_RESEND ok id=' + r.id + '\n'))
|
|
908
959
|
.catch(e => _whaDbg('[DBG] RETRY_RESEND_ERR: ' + e.message));
|
package/lib/DeviceManager.js
CHANGED
|
@@ -305,30 +305,15 @@ class DeviceManager {
|
|
|
305
305
|
async fetchBundles(jids) {
|
|
306
306
|
if (!jids || jids.length === 0) return new Map();
|
|
307
307
|
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const colon = raw.indexOf(':');
|
|
318
|
-
const user = colon >= 0 ? raw.slice(0, colon) : raw;
|
|
319
|
-
const device = colon >= 0 ? (parseInt(raw.slice(colon + 1), 10) || 0) : 0;
|
|
320
|
-
const self = str;
|
|
321
|
-
// AD_JID binary format is for @s.whatsapp.net with device > 0.
|
|
322
|
-
// JID_PAIR binary format is used for @lid and for device-0 @s.whatsapp.net.
|
|
323
|
-
if (server === 's.whatsapp.net' && device > 0) {
|
|
324
|
-
// AD_JID: agent=0, device=N
|
|
325
|
-
return { user, agent: 0, device, server, toString() { return self; } };
|
|
326
|
-
}
|
|
327
|
-
// JID_PAIR: user@server — server may be 'lid' or 's.whatsapp.net'
|
|
328
|
-
return { user, server, toString() { return self; } };
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const userNodes = jids.map(jid => new BinaryNode('user', { jid: toJidObj(jid) }, null));
|
|
308
|
+
// Use the shared converter. The local copy this replaced dropped the device
|
|
309
|
+
// suffix on @lid JIDs: it split "112713111982325:49@lid" into user and
|
|
310
|
+
// device, then fell through to a JID_PAIR built from `user` alone, so every
|
|
311
|
+
// device of a contact encoded to the identical "112713111982325@lid". The
|
|
312
|
+
// server saw one repeated JID, returned a single device-0 bundle, and no
|
|
313
|
+
// session was ever built for the other devices — leaving them unable to
|
|
314
|
+
// decrypt anything we sent. jidStrToObj keeps the colon inside the user
|
|
315
|
+
// part for @lid, which is what the server expects.
|
|
316
|
+
const userNodes = jids.map(jid => new BinaryNode('user', { jid: jidStrToObj(jid) }, null));
|
|
332
317
|
const iqId = this._client._genMsgId();
|
|
333
318
|
const iqNode = new BinaryNode('iq',
|
|
334
319
|
{ id: iqId, xmlns: 'encrypt', type: 'get', to: 's.whatsapp.net' },
|
|
@@ -772,7 +772,8 @@ class MessageSender {
|
|
|
772
772
|
// Cache plaintext so Client can re-send with fresh session on recipient retry
|
|
773
773
|
if (this._client._sentMsgCache) {
|
|
774
774
|
this._client._sentMsgCache.set(msgId, {
|
|
775
|
-
plaintext, toJid, msgId, mediaType, options, recipientPhone
|
|
775
|
+
plaintext, toJid, msgId, mediaType, options, recipientPhone,
|
|
776
|
+
retryDepth: options._retryDepth || 0
|
|
776
777
|
});
|
|
777
778
|
// Keep cache bounded — evict oldest entries beyond 200
|
|
778
779
|
if (this._client._sentMsgCache.size > 200) {
|
|
@@ -973,6 +974,22 @@ class MessageSender {
|
|
|
973
974
|
};
|
|
974
975
|
if (options.edit) stanzaAttrs.edit = String(options.edit);
|
|
975
976
|
|
|
977
|
+
// Cache the plaintext here too, not just on the DM path. A group recipient
|
|
978
|
+
// that cannot decrypt sends a retry receipt exactly like a DM recipient
|
|
979
|
+
// does, and without this the client had nothing to re-send and dropped it
|
|
980
|
+
// with "no cached plaintext" — so the message was acked by the server and
|
|
981
|
+
// then silently never arrived.
|
|
982
|
+
if (this._client._sentMsgCache) {
|
|
983
|
+
this._client._sentMsgCache.set(msgId, {
|
|
984
|
+
plaintext, toJid: groupJid, msgId, mediaType, options, isGroup: true,
|
|
985
|
+
retryDepth: options._retryDepth || 0
|
|
986
|
+
});
|
|
987
|
+
if (this._client._sentMsgCache.size > 200) {
|
|
988
|
+
const firstKey = this._client._sentMsgCache.keys().next().value;
|
|
989
|
+
this._client._sentMsgCache.delete(firstKey);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
976
993
|
const msgNode = new BinaryNode('message', stanzaAttrs, msgContent);
|
|
977
994
|
return this._dispatchAndAck(msgNode, msgId);
|
|
978
995
|
}
|