baileys-antiban 1.6.0 → 2.0.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.
- package/CHANGELOG.md +31 -0
- package/README.md +138 -1
- package/dist/antiban.d.ts +17 -0
- package/dist/antiban.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/sessionStability.d.ts +93 -0
- package/dist/sessionStability.js +222 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ 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
|
+
## [2.0.0] - 2026-04-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Session Stability Module** — New middleware layer for Baileys socket stability (opt-in, backward compatible)
|
|
12
|
+
- `wrapWithSessionStability()` — Proxy wrapper for Baileys socket with stability features
|
|
13
|
+
- `SessionHealthMonitor` — Track decrypt success/fail ratio, emit degradation alerts when Bad MAC rate exceeds threshold
|
|
14
|
+
- `classifyDisconnect()` — Typed disconnect reason classification with recovery recommendations
|
|
15
|
+
- Canonical JID normalization before `sendMessage()` — Auto-resolves PN↔LID using `LidResolver` to reduce mutex race triggers
|
|
16
|
+
- Comprehensive disconnect code coverage: 401, 408, 428, 429, 440, 500, 503, 515, 1000, unknown
|
|
17
|
+
- Degradation detection: triggers `onDegraded` callback when Bad MAC count exceeds threshold in time window (default: 3 in 60s)
|
|
18
|
+
- Recovery detection: triggers `onRecovered` callback when Bad MAC rate drops below threshold
|
|
19
|
+
- 19 new tests with 100% coverage of disconnect classification and health monitoring
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `AntiBan` class extended with optional `sessionStability` config (default: disabled)
|
|
23
|
+
- `AntiBanConfig` interface includes `sessionStability` options (enabled, canonicalJidNormalization, healthMonitoring, badMacThreshold, badMacWindowMs)
|
|
24
|
+
- `AntiBanStats` includes `sessionStability` stats when enabled
|
|
25
|
+
- `destroy()` now cleans up session stability monitor
|
|
26
|
+
- Exposed `sessionStability` getter for direct access to health monitor
|
|
27
|
+
|
|
28
|
+
### Technical Details
|
|
29
|
+
- Pure middleware layer — no Baileys internals modification required
|
|
30
|
+
- Works alongside existing v1.x LID resolver and canonicalizer modules
|
|
31
|
+
- Default configuration: disabled for backward compatibility, opt-in via `sessionStability: { enabled: true }`
|
|
32
|
+
- Health monitor uses sliding window for Bad MAC detection (default: 3 errors in 60 seconds)
|
|
33
|
+
- Socket wrapper uses ES6 Proxy for transparent method interception
|
|
34
|
+
- TypeScript strict mode compliant, no `any` types except socket wrapper generic
|
|
35
|
+
|
|
36
|
+
### Breaking Changes
|
|
37
|
+
None — all v2.0 features are opt-in and backward compatible with v1.x
|
|
38
|
+
|
|
8
39
|
## [1.6.0] - 2026-04-18
|
|
9
40
|
|
|
10
41
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,144 @@
|
|
|
6
6
|
|
|
7
7
|
**Transport-agnostic** anti-ban middleware — protect your WhatsApp number with human-like messaging patterns. Works with both [Baileys](https://github.com/WhiskeySockets/Baileys) and [@oxidezap/baileyrs](https://github.com/oxidezap/baileyrs) (Rust/WASM).
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## v2.0 New Features — Session Stability Module
|
|
10
|
+
|
|
11
|
+
### What's New in v2.0
|
|
12
|
+
|
|
13
|
+
Three powerful new features to improve session stability and reduce "Bad MAC" errors:
|
|
14
|
+
|
|
15
|
+
1. **Typed Disconnect Reason Classification** — Know exactly why you disconnected and how to recover
|
|
16
|
+
2. **Session Health Monitor** — Detect session degradation before it causes bans
|
|
17
|
+
3. **Socket Wrapper with JID Canonicalization** — Middleware-layer fix for LID/PN race conditions
|
|
18
|
+
|
|
19
|
+
All v2.0 features are **opt-in** and **100% backward compatible** with v1.x.
|
|
20
|
+
|
|
21
|
+
### 1. Typed Disconnect Reason Classification
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { classifyDisconnect } from 'baileys-antiban';
|
|
25
|
+
|
|
26
|
+
sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
|
|
27
|
+
if (connection === 'close' && lastDisconnect?.error) {
|
|
28
|
+
const statusCode = lastDisconnect.error.output?.statusCode;
|
|
29
|
+
const classification = classifyDisconnect(statusCode);
|
|
30
|
+
|
|
31
|
+
console.log(`Disconnected: ${classification.message}`);
|
|
32
|
+
console.log(`Category: ${classification.category}`); // fatal | recoverable | rate-limited | unknown
|
|
33
|
+
console.log(`Should reconnect: ${classification.shouldReconnect}`);
|
|
34
|
+
|
|
35
|
+
if (classification.shouldReconnect && classification.backoffMs) {
|
|
36
|
+
console.log(`Recommended backoff: ${classification.backoffMs}ms`);
|
|
37
|
+
setTimeout(() => connectToWhatsApp(), classification.backoffMs);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Supported disconnect codes**: 401 (logged out), 408 (timeout), 428 (connection replaced), 429 (rate limited), 440 (logged out), 500 (internal error), 503 (unavailable), 515 (restart required), 1000 (graceful close), and unknown codes.
|
|
44
|
+
|
|
45
|
+
### 2. Session Health Monitor
|
|
46
|
+
|
|
47
|
+
Track decrypt success/failure ratio to detect session degradation **before** it causes a ban:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { SessionHealthMonitor } from 'baileys-antiban';
|
|
51
|
+
|
|
52
|
+
const healthMonitor = new SessionHealthMonitor({
|
|
53
|
+
badMacThreshold: 3, // Alert after 3 Bad MACs
|
|
54
|
+
badMacWindowMs: 60_000, // ...in 60 seconds
|
|
55
|
+
onDegraded: (stats) => {
|
|
56
|
+
console.error(`🔴 SESSION DEGRADED: ${stats.badMacCount} Bad MACs in last minute`);
|
|
57
|
+
console.error('Action required: Restart session or switch to LID-based canonical form');
|
|
58
|
+
},
|
|
59
|
+
onRecovered: (stats) => {
|
|
60
|
+
console.log('🟢 Session recovered — decrypt success rate improved');
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Wire to Baileys events
|
|
65
|
+
sock.ev.on('messages.update', (updates) => {
|
|
66
|
+
for (const { key, update } of updates) {
|
|
67
|
+
if (update.messageStubType === Types.WAMessageStubType.CIPHERTEXT) {
|
|
68
|
+
healthMonitor.recordDecryptFail(true); // Bad MAC detected
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Check status anytime
|
|
74
|
+
const stats = healthMonitor.getStats();
|
|
75
|
+
console.log(`Decrypt success: ${stats.decryptSuccess}`);
|
|
76
|
+
console.log(`Bad MAC count: ${stats.badMacCount}`);
|
|
77
|
+
console.log(`Is degraded: ${stats.isDegraded}`);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Socket Wrapper with JID Canonicalization
|
|
81
|
+
|
|
82
|
+
The easiest way to use v2.0: wrap your socket for automatic JID canonicalization and health monitoring:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { wrapWithSessionStability, LidResolver } from 'baileys-antiban';
|
|
86
|
+
|
|
87
|
+
const resolver = new LidResolver({ canonical: 'pn' });
|
|
88
|
+
const sock = makeWASocket({ ... });
|
|
89
|
+
|
|
90
|
+
const safeSock = wrapWithSessionStability(sock, {
|
|
91
|
+
canonicalJidNormalization: true, // Auto-canonicalize JIDs before sendMessage
|
|
92
|
+
healthMonitoring: true, // Auto-track decrypt health
|
|
93
|
+
lidResolver: resolver,
|
|
94
|
+
health: {
|
|
95
|
+
badMacThreshold: 3,
|
|
96
|
+
badMacWindowMs: 60_000,
|
|
97
|
+
onDegraded: (stats) => console.error('Session degraded!'),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Use safeSock exactly like normal sock
|
|
102
|
+
await safeSock.sendMessage('123456@lid', { text: 'hello' });
|
|
103
|
+
// ^ Automatically canonicalized to '27825651069@s.whatsapp.net' if mapping exists
|
|
104
|
+
|
|
105
|
+
// Access health stats
|
|
106
|
+
const healthStats = safeSock.sessionHealthStats;
|
|
107
|
+
console.log(`Bad MAC count: ${healthStats.badMacCount}`);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Integration with AntiBan Class
|
|
111
|
+
|
|
112
|
+
You can also enable session stability via the main `AntiBan` config:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { AntiBan } from 'baileys-antiban';
|
|
116
|
+
|
|
117
|
+
const antiban = new AntiBan({
|
|
118
|
+
sessionStability: {
|
|
119
|
+
enabled: true,
|
|
120
|
+
canonicalJidNormalization: true, // Auto-canonicalize JIDs
|
|
121
|
+
healthMonitoring: true, // Track Bad MAC rate
|
|
122
|
+
badMacThreshold: 3,
|
|
123
|
+
badMacWindowMs: 60_000,
|
|
124
|
+
},
|
|
125
|
+
jidCanonicalizer: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
canonical: 'pn',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Access health monitor directly
|
|
132
|
+
const healthMonitor = antiban.sessionStability;
|
|
133
|
+
if (healthMonitor) {
|
|
134
|
+
console.log(healthMonitor.getStats());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Stats include session stability
|
|
138
|
+
const stats = antiban.getStats();
|
|
139
|
+
console.log(stats.sessionStability); // Health stats when enabled
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Why v2.0?** Bad MAC errors are the #1 reported Baileys issue. Session stability features give you early warning and automated mitigation, reducing bans caused by session degradation.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## v1.5 Features
|
|
10
147
|
|
|
11
148
|
### RetryReasonTracker
|
|
12
149
|
Tracks message retry reasons and detects retry spirals (when the same message keeps failing). Inspired by whatsapp-rust's protocol/retry.rs module.
|
package/dist/antiban.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { RetryReasonTracker, type RetryTrackerConfig, type RetryStats } from './
|
|
|
24
24
|
import { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThrottleStats } from './reconnectThrottle.js';
|
|
25
25
|
import { LidResolver, type LidResolverConfig, type LidResolverStats } from './lidResolver.js';
|
|
26
26
|
import { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
|
|
27
|
+
import { SessionHealthMonitor, type SessionHealthStats } from './sessionStability.js';
|
|
27
28
|
export interface AntiBanConfig {
|
|
28
29
|
rateLimiter?: Partial<RateLimiterConfig>;
|
|
29
30
|
warmUp?: Partial<WarmUpConfig>;
|
|
@@ -36,6 +37,18 @@ export interface AntiBanConfig {
|
|
|
36
37
|
reconnectThrottle?: Partial<ReconnectThrottleConfig>;
|
|
37
38
|
lidResolver?: LidResolverConfig;
|
|
38
39
|
jidCanonicalizer?: JidCanonicalizerConfig;
|
|
40
|
+
/** Session stability features (v2.0) — default disabled for backward compatibility */
|
|
41
|
+
sessionStability?: {
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
/** Enable canonical JID normalization before sendMessage (default: true if enabled) */
|
|
44
|
+
canonicalJidNormalization?: boolean;
|
|
45
|
+
/** Enable session health monitoring (default: true if enabled) */
|
|
46
|
+
healthMonitoring?: boolean;
|
|
47
|
+
/** Bad MAC threshold before declaring session degraded (default: 3) */
|
|
48
|
+
badMacThreshold?: number;
|
|
49
|
+
/** Time window for Bad MAC threshold in ms (default: 60000) */
|
|
50
|
+
badMacWindowMs?: number;
|
|
51
|
+
};
|
|
39
52
|
/** Log warnings and blocks to console (default: true) */
|
|
40
53
|
logging?: boolean;
|
|
41
54
|
}
|
|
@@ -60,6 +73,7 @@ export interface AntiBanStats {
|
|
|
60
73
|
reconnectThrottle?: ReconnectThrottleStats | null;
|
|
61
74
|
lidResolver?: LidResolverStats | null;
|
|
62
75
|
jidCanonicalizer?: JidCanonicalizerStats | null;
|
|
76
|
+
sessionStability?: SessionHealthStats | null;
|
|
63
77
|
}
|
|
64
78
|
export declare class AntiBan {
|
|
65
79
|
private rateLimiter;
|
|
@@ -73,6 +87,7 @@ export declare class AntiBan {
|
|
|
73
87
|
private reconnectThrottleModule;
|
|
74
88
|
private lidResolverModule;
|
|
75
89
|
private jidCanonicalizerModule;
|
|
90
|
+
private sessionStabilityMonitor;
|
|
76
91
|
private logging;
|
|
77
92
|
private stats;
|
|
78
93
|
constructor(config?: AntiBanConfig, warmUpState?: WarmUpState);
|
|
@@ -126,6 +141,8 @@ export declare class AntiBan {
|
|
|
126
141
|
get lidResolver(): LidResolver | null;
|
|
127
142
|
/** Get the JID canonicalizer for direct access */
|
|
128
143
|
get jidCanonicalizer(): JidCanonicalizer | null;
|
|
144
|
+
/** Get the session stability monitor for direct access */
|
|
145
|
+
get sessionStability(): SessionHealthMonitor | null;
|
|
129
146
|
/**
|
|
130
147
|
* Export warm-up state for persistence between restarts
|
|
131
148
|
*/
|
package/dist/antiban.js
CHANGED
|
@@ -24,6 +24,7 @@ import { RetryReasonTracker } from './retryTracker.js';
|
|
|
24
24
|
import { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
25
25
|
import { LidResolver } from './lidResolver.js';
|
|
26
26
|
import { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
27
|
+
import { SessionHealthMonitor } from './sessionStability.js';
|
|
27
28
|
export class AntiBan {
|
|
28
29
|
rateLimiter;
|
|
29
30
|
warmUp;
|
|
@@ -36,6 +37,7 @@ export class AntiBan {
|
|
|
36
37
|
reconnectThrottleModule;
|
|
37
38
|
lidResolverModule = null;
|
|
38
39
|
jidCanonicalizerModule = null;
|
|
40
|
+
sessionStabilityMonitor = null;
|
|
39
41
|
logging;
|
|
40
42
|
stats = {
|
|
41
43
|
messagesAllowed: 0,
|
|
@@ -114,6 +116,25 @@ export class AntiBan {
|
|
|
114
116
|
// Standalone resolver without canonicalizer
|
|
115
117
|
this.lidResolverModule = new LidResolver(config.lidResolver);
|
|
116
118
|
}
|
|
119
|
+
// Initialize session stability monitor if enabled
|
|
120
|
+
if (config.sessionStability?.enabled) {
|
|
121
|
+
const healthConfig = {
|
|
122
|
+
badMacThreshold: config.sessionStability.badMacThreshold,
|
|
123
|
+
badMacWindowMs: config.sessionStability.badMacWindowMs,
|
|
124
|
+
onDegraded: (stats) => {
|
|
125
|
+
if (this.logging) {
|
|
126
|
+
console.log(`[baileys-antiban] 🔴 SESSION DEGRADED — Bad MAC rate: ${stats.badMacCount} in last ${config.sessionStability?.badMacWindowMs || 60000}ms`);
|
|
127
|
+
console.log(`[baileys-antiban] Consider restarting session or switching to LID-based canonical form`);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
onRecovered: () => {
|
|
131
|
+
if (this.logging) {
|
|
132
|
+
console.log(`[baileys-antiban] 🟢 SESSION RECOVERED — decrypt success rate improved`);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
this.sessionStabilityMonitor = new SessionHealthMonitor(healthConfig);
|
|
137
|
+
}
|
|
117
138
|
}
|
|
118
139
|
/**
|
|
119
140
|
* Check if a message can be sent and get required delay.
|
|
@@ -320,6 +341,9 @@ export class AntiBan {
|
|
|
320
341
|
if (this.jidCanonicalizerModule) {
|
|
321
342
|
stats.jidCanonicalizer = this.jidCanonicalizerModule.getStats();
|
|
322
343
|
}
|
|
344
|
+
if (this.sessionStabilityMonitor) {
|
|
345
|
+
stats.sessionStability = this.sessionStabilityMonitor.getStats();
|
|
346
|
+
}
|
|
323
347
|
return stats;
|
|
324
348
|
}
|
|
325
349
|
/** Get the timelock guard for direct access */
|
|
@@ -354,6 +378,10 @@ export class AntiBan {
|
|
|
354
378
|
get jidCanonicalizer() {
|
|
355
379
|
return this.jidCanonicalizerModule;
|
|
356
380
|
}
|
|
381
|
+
/** Get the session stability monitor for direct access */
|
|
382
|
+
get sessionStability() {
|
|
383
|
+
return this.sessionStabilityMonitor;
|
|
384
|
+
}
|
|
357
385
|
/**
|
|
358
386
|
* Export warm-up state for persistence between restarts
|
|
359
387
|
*/
|
|
@@ -408,6 +436,7 @@ export class AntiBan {
|
|
|
408
436
|
this.reconnectThrottleModule.destroy();
|
|
409
437
|
this.jidCanonicalizerModule?.destroy();
|
|
410
438
|
this.lidResolverModule?.destroy();
|
|
439
|
+
this.sessionStabilityMonitor?.reset();
|
|
411
440
|
if (this.logging) {
|
|
412
441
|
console.log('[baileys-antiban] 🧹 Destroyed — all timers cleared');
|
|
413
442
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +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
23
|
export { wrapSocket, type WrappedSocket, type WrapSocketOptions } from './wrapper.js';
|
|
23
24
|
export { MessageQueue, type QueuedMessage, type MessageQueueConfig } from './messageQueue.js';
|
|
24
25
|
export { ContentVariator, type VariatorConfig } from './contentVariator.js';
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,8 @@ export { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
|
23
23
|
// v1.6 new modules
|
|
24
24
|
export { LidResolver } from './lidResolver.js';
|
|
25
25
|
export { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
26
|
+
// v2.0 new modules
|
|
27
|
+
export { SessionHealthMonitor, wrapWithSessionStability, classifyDisconnect, } from './sessionStability.js';
|
|
26
28
|
// Socket wrapper
|
|
27
29
|
export { wrapSocket } from './wrapper.js';
|
|
28
30
|
// Optional features
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Stability Module — Middleware layer for Baileys socket stability
|
|
3
|
+
*
|
|
4
|
+
* Wraps Baileys socket to provide:
|
|
5
|
+
* 1. Canonical JID normalization before sendMessage (reduces mutex race triggers)
|
|
6
|
+
* 2. Typed disconnect reason classification with recovery recommendations
|
|
7
|
+
* 3. Session health monitoring (Bad MAC detection and degradation alerts)
|
|
8
|
+
*
|
|
9
|
+
* This is a pure middleware layer — cannot modify Baileys internals, but can wrap
|
|
10
|
+
* the socket interface to provide stability improvements.
|
|
11
|
+
*
|
|
12
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
13
|
+
* @license MIT
|
|
14
|
+
*/
|
|
15
|
+
import { LidResolver } from './lidResolver.js';
|
|
16
|
+
export type DisconnectCategory = 'fatal' | 'recoverable' | 'rate-limited' | 'unknown';
|
|
17
|
+
export interface DisconnectClassification {
|
|
18
|
+
category: DisconnectCategory;
|
|
19
|
+
shouldReconnect: boolean;
|
|
20
|
+
backoffMs?: number;
|
|
21
|
+
message: string;
|
|
22
|
+
code: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Classify Baileys DisconnectReason codes into typed categories.
|
|
26
|
+
* Based on PR #2367 and observed behavior from production bots.
|
|
27
|
+
*/
|
|
28
|
+
export declare function classifyDisconnect(statusCode: number): DisconnectClassification;
|
|
29
|
+
export interface SessionHealthStats {
|
|
30
|
+
decryptSuccess: number;
|
|
31
|
+
decryptFail: number;
|
|
32
|
+
badMacCount: number;
|
|
33
|
+
lastBadMac?: Date;
|
|
34
|
+
isDegraded: boolean;
|
|
35
|
+
degradedSince?: Date;
|
|
36
|
+
}
|
|
37
|
+
export interface SessionHealthConfig {
|
|
38
|
+
/** Threshold for Bad MAC errors in window before declaring degraded (default: 3) */
|
|
39
|
+
badMacThreshold?: number;
|
|
40
|
+
/** Time window for Bad MAC threshold in ms (default: 60000 = 1 minute) */
|
|
41
|
+
badMacWindowMs?: number;
|
|
42
|
+
/** Callback when session enters degraded state */
|
|
43
|
+
onDegraded?: (stats: SessionHealthStats) => void;
|
|
44
|
+
/** Callback when session recovers from degraded state */
|
|
45
|
+
onRecovered?: (stats: SessionHealthStats) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Track session health via decrypt success/failure ratio.
|
|
49
|
+
* Emits 'session:degraded' event when Bad MAC rate exceeds threshold.
|
|
50
|
+
*/
|
|
51
|
+
export declare class SessionHealthMonitor {
|
|
52
|
+
private config;
|
|
53
|
+
private onDegraded?;
|
|
54
|
+
private onRecovered?;
|
|
55
|
+
private stats;
|
|
56
|
+
private badMacTimestamps;
|
|
57
|
+
constructor(config?: SessionHealthConfig);
|
|
58
|
+
/**
|
|
59
|
+
* Record successful decrypt
|
|
60
|
+
*/
|
|
61
|
+
recordDecryptSuccess(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Record failed decrypt (Bad MAC or similar)
|
|
64
|
+
*/
|
|
65
|
+
recordDecryptFail(isBadMac?: boolean): void;
|
|
66
|
+
/**
|
|
67
|
+
* Check if session has recovered from degraded state
|
|
68
|
+
*/
|
|
69
|
+
private checkRecovery;
|
|
70
|
+
/**
|
|
71
|
+
* Get current health stats
|
|
72
|
+
*/
|
|
73
|
+
getStats(): SessionHealthStats;
|
|
74
|
+
/**
|
|
75
|
+
* Reset all counters
|
|
76
|
+
*/
|
|
77
|
+
reset(): void;
|
|
78
|
+
}
|
|
79
|
+
export interface SessionStabilityConfig {
|
|
80
|
+
/** Enable canonical JID normalization before sendMessage (default: true) */
|
|
81
|
+
canonicalJidNormalization?: boolean;
|
|
82
|
+
/** Enable session health monitoring (default: true) */
|
|
83
|
+
healthMonitoring?: boolean;
|
|
84
|
+
/** Session health config (only used if healthMonitoring enabled) */
|
|
85
|
+
health?: SessionHealthConfig;
|
|
86
|
+
/** LID resolver instance (required for canonicalJidNormalization) */
|
|
87
|
+
lidResolver?: LidResolver;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Wrap a Baileys socket with session stability features.
|
|
91
|
+
* Returns a Proxy that intercepts sendMessage to canonicalize JIDs.
|
|
92
|
+
*/
|
|
93
|
+
export declare function wrapWithSessionStability<T extends Record<string, any>>(sock: T, config?: SessionStabilityConfig): T;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Stability Module — Middleware layer for Baileys socket stability
|
|
3
|
+
*
|
|
4
|
+
* Wraps Baileys socket to provide:
|
|
5
|
+
* 1. Canonical JID normalization before sendMessage (reduces mutex race triggers)
|
|
6
|
+
* 2. Typed disconnect reason classification with recovery recommendations
|
|
7
|
+
* 3. Session health monitoring (Bad MAC detection and degradation alerts)
|
|
8
|
+
*
|
|
9
|
+
* This is a pure middleware layer — cannot modify Baileys internals, but can wrap
|
|
10
|
+
* the socket interface to provide stability improvements.
|
|
11
|
+
*
|
|
12
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
13
|
+
* @license MIT
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Classify Baileys DisconnectReason codes into typed categories.
|
|
17
|
+
* Based on PR #2367 and observed behavior from production bots.
|
|
18
|
+
*/
|
|
19
|
+
export function classifyDisconnect(statusCode) {
|
|
20
|
+
// Fatal errors — logged out or banned, need QR restart
|
|
21
|
+
if (statusCode === 401 || statusCode === 440) {
|
|
22
|
+
return {
|
|
23
|
+
category: 'fatal',
|
|
24
|
+
shouldReconnect: false,
|
|
25
|
+
message: 'Logged out — restart with QR code required',
|
|
26
|
+
code: statusCode,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (statusCode === 515) {
|
|
30
|
+
return {
|
|
31
|
+
category: 'fatal',
|
|
32
|
+
shouldReconnect: false,
|
|
33
|
+
message: 'Restart required by WhatsApp — client too old or protocol mismatch',
|
|
34
|
+
code: statusCode,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Connection replaced — user logged in elsewhere or multi-device conflict
|
|
38
|
+
if (statusCode === 428) {
|
|
39
|
+
return {
|
|
40
|
+
category: 'fatal',
|
|
41
|
+
shouldReconnect: false,
|
|
42
|
+
message: 'Connection replaced — another device logged in',
|
|
43
|
+
code: statusCode,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Rate limited — back off before reconnecting
|
|
47
|
+
if (statusCode === 429) {
|
|
48
|
+
return {
|
|
49
|
+
category: 'rate-limited',
|
|
50
|
+
shouldReconnect: true,
|
|
51
|
+
backoffMs: 300_000, // 5 minutes
|
|
52
|
+
message: 'Rate limited by WhatsApp — cool-off period required',
|
|
53
|
+
code: statusCode,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (statusCode === 503) {
|
|
57
|
+
return {
|
|
58
|
+
category: 'rate-limited',
|
|
59
|
+
shouldReconnect: true,
|
|
60
|
+
backoffMs: 60_000, // 1 minute
|
|
61
|
+
message: 'WhatsApp service unavailable — temporary outage',
|
|
62
|
+
code: statusCode,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Timeout — transient network issue
|
|
66
|
+
if (statusCode === 408) {
|
|
67
|
+
return {
|
|
68
|
+
category: 'recoverable',
|
|
69
|
+
shouldReconnect: true,
|
|
70
|
+
backoffMs: 5_000, // 5 seconds
|
|
71
|
+
message: 'Connection timeout — network issue, safe to retry',
|
|
72
|
+
code: statusCode,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Internal server error — WhatsApp hiccup
|
|
76
|
+
if (statusCode === 500) {
|
|
77
|
+
return {
|
|
78
|
+
category: 'recoverable',
|
|
79
|
+
shouldReconnect: true,
|
|
80
|
+
backoffMs: 10_000, // 10 seconds
|
|
81
|
+
message: 'WhatsApp internal error — temporary server issue',
|
|
82
|
+
code: statusCode,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Connection closed gracefully
|
|
86
|
+
if (statusCode === 1000) {
|
|
87
|
+
return {
|
|
88
|
+
category: 'recoverable',
|
|
89
|
+
shouldReconnect: true,
|
|
90
|
+
backoffMs: 2_000, // 2 seconds
|
|
91
|
+
message: 'Connection closed gracefully — safe to reconnect',
|
|
92
|
+
code: statusCode,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Unknown code — conservative approach
|
|
96
|
+
return {
|
|
97
|
+
category: 'unknown',
|
|
98
|
+
shouldReconnect: true,
|
|
99
|
+
backoffMs: 15_000, // 15 seconds
|
|
100
|
+
message: `Unknown disconnect reason (code ${statusCode}) — reconnect with caution`,
|
|
101
|
+
code: statusCode,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const DEFAULT_HEALTH_CONFIG = {
|
|
105
|
+
badMacThreshold: 3,
|
|
106
|
+
badMacWindowMs: 60_000,
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Track session health via decrypt success/failure ratio.
|
|
110
|
+
* Emits 'session:degraded' event when Bad MAC rate exceeds threshold.
|
|
111
|
+
*/
|
|
112
|
+
export class SessionHealthMonitor {
|
|
113
|
+
config;
|
|
114
|
+
onDegraded;
|
|
115
|
+
onRecovered;
|
|
116
|
+
stats = {
|
|
117
|
+
decryptSuccess: 0,
|
|
118
|
+
decryptFail: 0,
|
|
119
|
+
badMacCount: 0,
|
|
120
|
+
isDegraded: false,
|
|
121
|
+
};
|
|
122
|
+
badMacTimestamps = [];
|
|
123
|
+
constructor(config = {}) {
|
|
124
|
+
this.config = { ...DEFAULT_HEALTH_CONFIG, ...config };
|
|
125
|
+
this.onDegraded = config.onDegraded;
|
|
126
|
+
this.onRecovered = config.onRecovered;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Record successful decrypt
|
|
130
|
+
*/
|
|
131
|
+
recordDecryptSuccess() {
|
|
132
|
+
this.stats.decryptSuccess++;
|
|
133
|
+
this.checkRecovery();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Record failed decrypt (Bad MAC or similar)
|
|
137
|
+
*/
|
|
138
|
+
recordDecryptFail(isBadMac = false) {
|
|
139
|
+
this.stats.decryptFail++;
|
|
140
|
+
if (isBadMac) {
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
this.stats.badMacCount++;
|
|
143
|
+
this.stats.lastBadMac = new Date(now);
|
|
144
|
+
this.badMacTimestamps.push(now);
|
|
145
|
+
// Clean up old timestamps outside window
|
|
146
|
+
const cutoff = now - this.config.badMacWindowMs;
|
|
147
|
+
this.badMacTimestamps = this.badMacTimestamps.filter(ts => ts > cutoff);
|
|
148
|
+
// Check if we've crossed threshold
|
|
149
|
+
if (!this.stats.isDegraded && this.badMacTimestamps.length >= this.config.badMacThreshold) {
|
|
150
|
+
this.stats.isDegraded = true;
|
|
151
|
+
this.stats.degradedSince = new Date(now);
|
|
152
|
+
this.onDegraded?.(this.getStats());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if session has recovered from degraded state
|
|
158
|
+
*/
|
|
159
|
+
checkRecovery() {
|
|
160
|
+
if (!this.stats.isDegraded)
|
|
161
|
+
return;
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
const cutoff = now - this.config.badMacWindowMs;
|
|
164
|
+
this.badMacTimestamps = this.badMacTimestamps.filter(ts => ts > cutoff);
|
|
165
|
+
// Recovered if Bad MAC count dropped below threshold
|
|
166
|
+
if (this.badMacTimestamps.length < this.config.badMacThreshold) {
|
|
167
|
+
this.stats.isDegraded = false;
|
|
168
|
+
this.stats.degradedSince = undefined;
|
|
169
|
+
this.onRecovered?.(this.getStats());
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get current health stats
|
|
174
|
+
*/
|
|
175
|
+
getStats() {
|
|
176
|
+
return { ...this.stats };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Reset all counters
|
|
180
|
+
*/
|
|
181
|
+
reset() {
|
|
182
|
+
this.stats = {
|
|
183
|
+
decryptSuccess: 0,
|
|
184
|
+
decryptFail: 0,
|
|
185
|
+
badMacCount: 0,
|
|
186
|
+
isDegraded: false,
|
|
187
|
+
};
|
|
188
|
+
this.badMacTimestamps = [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Wrap a Baileys socket with session stability features.
|
|
193
|
+
* Returns a Proxy that intercepts sendMessage to canonicalize JIDs.
|
|
194
|
+
*/
|
|
195
|
+
export function wrapWithSessionStability(sock, config = {}) {
|
|
196
|
+
const { canonicalJidNormalization = true, healthMonitoring = true, health: healthConfig, lidResolver, } = config;
|
|
197
|
+
// Initialize health monitor if enabled
|
|
198
|
+
const healthMonitor = healthMonitoring ? new SessionHealthMonitor(healthConfig) : null;
|
|
199
|
+
// Return a Proxy that intercepts method calls
|
|
200
|
+
return new Proxy(sock, {
|
|
201
|
+
get(target, prop) {
|
|
202
|
+
// Intercept sendMessage for JID canonicalization
|
|
203
|
+
if (prop === 'sendMessage' && canonicalJidNormalization && lidResolver) {
|
|
204
|
+
return async (jid, content, options) => {
|
|
205
|
+
// Canonicalize JID using LID resolver
|
|
206
|
+
const canonical = lidResolver.resolveCanonical(jid);
|
|
207
|
+
return target.sendMessage(canonical, content, options);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Expose health monitor stats via a getter
|
|
211
|
+
if (prop === 'sessionHealthStats' && healthMonitor) {
|
|
212
|
+
return healthMonitor.getStats();
|
|
213
|
+
}
|
|
214
|
+
// Expose health monitor instance
|
|
215
|
+
if (prop === 'sessionHealthMonitor' && healthMonitor) {
|
|
216
|
+
return healthMonitor;
|
|
217
|
+
}
|
|
218
|
+
// Pass through everything else
|
|
219
|
+
return target[prop];
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Transport-agnostic anti-ban middleware for Baileys and baileyrs — human-like messaging patterns to protect your WhatsApp number",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|