violetics 7.0.4-alpha → 7.0.5-alpha

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.
@@ -53,7 +53,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
53
53
  emitOwnEvents: true,
54
54
  defaultQueryTimeoutMs: 60000,
55
55
  customUploadHosts: [],
56
- retryRequestDelayMs: 250,
56
+ retryRequestDelayMs: 0,
57
57
  maxMsgRetryCount: 3,
58
58
  fireInitQueries: true,
59
59
  auth: undefined,
@@ -5,6 +5,7 @@ export class WebSocketClient extends AbstractSocketClient {
5
5
  constructor() {
6
6
  super(...arguments);
7
7
  this.socket = null;
8
+ this._eventForwarders = [];
8
9
  }
9
10
  get isOpen() {
10
11
  return this.socket?.readyState === WebSocket.OPEN;
@@ -31,14 +32,22 @@ export class WebSocketClient extends AbstractSocketClient {
31
32
  });
32
33
  this.socket.setMaxListeners(0);
33
34
  const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response'];
35
+ this._eventForwarders = [];
34
36
  for (const event of events) {
35
- this.socket?.on(event, (...args) => this.emit(event, ...args));
37
+ const handler = (...args) => this.emit(event, ...args);
38
+ this.socket?.on(event, handler);
39
+ this._eventForwarders.push({ event, handler });
36
40
  }
37
41
  }
38
42
  async close() {
39
43
  if (!this.socket) {
40
44
  return;
41
45
  }
46
+ // Remove all forwarding listeners to prevent memory leaks
47
+ for (const { event, handler } of this._eventForwarders) {
48
+ this.socket?.off(event, handler);
49
+ }
50
+ this._eventForwarders = [];
42
51
  const closePromise = new Promise(resolve => {
43
52
  this.socket?.once('close', resolve);
44
53
  });
@@ -1,4 +1,5 @@
1
1
  import NodeCache from '@cacheable/node-cache';
2
+ import { LRUCache } from 'lru-cache';
2
3
  import { Boom } from '@hapi/boom';
3
4
  import { proto } from '../../WAProto/index.js';
4
5
  import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js';
@@ -12,12 +13,12 @@ import { getBinaryNodeChild, getBinaryNodeChildren, isLidUser, isPnUser, jidDeco
12
13
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
13
14
  import { makeSocket } from './socket.js';
14
15
  const MAX_SYNC_ATTEMPTS = 2;
15
- // Lia@Note 08-02-26 --- I know it's not efficient for RSS ಥ⁠‿⁠ಥ
16
- const USER_ID_CACHE = new Map();
17
16
  export const makeChatsSocket = (config) => {
18
17
  const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config;
19
18
  const sock = makeSocket(config);
20
19
  const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession } = sock;
20
+ // Scoped per-connection with LRU to prevent unbounded memory growth
21
+ const USER_ID_CACHE = new LRUCache({ max: 5000, ttl: 30 * 60 * 1000 });
21
22
  let privacySettings;
22
23
  let syncState = SyncState.Connecting;
23
24
  /** this mutex ensures that messages are processed in order */
@@ -65,7 +65,7 @@ export const makeMessagesRecvSocket = (config) => {
65
65
  // metadata (LID details, timestamps, etc.) that the phone may omit
66
66
  await placeholderResendCache.set(messageKey?.id, msgData || true);
67
67
  }
68
- await delay(2000);
68
+ await delay(500);
69
69
  if (!(await placeholderResendCache.get(messageKey?.id))) {
70
70
  logger.debug({ messageKey }, 'message received while resend requested');
71
71
  return 'RESOLVED';
@@ -1138,7 +1138,7 @@ export const makeMessagesRecvSocket = (config) => {
1138
1138
  logger.debug('Uploading pre-keys for error recovery');
1139
1139
  await uploadPreKeys(5);
1140
1140
  logger.debug('Waiting for server to process new pre-keys');
1141
- await delay(1000);
1141
+ await delay(200);
1142
1142
  }
1143
1143
  catch (uploadErr) {
1144
1144
  logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
@@ -1308,8 +1308,12 @@ export const makeMessagesRecvSocket = (config) => {
1308
1308
  /// and adds the task to the existing buffer if we're buffering events
1309
1309
  const processNodeWithBuffer = async (node, identifier, exec) => {
1310
1310
  ev.buffer();
1311
- await execTask();
1312
- ev.flush();
1311
+ try {
1312
+ await execTask();
1313
+ }
1314
+ finally {
1315
+ ev.flush();
1316
+ }
1313
1317
  function execTask() {
1314
1318
  return exec(node, false).catch(err => onUnexpectedError(err, identifier));
1315
1319
  }
@@ -123,12 +123,9 @@ export const makeSocket = (config) => {
123
123
  node.attrs.id = generateMessageTag();
124
124
  }
125
125
  const msgId = node.attrs.id;
126
- const result = await promiseTimeout(timeoutMs, async (resolve, reject) => {
127
- const result = waitForMessage(msgId, timeoutMs).catch(reject);
128
- sendNode(node)
129
- .then(async () => resolve(await result))
130
- .catch(reject);
131
- });
126
+ const resultPromise = waitForMessage(msgId, timeoutMs);
127
+ await sendNode(node);
128
+ const result = await resultPromise;
132
129
  if (result && 'tag' in result) {
133
130
  assertNodeErrorFree(result);
134
131
  }
@@ -483,15 +480,18 @@ export const makeSocket = (config) => {
483
480
  logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed');
484
481
  clearInterval(keepAliveReq);
485
482
  clearTimeout(qrTimer);
486
- ws.removeAllListeners('close');
487
- ws.removeAllListeners('open');
488
- ws.removeAllListeners('message');
483
+ // Clear all WS listeners (CB:*, TAG:*, frame, error, etc.) to prevent memory leaks
484
+ ws.removeAllListeners();
489
485
  if (!ws.isClosed && !ws.isClosing) {
490
486
  try {
491
487
  await ws.close();
492
488
  }
493
489
  catch { }
494
490
  }
491
+ // Clean up noise handler buffers
492
+ if (noise.destroy) {
493
+ noise.destroy();
494
+ }
495
495
  ev.emit('connection.update', {
496
496
  connection: 'close',
497
497
  lastDisconnect: {
@@ -499,7 +499,11 @@ export const makeSocket = (config) => {
499
499
  date: new Date()
500
500
  }
501
501
  });
502
+ // Remove only internal socket listeners — preserve user-registered listeners
502
503
  ev.removeAllListeners('connection.update');
504
+ ev.removeAllListeners('creds.update');
505
+ ev.removeAllListeners('call');
506
+ ev.removeAllListeners('lid-mapping.update');
503
507
  };
504
508
  const waitForSocketOpen = async () => {
505
509
  if (ws.isOpen) {
@@ -522,7 +526,8 @@ export const makeSocket = (config) => {
522
526
  ws.off('error', onClose);
523
527
  });
524
528
  };
525
- const startKeepAliveRequest = () => (keepAliveReq = setInterval(() => {
529
+ const startKeepAliveRequest = () => {
530
+ keepAliveReq = setInterval(() => {
526
531
  if (!lastDateRecv) {
527
532
  lastDateRecv = new Date();
528
533
  }
@@ -552,7 +557,12 @@ export const makeSocket = (config) => {
552
557
  else {
553
558
  logger.warn('keep alive called when WS not open');
554
559
  }
555
- }, keepAliveIntervalMs));
560
+ }, keepAliveIntervalMs);
561
+ // unref so the interval doesn't prevent Node.js process from exiting
562
+ if (keepAliveReq?.unref) {
563
+ keepAliveReq.unref();
564
+ }
565
+ };
556
566
  /** i have no idea why this exists. pls enlighten me */
557
567
  const sendPassiveIq = (tag) => query({
558
568
  tag: 'iq',
@@ -598,7 +608,7 @@ export const makeSocket = (config) => {
598
608
  id: jidEncode(phoneNumber, 's.whatsapp.net'),
599
609
  name: '~'
600
610
  };
601
- ev.emit('creds.update', authState.creds);
611
+ ev.emit('creds.update', { pairingCode: authState.creds.pairingCode, me: authState.creds.me });
602
612
  await sendNode({
603
613
  tag: 'iq',
604
614
  attrs: {
@@ -810,7 +820,7 @@ export const makeSocket = (config) => {
810
820
  const routingInfo = getBinaryNodeChild(edgeRoutingNode, 'routing_info');
811
821
  if (routingInfo?.content) {
812
822
  authState.creds.routingInfo = Buffer.from(routingInfo?.content);
813
- ev.emit('creds.update', authState.creds);
823
+ ev.emit('creds.update', { routingInfo: authState.creds.routingInfo });
814
824
  }
815
825
  });
816
826
  let didStartBuffer = false;
@@ -149,7 +149,7 @@ export const makeEventBuffer = (logger) => {
149
149
  if (isBuffering && bufferCount === 1) {
150
150
  flush();
151
151
  }
152
- }, 100); // Small delay to allow nested buffers
152
+ }, 10); // Minimal delay to allow nested buffers without adding latency
153
153
  }
154
154
  return result;
155
155
  }
@@ -161,7 +161,7 @@ export const makeEventBuffer = (logger) => {
161
161
  if (bufferCount === 0) {
162
162
  // Only schedule ONE timeout, not 10,000
163
163
  if (!flushPendingTimeout) {
164
- flushPendingTimeout = setTimeout(flush, 100);
164
+ flushPendingTimeout = setTimeout(flush, 10);
165
165
  }
166
166
  }
167
167
  }
@@ -4,7 +4,7 @@ const RECENT_MESSAGES_SIZE = 512;
4
4
  const MESSAGE_KEY_SEPARATOR = '\u0000';
5
5
  /** Timeout for session recreation - 1 hour */
6
6
  const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000; // 1 hour in milliseconds
7
- const PHONE_REQUEST_DELAY = 3000;
7
+ const PHONE_REQUEST_DELAY = 1000;
8
8
  // Retry reason codes matching WhatsApp Web's Signal error codes.
9
9
  export var RetryReason;
10
10
  (function (RetryReason) {
@@ -195,6 +195,12 @@ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publi
195
195
  inBytes = Buffer.concat([inBytes, newData]);
196
196
  }
197
197
  await processData(onFrame);
198
+ },
199
+ /** Clean up internal buffers to prevent memory leaks on connection close */
200
+ destroy() {
201
+ inBytes = Buffer.alloc(0);
202
+ transport = null;
203
+ pendingOnFrame = null;
198
204
  }
199
205
  };
200
206
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "violetics",
3
- "version": "7.0.4-alpha",
3
+ "version": "7.0.5-alpha",
4
4
  "description": "A simple fork of Baileys for WhatsApp automation",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -42,7 +42,7 @@
42
42
  "@hapi/boom": "^9.1.3",
43
43
  "async-mutex": "^0.5.0",
44
44
  "fflate": "^0.8.2",
45
- "libsignal": "github:WhiskeySockets/libsignal-wasm#master",
45
+ "libsignal": "git+https://github.com/whiskeysockets/libsignal-node",
46
46
  "lru-cache": "^11.2.6",
47
47
  "music-metadata": "^11.7.0",
48
48
  "p-queue": "^9.1.0",