@aunucore/baileys 1.0.80 → 1.0.82
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/Socket/chats.js +24 -2
- package/lib/Socket/interop.js +12 -1
- package/lib/Socket/messages-recv.js +199 -36
- package/lib/Socket/socket.js +24 -7
- package/lib/Utils/decode-wa-message.js +21 -0
- package/lib/Utils/event-buffer.js +40 -3
- package/package.json +1 -1
package/lib/Socket/chats.js
CHANGED
|
@@ -884,7 +884,19 @@ export const makeChatsSocket = (config) => {
|
|
|
884
884
|
}
|
|
885
885
|
// update our pushname too
|
|
886
886
|
if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
|
|
887
|
-
|
|
887
|
+
// AunuBaileys (1.0.81, HB-257): 'creds.update' is
|
|
888
|
+
// not a bufferable event, so this emit is
|
|
889
|
+
// synchronous — and it sits BEFORE the Promise.all
|
|
890
|
+
// below, so a throwing listener used to reject
|
|
891
|
+
// upsertMessage before processMessage ever ran for
|
|
892
|
+
// this message, costing a NACK 500 on a delivered
|
|
893
|
+
// stanza. Log and continue with the message.
|
|
894
|
+
try {
|
|
895
|
+
ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } });
|
|
896
|
+
}
|
|
897
|
+
catch (err) {
|
|
898
|
+
onUnexpectedError(err, 'creds.update (pushName)');
|
|
899
|
+
}
|
|
888
900
|
}
|
|
889
901
|
}
|
|
890
902
|
const historyMsg = getHistoryMsg(msg.message);
|
|
@@ -917,7 +929,17 @@ export const makeChatsSocket = (config) => {
|
|
|
917
929
|
logger.info('App state sync complete, transitioning to Online state and flushing buffer');
|
|
918
930
|
ev.flush();
|
|
919
931
|
const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1;
|
|
920
|
-
|
|
932
|
+
// AunuBaileys (1.0.81, HB-257): same guard as the
|
|
933
|
+
// pushName emit above — a throwing 'creds.update'
|
|
934
|
+
// listener must not reject the app-state sync that
|
|
935
|
+
// just completed (and skip the rest of this
|
|
936
|
+
// message's processing with it).
|
|
937
|
+
try {
|
|
938
|
+
ev.emit('creds.update', { accountSyncCounter });
|
|
939
|
+
}
|
|
940
|
+
catch (err) {
|
|
941
|
+
onUnexpectedError(err, 'creds.update (accountSyncCounter)');
|
|
942
|
+
}
|
|
921
943
|
}
|
|
922
944
|
};
|
|
923
945
|
await Promise.all([
|
package/lib/Socket/interop.js
CHANGED
|
@@ -425,7 +425,18 @@ export const makeInteropSocket = (sock) => {
|
|
|
425
425
|
throw new Error(`sendInteropMessage: not an interop JID: ${jid}`);
|
|
426
426
|
}
|
|
427
427
|
const sent = await sock.sendMessage(jid, content, options);
|
|
428
|
-
|
|
428
|
+
// AunuBaileys (1.0.81, HB-196): this emit reports a send
|
|
429
|
+
// that has ALREADY succeeded — a throwing 'interop'
|
|
430
|
+
// listener used to reject the promise anyway, so the
|
|
431
|
+
// caller saw a failure and retried, duplicating a
|
|
432
|
+
// delivered message. Log the listener error and return
|
|
433
|
+
// the send result.
|
|
434
|
+
try {
|
|
435
|
+
ev.emit('interop', { type: 'message.sent', jid, id: sent?.key?.id });
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
logger?.error?.({ err }, 'interop: error in message.sent listener');
|
|
439
|
+
}
|
|
429
440
|
return sent;
|
|
430
441
|
};
|
|
431
442
|
return {
|
|
@@ -44,6 +44,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
44
44
|
* upstream Baileys retries forever against the same stale session. */
|
|
45
45
|
const decryptFailCounts = new Map();
|
|
46
46
|
const DECRYPT_FAIL_SESSION_RESET_LIMIT = 3;
|
|
47
|
+
/** AunuBaileys (1.0.82, HB-153): upstream v7 skips the retry for
|
|
48
|
+
* status broadcasts older than this — the peer has already
|
|
49
|
+
* deleted the expired status, so a resend can never be honoured
|
|
50
|
+
* and the failure says nothing about the session. */
|
|
51
|
+
const STATUS_EXPIRY_SECONDS = 86400;
|
|
52
|
+
/** AunuBaileys (1.0.82, HB-152): upstream v7 only asks the phone
|
|
53
|
+
* to resend a placeholder for messages younger than this. */
|
|
54
|
+
const PLACEHOLDER_MAX_AGE_SECONDS = 14 * 86400;
|
|
47
55
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
48
56
|
const retryMutex = makeMutex();
|
|
49
57
|
// AunuBaileys: bounded by default (see bounded-cache.js) — a TTL
|
|
@@ -238,8 +246,28 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
238
246
|
}
|
|
239
247
|
if (retryCount > 1 || forceIncludeKeys) {
|
|
240
248
|
const { update, preKeys } = await getNextPreKeys(authState, 1);
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
let [keyId] = Object.keys(preKeys);
|
|
250
|
+
let key = preKeys[+keyId];
|
|
251
|
+
if (!key) {
|
|
252
|
+
// AunuBaileys (1.0.82, HB-154 + addendum): the
|
|
253
|
+
// stored head pre-key is missing entirely (store
|
|
254
|
+
// cleaned / partial restore) or was filtered out
|
|
255
|
+
// as corrupt by getNextPreKeys ({} / wrong-typed
|
|
256
|
+
// material, HB-83/98 filter) — the map came back
|
|
257
|
+
// empty and the bare deref below used to throw a
|
|
258
|
+
// TypeError inside this transaction: the receipt
|
|
259
|
+
// was never built, yet the retry counter above
|
|
260
|
+
// was already spent. Generate a fresh pair into
|
|
261
|
+
// the dead head slot instead: `update` advances
|
|
262
|
+
// firstUnuploadedPreKeyId past exactly this id,
|
|
263
|
+
// and the write joins this transaction, so the
|
|
264
|
+
// private half is persisted before the receipt
|
|
265
|
+
// carrying its public half is sent.
|
|
266
|
+
keyId = `${authState.creds.firstUnuploadedPreKeyId}`;
|
|
267
|
+
key = Curve.generateKeyPair();
|
|
268
|
+
await authState.keys.set({ 'pre-key': { [keyId]: key } });
|
|
269
|
+
logger.warn({ keyId }, 'aunu: no usable stored pre-key for the retry receipt; generated a fresh one into the head slot');
|
|
270
|
+
}
|
|
243
271
|
const content = receipt.content;
|
|
244
272
|
content.push({
|
|
245
273
|
tag: 'keys',
|
|
@@ -670,7 +698,21 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
670
698
|
msgRetryCache.set(key, newValue);
|
|
671
699
|
};
|
|
672
700
|
const sendMessagesAgain = async (key, ids, retryNode, sessionPreInjected = false) => {
|
|
673
|
-
|
|
701
|
+
// AunuBaileys (1.0.82, HB-107): per-id isolation at the FETCH
|
|
702
|
+
// stage, mirroring HB-61 at the relay stage below. getMessage
|
|
703
|
+
// is host code (a DB/file store can throw); one throwing id
|
|
704
|
+
// used to reject the whole Promise.all, so NO id in the batch
|
|
705
|
+
// was resent this round and no retry counter advanced — the
|
|
706
|
+
// next peer receipt walked into the same wall.
|
|
707
|
+
const msgs = await Promise.all(ids.map(async (id) => {
|
|
708
|
+
try {
|
|
709
|
+
return (await getMessage({ ...key, id })) ?? sentMessageCacheFor(config, (jid) => aunuLid?.getAltJid?.(jid)).get(key.remoteJid, id);
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
logger.warn({ err, jid: key.remoteJid, id }, 'aunu: getMessage failed for one id in a retry batch; treating it as unavailable and continuing with the rest');
|
|
713
|
+
return undefined;
|
|
714
|
+
}
|
|
715
|
+
}));
|
|
674
716
|
const remoteJid = key.remoteJid;
|
|
675
717
|
const participant = key.participant || remoteJid;
|
|
676
718
|
// if it's the primary jid sending the request
|
|
@@ -775,7 +817,18 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
775
817
|
// correctly set who is asking for the retry
|
|
776
818
|
key.participant = key.participant || attrs.from;
|
|
777
819
|
const retryNode = getBinaryNodeChild(node, 'retry');
|
|
778
|
-
|
|
820
|
+
// AunuBaileys (1.0.82, HB-160): the gate used
|
|
821
|
+
// to check ids[0] ALONE — a capped first id
|
|
822
|
+
// starved the fresh ids behind it, and a
|
|
823
|
+
// fresh first id let capped ids behind it be
|
|
824
|
+
// resent past the cap without limit. Filter
|
|
825
|
+
// per id; the loop below only ever sees ids
|
|
826
|
+
// that still have budget.
|
|
827
|
+
const eligibleIds = ids.filter(id => willSendMessageAgain(id, key.participant));
|
|
828
|
+
if (eligibleIds.length < ids.length) {
|
|
829
|
+
logger.debug({ skipped: ids.filter(id => !eligibleIds.includes(id)), participant: key.participant }, 'aunu: some ids in the retry receipt are at the resend cap; resending only the rest');
|
|
830
|
+
}
|
|
831
|
+
if (eligibleIds.length) {
|
|
779
832
|
if (key.fromMe) {
|
|
780
833
|
try {
|
|
781
834
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
@@ -796,7 +849,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
796
849
|
logger.debug({ err: injErr }, 'aunu: retry receipt bundle injection failed; falling back to server fetch');
|
|
797
850
|
}
|
|
798
851
|
}
|
|
799
|
-
await sendMessagesAgain(key,
|
|
852
|
+
await sendMessagesAgain(key, eligibleIds, retryNode, sessionPreInjected);
|
|
800
853
|
}
|
|
801
854
|
catch (error) {
|
|
802
855
|
logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
|
|
@@ -880,13 +933,37 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
880
933
|
let response;
|
|
881
934
|
if (getBinaryNodeChild(node, 'unavailable') && !encNode) {
|
|
882
935
|
await sendMessageAck(node);
|
|
883
|
-
|
|
936
|
+
// AunuBaileys (1.0.82, HB-146): this decode sat OUTSIDE
|
|
937
|
+
// any try (the HB-1 fix covered only the decrypt path
|
|
938
|
+
// below). A stanza whose `from` is not a recognised JID
|
|
939
|
+
// type — malformed today, a future JID domain tomorrow —
|
|
940
|
+
// threw 'Unknown message type' after the ack had already
|
|
941
|
+
// gone out: only logged by the caller, and the
|
|
942
|
+
// placeholder was never resent nor upserted. Classify
|
|
943
|
+
// and drop it here, like the main decode path.
|
|
944
|
+
let key;
|
|
945
|
+
try {
|
|
946
|
+
key = decodeMessageNode(node, authState.creds.me?.id, authState.creds.me?.lid || '').fullMessage.key;
|
|
947
|
+
}
|
|
948
|
+
catch (decodeErr) {
|
|
949
|
+
logger.error({ error: decodeErr, node }, 'aunu: error decoding unavailable message node; acked, skipping placeholder resend');
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
884
952
|
// AunuBaileys: bot / hosted / view_once placeholders can never
|
|
885
953
|
// be resent by the phone — don't burn a resend request on
|
|
886
954
|
// them (behaviour learned from @violetix/baileys).
|
|
887
955
|
if (isUnrecoverableUnavailable(node)) {
|
|
888
956
|
logger.debug({ msgId: key.id, unavailableType: unavailablePlaceholderType(node) }, 'unavailable placeholder cannot be resent; skipping resend request');
|
|
889
957
|
}
|
|
958
|
+
else if (!authState.creds.me?.id) {
|
|
959
|
+
// AunuBaileys (1.0.82, HB-106): requestPlaceholderResend
|
|
960
|
+
// throws Boom 'Not authenticated' in the pre-success
|
|
961
|
+
// window — and because the ack above has ALREADY gone
|
|
962
|
+
// out, the throw (caught only by the caller's logger)
|
|
963
|
+
// lost the placeholder silently: no resend, no upsert.
|
|
964
|
+
// Skip cleanly, like sendRetryRequest does (HB-6).
|
|
965
|
+
logger.warn({ msgId: key.id }, 'aunu: cannot request placeholder resend before pairing completes; skipping');
|
|
966
|
+
}
|
|
890
967
|
else {
|
|
891
968
|
response = await requestPlaceholderResend(key);
|
|
892
969
|
}
|
|
@@ -941,14 +1018,85 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
941
1018
|
}
|
|
942
1019
|
// message failed to decrypt
|
|
943
1020
|
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) {
|
|
944
|
-
|
|
945
|
-
|
|
1021
|
+
const stubParam = msg?.messageStubParameters?.[0];
|
|
1022
|
+
const isMissingKeysFailure = stubParam === MISSING_KEYS_ERROR_TEXT;
|
|
1023
|
+
const isNoMessageFailure = stubParam === NO_MESSAGE_FOUND_ERROR_TEXT;
|
|
1024
|
+
// AunuBaileys (1.0.82, HB-170): an msmsg (Meta
|
|
1025
|
+
// AI) failure is a message-SECRET problem, not
|
|
1026
|
+
// an E2E session problem — @bot has no resend
|
|
1027
|
+
// path, so a retry receipt only burns budget
|
|
1028
|
+
// and the fail counter marched toward a
|
|
1029
|
+
// pointless @bot session reset.
|
|
1030
|
+
const isMsmsgFailure = encNode?.attrs?.type === 'msmsg' ||
|
|
1031
|
+
(typeof stubParam === 'string' && stubParam.startsWith('msmsg'));
|
|
1032
|
+
// AunuBaileys (1.0.82, HB-153): a status that
|
|
1033
|
+
// expired on the peer can never be resent.
|
|
1034
|
+
const isExpiredStatusFailure = !isNoMessageFailure &&
|
|
1035
|
+
isJidStatusBroadcast(msg.key.remoteJid) &&
|
|
1036
|
+
Number.isFinite(+node.attrs.t) &&
|
|
1037
|
+
(unixTimestampSeconds() - +node.attrs.t) > STATUS_EXPIRY_SECONDS;
|
|
1038
|
+
if (isMissingKeysFailure) {
|
|
1039
|
+
// AunuBaileys (1.0.82, HB-206): the 487
|
|
1040
|
+
// NACK stays, but the early return is
|
|
1041
|
+
// gone. 'Key used already or never filled'
|
|
1042
|
+
// is a SESSION-layer failure, so it now
|
|
1043
|
+
// flows through the counting / self-heal
|
|
1044
|
+
// machinery below and the stub upsert at
|
|
1045
|
+
// the end of this branch, like every other
|
|
1046
|
+
// decrypt failure — only the retry receipt
|
|
1047
|
+
// is skipped (a resend of the same
|
|
1048
|
+
// ciphertext cannot restore a message key
|
|
1049
|
+
// our ratchet no longer holds).
|
|
1050
|
+
await sendMessageAck(node, NACK_REASONS.ParsingError);
|
|
1051
|
+
}
|
|
1052
|
+
if (isNoMessageFailure) {
|
|
1053
|
+
// AunuBaileys (1.0.82, HB-152): the stanza
|
|
1054
|
+
// carried no encrypted content at all —
|
|
1055
|
+
// there is nothing to re-decrypt and
|
|
1056
|
+
// nothing wrong with the session, so no
|
|
1057
|
+
// retry receipt and no fail count (three
|
|
1058
|
+
// of these used to reset a healthy
|
|
1059
|
+
// session). Ask the phone for a placeholder
|
|
1060
|
+
// resend when eligible (v7 policy:
|
|
1061
|
+
// recoverable type, fresh enough) and let
|
|
1062
|
+
// the stub upsert below stand as the
|
|
1063
|
+
// trace. Fire-and-forget: the resend waits
|
|
1064
|
+
// 5 s and must not hold the processing
|
|
1065
|
+
// mutex (see the 1.0.70 note in
|
|
1066
|
+
// sendRetryRequest).
|
|
1067
|
+
if (!response && !msg.key.fromMe && !isUnrecoverableUnavailable(node) &&
|
|
1068
|
+
Number.isFinite(+node.attrs.t) &&
|
|
1069
|
+
(unixTimestampSeconds() - +node.attrs.t) <= PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1070
|
+
requestPlaceholderResend(msg.key).then(
|
|
1071
|
+
(resendId) => logger.debug({ resendId }, 'aunu: requested placeholder resend for a message with no encrypted content'),
|
|
1072
|
+
(err) => logger.debug({ err }, 'aunu: placeholder resend for a message with no encrypted content failed')
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
logger.debug({ msgId: msg.key.id, author }, 'aunu: message stanza carried no encrypted content; no retry receipt, no session failure counted');
|
|
946
1076
|
}
|
|
947
|
-
|
|
1077
|
+
else if (isMsmsgFailure) {
|
|
1078
|
+
logger.debug({ msgId: msg.key.id, author, stubParam }, 'aunu: msmsg decrypt failure; no retry receipt and no session failure counted (a secret problem, not a session problem)');
|
|
1079
|
+
}
|
|
1080
|
+
else if (isExpiredStatusFailure) {
|
|
1081
|
+
logger.debug({ msgId: msg.key.id, author, ageSeconds: unixTimestampSeconds() - +node.attrs.t }, 'aunu: decrypt failure for an expired status; no retry receipt and no session failure counted');
|
|
1082
|
+
}
|
|
1083
|
+
else retryMutex.mutex(async () => {
|
|
948
1084
|
if (ws.isOpen) {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1085
|
+
// AunuBaileys (1.0.82, HB-183): the
|
|
1086
|
+
// blanket early return for stanzas
|
|
1087
|
+
// carrying an <unavailable> child is
|
|
1088
|
+
// gone — it suppressed the retry
|
|
1089
|
+
// receipt, the fail counting, the
|
|
1090
|
+
// self-heal AND the decrypt-failure
|
|
1091
|
+
// event for every enc-bearing failure
|
|
1092
|
+
// that rode along with one (view-once
|
|
1093
|
+
// above all), leaving the content
|
|
1094
|
+
// unrecoverable and the rotten session
|
|
1095
|
+
// uncounted. Upstream v7 has no such
|
|
1096
|
+
// guard on this path; the placeholder
|
|
1097
|
+
// choice for unavailable types lives in
|
|
1098
|
+
// the no-enc branch and in HB-152's
|
|
1099
|
+
// classification above.
|
|
952
1100
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
953
1101
|
let forceIncludeKeys = !encNode;
|
|
954
1102
|
if (author) {
|
|
@@ -1058,7 +1206,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1058
1206
|
// under that device's session; a retry
|
|
1059
1207
|
// receipt would ask ourselves to resend to
|
|
1060
1208
|
// ourselves.
|
|
1061
|
-
if (!msg.key.fromMe) {
|
|
1209
|
+
if (!msg.key.fromMe && !isMissingKeysFailure) {
|
|
1062
1210
|
await sendRetryRequest(node, forceIncludeKeys);
|
|
1063
1211
|
if (retryRequestDelayMs) {
|
|
1064
1212
|
await delay(retryRequestDelayMs);
|
|
@@ -1474,12 +1622,16 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1474
1622
|
const offlineNodeProcessor = makeOfflineNodeProcessor();
|
|
1475
1623
|
const processNode = (type, node, identifier, exec) => {
|
|
1476
1624
|
// AunuBaileys: ignoreKey filter — a matching stanza is dropped
|
|
1477
|
-
// before any handling (no decryption, no persistence);
|
|
1625
|
+
// before any handling (no decryption, no persistence); stanzas
|
|
1478
1626
|
// are still acked so the server stops redelivering them.
|
|
1627
|
+
// AunuBaileys (1.0.82, HB-210): the ack used to cover only
|
|
1628
|
+
// type 'message' — a dropped receipt/notification/call was
|
|
1629
|
+
// never acked, so the server redelivered it forever while the
|
|
1630
|
+
// rule stayed active (all four real handlers ack in a
|
|
1631
|
+
// finally; the drop bypassed every one of them, against the
|
|
1632
|
+
// filter's own documented intent).
|
|
1479
1633
|
if (sock.aunuIgnoreFilter?.shouldDrop(type, node, [authState.creds.me?.id, authState.creds.me?.lid].filter(Boolean))) {
|
|
1480
|
-
|
|
1481
|
-
sendMessageAck(node).catch(() => { });
|
|
1482
|
-
}
|
|
1634
|
+
sendMessageAck(node).catch(() => { });
|
|
1483
1635
|
logger.debug({ type, from: node.attrs.from, id: node.attrs.id }, 'aunu: stanza dropped by ignoreKey');
|
|
1484
1636
|
return;
|
|
1485
1637
|
}
|
|
@@ -1727,25 +1879,36 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1727
1879
|
// first without a catch, so a socket that closed in that window
|
|
1728
1880
|
// (sendNode throws Boom 'Connection Closed') swallowed the
|
|
1729
1881
|
// status event entirely.
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1882
|
+
// AunuBaileys (1.0.81, HB-192): the ack now sits in a
|
|
1883
|
+
// finally. The emit-first order is unchanged (HB-59), but
|
|
1884
|
+
// a throwing 'newsletter.status' listener can no longer
|
|
1885
|
+
// take the ack down with it — an un-acked <status> stanza
|
|
1886
|
+
// is redelivered (or the status lost) for the sake of a
|
|
1887
|
+
// host bug. The throw itself still propagates to the
|
|
1888
|
+
// CB:status registration's catch, as before.
|
|
1889
|
+
try {
|
|
1890
|
+
ev.emit('newsletter.status', {
|
|
1891
|
+
id: from,
|
|
1892
|
+
messageId: id,
|
|
1893
|
+
serverId,
|
|
1894
|
+
timestamp,
|
|
1895
|
+
isSender,
|
|
1896
|
+
...(offlineIndex !== undefined ? { offlineIndex } : {}),
|
|
1897
|
+
...(meta ? { meta } : {}),
|
|
1898
|
+
...(viewsCount !== undefined ? { viewsCount } : {}),
|
|
1899
|
+
...(responsesCount !== undefined ? { responsesCount } : {}),
|
|
1900
|
+
...(reactionCounts.length ? { reactionCounts } : {}),
|
|
1901
|
+
content
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
finally {
|
|
1905
|
+
const ackType = type === 'reaction' ? 'reaction' : edit ? 'revoke' : type;
|
|
1906
|
+
await sendNode({
|
|
1907
|
+
tag: 'ack',
|
|
1908
|
+
attrs: { id, to: from, class: 'status', type: ackType || 'text' },
|
|
1909
|
+
content: undefined
|
|
1910
|
+
}).catch((err) => logger.warn({ err }, 'failed to ack newsletter status'));
|
|
1911
|
+
}
|
|
1749
1912
|
};
|
|
1750
1913
|
// recv a message
|
|
1751
1914
|
ws.on('CB:message', (node) => {
|
package/lib/Socket/socket.js
CHANGED
|
@@ -367,13 +367,30 @@ export const makeSocket = (config) => {
|
|
|
367
367
|
}
|
|
368
368
|
catch { }
|
|
369
369
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
370
|
+
// AunuBaileys (1.0.81, HB-138): this emit sits BEFORE the
|
|
371
|
+
// socket-end handlers and ev.destroy() below — cleanup the
|
|
372
|
+
// lib owns (watchdog stop, offline-drain stop, scheduled-
|
|
373
|
+
// task cleanup). A throwing host 'connection.update'
|
|
374
|
+
// listener used to unwind straight out of end() — and,
|
|
375
|
+
// via the ws 'error'/'close' handlers that call end(),
|
|
376
|
+
// out of the ws emit chain into an uncaught exception —
|
|
377
|
+
// skipping all of it, with no recovery path: `closed` is
|
|
378
|
+
// already true, so a second end() is a no-op. The
|
|
379
|
+
// event-buffer dispatcher already logs listener errors
|
|
380
|
+
// (1.0.81); this guard keeps end()'s own cleanup
|
|
381
|
+
// unconditional regardless of the emit path.
|
|
382
|
+
try {
|
|
383
|
+
ev.emit('connection.update', {
|
|
384
|
+
connection: 'close',
|
|
385
|
+
lastDisconnect: {
|
|
386
|
+
error,
|
|
387
|
+
date: new Date()
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
logger.error({ err }, 'error emitting connection close update');
|
|
393
|
+
}
|
|
377
394
|
for (const handler of socketEndHandlers) {
|
|
378
395
|
try {
|
|
379
396
|
handler();
|
|
@@ -287,6 +287,17 @@ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
|
|
|
287
287
|
// 'last' = complete response, 'first' =
|
|
288
288
|
// streaming partial, intentionally skipped.
|
|
289
289
|
if (botType !== null && !['full', 'last'].includes(botType)) {
|
|
290
|
+
// AunuBaileys (1.0.82, HB-171
|
|
291
|
+
// addendum): the skip used to leave
|
|
292
|
+
// no trace at all — the caller took
|
|
293
|
+
// the SUCCESS branch, sent a
|
|
294
|
+
// 'delivered' receipt and upserted
|
|
295
|
+
// an empty message for content that
|
|
296
|
+
// was never read. Leave a CIPHERTEXT
|
|
297
|
+
// stub, like any other undecrypted
|
|
298
|
+
// content.
|
|
299
|
+
fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
|
|
300
|
+
fullMessage.messageStubParameters = [`msmsg streaming partial skipped (edit=${botType})`];
|
|
290
301
|
break;
|
|
291
302
|
}
|
|
292
303
|
const secretIdCandidates = [botEditTargetId, metaTargetId, fullMessage.key?.id].filter(Boolean);
|
|
@@ -315,6 +326,16 @@ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
|
|
|
315
326
|
}
|
|
316
327
|
if (!secretCandidates.length) {
|
|
317
328
|
logger.warn({ metaTargetId, botType, secretIdCandidates }, 'msmsg: no candidate messageSecret found, skipping');
|
|
329
|
+
// AunuBaileys (1.0.82, HB-171): same
|
|
330
|
+
// false-success as the partial skip
|
|
331
|
+
// above — without a stub the caller
|
|
332
|
+
// receipts this as delivered and
|
|
333
|
+
// stores an empty message. The stub
|
|
334
|
+
// is the trace; messages-recv
|
|
335
|
+
// excludes the msmsg class from
|
|
336
|
+
// retry/fail counting (HB-170).
|
|
337
|
+
fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
|
|
338
|
+
fullMessage.messageStubParameters = ['msmsg: no candidate messageSecret found'];
|
|
318
339
|
break;
|
|
319
340
|
}
|
|
320
341
|
const msMsg = proto.MessageSecretMessage.decode(content);
|
|
@@ -32,7 +32,25 @@ export const makeEventBuffer = (logger) => {
|
|
|
32
32
|
// take the generic event and fire it as a baileys event
|
|
33
33
|
ev.on('event', (map) => {
|
|
34
34
|
for (const event in map) {
|
|
35
|
-
|
|
35
|
+
// AunuBaileys (1.0.81, HB-227/HB-228/HB-229): a host
|
|
36
|
+
// listener that throws must never escape this
|
|
37
|
+
// dispatcher. In the host's own call stack a throw is
|
|
38
|
+
// the host's to catch — but most emits originate inside
|
|
39
|
+
// lib timers and async callbacks (the nextTick
|
|
40
|
+
// 'connecting' update, QR cycles, the session
|
|
41
|
+
// watchdog's interval, PDO resend timeouts, ws
|
|
42
|
+
// handlers), where no host frame exists to catch it:
|
|
43
|
+
// the throw became an uncaught exception and killed
|
|
44
|
+
// the process, or unwound past lib cleanup that still
|
|
45
|
+
// had to run. Log the error and keep dispatching the
|
|
46
|
+
// remaining events of the batch; the event itself
|
|
47
|
+
// still fired, with its payload unchanged.
|
|
48
|
+
try {
|
|
49
|
+
ev.emit(event, map[event]);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.error({ err, event }, 'error in event listener — dispatch continues');
|
|
53
|
+
}
|
|
36
54
|
}
|
|
37
55
|
});
|
|
38
56
|
function buffer() {
|
|
@@ -58,10 +76,29 @@ export const makeEventBuffer = (logger) => {
|
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
78
|
const consolidatedData = consolidateEvents(data);
|
|
79
|
+
// AunuBaileys (1.0.81, HB-166 + HB-224): swap the fresh
|
|
80
|
+
// buffer in BEFORE emitting, and guard the emit. The old
|
|
81
|
+
// order assigned `data = newData` after the emit, so
|
|
82
|
+
// (a) an event a listener re-buffered during the flush
|
|
83
|
+
// emit landed in the OLD buffer object and was silently
|
|
84
|
+
// discarded with it, and (b) a throw mid-emit left the
|
|
85
|
+
// whole batch in `data`, to be delivered a second time by
|
|
86
|
+
// the next flush. With the swap first, re-buffered events
|
|
87
|
+
// survive to the next flush and the emitted batch is
|
|
88
|
+
// detached no matter what a listener does. The guard
|
|
89
|
+
// matters because flush() is called from timer callbacks
|
|
90
|
+
// (chats.js history-sync transitions) and fire-and-forget
|
|
91
|
+
// paths (processNodeWithBuffer), where a throw became an
|
|
92
|
+
// uncaught exception / unhandled rejection.
|
|
93
|
+
data = newData;
|
|
61
94
|
if (Object.keys(consolidatedData).length) {
|
|
62
|
-
|
|
95
|
+
try {
|
|
96
|
+
ev.emit('event', consolidatedData);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
logger.error({ err }, 'error emitting flushed events');
|
|
100
|
+
}
|
|
63
101
|
}
|
|
64
|
-
data = newData;
|
|
65
102
|
logger.trace({ conditionalChatUpdatesLeft }, 'released buffered events');
|
|
66
103
|
return true;
|
|
67
104
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aunucore/baileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.82",
|
|
5
5
|
"description": "AunuBaileys — the self-healing WhatsApp Web API for Node.js. Bad MAC and \"2000 messages into the future\" errors repair themselves, LID and phone-number identities stay in sync, anti-ban is built in, pairing codes are custom — plus 29 experimental features no other Baileys fork ships: invisible watermarks, per-person tag-all cards, side-profile text, ghost mentions and more. ESM, CJS and full TypeScript types.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|