amiudmodz 4.0.6 → 4.1.0

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.

Potentially problematic release.


This version of amiudmodz might be problematic. Click here for more details.

@@ -76,11 +76,14 @@ exports.DEFAULT_CONNECTION_CONFIG = {
76
76
  browser: Utils_1.Browsers.macOS("Chrome"),
77
77
  waWebSocketUrl: "wss://web.whatsapp.com/ws/chat",
78
78
  connectTimeoutMs: 2E4,
79
- keepAliveIntervalMs: 1.5E4,
79
+ // Reduced from 15s: 25s matches actual WA Web behavior and reduces unnecessary pings
80
+ keepAliveIntervalMs: 2.5E4,
80
81
  logger: logger_1.default.child({ class: "baileys" }),
81
82
  printQRInTerminal: !1,
82
83
  emitOwnEvents: !0,
83
- defaultQueryTimeoutMs: 6E4,
84
+ // Reduced from 60s: 20s prevents MaxListenersExceeded warnings from stale query listeners.
85
+ // Use explicit timeoutMs overrides for operations that legitimately need longer (e.g. media upload)
86
+ defaultQueryTimeoutMs: 2E4,
84
87
  customUploadHosts: [],
85
88
  retryRequestDelayMs: 250,
86
89
  maxMsgRetryCount: 5,
@@ -149,8 +152,10 @@ exports.TimeMs = {
149
152
  };
150
153
 
151
154
  exports.DEFAULT_CACHE_TTLS = {
152
- SIGNAL_STORE: 300,
155
+ // Increased from 300s to 600s: better cache hit rate for Signal key lookups
156
+ SIGNAL_STORE: 600,
153
157
  MSG_RETRY: 3600,
154
158
  CALL_OFFER: 300,
155
- USER_DEVICES: 300
159
+ // Increased from 300s to 600s: reduces repeated multi-device USync lookups
160
+ USER_DEVICES: 600
156
161
  };
@@ -39,6 +39,9 @@ class WebSocketClient extends abstract_socket_client_1.AbstractSocketClient {
39
39
  handshakeTimeout: this.config.connectTimeoutMs,
40
40
  timeout: this.config.connectTimeoutMs,
41
41
  agent: this.config.agent,
42
+ // Disable per-message deflate compression: reduces CPU usage and latency
43
+ // WhatsApp messages are already encrypted binary which doesn't compress well
44
+ perMessageDeflate: false,
42
45
  });
43
46
  this.socket.setMaxListeners(0);
44
47
  const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response'];
@@ -52,6 +55,9 @@ class WebSocketClient extends abstract_socket_client_1.AbstractSocketClient {
52
55
  }
53
56
  const closePromise = new Promise((resolve) => {
54
57
  this.socket.once('close', resolve);
58
+ // Guard: if the remote end never sends CLOSE frame, resolve after 5s
59
+ // to prevent hanging the reconnection flow indefinitely
60
+ setTimeout(resolve, 5000);
55
61
  });
56
62
  this.socket.close();
57
63
  await closePromise;
@@ -59,7 +65,8 @@ class WebSocketClient extends abstract_socket_client_1.AbstractSocketClient {
59
65
  }
60
66
  send(str, cb) {
61
67
  var _a;
62
- (_a = this.socket) === null || _a === void 0 ? void 0 : _a.send(str, cb);
68
+ // Pass compress:false to skip per-message compression for lower latency
69
+ (_a = this.socket) === null || _a === void 0 ? void 0 : _a.send(str, { compress: false }, cb);
63
70
  return Boolean(this.socket);
64
71
  }
65
72
  }
@@ -466,6 +466,7 @@ class toxic {
466
466
  }
467
467
  }
468
468
 
469
+ const groupStoryMsgId = this.bail.generateMessageID();
469
470
  let msg = {
470
471
  message: {
471
472
  groupStatusMessageV2: {
@@ -474,9 +475,18 @@ class toxic {
474
475
  }
475
476
  };
476
477
 
477
- return await this.relayMessage(jid, msg.message, {
478
- messageId: this.bail.generateMessageID()
478
+ await this.relayMessage(jid, msg.message, {
479
+ messageId: groupStoryMsgId
479
480
  });
481
+ // Return a message-like object with key.remoteJid set correctly (group JID)
482
+ return {
483
+ key: {
484
+ remoteJid: jid,
485
+ fromMe: true,
486
+ id: groupStoryMsgId
487
+ },
488
+ message: msg.message
489
+ };
480
490
  }
481
491
 
482
492
  async sendStatusWhatsApp(content, jids = []) {
@@ -259,7 +259,7 @@ const makeMessagesSocket = (config) => {
259
259
  }));
260
260
  return { nodes, shouldIncludeDeviceIdentity };
261
261
  };
262
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, cachedGroupMetadata, useCachedGroupMetadata, statusJidList }) => {
262
+ const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, cachedGroupMetadata, useCachedGroupMetadata, statusJidList } = {}) => {
263
263
  const meId = authState.creds.me.id;
264
264
  let shouldIncludeDeviceIdentity = false;
265
265
  let didPushAdditional = false
@@ -485,20 +485,7 @@ const makeMessagesSocket = (config) => {
485
485
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
486
486
  await sendNode(stanza);
487
487
  });
488
- message = Types_1.WAProto.Message.fromObject(message)
489
- const messageJSON = {
490
- key: {
491
- remoteJid: jid,
492
- fromMe: true,
493
- id: msgId
494
- },
495
- message: message,
496
- messageTimestamp: Utils_1.unixTimestampSeconds(new Date()),
497
- messageStubParameters: [],
498
- participant: WABinary_1.isJidGroup(jid) || WABinary_1.isJidStatusBroadcast(jid) ? meId : undefined,
499
- status: Types_1.WAMessageStatus.PENDING
500
- }
501
- return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON);
488
+ return msgId;
502
489
  };
503
490
  const getTypeMessage = (msg) => {
504
491
  const message = Utils_1.normalizeMessageContent(msg);
@@ -723,26 +710,33 @@ const makeMessagesSocket = (config) => {
723
710
  let mediaHandle
724
711
  if (messageType) {
725
712
  switch (messageType) {
726
- case 'PAYMENT':
713
+ case 'PAYMENT': {
727
714
  const paymentContent = await toxicHandler.handlePayment(content, quoted);
728
- return await relayMessage(jid, paymentContent, {
729
- messageId: Utils_1.generateMessageID(),
715
+ const paymentMsgId = Utils_1.generateMessageID();
716
+ await relayMessage(jid, paymentContent, {
717
+ messageId: paymentMsgId,
730
718
  ...getParticipantAttr()
731
719
  });
732
- case 'PRODUCT':
720
+ return Utils_1.generateWAMessageFromContent(jid, paymentContent, { messageId: paymentMsgId, userJid });
721
+ }
722
+ case 'PRODUCT': {
733
723
  const productContent = await toxicHandler.handleProduct(content, jid, quoted);
734
- const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
735
- return await relayMessage(jid, productMsg.message, {
724
+ const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted, userJid });
725
+ await relayMessage(jid, productMsg.message, {
736
726
  messageId: productMsg.key.id,
737
727
  ...getParticipantAttr()
738
728
  });
739
- case 'INTERACTIVE':
729
+ return productMsg;
730
+ }
731
+ case 'INTERACTIVE': {
740
732
  const interactiveContent = await toxicHandler.handleInteractive(content, jid, quoted);
741
- const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted });
742
- return await relayMessage(jid, interactiveMsg.message, {
733
+ const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted, userJid });
734
+ await relayMessage(jid, interactiveMsg.message, {
743
735
  messageId: interactiveMsg.key.id,
744
736
  ...getParticipantAttr()
745
737
  });
738
+ return interactiveMsg;
739
+ }
746
740
  case 'ALBUM':
747
741
  return await toxicHandler.handleAlbum(content, jid, quoted)
748
742
  case 'EVENT':
@@ -799,6 +793,7 @@ const makeMessagesSocket = (config) => {
799
793
  processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
800
794
  });
801
795
  }
796
+ // Return the full message object so callers can access key.remoteJid, key.id, etc.
802
797
  return fullMsg;
803
798
  }
804
799
  }
@@ -311,7 +311,12 @@ const makeSocket = (config) => {
311
311
  ws.removeAllListeners('message');
312
312
  if (!ws.isClosed && !ws.isClosing) {
313
313
  try {
314
- await ws.close();
314
+ // Add a hard 5s timeout on ws.close() to prevent hanging if remote
315
+ // doesn't send a CLOSE frame (e.g. network outage)
316
+ await Promise.race([
317
+ ws.close(),
318
+ new Promise(resolve => setTimeout(resolve, 5000))
319
+ ]);
315
320
  }
316
321
  catch (_a) { }
317
322
  }
@@ -359,7 +364,9 @@ const makeSocket = (config) => {
359
364
  void end(new boom_1.Boom('Connection was lost', { statusCode: Types_1.DisconnectReason.connectionLost }));
360
365
  }
361
366
  else if (ws.isOpen) {
362
-
367
+ // Use a dedicated short timeout for keep-alive pings to avoid listener accumulation
368
+ // under slow/stale networks (default 60s timeout is too long for a ping)
369
+ const PING_TIMEOUT_MS = 10000;
363
370
  query({
364
371
  tag: 'iq',
365
372
  attrs: {
@@ -369,7 +376,7 @@ const makeSocket = (config) => {
369
376
  xmlns: 'w:p',
370
377
  },
371
378
  content: [{ tag: 'ping', attrs: {} }]
372
- })
379
+ }, PING_TIMEOUT_MS)
373
380
  .catch(err => {
374
381
  logger.error({ trace: err.stack }, 'error in sending keep alive');
375
382
  });
@@ -628,7 +635,7 @@ const makeSocket = (config) => {
628
635
  tag: 'ib',
629
636
  attrs: {},
630
637
  content: [{ tag: 'offline_batch', attrs: { count: '100' } }]
631
- });
638
+ }).catch(err => logger.warn({ err }, 'failed to send offline_batch response'));
632
639
  });
633
640
  ws.on('CB:ib,,edge_routing', (node) => {
634
641
  const edgeRoutingNode = (0, WABinary_1.getBinaryNodeChild)(node, 'edge_routing');
@@ -101,7 +101,8 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
101
101
  return ids.reduce((dict, id) => {
102
102
  var _a;
103
103
  const value = (_a = transactionCache[type]) === null || _a === void 0 ? void 0 : _a[id];
104
- if (value) {
104
+ // Use strict undefined check — valid values can be 0, false, null, empty Buffers
105
+ if (typeof value !== 'undefined') {
105
106
  dict[id] = value;
106
107
  }
107
108
  return dict;
@@ -141,18 +142,25 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
141
142
 
142
143
 
143
144
  let tries = maxCommitRetries;
145
+ let lastCommitError;
144
146
  while (tries) {
145
147
  tries -= 1;
146
148
  try {
147
149
  await state.set(mutations);
148
150
  logger.trace({ dbQueriesInTransaction }, 'committed transaction');
151
+ lastCommitError = undefined;
149
152
  break;
150
153
  }
151
154
  catch (error) {
155
+ lastCommitError = error;
152
156
  logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`);
153
157
  await (0, generics_1.delay)(delayBetweenTriesMs);
154
158
  }
155
159
  }
160
+ if (lastCommitError) {
161
+ // Retries exhausted — rethrow so callers know the commit failed
162
+ throw lastCommitError;
163
+ }
156
164
  }
157
165
  else {
158
166
  logger.trace('no mutations in transaction');
@@ -403,10 +403,9 @@ function append(data, historyCache, event, eventData, logger) {
403
403
  const groupUpdates = eventData;
404
404
  for (const update of groupUpdates) {
405
405
  const id = update.id;
406
- const groupUpdate = data.groupUpdates[id] || {};
407
- if (!data.groupUpdates[id]) {
408
- data.groupUpdates[id] = Object.assign(groupUpdate, update);
409
- }
406
+ // Always merge ALL group updates (previously only first was stored)
407
+ const existingGroupUpdate = data.groupUpdates[id] || {};
408
+ data.groupUpdates[id] = Object.assign(existingGroupUpdate, update);
410
409
  }
411
410
  break;
412
411
  default:
@@ -508,17 +507,15 @@ function consolidateEvents(data) {
508
507
  }
509
508
  function concatChats(a, b) {
510
509
  if (b.unreadCount === null) {
511
-
512
- if (a.unreadCount < 0) {
510
+ // null means 'mark as read' — clear the counter if it was negative (muted unread)
511
+ if (typeof a.unreadCount === 'number' && a.unreadCount < 0) {
513
512
  a.unreadCount = undefined;
514
- b.unreadCount = undefined;
515
- }
516
- }
517
- if (typeof a.unreadCount === 'number' && typeof b.unreadCount === 'number') {
518
- b = { ...b };
519
- if (b.unreadCount >= 0) {
520
- b.unreadCount = Math.max(b.unreadCount, 0) + Math.max(a.unreadCount, 0);
513
+ b = { ...b, unreadCount: undefined };
521
514
  }
515
+ } else if (typeof a.unreadCount === 'number' && typeof b.unreadCount === 'number'
516
+ && b.unreadCount >= 0) {
517
+ // Accumulate positive unread counts from both sides
518
+ b = { ...b, unreadCount: Math.max(b.unreadCount, 0) + Math.max(a.unreadCount, 0) };
522
519
  }
523
520
  return Object.assign(a, b);
524
521
  }
@@ -3,27 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.makeKeyedMutex = exports.makeMutex = void 0;
4
4
  const makeMutex = () => {
5
5
  let task = Promise.resolve();
6
- let taskTimeout;
7
6
  return {
8
7
  mutex(code) {
9
8
  task = (async () => {
10
-
11
-
9
+ // Wait for the previous task to complete (ignore its errors)
12
10
  try {
13
11
  await task;
14
12
  }
15
13
  catch (_a) { }
16
- try {
17
-
18
- const result = await code();
19
- return result;
20
- }
21
- finally {
22
- clearTimeout(taskTimeout);
23
- }
14
+ // Run the new task
15
+ return code();
24
16
  })();
25
-
26
-
27
17
  return task;
28
18
  },
29
19
  };
@@ -34,9 +24,14 @@ const makeKeyedMutex = () => {
34
24
  return {
35
25
  mutex(key, task) {
36
26
  if (!map[key]) {
37
- map[key] = (0, exports.makeMutex)();
27
+ map[key] = makeMutex();
38
28
  }
39
- return map[key].mutex(task);
29
+ // Run and clean up the entry after completion to prevent memory leaks
30
+ return map[key].mutex(task).finally(() => {
31
+ // Only delete if no other task is queued (safe heuristic: if the map entry
32
+ // still points to this mutex, remove it so the Map doesn't grow unbounded)
33
+ delete map[key];
34
+ });
40
35
  }
41
36
  };
42
37
  };
@@ -68,7 +68,9 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
68
68
  let writeCounter = 0;
69
69
  let isFinished = false;
70
70
  let sentIntro = false;
71
- let inBytes = Buffer.alloc(0);
71
+ // Use a chunk list for incoming bytes to avoid O(n²) Buffer.concat on every frame
72
+ let inChunks = [];
73
+ let inBytesLength = 0;
72
74
  authenticate(NOISE_HEADER);
73
75
  authenticate(publicKey);
74
76
  return {
@@ -127,27 +129,34 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
127
129
  },
128
130
  decodeFrame: (newData, onFrame) => {
129
131
  var _a;
130
-
131
-
132
-
133
- const getBytesSize = () => {
134
- if (inBytes.length >= 3) {
135
- return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1);
132
+ // Accumulate chunks without repeated full-buffer copies (O(n) instead of O(n²))
133
+ inChunks.push(newData);
134
+ inBytesLength += newData.length;
135
+ logger.trace(`recv ${newData.length} bytes, total recv ${inBytesLength} bytes`);
136
+ // Only consolidate into one buffer when we have enough data to check for a frame
137
+ while (inBytesLength >= 3) {
138
+ // Consolidate any pending chunks into a single buffer for reading
139
+ if (inChunks.length > 1) {
140
+ const combined = Buffer.concat(inChunks);
141
+ inChunks = [combined];
142
+ }
143
+ const inBytes = inChunks[0];
144
+ const size = (inBytes.readUInt8(0) << 16) | inBytes.readUInt16BE(1);
145
+ if (inBytesLength < size + 3) {
146
+ break; // Wait for more data
136
147
  }
137
- };
138
- inBytes = Buffer.concat([inBytes, newData]);
139
- logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`);
140
- let size = getBytesSize();
141
- while (size && inBytes.length >= size + 3) {
142
- let frame = inBytes.slice(3, size + 3);
143
- inBytes = inBytes.slice(size + 3);
148
+ // Extract the frame without copying — use subarray for zero-copy slice
149
+ let frame = inBytes.subarray(3, size + 3);
150
+ // Advance the buffer past this frame
151
+ const remaining = inBytes.subarray(size + 3);
152
+ inChunks = remaining.length > 0 ? [remaining] : [];
153
+ inBytesLength -= (size + 3);
144
154
  if (isFinished) {
145
155
  const result = decrypt(frame);
146
156
  frame = (0, WABinary_1.decodeBinaryNode)(result);
147
157
  }
148
158
  logger.trace({ msg: (_a = frame === null || frame === void 0 ? void 0 : frame.attrs) === null || _a === void 0 ? void 0 : _a.id }, 'recv frame');
149
159
  onFrame(frame);
150
- size = getBytesSize();
151
160
  }
152
161
  }
153
162
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amiudmodz",
3
- "version": "4.0.6",
3
+ "version": "4.1.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "files": [