aunubaileys 1.0.17 β†’ 1.0.18

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 CHANGED
@@ -64,6 +64,10 @@ Everything else is pure upstream. When upstream moves, we rebase the patch layer
64
64
 
65
65
 
66
66
 
67
+ ### πŸ†• What's new in 1.0.18 / Baru di 1.0.18
68
+
69
+ - πŸ” **Retry receipts: the peer's own key bundle is finally used** β€” when a recipient device asks for a resend, its retry receipt often embeds that device's own current key bundle. Upstream ignored it and always re-fetched a bundle from the server (extra round trip, and JIDβ†’bundle mapping is fragile for LID participants). AunuBaileys now parses the bundle (new exported `parseRetryReceiptSession`, unit-verified) and injects it as the fresh session for the resend, skipping the server fetch for that resend. Receipts without a bundle behave exactly as before.
70
+
67
71
  ### πŸ†• What's new in 1.0.17 / Baru di 1.0.17
68
72
 
69
73
  - πŸ‘₯ **Group sender-key heal** β€” the 1.0.14 failure counter now covers group messages too: after 3 consecutive group decrypt failures from the same author in the same group, the stale sender-key record is deleted (new `signalRepository.deleteSenderKey`), so the next retry makes the author resend with a fresh distribution message and the group cipher rebuilds from scratch. One rotten sender-key record can no longer mute a group member forever.
@@ -5,7 +5,7 @@ import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
7
7
  import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, parseRetryReceiptSession, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
10
  import { sentMessageCacheFor } from '../Utils/sent-message-cache.js';
11
11
  import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
@@ -481,8 +481,7 @@ export const makeMessagesRecvSocket = (config) => {
481
481
  const newValue = (msgRetryCache.get(key) || 0) + 1;
482
482
  msgRetryCache.set(key, newValue);
483
483
  };
484
- const sendMessagesAgain = async (key, ids, retryNode) => {
485
- // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
484
+ const sendMessagesAgain = async (key, ids, retryNode, sessionPreInjected = false) => {
486
485
  const msgs = await Promise.all(ids.map(async (id) => (await getMessage({ ...key, id })) ?? sentMessageCacheFor(config).get(key.remoteJid, id)));
487
486
  const remoteJid = key.remoteJid;
488
487
  const participant = key.participant || remoteJid;
@@ -490,7 +489,9 @@ export const makeMessagesRecvSocket = (config) => {
490
489
  // just re-send the message to everyone
491
490
  // prevents the first message decryption failure
492
491
  const sendToAll = !jidDecode(participant)?.device;
493
- await assertSessions([participant], true);
492
+ if (!sessionPreInjected) {
493
+ await assertSessions([participant], true);
494
+ }
494
495
  if (isJidGroup(remoteJid)) {
495
496
  await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
496
497
  }
@@ -572,7 +573,24 @@ export const makeMessagesRecvSocket = (config) => {
572
573
  if (key.fromMe) {
573
574
  try {
574
575
  logger.debug({ attrs, key }, 'recv retry request');
575
- await sendMessagesAgain(key, ids, retryNode);
576
+ // AunuBaileys: when the retry receipt carries the
577
+ // requester's own key bundle, build the fresh
578
+ // session from THAT (authoritative for the exact
579
+ // device, no server round trip) and skip the
580
+ // force-fetch inside sendMessagesAgain.
581
+ let sessionPreInjected = false;
582
+ const bundleSession = parseRetryReceiptSession(node);
583
+ if (bundleSession && key.participant) {
584
+ try {
585
+ await signalRepository.injectE2ESession({ jid: key.participant, session: bundleSession });
586
+ sessionPreInjected = true;
587
+ logger.debug({ participant: key.participant }, 'aunu: injected E2E session from retry receipt bundle');
588
+ }
589
+ catch (injErr) {
590
+ logger.debug({ err: injErr }, 'aunu: retry receipt bundle injection failed; falling back to server fetch');
591
+ }
592
+ }
593
+ await sendMessagesAgain(key, ids, retryNode, sessionPreInjected);
576
594
  }
577
595
  catch (error) {
578
596
  logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
@@ -16,6 +16,8 @@ export declare const generateOrGetPreKeys: (creds: AuthenticationCreds, range: n
16
16
  export declare const xmppSignedPreKey: (key: SignedKeyPair) => BinaryNode;
17
17
  export declare const xmppPreKey: (pair: KeyPair, id: number) => BinaryNode;
18
18
  export declare const parseAndInjectE2ESessions: (node: BinaryNode, repository: SignalRepository) => Promise<void>;
19
+ /** AunuBaileys: session parsed from a retry receipt's embedded key bundle, or undefined when absent. */
20
+ export declare const parseRetryReceiptSession: (node: BinaryNode) => { registrationId: number; identityKey: Buffer; signedPreKey?: { keyId: number; publicKey: Buffer; signature?: Buffer }; preKey?: { keyId: number; publicKey: Buffer } } | undefined;
19
21
  export declare const extractDeviceJids: (result: USyncQueryResultList[], myJid: string, excludeZeroDevices: boolean) => JidWithDevice[];
20
22
  /**
21
23
  * get the next N keys for upload or processing
@@ -55,6 +55,37 @@ export const xmppPreKey = (pair, id) => ({
55
55
  { tag: 'value', attrs: {}, content: pair.public }
56
56
  ]
57
57
  });
58
+ /** AunuBaileys: parse the E2E key bundle embedded in a retry receipt.
59
+ * When a recipient device asks us to resend, its receipt often carries the
60
+ * device's OWN current key bundle (<keys> + <registration>) β€” the device
61
+ * itself stating "build your session to me from THIS". Upstream ignores it
62
+ * and re-fetches a bundle from the server instead; consuming the receipt's
63
+ * bundle skips that round trip and, more importantly, is authoritative for
64
+ * the exact requesting device (server lookups by JID are fragile for LID
65
+ * participants). Returns undefined when the receipt carries no bundle. */
66
+ export const parseRetryReceiptSession = (node) => {
67
+ const keysNode = getBinaryNodeChild(node, 'keys');
68
+ if (!keysNode) {
69
+ return undefined;
70
+ }
71
+ const identity = getBinaryNodeChildBuffer(keysNode, 'identity');
72
+ if (!identity) {
73
+ return undefined;
74
+ }
75
+ const extractKey = (key) => key
76
+ ? {
77
+ keyId: getBinaryNodeChildUInt(key, 'id', 3),
78
+ publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')),
79
+ signature: getBinaryNodeChildBuffer(key, 'signature')
80
+ }
81
+ : undefined;
82
+ return {
83
+ registrationId: getBinaryNodeChildUInt(node, 'registration', 4),
84
+ identityKey: generateSignalPubKey(identity),
85
+ signedPreKey: extractKey(getBinaryNodeChild(keysNode, 'skey')),
86
+ preKey: extractKey(getBinaryNodeChild(keysNode, 'key'))
87
+ };
88
+ };
58
89
  export const parseAndInjectE2ESessions = async (node, repository) => {
59
90
  const extractKey = (key) => key
60
91
  ? {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aunubaileys",
3
3
  "type": "module",
4
- "version": "1.0.17",
4
+ "version": "1.0.18",
5
5
  "description": "AunuBaileys \u2014 a WhatsApp Web API for bots that never sleep. Full upstream power with the beloved in-memory store restored and pairing that just works. Forged by AunuCore.",
6
6
  "keywords": [
7
7
  "whatsapp",