violetics 7.0.4-alpha → 7.0.6-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.
- package/lib/Defaults/index.js +1 -1
- package/lib/Socket/Client/websocket.js +10 -1
- package/lib/Socket/chats.js +3 -2
- package/lib/Socket/messages-recv.js +8 -4
- package/lib/Socket/socket.js +23 -13
- package/lib/Utils/event-buffer.js +2 -2
- package/lib/Utils/generics.js +14 -15
- package/lib/Utils/message-retry-manager.js +1 -1
- package/lib/Utils/noise-handler.js +6 -0
- package/package.json +2 -2
package/lib/Defaults/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
});
|
package/lib/Socket/chats.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
1312
|
-
|
|
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
|
}
|
package/lib/Socket/socket.js
CHANGED
|
@@ -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
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
487
|
-
ws.removeAllListeners(
|
|
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 = () =>
|
|
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
|
-
},
|
|
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,
|
|
164
|
+
flushPendingTimeout = setTimeout(flush, 10);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
}
|
package/lib/Utils/generics.js
CHANGED
|
@@ -132,24 +132,23 @@ export async function promiseTimeout(ms, promise) {
|
|
|
132
132
|
// inspired from whatsmeow code
|
|
133
133
|
// https://github.com/tulir/whatsmeow/blob/64bc969fbe78d31ae0dd443b8d4c80a5d026d07a/send.go#L42
|
|
134
134
|
export const generateMessageIDV2 = (userId) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const pos = 4 + (hash[0] & 15);
|
|
149
|
-
return baseId.slice(0, pos) + 'STARFALL' + baseId.slice(pos);
|
|
135
|
+
const data = Buffer.alloc(8 + 20 + 16);
|
|
136
|
+
data.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)));
|
|
137
|
+
if (userId) {
|
|
138
|
+
const id = jidDecode(userId);
|
|
139
|
+
if (id?.user) {
|
|
140
|
+
data.write(id.user, 8);
|
|
141
|
+
data.write('@c.us', 8 + id.user.length);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const random = randomBytes(16);
|
|
145
|
+
random.copy(data, 28);
|
|
146
|
+
const hash = createHash('sha256').update(data).digest();
|
|
147
|
+
return '3EB0' + hash.toString('hex').toUpperCase().substring(0, 18);
|
|
150
148
|
};
|
|
151
149
|
// generate a random ID to attach to a message
|
|
152
150
|
export const generateMessageID = () => '3EB0' + randomBytes(18).toString('hex').toUpperCase();
|
|
151
|
+
|
|
153
152
|
export function bindWaitForEvent(ev, event) {
|
|
154
153
|
return async (check, timeoutMs) => {
|
|
155
154
|
let listener;
|
|
@@ -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 =
|
|
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.
|
|
3
|
+
"version": "7.0.6-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
|
|
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",
|