aunubaileys 1.0.17 β 1.0.19
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 +8 -0
- package/WAProto/fix-imports.js +1 -1
- package/lib/Socket/messages-recv.js +30 -6
- package/lib/Utils/signal.d.ts +2 -0
- package/lib/Utils/signal.js +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,14 @@ 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.19 / Baru di 1.0.19
|
|
68
|
+
|
|
69
|
+
- π **A failed retry request can no longer kill your bot** β the retry-receipt path after a decrypt failure runs fire-and-forget (by design), but its promise had no error handler at all: one failure inside it became an unhandled rejection, and Node's default policy terminates the whole process. It now routes to the standard error handler β logged, contained, and the bot keeps processing messages.
|
|
70
|
+
|
|
71
|
+
### π What's new in 1.0.18 / Baru di 1.0.18
|
|
72
|
+
|
|
73
|
+
- π **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.
|
|
74
|
+
|
|
67
75
|
### π What's new in 1.0.17 / Baru di 1.0.17
|
|
68
76
|
|
|
69
77
|
- π₯ **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.
|
package/WAProto/fix-imports.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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');
|
|
@@ -711,7 +729,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
711
729
|
else {
|
|
712
730
|
logger.debug({ node }, 'connection closed, ignoring retry req');
|
|
713
731
|
}
|
|
714
|
-
});
|
|
732
|
+
}).catch(err => onUnexpectedError(err, 'sending retry request'));
|
|
733
|
+
// AunuBaileys: this mutex call is deliberately fire-and-forget
|
|
734
|
+
// (it must not block message processing), but until now its
|
|
735
|
+
// rejection was UNHANDLED β a failed retry request (socket
|
|
736
|
+
// died mid-flight, pre-key fetch error) crashed the whole
|
|
737
|
+
// process under Node's default unhandled-rejection policy.
|
|
738
|
+
// It is now routed to the standard error handler instead.
|
|
715
739
|
}
|
|
716
740
|
else {
|
|
717
741
|
if (author) {
|
package/lib/Utils/signal.d.ts
CHANGED
|
@@ -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
|
package/lib/Utils/signal.js
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "1.0.19",
|
|
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",
|