@aunucore/baileys 1.0.73 โ†’ 1.0.75

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
@@ -128,10 +128,9 @@ Everything else is the upstream core. The patch layer stays small and deliberate
128
128
 
129
129
  ### ๐Ÿ“ฐ Recent releases
130
130
 
131
+ - **1.0.75 โ€” Partial-config safety + watchdog cleanup.** A partial `transactionOpts` in the socket config can no longer kill every key-store transaction: the config is deep-merged over the defaults and the transaction layer falls back per-field (`maxCommitRetries` 10, `delayBetweenTriesMs` 3000, retries clamped to at least 1 so an explicit `0` means exactly one attempt) โ€” previously the shallow config spread replaced the whole default object, `maxCommitRetries` came out `undefined`, and every transaction threw `failed to commit transaction mutations` without a single commit attempt. The session watchdog's interval is now stopped when the socket ends (its `stop()` existed but was never called anywhere), closing a one-timer-and-socket-graph leak per reconnect cycle.
132
+ - **1.0.74 โ€” Fix-all batch.** The entire 1.0.74 heartbeat queue, closed in one version: `resyncAppState` can no longer spin forever holding the key-store transaction mutex (server-omitted collections finish with a warning, plus a 25-round cap), `startByte`-only media downloads no longer silently return an empty stream, an all-failed sticker pack throws Boom 400 instead of uploading empty, key-material guards now check TYPES (Buffer/string only) at six sites so wrong-typed creds fail descriptively instead of as raw TypeErrors, the noise handshake guards a missing `noiseKey` up front, the pre-login `creds.me` dereference family is closed (acks for `<unavailable>` stanzas survive fresh pairing), and two per-entity maps (`RateLimiter.hits`, bug-guard `recentBySender`) evict stale keys with a lazy sweep.
131
133
  - **1.0.73 โ€” Heartbeat queue + a new README.** Three verified heartbeat fixes: a corrupt frame header can no longer wedge `decodeFrame` (zero-length headers are skipped, frames over 10 MiB fail loudly instead of buffering forever), `botRecentSecretsByChat` is capped at 1,000 chats FIFO, and bare key-store writes are serialized through the transaction mutex so a `migrateSession` commit can't overwrite a fresher session record. Plus a fully rewritten README and 46 curated npm keywords.
132
- - **1.0.72 โ€” Send integrity.** Fixes a critical regression in 1.0.71 that made `sendMessage` relay an empty message, plus auditor-verified fixes: media-cache contamination, sent-message cache LID/PN resolution, newsletter media stanza typing, USync backoff wired with a 60s clamp, and a 30-item heartbeat sweep (HB-61โ€ฆHB-90).
133
- - **1.0.71 โ€” The farm batch.** Polls V6 + image polls, spoiler & group-status proto fields, A2UI widgets, the crash-payload detector, MEX username management, six USync protocols, msmsg decryption, heartbeat fixes HB-49โ€ฆHB-59 โ€” every viable harvest from a 2,500-package ecosystem sweep, in one release.
134
-
135
134
  Full changelog for every version since 1.0.2: **[docs](https://docsjs.aunucore.eu.cc)** ยท **[GitHub Releases](https://github.com/AunuCore/AunuBaileys-lib/releases)**.
136
135
 
137
136
  ### ๐Ÿ™ Credits
@@ -5,6 +5,10 @@ import { jidDecode } from '../WABinary/index.js';
5
5
  import { SenderKeyName } from './Group/sender-key-name.js';
6
6
  import { SenderKeyRecord } from './Group/sender-key-record.js';
7
7
  import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage } from './Group/index.js';
8
+ // AunuBaileys (1.0.74): HB-98 โ€” key material is only ever a Buffer
9
+ // (or a string in a hand-edited creds file). Anything else is a
10
+ // wrong-typed corrupt value, not key material.
11
+ const isKeyMaterial = (value) => Buffer.isBuffer(value) || typeof value === 'string';
8
12
  export function makeLibSignalRepository(auth) {
9
13
  const storage = signalStorage(auth);
10
14
  return {
@@ -153,7 +157,10 @@ function signalStorage({ creds, keys }) {
153
157
  // that peer's pkmsg forever with no self-heal match.
154
158
  // Return undefined instead โ€” libsignal raises the
155
159
  // diagnosable `PreKeyError: Invalid PreKey ID`.
156
- if (!key.private || !key.public) {
160
+ // (1.0.74, HB-98: wrong-TYPED material โ€” e.g. a number โ€”
161
+ // slipped past the truthiness check and threw the bare
162
+ // TypeError from Buffer.from anyway; same treatment.)
163
+ if (!isKeyMaterial(key.private) || !isKeyMaterial(key.public)) {
157
164
  return undefined;
158
165
  }
159
166
  return {
@@ -169,7 +176,7 @@ function signalStorage({ creds, keys }) {
169
176
  // keyPair object itself โ€” {keyPair:{}} or {keyPair:{public}}
170
177
  // slipped through and threw a bare TypeError on EVERY pkmsg.
171
178
  // Check the fields, like getOurIdentity does (HB-26).
172
- if (!key?.keyPair?.private || !key?.keyPair?.public) {
179
+ if (!isKeyMaterial(key?.keyPair?.private) || !isKeyMaterial(key?.keyPair?.public)) {
173
180
  // AunuBaileys: a partial/corrupt creds restore (creds
174
181
  // present, signedPreKey gone) otherwise fails EVERY
175
182
  // pre-key message with a cryptic TypeError and never
@@ -201,7 +208,7 @@ function signalStorage({ creds, keys }) {
201
208
  // throw a bare TypeError here on EVERY pkmsg, forever, with
202
209
  // no self-heal classifier matching it. Fail descriptively
203
210
  // so the host knows a re-pair is the fix.
204
- if (!signedIdentityKey?.private || !signedIdentityKey?.public) {
211
+ if (!isKeyMaterial(signedIdentityKey?.private) || !isKeyMaterial(signedIdentityKey?.public)) {
205
212
  throw new Error('Our signed identity key is missing or incomplete โ€” the auth state is corrupt; re-pair the device');
206
213
  }
207
214
  return {
@@ -11,6 +11,8 @@ import { getBinaryNodeChild, getBinaryNodeChildren, isJidUser, isLidUser, jidDec
11
11
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
12
  import { makeUSyncSocket } from './usync.js';
13
13
  const MAX_SYNC_ATTEMPTS = 2;
14
+ // AunuBaileys (1.0.74): HB-95 โ€” hard cap on resyncAppState rounds.
15
+ const MAX_RESYNC_ROUNDS = 25;
14
16
  export const makeChatsSocket = (config) => {
15
17
  const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage } = config;
16
18
  const sock = makeUSyncSocket(config);
@@ -373,7 +375,17 @@ export const makeChatsSocket = (config) => {
373
375
  // keep executing till all collections are done
374
376
  // sometimes a single patch request will not return all the patches (God knows why)
375
377
  // so we fetch till they're all done (this is determined by the "has_more_patches" flag)
378
+ let resyncRounds = 0;
376
379
  while (collectionsToHandle.size) {
380
+ // AunuBaileys (1.0.74): HB-95 second safety net โ€” even
381
+ // with absent collections handled below, never let this
382
+ // loop spin forever inside the key-store transaction:
383
+ // past the round cap, bail out descriptively so the
384
+ // transaction (and its mutex) is always released.
385
+ resyncRounds += 1;
386
+ if (resyncRounds > MAX_RESYNC_ROUNDS) {
387
+ throw new Boom(`resyncAppState did not converge after ${MAX_RESYNC_ROUNDS} rounds (still pending: ${[...collectionsToHandle].join(', ')})`, { statusCode: 500 });
388
+ }
377
389
  const states = {};
378
390
  const nodes = [];
379
391
  for (const name of collectionsToHandle) {
@@ -416,6 +428,19 @@ export const makeChatsSocket = (config) => {
416
428
  });
417
429
  // extract from binary node
418
430
  const decoded = await extractSyncdPatches(result, config?.options);
431
+ // AunuBaileys (1.0.74): HB-95 โ€” a collection the server
432
+ // omits from its sync response never enters `decoded`,
433
+ // so it was never deleted from collectionsToHandle and
434
+ // the loop above re-issued the identical IQ forever,
435
+ // holding the key-store transaction mutex the whole
436
+ // time. Treat an absent collection as done, with a
437
+ // warning.
438
+ for (const name of [...collectionsToHandle]) {
439
+ if (!(name in decoded)) {
440
+ logger.warn({ name }, `collection ${name} missing from app-state sync response; treating it as done`);
441
+ collectionsToHandle.delete(name);
442
+ }
443
+ }
419
444
  for (const key in decoded) {
420
445
  const name = key;
421
446
  const { patches, hasMorePatches, snapshot } = decoded[name];
@@ -16,6 +16,15 @@ const makeWASocket = (config) => {
16
16
  ...DEFAULT_CONNECTION_CONFIG,
17
17
  ...config
18
18
  };
19
+ // AunuBaileys (1.0.75, HB-104): transactionOpts is merged per-field โ€”
20
+ // the shallow spread above replaced the whole default object, so a
21
+ // host passing a PARTIAL transactionOpts silently lost the other
22
+ // field's default (and, before the auth-utils fallback, every
23
+ // key-store commit with it).
24
+ newConfig.transactionOpts = {
25
+ ...DEFAULT_CONNECTION_CONFIG.transactionOpts,
26
+ ...(config?.transactionOpts ?? {})
27
+ };
19
28
  // If the user hasn't provided their own history sync function,
20
29
  // let's create a default one that respects the syncFullHistory flag.
21
30
  if (config.shouldSyncHistoryMessage === undefined) {
@@ -30,7 +39,18 @@ const makeWASocket = (config) => {
30
39
  // config.sessionWatchdog (object = options, true = defaults).
31
40
  // It only reports; it never reconnects.
32
41
  if (newConfig.sessionWatchdog) {
33
- sock.aunuSessionWatchdog = attachSessionWatchdog(sock, newConfig.sessionWatchdog === true ? undefined : newConfig.sessionWatchdog);
42
+ const watchdog = attachSessionWatchdog(sock, newConfig.sessionWatchdog === true ? undefined : newConfig.sessionWatchdog);
43
+ sock.aunuSessionWatchdog = watchdog;
44
+ // AunuBaileys (1.0.75, HB-105): stop() existed but was never
45
+ // called anywhere in the lib โ€” the interval is unref'd yet its
46
+ // closure holds ev + sock, so every ended socket with the
47
+ // watchdog enabled leaked one 30s timer and its whole socket
48
+ // graph for the life of the process, one per reconnect cycle.
49
+ // Register stop() as a socket-end handler, the same pattern as
50
+ // stopOfflineDrain (HB-66). stop() is idempotent (safe on the
51
+ // double-end path), and sockets without a watchdog never reach
52
+ // this branch.
53
+ sock.registerSocketEndHandler?.(() => watchdog.stop());
34
54
  }
35
55
  sock.aunuHandler = new AunuHandler(sock);
36
56
  attachAunuAutoJoin(sock, newConfig);
@@ -74,7 +74,7 @@ export const makeMessagesRecvSocket = (config) => {
74
74
  stanza.attrs.type = attrs.type;
75
75
  }
76
76
  if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
77
- stanza.attrs.from = authState.creds.me.id;
77
+ stanza.attrs.from = authState.creds.me?.id;
78
78
  }
79
79
  logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
80
80
  await sendNode(stanza);
@@ -407,7 +407,7 @@ export const makeMessagesRecvSocket = (config) => {
407
407
  break;
408
408
  case 'devices': {
409
409
  const devices = getBinaryNodeChildren(child, 'device');
410
- if (areJidsSameUser(child.attrs.jid, authState.creds.me.id)) {
410
+ if (areJidsSameUser(child.attrs.jid, authState.creds.me?.id)) {
411
411
  const deviceJids = devices.map(d => d.attrs.jid);
412
412
  logger.info({ deviceJids }, 'got my own devices');
413
413
  }
@@ -773,7 +773,7 @@ export const makeMessagesRecvSocket = (config) => {
773
773
  processingMutex.mutex(async () => {
774
774
  const msg = await processNotification(node);
775
775
  if (msg) {
776
- const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
776
+ const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me?.id);
777
777
  msg.key = {
778
778
  remoteJid,
779
779
  fromMe,
@@ -824,7 +824,7 @@ export const makeMessagesRecvSocket = (config) => {
824
824
  let response;
825
825
  if (getBinaryNodeChild(node, 'unavailable') && !encNode) {
826
826
  await sendMessageAck(node);
827
- const { key } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage;
827
+ const { key } = decodeMessageNode(node, authState.creds.me?.id, authState.creds.me?.lid || '').fullMessage;
828
828
  // AunuBaileys: bot / hosted / view_once placeholders can never
829
829
  // be resent by the phone โ€” don't burn a resend request on
830
830
  // them (behaviour learned from @violetix/baileys).
@@ -89,8 +89,8 @@ export type SignalKeyStoreWithTransaction = SignalKeyStore & {
89
89
  transaction<T>(exec: () => Promise<T>): Promise<T>;
90
90
  };
91
91
  export type TransactionCapabilityOptions = {
92
- maxCommitRetries: number;
93
- delayBetweenTriesMs: number;
92
+ maxCommitRetries?: number;
93
+ delayBetweenTriesMs?: number;
94
94
  };
95
95
  export type SignalAuthState = {
96
96
  creds: SignalCreds;
@@ -28,9 +28,28 @@ export class RateLimiter {
28
28
  this.windowMs = windowMs;
29
29
  this.hits = new Map();
30
30
  }
31
+ /**
32
+ * AunuBaileys (1.0.74): HB-103 โ€” acquire/tryAcquire prune a key's
33
+ * old timestamps but never deleted the KEY, so one entry per
34
+ * recipient JID accumulated for the life of the socket. Lazy
35
+ * sweep, run only once the map passes the threshold: evict every
36
+ * key whose newest hit is already outside its rate-limit window
37
+ * (such a key would behave as brand-new anyway โ€” no behaviour
38
+ * change, amortised cost).
39
+ */
40
+ sweepStaleKeys(now = Date.now()) {
41
+ for (const [key, arr] of this.hits) {
42
+ if (!arr.length || now - arr[arr.length - 1] >= this.windowMs) {
43
+ this.hits.delete(key);
44
+ }
45
+ }
46
+ }
31
47
  /** Tunggu sampai ada slot untuk key; kembalikan waktu tunggu (ms). */
32
48
  async acquire(key) {
33
49
  const now = Date.now();
50
+ if (this.hits.size > 10000) {
51
+ this.sweepStaleKeys(now);
52
+ }
34
53
  let arr = this.hits.get(key);
35
54
  if (!arr) {
36
55
  arr = [];
@@ -49,6 +68,9 @@ export class RateLimiter {
49
68
  /** Cek tanpa menunggu; true bila masih boleh. */
50
69
  tryAcquire(key) {
51
70
  const now = Date.now();
71
+ if (this.hits.size > 10000) {
72
+ this.sweepStaleKeys(now);
73
+ }
52
74
  let arr = this.hits.get(key);
53
75
  if (!arr) {
54
76
  arr = [];
@@ -303,6 +303,20 @@ export const attachAunuBugGuard = (sock, options = {}) => {
303
303
  const stamps = (recentBySender.get(sender) || []).filter((t) => now - t < burstWindowMs);
304
304
  stamps.push(now);
305
305
  recentBySender.set(sender, stamps);
306
+ // AunuBaileys (1.0.74): HB-103 โ€” stale senders were never
307
+ // evicted from this map (their stamp lists got filtered, the
308
+ // keys stayed), so it grew by one entry per distinct flagged
309
+ // sender for the life of the process. Lazy sweep, only past
310
+ // the threshold: drop senders whose newest detection is
311
+ // already outside the burst window.
312
+ if (recentBySender.size > 1000) {
313
+ for (const [other, otherStamps] of recentBySender) {
314
+ const newest = otherStamps[otherStamps.length - 1];
315
+ if (newest === undefined || now - newest >= burstWindowMs) {
316
+ recentBySender.delete(other);
317
+ }
318
+ }
319
+ }
306
320
  return stamps.length >= burstThreshold;
307
321
  };
308
322
  const handleBug = async (message, result, where) => {
@@ -87,7 +87,17 @@ export function makeCacheableSignalKeyStore(store, logger, _cache) {
87
87
  * @param logger logger to log events
88
88
  * @returns SignalKeyStore with transaction capability
89
89
  */
90
- export const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetweenTriesMs }) => {
90
+ export const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetweenTriesMs } = {}) => {
91
+ // AunuBaileys (1.0.75, HB-104): a partial transactionOpts from the
92
+ // host (makeWASocket's shallow spread used to replace the whole
93
+ // default object) left maxCommitRetries undefined, so the commit
94
+ // loop below never ran even once and EVERY mutating transaction
95
+ // threw 'failed to commit transaction mutations' without a single
96
+ // commit attempt. Fall back per-field to the Defaults values, and
97
+ // clamp retries to at least 1 so an explicit 0 means exactly one
98
+ // attempt, matching host intent.
99
+ const commitRetries = Math.max(1, maxCommitRetries ?? 10);
100
+ const commitDelayMs = delayBetweenTriesMs ?? 3000;
91
101
  // number of queries made to the DB during the transaction
92
102
  // only there for logging purposes
93
103
  let dbQueriesInTransaction = 0;
@@ -116,7 +126,7 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
116
126
  logger.trace('committing transaction');
117
127
  // retry mechanism to ensure we've some recovery
118
128
  // in case a transaction fails in the first attempt
119
- let tries = maxCommitRetries;
129
+ let tries = commitRetries;
120
130
  let committed = false;
121
131
  let lastCommitError;
122
132
  while (tries) {
@@ -131,7 +141,7 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela
131
141
  catch (error) {
132
142
  lastCommitError = error;
133
143
  logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`);
134
- await delay(delayBetweenTriesMs);
144
+ await delay(commitDelayMs);
135
145
  }
136
146
  }
137
147
  if (!committed) {
@@ -5,7 +5,18 @@ import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
5
5
  // insure browser & node compatibility
6
6
  const { subtle } = globalThis.crypto;
7
7
  /** prefix version byte to the pub keys, required for some curve crypto functions */
8
- export const generateSignalPubKey = (pubKey) => pubKey.length === 33 ? pubKey : Buffer.concat([KEY_BUNDLE_TYPE, pubKey]);
8
+ export const generateSignalPubKey = (pubKey) => {
9
+ // AunuBaileys (1.0.74): HB-98 โ€” a wrong-typed public key (e.g. a
10
+ // number from a hand-edited creds file, via getOurIdentity) used
11
+ // to explode as a bare TypeError inside Buffer.concat. Key
12
+ // material is a Buffer (or a string in an edited creds file);
13
+ // anything else is corrupt โ€” fail descriptively.
14
+ if (!Buffer.isBuffer(pubKey) && typeof pubKey !== 'string') {
15
+ throw new Error('AunuBaileys: invalid public key material โ€” the auth state is incomplete or corrupt; restore a full backup or pair again');
16
+ }
17
+ const buf = typeof pubKey === 'string' ? Buffer.from(pubKey) : pubKey;
18
+ return buf.length === 33 ? buf : Buffer.concat([KEY_BUNDLE_TYPE, buf]);
19
+ };
9
20
  export const Curve = {
10
21
  generateKeyPair: () => {
11
22
  const { pubKey, privKey } = libsignal.curve.generateKeyPair();
@@ -664,7 +664,13 @@ export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, {
664
664
  const pushBytes = (bytes, push) => {
665
665
  if (startByte || endByte) {
666
666
  const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
667
- const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0);
667
+ // AunuBaileys (1.0.74): HB-96 โ€” with a startByte but NO
668
+ // endByte, `endByte - bytesFetched` was NaN and every chunk
669
+ // sliced to zero bytes (a silent empty download). No
670
+ // endByte means "to the end of the stream": end stays
671
+ // undefined. (When endByte IS set, an end past this chunk
672
+ // clamps inside slice() exactly like undefined did.)
673
+ const end = endByte == null ? undefined : Math.max(endByte - bytesFetched, 0);
668
674
  push(bytes.slice(start, end));
669
675
  bytesFetched += bytes.length;
670
676
  }
@@ -453,6 +453,14 @@ async function prepareStickerPackMessage(stickerPack, options) {
453
453
  }
454
454
  });
455
455
  const stickerMetadata = (await Promise.all(stickerPromises)).filter(Boolean);
456
+ // AunuBaileys (1.0.74): HB-97 โ€” the input guard above only checks
457
+ // the REQUESTED stickers; if every one of them failed (404, too
458
+ // big, unconvertible) the pack used to upload happily with
459
+ // `stickers: []` and report success. An all-failed pack is an
460
+ // error; a partially-failed pack still succeeds.
461
+ if (stickerMetadata.length === 0) {
462
+ throw new Boom('All stickers in the pack failed to process โ€” refusing to upload an empty sticker pack', { statusCode: 400 });
463
+ }
456
464
  // Process and add cover/tray icon to the ZIP
457
465
  const trayIconFileName = `${stickerPackIdValue}.webp`;
458
466
  const { stream: coverStream } = await getStream(cover);
@@ -85,6 +85,15 @@ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publi
85
85
  mixIntoKey,
86
86
  finishInit,
87
87
  processHandshake: async ({ serverHello }, noiseKey) => {
88
+ // AunuBaileys (1.0.74): HB-102 โ€” a partial creds restore can
89
+ // reach the handshake with noiseKey missing; dereferencing
90
+ // it below threw a bare TypeError BEFORE the descriptive
91
+ // HB-80 guard in generateRegistrationNode could run, and
92
+ // the host reconnect-looped on it at every boot. Fail
93
+ // descriptively, in the HB-80 style.
94
+ if (!noiseKey?.public || !noiseKey?.private) {
95
+ throw new Error('AunuBaileys: creds.noiseKey is missing or incomplete โ€” the auth state is incomplete or corrupt; restore a full backup or pair again');
96
+ }
88
97
  authenticate(serverHello.ephemeral);
89
98
  await mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
90
99
  const decStaticContent = decrypt(serverHello.static);
@@ -2,6 +2,10 @@ import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
2
2
  import { assertNodeErrorFree, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js';
3
3
  import { Curve, generateSignalPubKey } from './crypto.js';
4
4
  import { encodeBigEndian } from './generics.js';
5
+ // AunuBaileys (1.0.74): HB-98 โ€” key material is only ever a Buffer
6
+ // (or a string in a hand-edited creds file). Anything else is a
7
+ // wrong-typed corrupt value, not key material.
8
+ const isKeyMaterial = (value) => Buffer.isBuffer(value) || typeof value === 'string';
5
9
  function chunk(array, size) {
6
10
  const chunks = [];
7
11
  for (let i = 0; i < array.length; i += size) {
@@ -204,9 +208,12 @@ export const getNextPreKeys = async ({ creds, keys }, count) => {
204
208
  // ({} โ€” valid JSON, no key material) used to be uploaded as an
205
209
  // EMPTY <value> (HB-83); the decrypt path has guarded this since
206
210
  // 1.0.71 (loadPreKey). Skip entries without a public key here too.
211
+ // (1.0.74, HB-98: truthiness alone also let a wrong-TYPED public โ€”
212
+ // e.g. a number โ€” through to the node encoder, which threw
213
+ // "invalid children". Only Buffer/string material counts.)
207
214
  const preKeys = {};
208
215
  for (const [preKeyId, pair] of Object.entries(storedPreKeys)) {
209
- if (pair?.public) {
216
+ if (isKeyMaterial(pair?.public)) {
210
217
  preKeys[preKeyId] = pair;
211
218
  }
212
219
  }
@@ -218,7 +225,7 @@ export const getNextPreKeysNode = async (state, count) => {
218
225
  // below โ€” partial creds turned that into a bare TypeError and a
219
226
  // reconnect loop at every boot (HB-68). Fail descriptively, like
220
227
  // loadSignedPreKey (HB-26/54).
221
- if (!creds.signedIdentityKey?.public || !creds.signedPreKey?.keyPair?.public || typeof creds.signedPreKey?.keyId === 'undefined') {
228
+ if (!isKeyMaterial(creds.signedIdentityKey?.public) || !isKeyMaterial(creds.signedPreKey?.keyPair?.public) || !isKeyMaterial(creds.signedPreKey?.signature) || typeof creds.signedPreKey?.keyId === 'undefined') {
222
229
  throw new Error('AunuBaileys: creds.signedIdentityKey / creds.signedPreKey are missing or incomplete โ€” the auth state is incomplete or corrupt; restore a full backup or pair again');
223
230
  }
224
231
  const { update, preKeys } = await getNextPreKeys(state, count);
@@ -6,6 +6,10 @@ import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index
6
6
  import { Curve, hmacSign } from './crypto.js';
7
7
  import { encodeBigEndian } from './generics.js';
8
8
  import { createSignalIdentity } from './signal.js';
9
+ // AunuBaileys (1.0.74): HB-98 โ€” key material is only ever a Buffer
10
+ // (or a string in a hand-edited creds file). Anything else is a
11
+ // wrong-typed corrupt value, not key material.
12
+ const isKeyMaterial = (value) => Buffer.isBuffer(value) || typeof value === 'string';
9
13
  const getUserAgent = (config) => {
10
14
  // AunuBaileys (1.0.70): a host overriding version with undefined
11
15
  // used to throw a raw TypeError here; fall back to the bundled
@@ -76,7 +80,7 @@ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedI
76
80
  // below โ€” partial creds turned that into a bare TypeError deep in
77
81
  // the pairing flow (HB-80). Fail descriptively, like
78
82
  // loadSignedPreKey (HB-26/54).
79
- if (!signedIdentityKey?.public || !signedPreKey?.keyPair?.public || typeof signedPreKey?.keyId === 'undefined' || !signedPreKey?.signature) {
83
+ if (!isKeyMaterial(signedIdentityKey?.public) || !isKeyMaterial(signedPreKey?.keyPair?.public) || typeof signedPreKey?.keyId === 'undefined' || !isKeyMaterial(signedPreKey?.signature)) {
80
84
  throw new Error('AunuBaileys: creds.signedIdentityKey / creds.signedPreKey are missing or incomplete โ€” the auth state is incomplete or corrupt; restore a full backup or pair again');
81
85
  }
82
86
  // the app version needs to be md5 hashed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aunucore/baileys",
3
3
  "type": "module",
4
- "version": "1.0.73",
4
+ "version": "1.0.75",
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",
@@ -69,17 +69,17 @@
69
69
  "preinstall": "node ./engine-requirements.js"
70
70
  },
71
71
  "dependencies": {
72
+ "@adiwajshing/keyed-db": "^0.2.4",
72
73
  "@cacheable/node-cache": "^1.4.0",
73
74
  "@hapi/boom": "^9.1.3",
74
75
  "async-mutex": "^0.5.0",
75
76
  "axios": "^1.6.0",
77
+ "fflate": "^0.8.2",
76
78
  "libsignal": "git+https://github.com/whiskeysockets/libsignal-node",
77
79
  "music-metadata": "^11.7.0",
78
80
  "pino": "^9.6",
79
81
  "protobufjs": "^7.2.4",
80
- "ws": "^8.13.0",
81
- "@adiwajshing/keyed-db": "^0.2.4",
82
- "fflate": "^0.8.2"
82
+ "ws": "^8.13.0"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@eslint/eslintrc": "^3.3.1",
@@ -112,11 +112,11 @@
112
112
  "typescript": "^5.8.2"
113
113
  },
114
114
  "peerDependencies": {
115
+ "@napi-rs/image": "^1.12.0",
115
116
  "audio-decode": "^2.1.3",
116
117
  "jimp": "^1.6.0",
117
118
  "link-preview-js": "^3.0.0",
118
- "sharp": "*",
119
- "@napi-rs/image": "^1.12.0"
119
+ "sharp": "*"
120
120
  },
121
121
  "peerDependenciesMeta": {
122
122
  "audio-decode": {