whalibmob 5.5.64 → 5.5.65

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/BinaryNode.js CHANGED
@@ -127,9 +127,25 @@ function _writeBytes(buf, parts) {
127
127
  parts.push(buf);
128
128
  }
129
129
 
130
+ // AD_JID agent byte ↔ domain. 0 = phone JIDs, 1 = LIDs.
131
+ const _AGENT_SERVER = { 0: 's.whatsapp.net', 1: 'lid' };
132
+ function _serverForAgent(agent) {
133
+ return _AGENT_SERVER[agent] || 's.whatsapp.net';
134
+ }
135
+ function _agentForServer(server) {
136
+ return server === 'lid' ? 1 : 0;
137
+ }
138
+
130
139
  function _writeJid(jid, parts) {
131
- if (jid.agent || jid.device) {
132
- parts.push(Buffer.from([TAG.AD_JID, jid.agent || 0, jid.device || 0]));
140
+ // Derive the agent from the domain only when this JID is going out as AD_JID
141
+ // anyway (explicit agent, or a device suffix). A plain LID keeps its
142
+ // JID_PAIR + 'lid' server-token encoding, which is what the server expects
143
+ // and what the rest of the codebase already produces.
144
+ const agent = (jid.agent !== undefined && jid.agent !== null)
145
+ ? jid.agent
146
+ : (jid.device ? _agentForServer(jid.server) : 0);
147
+ if (agent || jid.device) {
148
+ parts.push(Buffer.from([TAG.AD_JID, agent || 0, jid.device || 0]));
133
149
  _writeString(jid.user, parts);
134
150
  } else {
135
151
  parts.push(Buffer.from([TAG.JID_PAIR]));
@@ -289,7 +305,15 @@ class NodeDecoder {
289
305
  const device = this._readByte();
290
306
  const userToken = this._readByte();
291
307
  const user = this._readString(userToken);
292
- return { user, agent, device, server: 's.whatsapp.net', toString() { return `${user}@s.whatsapp.net`; } };
308
+ // The agent byte selects the domain it is not decoration. Hardcoding
309
+ // s.whatsapp.net turned every LID participant (agent 1) into a phone JID
310
+ // that does not exist, so device queries for it came back empty, no Signal
311
+ // session was ever built, and group sends were rejected with ack 479.
312
+ const server = _serverForAgent(agent);
313
+ return {
314
+ user, agent, device, server,
315
+ toString() { return `${user}@${server}`; }
316
+ };
293
317
  }
294
318
  }
295
319
 
package/lib/Client.js CHANGED
@@ -148,8 +148,11 @@ class WhalibmobClient extends EventEmitter {
148
148
  this._pnToLid = new Map(); // phone number → LID user
149
149
  this._myLid = null; // own LID JID received from <success> node
150
150
  this._groupAddressingMode = new Map(); // groupJid → 'lid' | 'pn'
151
- this._retryPending = new Map(); // msgId → {node, retryCount}
152
- this._retryPreKeyIdx = 0; // rotating index for assigning unique prekeys to retries
151
+ this._retryPending = new Map(); // msgId → {node, count, pkId}
152
+ this._retryPreKeyIdx = 0; // rotating index, fallback when every prekey is reserved
153
+ // keyIds handed out by unresolved retries — never advertise one twice, or
154
+ // the second sender's pkmsg arrives after the key was consumed and deleted.
155
+ this._retryAdvertisedPreKeys = new Set();
153
156
  this._appStateVersions = {}; // collectionName → version (int)
154
157
  this._sentMsgCache = new Map(); // msgId → {plaintext, toJid, msgId, mediaType, options, recipientPhone}
155
158
  this._tcTokenStore = null; // TcTokenStore — loaded in init()
@@ -686,6 +689,7 @@ class WhalibmobClient extends EventEmitter {
686
689
 
687
690
  this._signal.decrypt(sigJid, encType, cipherBuf)
688
691
  .then(async plaintext => {
692
+ this._resolveRetry(id);
689
693
  const decoded = this._decodeMsg(plaintext);
690
694
  _whaDbg('[DBG] DM_DECODED id=' + id + ' type=' + decoded.type + ' ptLen=' + (plaintext ? plaintext.length : 0) + ' hasSKDM=' + !!(decoded.skdm || decoded.type === 'senderKeyDistribution'));
691
695
 
@@ -756,6 +760,7 @@ class WhalibmobClient extends EventEmitter {
756
760
 
757
761
  this._signal.senderKeyDecrypt(from, participant, cipherBuf)
758
762
  .then(plaintext => {
763
+ this._resolveRetry(id);
759
764
  const decoded = this._decodeMsg(plaintext);
760
765
  if (decoded.type === 'senderKeyDistribution') return;
761
766
  if (decoded.type === 'protocol') {
@@ -959,6 +964,17 @@ class WhalibmobClient extends EventEmitter {
959
964
  // - <registration> 4-byte reg-id
960
965
  // - <keys> block (from retry #1): type + identity + key + skey + device-identity
961
966
 
967
+ // A message we asked to be re-sent finally decrypted (or we gave up on it).
968
+ // Release its reserved prekey so the pool does not drain over a long session.
969
+ _resolveRetry(msgId) {
970
+ const pending = this._retryPending.get(msgId);
971
+ if (!pending) return;
972
+ if (pending.pkId !== null && pending.pkId !== undefined) {
973
+ this._retryAdvertisedPreKeys.delete(pending.pkId);
974
+ }
975
+ this._retryPending.delete(msgId);
976
+ }
977
+
962
978
  _sendRetryRequest(msgId, origNode) {
963
979
  const existing = this._retryPending.get(msgId);
964
980
  const count = existing ? existing.count + 1 : 1;
@@ -966,13 +982,25 @@ class WhalibmobClient extends EventEmitter {
966
982
 
967
983
  if (!this._socket || !this._connected) return;
968
984
 
969
- // Assign a stable, unique prekey index per msgId on first retry
970
- let pkIdx = existing ? existing.pkIdx : -1;
971
- if (pkIdx < 0 && this._signal) {
985
+ // Pick a prekey that is not already advertised by another unresolved retry.
986
+ // A prekey is deleted the moment a pkmsg using it is decrypted, so handing
987
+ // the same one to two senders means the second arrives after the key is
988
+ // gone and dies with "Invalid PreKey ID". Indexing modulo the key count
989
+ // could not prevent that: the list shrinks as keys are consumed, so the
990
+ // rotating index wraps back onto ids that are still in flight.
991
+ let pkId = existing ? existing.pkId : null;
992
+ if (pkId === null && this._signal) {
972
993
  const allKeys = this._signal.getPreKeysForUpload(800);
973
- pkIdx = this._retryPreKeyIdx++ % Math.max(1, allKeys.length);
994
+ const free = allKeys.find(k => !this._retryAdvertisedPreKeys.has(k.keyId));
995
+ // If every key is already spoken for, fall back to rotating rather than
996
+ // sending no bundle at all — a stale id beats no retry.
997
+ const chosen = free || allKeys[this._retryPreKeyIdx++ % Math.max(1, allKeys.length)];
998
+ if (chosen) {
999
+ pkId = chosen.keyId;
1000
+ this._retryAdvertisedPreKeys.add(pkId);
1001
+ }
974
1002
  }
975
- this._retryPending.set(msgId, { node: origNode, count, pkIdx });
1003
+ this._retryPending.set(msgId, { node: origNode, count, pkId });
976
1004
 
977
1005
  // Use original message timestamp (not current time)
978
1006
  const origAttrs = (origNode && origNode.attrs) || {};
@@ -991,7 +1019,7 @@ class WhalibmobClient extends EventEmitter {
991
1019
  const spk = this._signal.getSignedPreKeyForUpload();
992
1020
  const identKey = this._signal.getIdentityKey();
993
1021
  const allPreKeys = this._signal.getPreKeysForUpload(800);
994
- const pk = allPreKeys[pkIdx] || allPreKeys[0];
1022
+ const pk = allPreKeys.find(k => k.keyId === pkId) || allPreKeys[0];
995
1023
 
996
1024
  if (spk && identKey && pk) {
997
1025
  const keysChildren = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.5.64",
3
+ "version": "5.5.65",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto50",
6
6
  "main": "index.js",