baileys-antiban 3.8.3 → 3.8.4
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/CHANGELOG.md +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sessionStability.d.ts +71 -0
- package/dist/sessionStability.js +89 -0
- package/dist/wrapper.d.ts +7 -0
- package/dist/wrapper.js +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.8.4] - 2026-05-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`DeafSessionDetector`** — detects WebSocket sessions that stay open but stop delivering `messages.upsert` / `messages.update` events (Baileys issue #2491). Root cause: `messageMutex` holding ACKs under Redis latency spikes triggers WhatsApp server-side flow control, silently stopping message delivery while keepAlive pings still succeed. The detector runs a 30-second interval, fires `onDeafSession` callback with silence duration info, and optionally calls `sock.end(new Error('deaf-session'))` for auto-reconnect. Configurable `timeoutMs` (default 5 min), `minUptimeMs` warmup guard (default 2 min), `autoReconnect` flag.
|
|
12
|
+
- **`wrapSocket` `deafSession` option** — pass `deafSession: DeafSessionConfig` to `wrapOptions` to enable automatic deaf-session detection on the wrapped socket. Activity signals are wired to all `messages.upsert` and `messages.update` events; cleanup is tied to `connection.update` close events.
|
|
13
|
+
- New exports: `DeafSessionDetector`, `DeafSessionConfig`, `DeafSessionInfo`.
|
|
14
|
+
|
|
8
15
|
## [3.8.3] - 2026-05-09
|
|
9
16
|
|
|
10
17
|
### Fixed
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export { RetryReasonTracker, type RetryTrackerConfig, type RetryStats, type Retr
|
|
|
19
19
|
export { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThrottleStats } from './reconnectThrottle.js';
|
|
20
20
|
export { LidResolver, type LidResolverConfig, type LidResolverStats, type LidMapping } from './lidResolver.js';
|
|
21
21
|
export { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
|
|
22
|
-
export { SessionHealthMonitor, type SessionHealthStats, type SessionHealthConfig, wrapWithSessionStability, type SessionStabilityConfig, classifyDisconnect, type DisconnectClassification, type DisconnectCategory, } from './sessionStability.js';
|
|
22
|
+
export { SessionHealthMonitor, type SessionHealthStats, type SessionHealthConfig, wrapWithSessionStability, type SessionStabilityConfig, classifyDisconnect, type DisconnectClassification, type DisconnectCategory, DeafSessionDetector, type DeafSessionConfig, type DeafSessionInfo, } from './sessionStability.js';
|
|
23
23
|
export { LidFirstResolver, createLidFirstResolver, type LidPhoneMapping, } from './lidFirstResolver.js';
|
|
24
24
|
export { MessageRetryReason, MAC_ERROR_CODES, parseRetryReason, isMacError, getRetryReasonDescription, } from './retryReason.js';
|
|
25
25
|
export { wrapSocket, type WrappedSocket, type WrapSocketOptions } from './wrapper.js';
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
|
24
24
|
export { LidResolver } from './lidResolver.js';
|
|
25
25
|
export { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
26
26
|
// v2.0 new modules
|
|
27
|
-
export { SessionHealthMonitor, wrapWithSessionStability, classifyDisconnect, } from './sessionStability.js';
|
|
27
|
+
export { SessionHealthMonitor, wrapWithSessionStability, classifyDisconnect, DeafSessionDetector, } from './sessionStability.js';
|
|
28
28
|
// v2.1 new modules
|
|
29
29
|
export { LidFirstResolver, createLidFirstResolver, } from './lidFirstResolver.js';
|
|
30
30
|
export { MessageRetryReason, MAC_ERROR_CODES, parseRetryReason, isMacError, getRetryReasonDescription, } from './retryReason.js';
|
|
@@ -76,6 +76,77 @@ export declare class SessionHealthMonitor {
|
|
|
76
76
|
*/
|
|
77
77
|
reset(): void;
|
|
78
78
|
}
|
|
79
|
+
export interface DeafSessionConfig {
|
|
80
|
+
/**
|
|
81
|
+
* How long the session must be silent (no messages.upsert or messages.update)
|
|
82
|
+
* while the WS connection is open before it is declared "deaf".
|
|
83
|
+
* Default: 5 minutes.
|
|
84
|
+
*/
|
|
85
|
+
timeoutMs?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Minimum uptime before the detector starts checking.
|
|
88
|
+
* Avoids false positives immediately after a fresh connect.
|
|
89
|
+
* Default: 2 minutes.
|
|
90
|
+
*/
|
|
91
|
+
minUptimeMs?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Called when a deaf session is detected, before any auto-reconnect.
|
|
94
|
+
* Use this to log, alert, or run custom recovery logic.
|
|
95
|
+
*/
|
|
96
|
+
onDeafSession?: (info: DeafSessionInfo) => void;
|
|
97
|
+
/**
|
|
98
|
+
* If true, call sock.end(new Error('deaf-session')) automatically.
|
|
99
|
+
* Set false if you want to handle reconnection yourself in onDeafSession.
|
|
100
|
+
* Default: true.
|
|
101
|
+
*/
|
|
102
|
+
autoReconnect?: boolean;
|
|
103
|
+
}
|
|
104
|
+
export interface DeafSessionInfo {
|
|
105
|
+
/** Timestamp of last observed message activity, or null if none since connect. */
|
|
106
|
+
lastMessageAt: Date | null;
|
|
107
|
+
/** How long the session has been silent in ms. */
|
|
108
|
+
silenceDurationMs: number;
|
|
109
|
+
/** How long the WS has been open in ms. */
|
|
110
|
+
connectedSinceMs: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Detects "deaf sessions" — WebSocket connections that stay open but stop
|
|
114
|
+
* delivering messages.upsert / messages.update events.
|
|
115
|
+
*
|
|
116
|
+
* Root cause (Baileys issue #2491): messageMutex holding ACKs hostage under
|
|
117
|
+
* Redis latency spikes causes WhatsApp's server-side flow control to stop
|
|
118
|
+
* delivering messages to that client, while keepAlive pings still succeed.
|
|
119
|
+
*
|
|
120
|
+
* Usage: call onConnect() / onDisconnect() from connection.update events,
|
|
121
|
+
* onMessageActivity() from messages.upsert and messages.update events.
|
|
122
|
+
* Pass a sock reference via attach() so auto-reconnect can call sock.end().
|
|
123
|
+
*/
|
|
124
|
+
export declare class DeafSessionDetector {
|
|
125
|
+
private readonly timeoutMs;
|
|
126
|
+
private readonly minUptimeMs;
|
|
127
|
+
private readonly autoReconnect;
|
|
128
|
+
private readonly onDeafSessionCb?;
|
|
129
|
+
private lastMessageAt;
|
|
130
|
+
private connectedAt;
|
|
131
|
+
private timer;
|
|
132
|
+
private sockRef;
|
|
133
|
+
constructor(config?: DeafSessionConfig);
|
|
134
|
+
/** Attach a socket so auto-reconnect can call sock.end() */
|
|
135
|
+
attach(sock: {
|
|
136
|
+
end: (err?: Error) => void;
|
|
137
|
+
}): void;
|
|
138
|
+
/** Call when connection.update → connection === 'open' */
|
|
139
|
+
onConnect(): void;
|
|
140
|
+
/** Call when connection.update → connection === 'close' */
|
|
141
|
+
onDisconnect(): void;
|
|
142
|
+
/** Call on every messages.upsert and messages.update event */
|
|
143
|
+
onMessageActivity(): void;
|
|
144
|
+
/** Release the interval — call when discarding the socket */
|
|
145
|
+
destroy(): void;
|
|
146
|
+
private startTimer;
|
|
147
|
+
private stopTimer;
|
|
148
|
+
private check;
|
|
149
|
+
}
|
|
79
150
|
export interface SessionStabilityConfig {
|
|
80
151
|
/** Enable canonical JID normalization before sendMessage (default: true) */
|
|
81
152
|
canonicalJidNormalization?: boolean;
|
package/dist/sessionStability.js
CHANGED
|
@@ -207,6 +207,95 @@ export class SessionHealthMonitor {
|
|
|
207
207
|
this.badMacTimestamps = [];
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Detects "deaf sessions" — WebSocket connections that stay open but stop
|
|
212
|
+
* delivering messages.upsert / messages.update events.
|
|
213
|
+
*
|
|
214
|
+
* Root cause (Baileys issue #2491): messageMutex holding ACKs hostage under
|
|
215
|
+
* Redis latency spikes causes WhatsApp's server-side flow control to stop
|
|
216
|
+
* delivering messages to that client, while keepAlive pings still succeed.
|
|
217
|
+
*
|
|
218
|
+
* Usage: call onConnect() / onDisconnect() from connection.update events,
|
|
219
|
+
* onMessageActivity() from messages.upsert and messages.update events.
|
|
220
|
+
* Pass a sock reference via attach() so auto-reconnect can call sock.end().
|
|
221
|
+
*/
|
|
222
|
+
export class DeafSessionDetector {
|
|
223
|
+
timeoutMs;
|
|
224
|
+
minUptimeMs;
|
|
225
|
+
autoReconnect;
|
|
226
|
+
onDeafSessionCb;
|
|
227
|
+
lastMessageAt = null;
|
|
228
|
+
connectedAt = null;
|
|
229
|
+
timer = null;
|
|
230
|
+
sockRef = null;
|
|
231
|
+
constructor(config = {}) {
|
|
232
|
+
this.timeoutMs = config.timeoutMs ?? 5 * 60_000;
|
|
233
|
+
this.minUptimeMs = config.minUptimeMs ?? 2 * 60_000;
|
|
234
|
+
this.autoReconnect = config.autoReconnect ?? true;
|
|
235
|
+
this.onDeafSessionCb = config.onDeafSession;
|
|
236
|
+
}
|
|
237
|
+
/** Attach a socket so auto-reconnect can call sock.end() */
|
|
238
|
+
attach(sock) {
|
|
239
|
+
this.sockRef = sock;
|
|
240
|
+
}
|
|
241
|
+
/** Call when connection.update → connection === 'open' */
|
|
242
|
+
onConnect() {
|
|
243
|
+
this.connectedAt = Date.now();
|
|
244
|
+
this.lastMessageAt = Date.now(); // reset — fresh connect is not silence
|
|
245
|
+
this.startTimer();
|
|
246
|
+
}
|
|
247
|
+
/** Call when connection.update → connection === 'close' */
|
|
248
|
+
onDisconnect() {
|
|
249
|
+
this.connectedAt = null;
|
|
250
|
+
this.stopTimer();
|
|
251
|
+
}
|
|
252
|
+
/** Call on every messages.upsert and messages.update event */
|
|
253
|
+
onMessageActivity() {
|
|
254
|
+
this.lastMessageAt = Date.now();
|
|
255
|
+
}
|
|
256
|
+
/** Release the interval — call when discarding the socket */
|
|
257
|
+
destroy() {
|
|
258
|
+
this.stopTimer();
|
|
259
|
+
this.sockRef = null;
|
|
260
|
+
}
|
|
261
|
+
startTimer() {
|
|
262
|
+
this.stopTimer();
|
|
263
|
+
this.timer = setInterval(() => this.check(), 30_000);
|
|
264
|
+
}
|
|
265
|
+
stopTimer() {
|
|
266
|
+
if (this.timer !== null) {
|
|
267
|
+
clearInterval(this.timer);
|
|
268
|
+
this.timer = null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
check() {
|
|
272
|
+
if (this.connectedAt === null)
|
|
273
|
+
return;
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
const connectedSinceMs = now - this.connectedAt;
|
|
276
|
+
if (connectedSinceMs < this.minUptimeMs)
|
|
277
|
+
return;
|
|
278
|
+
const silenceDurationMs = now - (this.lastMessageAt ?? this.connectedAt);
|
|
279
|
+
if (silenceDurationMs < this.timeoutMs)
|
|
280
|
+
return;
|
|
281
|
+
const info = {
|
|
282
|
+
lastMessageAt: this.lastMessageAt !== null ? new Date(this.lastMessageAt) : null,
|
|
283
|
+
silenceDurationMs,
|
|
284
|
+
connectedSinceMs,
|
|
285
|
+
};
|
|
286
|
+
this.onDeafSessionCb?.(info);
|
|
287
|
+
if (this.autoReconnect && this.sockRef) {
|
|
288
|
+
try {
|
|
289
|
+
this.sockRef.end(new Error('deaf-session'));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// socket may already be closing
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Stop checking after triggering — let onConnect reset on next reconnect
|
|
296
|
+
this.stopTimer();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
210
299
|
/**
|
|
211
300
|
* Wrap a Baileys socket with session stability features.
|
|
212
301
|
* Returns a Proxy that intercepts sendMessage to canonicalize JIDs.
|
package/dist/wrapper.d.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* Timelock guard will operate in detection-only mode (relies on 463 errors only).
|
|
29
29
|
*/
|
|
30
30
|
import { AntiBan, type AntiBanConfig } from './antiban.js';
|
|
31
|
+
import { type DeafSessionConfig } from './sessionStability.js';
|
|
31
32
|
import type { WarmUpState } from './warmup.js';
|
|
32
33
|
export type WASocket = {
|
|
33
34
|
sendMessage: (jid: string, content: any, options?: any) => Promise<any>;
|
|
@@ -44,6 +45,12 @@ export type WASocket = {
|
|
|
44
45
|
export interface WrapSocketOptions {
|
|
45
46
|
/** Auto-respond to incoming messages when reply ratio suggests it (default: false) */
|
|
46
47
|
autoRespondToIncoming?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Deaf session detection — monitors for WS connections that stop delivering
|
|
50
|
+
* messages while keepAlive pings still succeed (Baileys issue #2491).
|
|
51
|
+
* Pass a config object to enable; omit to disable.
|
|
52
|
+
*/
|
|
53
|
+
deafSession?: DeafSessionConfig;
|
|
47
54
|
}
|
|
48
55
|
export type WrappedSocket<T extends WASocket = WASocket> = T & {
|
|
49
56
|
antiban: AntiBan;
|
package/dist/wrapper.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* Timelock guard will operate in detection-only mode (relies on 463 errors only).
|
|
29
29
|
*/
|
|
30
30
|
import { AntiBan } from './antiban.js';
|
|
31
|
+
import { DeafSessionDetector } from './sessionStability.js';
|
|
31
32
|
/**
|
|
32
33
|
* Wrap a Baileys socket with anti-ban protection.
|
|
33
34
|
* The returned socket has the same API but sendMessage() is protected.
|
|
@@ -38,6 +39,12 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
38
39
|
autoRespondToIncoming: false,
|
|
39
40
|
...wrapOptions,
|
|
40
41
|
};
|
|
42
|
+
// Deaf session detector — optional, enabled via wrapOptions.deafSession
|
|
43
|
+
const deafDetector = options.deafSession
|
|
44
|
+
? new DeafSessionDetector(options.deafSession)
|
|
45
|
+
: null;
|
|
46
|
+
if (deafDetector)
|
|
47
|
+
deafDetector.attach(sock);
|
|
41
48
|
// Hook into Baileys events for health monitoring
|
|
42
49
|
// Prefer ev.process() (Baileys ≥ late 2022) for batched event handling
|
|
43
50
|
// Fall back to ev.on() for older versions
|
|
@@ -50,9 +57,12 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
50
57
|
const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
|
|
51
58
|
antiban.onDisconnect(reason);
|
|
52
59
|
antiban.destroy(); // Clean up all timers
|
|
60
|
+
deafDetector?.onDisconnect();
|
|
61
|
+
deafDetector?.destroy();
|
|
53
62
|
}
|
|
54
63
|
if (update.connection === 'open') {
|
|
55
64
|
antiban.onReconnect();
|
|
65
|
+
deafDetector?.onConnect();
|
|
56
66
|
}
|
|
57
67
|
// Reachout timelock detection
|
|
58
68
|
if (update.reachoutTimeLock) {
|
|
@@ -66,6 +76,7 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
66
76
|
// Catch 463 errors from message updates + track retries + learn LID mappings
|
|
67
77
|
if (events['messages.update']) {
|
|
68
78
|
const updates = events['messages.update'];
|
|
79
|
+
deafDetector?.onMessageActivity();
|
|
69
80
|
for (const update of updates) {
|
|
70
81
|
// 463 error detection
|
|
71
82
|
if (update?.update?.messageStubParameters) {
|
|
@@ -85,6 +96,7 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
85
96
|
// Register known chats from incoming messages + handle reply suggestions + learn LID mappings
|
|
86
97
|
if (events['messages.upsert']) {
|
|
87
98
|
const { messages } = events['messages.upsert'];
|
|
99
|
+
deafDetector?.onMessageActivity();
|
|
88
100
|
// Learn LID mappings FIRST (before any other processing)
|
|
89
101
|
antiban.jidCanonicalizer?.onIncomingEvent(events['messages.upsert']);
|
|
90
102
|
for (const msg of messages || []) {
|
|
@@ -129,9 +141,12 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
129
141
|
const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
|
|
130
142
|
antiban.onDisconnect(reason);
|
|
131
143
|
antiban.destroy(); // Clean up all timers
|
|
144
|
+
deafDetector?.onDisconnect();
|
|
145
|
+
deafDetector?.destroy();
|
|
132
146
|
}
|
|
133
147
|
if (update.connection === 'open') {
|
|
134
148
|
antiban.onReconnect();
|
|
149
|
+
deafDetector?.onConnect();
|
|
135
150
|
}
|
|
136
151
|
// Reachout timelock detection
|
|
137
152
|
if (update.reachoutTimeLock) {
|
|
@@ -144,6 +159,7 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
144
159
|
});
|
|
145
160
|
// Catch 463 errors from message updates + track retries + learn LID mappings
|
|
146
161
|
sock.ev.on('messages.update', (updates) => {
|
|
162
|
+
deafDetector?.onMessageActivity();
|
|
147
163
|
for (const update of updates) {
|
|
148
164
|
// 463 error detection
|
|
149
165
|
if (update?.update?.messageStubParameters) {
|
|
@@ -161,6 +177,7 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
161
177
|
// Register known chats from incoming messages + handle reply suggestions + learn LID mappings
|
|
162
178
|
sock.ev.on('messages.upsert', (upsert) => {
|
|
163
179
|
const { messages } = upsert;
|
|
180
|
+
deafDetector?.onMessageActivity();
|
|
164
181
|
// Learn LID mappings FIRST (before any other processing)
|
|
165
182
|
antiban.jidCanonicalizer?.onIncomingEvent(upsert);
|
|
166
183
|
for (const msg of messages || []) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.4",
|
|
4
4
|
"description": "Anti-ban middleware for Baileys WhatsApp bots. Rate limiting, warmup, health monitor, LID resolver, disconnect classifier. Free Whapi.Cloud alternative.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|