@aunucore/baileys 1.0.86 → 1.0.87

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.
@@ -82,8 +82,30 @@ export const makeChatsSocket = (config) => {
82
82
  // AunuBaileys (1.0.71): a privacy change is a settings change —
83
83
  // refetch the full set (the cached copy is now stale) and publish
84
84
  // it (donor event: violetix 'privacy.update' on account_sync).
85
- privacySettings = await fetchPrivacySettings(true);
86
- ev.emit('privacy.update', privacySettings);
85
+ // AunuBaileys (1.0.87, HB-163): the SET above has already
86
+ // been applied by the server when this refetch runs. A
87
+ // refetch failure used to reject the whole privacyQuery —
88
+ // the caller saw a failed update that had in fact taken
89
+ // effect, the cache kept the OLD value, and readMessages
90
+ // derived its receipt type from that stale cache (sending
91
+ // 'read' receipts against a receipts setting the user had
92
+ // just switched off). Make the refetch best-effort: on
93
+ // failure, fold the applied change into the cached copy so
94
+ // cache and event reflect what the server now holds for
95
+ // this key. Never fabricate a partial cache when none
96
+ // exists — that would suppress the next full fetch.
97
+ try {
98
+ privacySettings = await fetchPrivacySettings(true);
99
+ }
100
+ catch (err) {
101
+ logger.warn({ err, name, value }, 'failed to refetch privacy settings after a successful set; folding the change into the cached copy');
102
+ if (privacySettings) {
103
+ privacySettings = { ...privacySettings, [name]: value };
104
+ }
105
+ }
106
+ if (privacySettings) {
107
+ ev.emit('privacy.update', privacySettings);
108
+ }
87
109
  };
88
110
  const updateMessagesPrivacy = async (value) => {
89
111
  await privacyQuery('messages', value);
@@ -946,11 +968,35 @@ export const makeChatsSocket = (config) => {
946
968
  const doAppStateSync = async () => {
947
969
  if (syncState === SyncState.Syncing) {
948
970
  logger.info('Doing app state sync');
949
- await resyncAppState(ALL_WA_PATCH_NAMES, true);
950
- // Sync is complete, go online and flush everything
971
+ // AunuBaileys (1.0.87, HB-241): resyncAppState can
972
+ // throw OUTSIDE its per-collection handling — the
973
+ // sync IQ itself, a malformed blob in
974
+ // extractSyncdPatches, the resync-round cap. That
975
+ // throw used to reject upsertMessage and leave
976
+ // syncState at Syncing for the rest of the session:
977
+ // the full initial sync was never retried (nothing
978
+ // re-triggers it) and the state machine never
979
+ // reached Online. A failed sync must END the
980
+ // Syncing state, not freeze it — go Online and
981
+ // flush either way, and only count the sync as
982
+ // complete (the accountSyncCounter advance below)
983
+ // when it actually succeeded.
984
+ let syncFailed = false;
985
+ try {
986
+ await resyncAppState(ALL_WA_PATCH_NAMES, true);
987
+ }
988
+ catch (err) {
989
+ syncFailed = true;
990
+ logger.error({ err }, 'app state sync failed; transitioning to Online state anyway');
991
+ }
992
+ // Sync ran its course, go online and flush everything
951
993
  syncState = SyncState.Online;
952
994
  logger.info('App state sync complete, transitioning to Online state and flushing buffer');
953
995
  ev.flush();
996
+ // (HB-241) a failed sync is not a completed one:
997
+ // do not advance the counter that signals the
998
+ // initial sync finished.
999
+ if (!syncFailed) {
954
1000
  const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1;
955
1001
  // AunuBaileys (1.0.81, HB-257): same guard as the
956
1002
  // pushName emit above — a throwing 'creds.update'
@@ -963,6 +1009,7 @@ export const makeChatsSocket = (config) => {
963
1009
  catch (err) {
964
1010
  onUnexpectedError(err, 'creds.update (accountSyncCounter)');
965
1011
  }
1012
+ }
966
1013
  }
967
1014
  };
968
1015
  await Promise.all([
@@ -1771,7 +1771,14 @@ export const makeMessagesRecvSocket = (config) => {
1771
1771
  }
1772
1772
  const operation = data?.operation;
1773
1773
  const updates = data?.updates;
1774
- if (!updates || !operation) {
1774
+ // AunuBaileys (1.0.87, HB-121): this guard used to be a
1775
+ // truthiness check only — a MEX payload whose `updates`
1776
+ // is a non-array (object/number/boolean) passed it, and
1777
+ // the for...of loops in every switch branch below threw a
1778
+ // TypeError, silently dropping the whole notification
1779
+ // (handleNotification acks the stanza in its finally
1780
+ // either way, so nothing ever retried it).
1781
+ if (!Array.isArray(updates) || !operation) {
1775
1782
  logger.warn({ data }, 'Invalid mex newsletter notification content');
1776
1783
  return;
1777
1784
  }
package/lib/Socket/mex.js CHANGED
@@ -22,11 +22,29 @@ export const executeWMexQuery = async (variables, queryId, dataPath, query, gene
22
22
  const result = await wMexQuery(variables, queryId, query, generateMessageTag);
23
23
  const child = getBinaryNodeChild(result, 'result');
24
24
  if (child?.content) {
25
- const data = JSON.parse(child.content.toString());
25
+ // AunuBaileys (1.0.87, HB-211): a <result> that is not JSON
26
+ // (including a child-node array body, which stringifies to
27
+ // "[object Object]") used to reject with a raw SyntaxError
28
+ // the host could not classify.
29
+ let data;
30
+ try {
31
+ data = JSON.parse(child.content.toString());
32
+ }
33
+ catch (err) {
34
+ throw new Boom('GraphQL server error: unparseable response', { statusCode: 400, data: result });
35
+ }
26
36
  if (data.errors && data.errors.length > 0) {
27
37
  const errorMessages = data.errors.map((err) => err.message || 'Unknown error').join(', ');
28
38
  const firstError = data.errors[0];
29
- const errorCode = firstError.extensions?.error_code || 400;
39
+ // AunuBaileys (1.0.87, HB-211): the server's error_code
40
+ // went into the Boom constructor unsanitised — a string
41
+ // code ('RATE_LIMITED') or an out-of-range one (200)
42
+ // made `new Boom` itself throw a non-Boom TypeError,
43
+ // and the server's actual error message was lost with
44
+ // it. Same sanitation as HB-42: only a numeric HTTP
45
+ // error code survives, everything else becomes 400.
46
+ const rawErrorCode = firstError.extensions?.error_code;
47
+ const errorCode = typeof rawErrorCode === 'number' && rawErrorCode >= 400 && rawErrorCode <= 599 ? rawErrorCode : 400;
30
48
  throw new Boom(`GraphQL server error: ${errorMessages}`, { statusCode: errorCode, data: firstError });
31
49
  }
32
50
  const response = dataPath ? data?.data?.[dataPath] : data?.data;
@@ -37,31 +37,37 @@ import { jidDecode } from '../WABinary/index.js';
37
37
  import { getKeyAuthor } from './generics.js';
38
38
  import { getAggregateVotesInPollMessage, normalizeMessageContent } from './messages.js';
39
39
 
40
+ // AunuBaileys (1.0.87, HB-218): every operand is optional-chained,
41
+ // exactly like the base getFutureProofMessage in messages.js. A
42
+ // wrapper whose `message` field is absent decodes with message ===
43
+ // null; the bare derefs used to throw a raw TypeError on the next
44
+ // loop iteration of normalizeAunuMessageContentExtended (and in
45
+ // getAggregateVotesInPollMessageExtended downstream of it).
40
46
  const getFutureProofMessageExtended = (message) => (
41
- message.ephemeralMessage ||
42
- message.viewOnceMessage ||
43
- message.documentWithCaptionMessage ||
44
- message.viewOnceMessageV2 ||
45
- message.viewOnceMessageV2Extension ||
46
- message.editedMessage ||
47
- message.groupMentionedMessage ||
48
- message.botInvokeMessage ||
49
- message.lottieStickerMessage ||
50
- message.eventCoverImage ||
51
- message.statusMentionMessage ||
52
- message.pollCreationOptionImageMessage ||
53
- message.associatedChildMessage ||
54
- message.groupStatusMentionMessage ||
55
- message.pollCreationMessageV4 ||
56
- message.pollCreationMessageV5 ||
57
- message.statusAddYours ||
58
- message.groupStatusMessage ||
59
- message.limitSharingMessage ||
60
- message.botTaskMessage ||
61
- message.questionMessage ||
62
- message.groupStatusMessageV2 ||
63
- message.botForwardedMessage ||
64
- message.spoilerMessage
47
+ message?.ephemeralMessage ||
48
+ message?.viewOnceMessage ||
49
+ message?.documentWithCaptionMessage ||
50
+ message?.viewOnceMessageV2 ||
51
+ message?.viewOnceMessageV2Extension ||
52
+ message?.editedMessage ||
53
+ message?.groupMentionedMessage ||
54
+ message?.botInvokeMessage ||
55
+ message?.lottieStickerMessage ||
56
+ message?.eventCoverImage ||
57
+ message?.statusMentionMessage ||
58
+ message?.pollCreationOptionImageMessage ||
59
+ message?.associatedChildMessage ||
60
+ message?.groupStatusMentionMessage ||
61
+ message?.pollCreationMessageV4 ||
62
+ message?.pollCreationMessageV5 ||
63
+ message?.statusAddYours ||
64
+ message?.groupStatusMessage ||
65
+ message?.limitSharingMessage ||
66
+ message?.botTaskMessage ||
67
+ message?.questionMessage ||
68
+ message?.groupStatusMessageV2 ||
69
+ message?.botForwardedMessage ||
70
+ message?.spoilerMessage
65
71
  );
66
72
 
67
73
  /**
@@ -286,6 +286,22 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
286
286
  };
287
287
  const mutationMap = {};
288
288
  for (const syncd of syncds) {
289
+ // AunuBaileys (1.0.87, HB-141 + HB-149): snapshot the state
290
+ // before this patch. The version assignment inside the try
291
+ // runs BEFORE the patch is decoded, so a patch that failed
292
+ // (bad content HMAC, missing key, snapshot-MAC mismatch)
293
+ // still advanced the persisted version past itself — its
294
+ // mutations were never re-sent by the server, and the LT
295
+ // hash diverged from the true state permanently: every
296
+ // later snapshot-MAC check failed while the log claimed the
297
+ // patch had merely been "skipped". decodeSyncdPatch never
298
+ // mutates newState in place (the LT-hash generator copies
299
+ // the index map and returns fresh state), so restoring
300
+ // these three references in the catch is an exact rollback.
301
+ const prevVersion = newState.version;
302
+ const prevHash = newState.hash;
303
+ const prevIndexValueMap = newState.indexValueMap;
304
+ const appliedMutations = [];
289
305
  try {
290
306
  const { version, keyId, snapshotMac } = syncd;
291
307
  if (syncd.externalMutations) {
@@ -300,6 +316,11 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
300
316
  const decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, shouldMutate
301
317
  ? mutation => {
302
318
  const index = mutation.syncAction.index?.toString();
319
+ // (HB-141 + HB-149) record for rollback: a patch
320
+ // whose snapshot-MAC check fails below has already
321
+ // delivered its mutations here — they must not
322
+ // survive being "skipped" either.
323
+ appliedMutations.push({ index, had: index in mutationMap, prev: mutationMap[index] });
303
324
  mutationMap[index] = mutation;
304
325
  }
305
326
  : () => { }, true);
@@ -324,6 +345,28 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
324
345
  // AunuBaileys: satu patch rusak tidak membunuh seluruh sinkronisasi
325
346
  logger?.warn({ err, name, version: syncd.version }, 'skipping undecodable app state patch');
326
347
  syncd.mutations = [];
348
+ // AunuBaileys (1.0.87, HB-141 + HB-149): roll the failed
349
+ // patch back completely, then stop this batch — later
350
+ // patches build on this one's state, and letting one of
351
+ // them advance the persisted version past the hole would
352
+ // cement the loss. The version stays at the last good
353
+ // patch, so the next resync re-requests the failed patch
354
+ // instead of reporting a version that silently skips it;
355
+ // a transient cause (failed external download, a sync key
356
+ // that had not arrived yet) now self-heals.
357
+ newState.version = prevVersion;
358
+ newState.hash = prevHash;
359
+ newState.indexValueMap = prevIndexValueMap;
360
+ for (let i = appliedMutations.length - 1; i >= 0; i--) {
361
+ const { index, had, prev } = appliedMutations[i];
362
+ if (had) {
363
+ mutationMap[index] = prev;
364
+ }
365
+ else {
366
+ delete mutationMap[index];
367
+ }
368
+ }
369
+ break;
327
370
  }
328
371
  }
329
372
  return { state: newState, mutationMap };
@@ -110,7 +110,22 @@ export class USyncQuery {
110
110
  if (errCode === 401 || errCode === 403 || errCode === 405) {
111
111
  return ['isBlockedByContact', true];
112
112
  }
113
- throw err;
113
+ // AunuBaileys (1.0.87, HB-175): any
114
+ // OTHER per-user parser error — the
115
+ // protocol node carried an <error>
116
+ // child with e.g. a 404 — used to be
117
+ // rethrown, aborting the parse of
118
+ // the ENTIRE batch: one user's error
119
+ // cost every other user's devices /
120
+ // contacts, and downstream sends that
121
+ // depend on the device list failed
122
+ // outright. Treat it the way the
123
+ // business parser treats an error
124
+ // child: this user simply has no data
125
+ // for this protocol (the null is
126
+ // dropped by the filter below); the
127
+ // rest of the batch continues.
128
+ return [protocol, null];
114
129
  }
115
130
  }
116
131
  else {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aunucore/baileys",
3
3
  "type": "module",
4
- "version": "1.0.86",
4
+ "version": "1.0.87",
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",