baileys-antiban 1.4.0 → 1.6.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 +47 -0
- package/README.md +121 -1
- package/dist/antiban.d.ts +24 -0
- package/dist/antiban.js +95 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/jidCanonicalizer.d.ts +78 -0
- package/dist/jidCanonicalizer.js +172 -0
- package/dist/lidResolver.d.ts +96 -0
- package/dist/lidResolver.js +292 -0
- package/dist/reconnectThrottle.d.ts +89 -0
- package/dist/reconnectThrottle.js +180 -0
- package/dist/retryTracker.d.ts +87 -0
- package/dist/retryTracker.js +171 -0
- package/dist/wrapper.js +40 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,53 @@ 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
|
+
## [1.6.0] - 2026-04-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **LID/PN Race Condition Mitigation** — New modules to address the #1 reported Baileys bug: "Bad MAC / No Session / Invalid PreKey" errors caused by WhatsApp's Linked Identity (LID) migration
|
|
12
|
+
- `LidResolver` — Standalone utility for maintaining bidirectional LID↔PN mappings learned from message events
|
|
13
|
+
- `JidCanonicalizer` — Opt-in middleware that auto-learns from incoming events and canonicalizes outbound send targets to a single form (phone number by default)
|
|
14
|
+
- Both modules default to **disabled** — backward compatible, zero behavior change for existing users
|
|
15
|
+
- Middleware-layer mitigation only — root fix still requires [PR #2372](https://github.com/WhiskeySockets/Baileys/pull/2372) merged upstream
|
|
16
|
+
- Comprehensive test coverage: 56 new tests (29 LidResolver + 18 JidCanonicalizer + 9 integration)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `AntiBan` class now exposes `lidResolver` and `jidCanonicalizer` getters for direct access
|
|
20
|
+
- `AntiBanConfig` extended with `lidResolver` and `jidCanonicalizer` config options
|
|
21
|
+
- `AntiBanStats` includes `lidResolver` and `jidCanonicalizer` stats when enabled
|
|
22
|
+
- Wrapper's `sendMessage` now canonicalizes JID before all rate-limit/timelock/graph checks
|
|
23
|
+
- `messages.upsert` and `messages.update` handlers now auto-learn LID mappings when canonicalizer enabled
|
|
24
|
+
|
|
25
|
+
### Technical Details
|
|
26
|
+
- LRU eviction at configurable `maxEntries` (default 10,000)
|
|
27
|
+
- Optional persistence hooks for cross-restart state survival
|
|
28
|
+
- Device suffix stripping (`:N` in JIDs) for robust matching
|
|
29
|
+
- Supports both `canonical: 'pn'` (phone number) and `canonical: 'lid'` modes
|
|
30
|
+
- Shared resolver mode allows multiple canonicalizers to reference same mapping state
|
|
31
|
+
|
|
32
|
+
## [1.5.0] - 2026-04-18
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **RetryReasonTracker** module: Track message retry reasons and detect retry spirals
|
|
36
|
+
- Classifies 10 retry reason types (no_session, invalid_key, bad_mac, decryption_failure, server_error_463, server_error_429, timeout, no_route, node_malformed, unknown)
|
|
37
|
+
- Detects retry spirals when same message retries exceed threshold (default: 3)
|
|
38
|
+
- Provides stats on total retries, retries by reason, spirals detected, and active retries
|
|
39
|
+
- Auto-integrates with messages.update events in wrapper
|
|
40
|
+
- Inspired by whatsapp-rust's protocol/retry.rs module
|
|
41
|
+
- **PostReconnectThrottle** module: Throttle outbound messages after reconnection
|
|
42
|
+
- Prevents burst-floods on reconnect that trigger WhatsApp rate limits
|
|
43
|
+
- Configurable ramp-up from initial rate multiplier (default: 10%) to full rate over ramp duration (default: 60s)
|
|
44
|
+
- Linear ramp with configurable steps (default: 6 steps)
|
|
45
|
+
- Auto-integrates with connection.update events
|
|
46
|
+
- Inspired by whatsapp-rust's client/sessions.rs semaphore swap pattern
|
|
47
|
+
- Both modules are opt-in (enabled: false by default) for backward compatibility
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- `AntiBan.beforeSend()` now also consults reconnect throttle
|
|
51
|
+
- `AntiBan.onReconnect()` triggers reconnect throttle window
|
|
52
|
+
- `AntiBan.getStats()` includes retry tracker and reconnect throttle stats when enabled
|
|
53
|
+
- Wrapper now tracks message updates for retry classification and clears on successful send
|
|
54
|
+
|
|
8
55
|
## [1.4.0] - 2026-04-18
|
|
9
56
|
|
|
10
57
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,114 @@
|
|
|
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
|
-
## v1.
|
|
9
|
+
## v1.5 New Features
|
|
10
|
+
|
|
11
|
+
### RetryReasonTracker
|
|
12
|
+
Tracks message retry reasons and detects retry spirals (when the same message keeps failing). Inspired by whatsapp-rust's protocol/retry.rs module.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { AntiBan } from 'baileys-antiban';
|
|
16
|
+
|
|
17
|
+
const antiban = new AntiBan({
|
|
18
|
+
retryTracker: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
maxRetries: 5, // Max retries before considering a message failed
|
|
21
|
+
spiralThreshold: 3, // Retries before warning about retry spiral
|
|
22
|
+
onSpiral: (msgId, reason) => {
|
|
23
|
+
console.warn(`Message ${msgId} stuck in retry spiral: ${reason}`);
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Stats show retry patterns
|
|
29
|
+
const stats = antiban.getStats().retryTracker;
|
|
30
|
+
console.log(stats.totalRetries); // Total retries across all messages
|
|
31
|
+
console.log(stats.byReason.timeout); // Retries due to timeout
|
|
32
|
+
console.log(stats.spiralsDetected); // Messages stuck in retry loops
|
|
33
|
+
console.log(stats.activeRetries); // Messages currently retrying
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Retry reasons tracked**: no_session, invalid_key, bad_mac, decryption_failure, server_error_463, server_error_429, timeout, no_route, node_malformed, unknown
|
|
37
|
+
|
|
38
|
+
### PostReconnectThrottle
|
|
39
|
+
Throttles outbound messages after reconnection to prevent burst-floods that trigger rate limits. Inspired by whatsapp-rust's client/sessions.rs semaphore swap pattern.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const antiban = new AntiBan({
|
|
43
|
+
reconnectThrottle: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
rampDurationMs: 60_000, // 60s ramp-up to full rate
|
|
46
|
+
initialRateMultiplier: 0.1, // Start at 10% of normal rate
|
|
47
|
+
rampSteps: 6, // 10% → 25% → 50% → 75% → 90% → 100%
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// After reconnect, sends are automatically throttled for 60 seconds
|
|
52
|
+
// Ramps from 10% rate to 100% rate linearly over 6 steps
|
|
53
|
+
|
|
54
|
+
// Stats show throttle state
|
|
55
|
+
const stats = antiban.getStats().reconnectThrottle;
|
|
56
|
+
console.log(stats.isThrottled); // Currently throttled?
|
|
57
|
+
console.log(stats.currentMultiplier); // 0.1 to 1.0
|
|
58
|
+
console.log(stats.remainingMs); // Time until full rate
|
|
59
|
+
console.log(stats.throttledSendCount); // Sends gated since reconnect
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Why?** When WhatsApp reconnects after a disconnection, sending messages at full rate immediately can trigger rate limit alarms. The reconnect throttle gradually ramps up sending rate over 60 seconds, mimicking how a human would resume messaging after their internet came back.
|
|
63
|
+
|
|
64
|
+
## LID / Phone Number Canonicalization
|
|
65
|
+
|
|
66
|
+
WhatsApp migrated to **Linked Identity (LID)** in 2024. A contact now has two JID forms:
|
|
67
|
+
- Phone number: `27825651069@s.whatsapp.net`
|
|
68
|
+
- LID: `123456789@lid`
|
|
69
|
+
|
|
70
|
+
Messages can arrive under either form. If an encryption session was established under one form and a message arrives under the other, decryption fails → **"Bad MAC / No Session / Invalid PreKey"** errors (the #1 reported Baileys bug).
|
|
71
|
+
|
|
72
|
+
baileys-antiban v1.6+ provides **middleware-layer mitigation** via two new modules:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { wrapSocket } from 'baileys-antiban';
|
|
76
|
+
|
|
77
|
+
const sock = makeWASocket({ ... });
|
|
78
|
+
const safeSock = wrapSocket(sock, {
|
|
79
|
+
jidCanonicalizer: {
|
|
80
|
+
enabled: true, // Enable LID/PN canonicalization
|
|
81
|
+
canonical: 'pn', // Normalize to phone-number form (default)
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// That's it! Incoming events auto-learn LID↔PN mappings.
|
|
86
|
+
// Outbound sends are auto-canonicalized to phone-number form.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Advanced: Standalone Resolver**
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { LidResolver } from 'baileys-antiban';
|
|
93
|
+
|
|
94
|
+
const resolver = new LidResolver({
|
|
95
|
+
canonical: 'pn',
|
|
96
|
+
maxEntries: 10_000, // LRU cache size
|
|
97
|
+
persistence: {
|
|
98
|
+
load: async () => JSON.parse(await fs.readFile('lid-map.json', 'utf8')),
|
|
99
|
+
save: async (map) => fs.writeFile('lid-map.json', JSON.stringify(map)),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Learn from message events
|
|
104
|
+
resolver.learn({
|
|
105
|
+
lid: '123456789@lid',
|
|
106
|
+
pn: '27825651069@s.whatsapp.net',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Resolve canonical form
|
|
110
|
+
const canonical = resolver.resolveCanonical('123456789@lid');
|
|
111
|
+
// → '27825651069@s.whatsapp.net'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Note:** This is a middleware-layer workaround. The root fix lives inside Baileys' crypto pipeline ([PR #2372](https://github.com/WhiskeySockets/Baileys/pull/2372)).
|
|
115
|
+
|
|
116
|
+
## v1.3 Features
|
|
10
117
|
|
|
11
118
|
### ReplyRatioGuard
|
|
12
119
|
Tracks outbound:inbound message ratio per contact. Blocks sends to non-responsive contacts to avoid "spray-and-pray" ban patterns. Optionally suggests auto-replies to maintain healthy engagement.
|
|
@@ -92,6 +199,8 @@ WhatsApp bans numbers that behave like bots. This library makes your Baileys bot
|
|
|
92
199
|
- **Reply ratio tracking** (v1.3) — blocks sends to non-responsive contacts
|
|
93
200
|
- **Contact graph enforcement** (v1.3) — requires handshakes before bulk/group sends
|
|
94
201
|
- **Circadian rhythm** (v1.3) — realistic time-of-day activity patterns
|
|
202
|
+
- **Retry tracking** (v1.5) — detect retry spirals and classify retry reasons
|
|
203
|
+
- **Reconnect throttle** (v1.5) — prevent burst-floods after reconnection
|
|
95
204
|
|
|
96
205
|
## Supported Transports
|
|
97
206
|
|
|
@@ -223,6 +332,17 @@ const antiban = new AntiBan({
|
|
|
223
332
|
console.log('Timelock lifted — resuming normal operation');
|
|
224
333
|
},
|
|
225
334
|
},
|
|
335
|
+
retryTracker: {
|
|
336
|
+
enabled: false, // Opt-in (default: false)
|
|
337
|
+
maxRetries: 5,
|
|
338
|
+
spiralThreshold: 3,
|
|
339
|
+
},
|
|
340
|
+
reconnectThrottle: {
|
|
341
|
+
enabled: false, // Opt-in (default: false)
|
|
342
|
+
rampDurationMs: 60_000,
|
|
343
|
+
initialRateMultiplier: 0.1,
|
|
344
|
+
rampSteps: 6,
|
|
345
|
+
},
|
|
226
346
|
logging: true, // Console logging (default: true)
|
|
227
347
|
});
|
|
228
348
|
```
|
package/dist/antiban.d.ts
CHANGED
|
@@ -20,6 +20,10 @@ import { TimelockGuard, type TimelockGuardConfig } from './timelockGuard.js';
|
|
|
20
20
|
import { ReplyRatioGuard, type ReplyRatioConfig, type ReplyRatioStats } from './replyRatio.js';
|
|
21
21
|
import { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats } from './contactGraph.js';
|
|
22
22
|
import { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats } from './presenceChoreographer.js';
|
|
23
|
+
import { RetryReasonTracker, type RetryTrackerConfig, type RetryStats } from './retryTracker.js';
|
|
24
|
+
import { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThrottleStats } from './reconnectThrottle.js';
|
|
25
|
+
import { LidResolver, type LidResolverConfig, type LidResolverStats } from './lidResolver.js';
|
|
26
|
+
import { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
|
|
23
27
|
export interface AntiBanConfig {
|
|
24
28
|
rateLimiter?: Partial<RateLimiterConfig>;
|
|
25
29
|
warmUp?: Partial<WarmUpConfig>;
|
|
@@ -28,6 +32,10 @@ export interface AntiBanConfig {
|
|
|
28
32
|
replyRatio?: Partial<ReplyRatioConfig>;
|
|
29
33
|
contactGraph?: Partial<ContactGraphConfig>;
|
|
30
34
|
presence?: Partial<PresenceChoreographerConfig>;
|
|
35
|
+
retryTracker?: Partial<RetryTrackerConfig>;
|
|
36
|
+
reconnectThrottle?: Partial<ReconnectThrottleConfig>;
|
|
37
|
+
lidResolver?: LidResolverConfig;
|
|
38
|
+
jidCanonicalizer?: JidCanonicalizerConfig;
|
|
31
39
|
/** Log warnings and blocks to console (default: true) */
|
|
32
40
|
logging?: boolean;
|
|
33
41
|
}
|
|
@@ -48,6 +56,10 @@ export interface AntiBanStats {
|
|
|
48
56
|
replyRatio?: ReplyRatioStats;
|
|
49
57
|
contactGraph?: ContactGraphStats;
|
|
50
58
|
presence?: PresenceChoreographerStats;
|
|
59
|
+
retryTracker?: RetryStats | null;
|
|
60
|
+
reconnectThrottle?: ReconnectThrottleStats | null;
|
|
61
|
+
lidResolver?: LidResolverStats | null;
|
|
62
|
+
jidCanonicalizer?: JidCanonicalizerStats | null;
|
|
51
63
|
}
|
|
52
64
|
export declare class AntiBan {
|
|
53
65
|
private rateLimiter;
|
|
@@ -57,6 +69,10 @@ export declare class AntiBan {
|
|
|
57
69
|
private replyRatioGuard;
|
|
58
70
|
private contactGraphWarmer;
|
|
59
71
|
private presenceChoreographer;
|
|
72
|
+
private retryTrackerModule;
|
|
73
|
+
private reconnectThrottleModule;
|
|
74
|
+
private lidResolverModule;
|
|
75
|
+
private jidCanonicalizerModule;
|
|
60
76
|
private logging;
|
|
61
77
|
private stats;
|
|
62
78
|
constructor(config?: AntiBanConfig, warmUpState?: WarmUpState);
|
|
@@ -102,6 +118,14 @@ export declare class AntiBan {
|
|
|
102
118
|
get contactGraph(): ContactGraphWarmer;
|
|
103
119
|
/** Get the presence choreographer for direct access */
|
|
104
120
|
get presence(): PresenceChoreographer;
|
|
121
|
+
/** Get the retry tracker for direct access */
|
|
122
|
+
get retryTracker(): RetryReasonTracker;
|
|
123
|
+
/** Get the reconnect throttle for direct access */
|
|
124
|
+
get reconnectThrottle(): PostReconnectThrottle;
|
|
125
|
+
/** Get the LID resolver for direct access */
|
|
126
|
+
get lidResolver(): LidResolver | null;
|
|
127
|
+
/** Get the JID canonicalizer for direct access */
|
|
128
|
+
get jidCanonicalizer(): JidCanonicalizer | null;
|
|
105
129
|
/**
|
|
106
130
|
* Export warm-up state for persistence between restarts
|
|
107
131
|
*/
|
package/dist/antiban.js
CHANGED
|
@@ -20,6 +20,10 @@ import { TimelockGuard } from './timelockGuard.js';
|
|
|
20
20
|
import { ReplyRatioGuard } from './replyRatio.js';
|
|
21
21
|
import { ContactGraphWarmer } from './contactGraph.js';
|
|
22
22
|
import { PresenceChoreographer } from './presenceChoreographer.js';
|
|
23
|
+
import { RetryReasonTracker } from './retryTracker.js';
|
|
24
|
+
import { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
25
|
+
import { LidResolver } from './lidResolver.js';
|
|
26
|
+
import { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
23
27
|
export class AntiBan {
|
|
24
28
|
rateLimiter;
|
|
25
29
|
warmUp;
|
|
@@ -28,6 +32,10 @@ export class AntiBan {
|
|
|
28
32
|
replyRatioGuard;
|
|
29
33
|
contactGraphWarmer;
|
|
30
34
|
presenceChoreographer;
|
|
35
|
+
retryTrackerModule;
|
|
36
|
+
reconnectThrottleModule;
|
|
37
|
+
lidResolverModule = null;
|
|
38
|
+
jidCanonicalizerModule = null;
|
|
31
39
|
logging;
|
|
32
40
|
stats = {
|
|
33
41
|
messagesAllowed: 0,
|
|
@@ -69,6 +77,43 @@ export class AntiBan {
|
|
|
69
77
|
this.replyRatioGuard = new ReplyRatioGuard(config.replyRatio);
|
|
70
78
|
this.contactGraphWarmer = new ContactGraphWarmer(config.contactGraph);
|
|
71
79
|
this.presenceChoreographer = new PresenceChoreographer(config.presence);
|
|
80
|
+
this.retryTrackerModule = new RetryReasonTracker({
|
|
81
|
+
...config.retryTracker,
|
|
82
|
+
onSpiral: (msgId, reason) => {
|
|
83
|
+
if (this.logging) {
|
|
84
|
+
console.log(`[baileys-antiban] ⚠️ Message ${msgId} stuck in retry spiral (${reason})`);
|
|
85
|
+
}
|
|
86
|
+
config.retryTracker?.onSpiral?.(msgId, reason);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
this.reconnectThrottleModule = new PostReconnectThrottle({
|
|
90
|
+
...config.reconnectThrottle,
|
|
91
|
+
baselineRatePerMinute: () => this.rateLimiter.getStats().limits.perMinute,
|
|
92
|
+
});
|
|
93
|
+
// Initialize LID resolver and canonicalizer if configured
|
|
94
|
+
// If jidCanonicalizer is enabled but no resolver provided, create standalone resolver
|
|
95
|
+
if (config.jidCanonicalizer?.enabled) {
|
|
96
|
+
// Create or use provided resolver
|
|
97
|
+
if (config.jidCanonicalizer.resolver) {
|
|
98
|
+
// User provided their own resolver
|
|
99
|
+
this.jidCanonicalizerModule = new JidCanonicalizer(config.jidCanonicalizer);
|
|
100
|
+
this.lidResolverModule = config.jidCanonicalizer.resolver;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Create new resolver using lidResolver config if provided
|
|
104
|
+
const resolverConfig = config.lidResolver || config.jidCanonicalizer.resolverConfig;
|
|
105
|
+
const resolver = new LidResolver(resolverConfig);
|
|
106
|
+
this.lidResolverModule = resolver;
|
|
107
|
+
this.jidCanonicalizerModule = new JidCanonicalizer({
|
|
108
|
+
...config.jidCanonicalizer,
|
|
109
|
+
resolver,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (config.lidResolver) {
|
|
114
|
+
// Standalone resolver without canonicalizer
|
|
115
|
+
this.lidResolverModule = new LidResolver(config.lidResolver);
|
|
116
|
+
}
|
|
72
117
|
}
|
|
73
118
|
/**
|
|
74
119
|
* Check if a message can be sent and get required delay.
|
|
@@ -146,6 +191,20 @@ export class AntiBan {
|
|
|
146
191
|
health: healthStatus,
|
|
147
192
|
};
|
|
148
193
|
}
|
|
194
|
+
// Reconnect throttle check
|
|
195
|
+
const reconnectThrottleDecision = this.reconnectThrottleModule.beforeSend();
|
|
196
|
+
if (!reconnectThrottleDecision.allowed) {
|
|
197
|
+
this.stats.messagesBlocked++;
|
|
198
|
+
if (this.logging) {
|
|
199
|
+
console.log(`[baileys-antiban] 🔄 BLOCKED — reconnect throttle: ${reconnectThrottleDecision.reason}`);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
allowed: false,
|
|
203
|
+
delayMs: reconnectThrottleDecision.retryAfterMs || 0,
|
|
204
|
+
reason: reconnectThrottleDecision.reason || 'Post-reconnect throttle',
|
|
205
|
+
health: healthStatus,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
149
208
|
// Rate limiter delay
|
|
150
209
|
let delay = await this.rateLimiter.getDelay(recipient, content);
|
|
151
210
|
if (delay === -1) {
|
|
@@ -211,12 +270,14 @@ export class AntiBan {
|
|
|
211
270
|
*/
|
|
212
271
|
onDisconnect(reason) {
|
|
213
272
|
this.health.recordDisconnect(reason);
|
|
273
|
+
this.reconnectThrottleModule.onDisconnect();
|
|
214
274
|
}
|
|
215
275
|
/**
|
|
216
276
|
* Record a successful reconnection
|
|
217
277
|
*/
|
|
218
278
|
onReconnect() {
|
|
219
279
|
this.health.recordReconnect();
|
|
280
|
+
this.reconnectThrottleModule.onReconnect();
|
|
220
281
|
}
|
|
221
282
|
/**
|
|
222
283
|
* Handle incoming message — record in reply ratio + contact graph.
|
|
@@ -247,6 +308,18 @@ export class AntiBan {
|
|
|
247
308
|
if (this.presenceChoreographer['config']?.enabled) {
|
|
248
309
|
stats.presence = this.presenceChoreographer.getStats();
|
|
249
310
|
}
|
|
311
|
+
if (this.retryTrackerModule['config']?.enabled) {
|
|
312
|
+
stats.retryTracker = this.retryTrackerModule.getStats();
|
|
313
|
+
}
|
|
314
|
+
if (this.reconnectThrottleModule['config']?.enabled) {
|
|
315
|
+
stats.reconnectThrottle = this.reconnectThrottleModule.getStats();
|
|
316
|
+
}
|
|
317
|
+
if (this.lidResolverModule) {
|
|
318
|
+
stats.lidResolver = this.lidResolverModule.getStats();
|
|
319
|
+
}
|
|
320
|
+
if (this.jidCanonicalizerModule) {
|
|
321
|
+
stats.jidCanonicalizer = this.jidCanonicalizerModule.getStats();
|
|
322
|
+
}
|
|
250
323
|
return stats;
|
|
251
324
|
}
|
|
252
325
|
/** Get the timelock guard for direct access */
|
|
@@ -265,6 +338,22 @@ export class AntiBan {
|
|
|
265
338
|
get presence() {
|
|
266
339
|
return this.presenceChoreographer;
|
|
267
340
|
}
|
|
341
|
+
/** Get the retry tracker for direct access */
|
|
342
|
+
get retryTracker() {
|
|
343
|
+
return this.retryTrackerModule;
|
|
344
|
+
}
|
|
345
|
+
/** Get the reconnect throttle for direct access */
|
|
346
|
+
get reconnectThrottle() {
|
|
347
|
+
return this.reconnectThrottleModule;
|
|
348
|
+
}
|
|
349
|
+
/** Get the LID resolver for direct access */
|
|
350
|
+
get lidResolver() {
|
|
351
|
+
return this.lidResolverModule;
|
|
352
|
+
}
|
|
353
|
+
/** Get the JID canonicalizer for direct access */
|
|
354
|
+
get jidCanonicalizer() {
|
|
355
|
+
return this.jidCanonicalizerModule;
|
|
356
|
+
}
|
|
268
357
|
/**
|
|
269
358
|
* Export warm-up state for persistence between restarts
|
|
270
359
|
*/
|
|
@@ -299,6 +388,8 @@ export class AntiBan {
|
|
|
299
388
|
this.replyRatioGuard.reset();
|
|
300
389
|
this.contactGraphWarmer.reset();
|
|
301
390
|
this.presenceChoreographer.reset();
|
|
391
|
+
this.retryTrackerModule.destroy();
|
|
392
|
+
this.reconnectThrottleModule.destroy();
|
|
302
393
|
this.stats = { messagesAllowed: 0, messagesBlocked: 0, totalDelayMs: 0 };
|
|
303
394
|
if (this.logging) {
|
|
304
395
|
console.log('[baileys-antiban] 🔄 Reset — starting fresh warm-up');
|
|
@@ -313,6 +404,10 @@ export class AntiBan {
|
|
|
313
404
|
this.replyRatioGuard.reset();
|
|
314
405
|
this.contactGraphWarmer.reset();
|
|
315
406
|
this.presenceChoreographer.reset();
|
|
407
|
+
this.retryTrackerModule.destroy();
|
|
408
|
+
this.reconnectThrottleModule.destroy();
|
|
409
|
+
this.jidCanonicalizerModule?.destroy();
|
|
410
|
+
this.lidResolverModule?.destroy();
|
|
316
411
|
if (this.logging) {
|
|
317
412
|
console.log('[baileys-antiban] 🧹 Destroyed — all timers cleared');
|
|
318
413
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export { TimelockGuard, type TimelockGuardConfig, type TimelockState } from './t
|
|
|
15
15
|
export { ReplyRatioGuard, type ReplyRatioConfig, type ReplyRatioStats } from './replyRatio.js';
|
|
16
16
|
export { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats, type ContactState } from './contactGraph.js';
|
|
17
17
|
export { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats } from './presenceChoreographer.js';
|
|
18
|
+
export { RetryReasonTracker, type RetryTrackerConfig, type RetryStats, type RetryReason } from './retryTracker.js';
|
|
19
|
+
export { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThrottleStats } from './reconnectThrottle.js';
|
|
20
|
+
export { LidResolver, type LidResolverConfig, type LidResolverStats, type LidMapping } from './lidResolver.js';
|
|
21
|
+
export { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
|
|
18
22
|
export { wrapSocket, type WrappedSocket, type WrapSocketOptions } from './wrapper.js';
|
|
19
23
|
export { MessageQueue, type QueuedMessage, type MessageQueueConfig } from './messageQueue.js';
|
|
20
24
|
export { ContentVariator, type VariatorConfig } from './contentVariator.js';
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,12 @@ export { TimelockGuard } from './timelockGuard.js';
|
|
|
17
17
|
export { ReplyRatioGuard } from './replyRatio.js';
|
|
18
18
|
export { ContactGraphWarmer } from './contactGraph.js';
|
|
19
19
|
export { PresenceChoreographer } from './presenceChoreographer.js';
|
|
20
|
+
// v1.5 new modules
|
|
21
|
+
export { RetryReasonTracker } from './retryTracker.js';
|
|
22
|
+
export { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
23
|
+
// v1.6 new modules
|
|
24
|
+
export { LidResolver } from './lidResolver.js';
|
|
25
|
+
export { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
20
26
|
// Socket wrapper
|
|
21
27
|
export { wrapSocket } from './wrapper.js';
|
|
22
28
|
// Optional features
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JID Canonicalizer — Opt-in middleware for LID/PN normalization
|
|
3
|
+
*
|
|
4
|
+
* Wraps LidResolver to provide automatic:
|
|
5
|
+
* 1. Learning from incoming message events
|
|
6
|
+
* 2. Canonicalization of outbound send targets
|
|
7
|
+
*
|
|
8
|
+
* This mitigates the LID/PN race condition that causes "Bad MAC / No Session /
|
|
9
|
+
* Invalid PreKey" errors (Baileys issue #1769, our PR #2372).
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const canonicalizer = new JidCanonicalizer({ enabled: true });
|
|
13
|
+
*
|
|
14
|
+
* // On incoming event
|
|
15
|
+
* canonicalizer.onIncomingEvent({ messages: [...] });
|
|
16
|
+
*
|
|
17
|
+
* // On outbound send
|
|
18
|
+
* const canonicalJid = canonicalizer.canonicalizeTarget(jid);
|
|
19
|
+
* await sock.sendMessage(canonicalJid, content);
|
|
20
|
+
*
|
|
21
|
+
* Note: This is a middleware-layer mitigation. The root fix requires merging
|
|
22
|
+
* PR #2372 into Baileys' crypto pipeline.
|
|
23
|
+
*/
|
|
24
|
+
import { LidResolver, type LidResolverConfig, type LidResolverStats } from './lidResolver.js';
|
|
25
|
+
export interface JidCanonicalizerConfig {
|
|
26
|
+
/** Enable canonicalization (default: false — opt-in) */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
/** Provide your own resolver to share across modules. Otherwise one is created. */
|
|
29
|
+
resolver?: LidResolver;
|
|
30
|
+
/** Config for creating a new resolver (ignored if resolver provided) */
|
|
31
|
+
resolverConfig?: LidResolverConfig;
|
|
32
|
+
/** Canonicalize outbound sendMessage targets. Default true. */
|
|
33
|
+
canonicalizeOutbound?: boolean;
|
|
34
|
+
/** Learn from inbound events. Default true. */
|
|
35
|
+
learnFromEvents?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface JidCanonicalizerStats {
|
|
38
|
+
resolver: LidResolverStats;
|
|
39
|
+
outboundCanonicalized: number;
|
|
40
|
+
outboundPassthrough: number;
|
|
41
|
+
inboundLearned: number;
|
|
42
|
+
}
|
|
43
|
+
export declare class JidCanonicalizer {
|
|
44
|
+
private config;
|
|
45
|
+
private lidResolver;
|
|
46
|
+
private ownsResolver;
|
|
47
|
+
private stats;
|
|
48
|
+
constructor(config?: JidCanonicalizerConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Access the underlying resolver (for cross-module sharing)
|
|
51
|
+
*/
|
|
52
|
+
get resolver(): LidResolver;
|
|
53
|
+
/**
|
|
54
|
+
* Called by wrapper on every outbound send. Returns canonical JID.
|
|
55
|
+
*/
|
|
56
|
+
canonicalizeTarget(jid: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Called by wrapper on messages.upsert event. Learns mappings.
|
|
59
|
+
*/
|
|
60
|
+
onIncomingEvent(upsert: {
|
|
61
|
+
messages: Array<any>;
|
|
62
|
+
type?: string;
|
|
63
|
+
}): void;
|
|
64
|
+
/**
|
|
65
|
+
* Called by wrapper on messages.update event. Learns from sent-message refs.
|
|
66
|
+
*/
|
|
67
|
+
onMessageUpdate(updates: Array<any>): void;
|
|
68
|
+
getStats(): JidCanonicalizerStats;
|
|
69
|
+
destroy(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Extract LID↔PN mappings from a message object
|
|
72
|
+
*/
|
|
73
|
+
private learnFromMessage;
|
|
74
|
+
/**
|
|
75
|
+
* Extract mappings from message.key
|
|
76
|
+
*/
|
|
77
|
+
private learnFromMessageKey;
|
|
78
|
+
}
|