baileys-antiban 1.5.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 CHANGED
@@ -5,6 +5,61 @@ 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
+
39
+ ## [1.6.0] - 2026-04-18
40
+
41
+ ### Added
42
+ - **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
43
+ - `LidResolver` — Standalone utility for maintaining bidirectional LID↔PN mappings learned from message events
44
+ - `JidCanonicalizer` — Opt-in middleware that auto-learns from incoming events and canonicalizes outbound send targets to a single form (phone number by default)
45
+ - Both modules default to **disabled** — backward compatible, zero behavior change for existing users
46
+ - Middleware-layer mitigation only — root fix still requires [PR #2372](https://github.com/WhiskeySockets/Baileys/pull/2372) merged upstream
47
+ - Comprehensive test coverage: 56 new tests (29 LidResolver + 18 JidCanonicalizer + 9 integration)
48
+
49
+ ### Changed
50
+ - `AntiBan` class now exposes `lidResolver` and `jidCanonicalizer` getters for direct access
51
+ - `AntiBanConfig` extended with `lidResolver` and `jidCanonicalizer` config options
52
+ - `AntiBanStats` includes `lidResolver` and `jidCanonicalizer` stats when enabled
53
+ - Wrapper's `sendMessage` now canonicalizes JID before all rate-limit/timelock/graph checks
54
+ - `messages.upsert` and `messages.update` handlers now auto-learn LID mappings when canonicalizer enabled
55
+
56
+ ### Technical Details
57
+ - LRU eviction at configurable `maxEntries` (default 10,000)
58
+ - Optional persistence hooks for cross-restart state survival
59
+ - Device suffix stripping (`:N` in JIDs) for robust matching
60
+ - Supports both `canonical: 'pn'` (phone number) and `canonical: 'lid'` modes
61
+ - Shared resolver mode allows multiple canonicalizers to reference same mapping state
62
+
8
63
  ## [1.5.0] - 2026-04-18
9
64
 
10
65
  ### 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
- ## v1.5 New Features
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.
@@ -61,6 +198,58 @@ console.log(stats.throttledSendCount); // Sends gated since reconnect
61
198
 
62
199
  **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
200
 
201
+ ## LID / Phone Number Canonicalization
202
+
203
+ WhatsApp migrated to **Linked Identity (LID)** in 2024. A contact now has two JID forms:
204
+ - Phone number: `27825651069@s.whatsapp.net`
205
+ - LID: `123456789@lid`
206
+
207
+ 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).
208
+
209
+ baileys-antiban v1.6+ provides **middleware-layer mitigation** via two new modules:
210
+
211
+ ```typescript
212
+ import { wrapSocket } from 'baileys-antiban';
213
+
214
+ const sock = makeWASocket({ ... });
215
+ const safeSock = wrapSocket(sock, {
216
+ jidCanonicalizer: {
217
+ enabled: true, // Enable LID/PN canonicalization
218
+ canonical: 'pn', // Normalize to phone-number form (default)
219
+ },
220
+ });
221
+
222
+ // That's it! Incoming events auto-learn LID↔PN mappings.
223
+ // Outbound sends are auto-canonicalized to phone-number form.
224
+ ```
225
+
226
+ **Advanced: Standalone Resolver**
227
+
228
+ ```typescript
229
+ import { LidResolver } from 'baileys-antiban';
230
+
231
+ const resolver = new LidResolver({
232
+ canonical: 'pn',
233
+ maxEntries: 10_000, // LRU cache size
234
+ persistence: {
235
+ load: async () => JSON.parse(await fs.readFile('lid-map.json', 'utf8')),
236
+ save: async (map) => fs.writeFile('lid-map.json', JSON.stringify(map)),
237
+ },
238
+ });
239
+
240
+ // Learn from message events
241
+ resolver.learn({
242
+ lid: '123456789@lid',
243
+ pn: '27825651069@s.whatsapp.net',
244
+ });
245
+
246
+ // Resolve canonical form
247
+ const canonical = resolver.resolveCanonical('123456789@lid');
248
+ // → '27825651069@s.whatsapp.net'
249
+ ```
250
+
251
+ **Note:** This is a middleware-layer workaround. The root fix lives inside Baileys' crypto pipeline ([PR #2372](https://github.com/WhiskeySockets/Baileys/pull/2372)).
252
+
64
253
  ## v1.3 Features
65
254
 
66
255
  ### ReplyRatioGuard
package/dist/antiban.d.ts CHANGED
@@ -22,6 +22,9 @@ import { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats } f
22
22
  import { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats } from './presenceChoreographer.js';
23
23
  import { RetryReasonTracker, type RetryTrackerConfig, type RetryStats } from './retryTracker.js';
24
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';
27
+ import { SessionHealthMonitor, type SessionHealthStats } from './sessionStability.js';
25
28
  export interface AntiBanConfig {
26
29
  rateLimiter?: Partial<RateLimiterConfig>;
27
30
  warmUp?: Partial<WarmUpConfig>;
@@ -32,6 +35,20 @@ export interface AntiBanConfig {
32
35
  presence?: Partial<PresenceChoreographerConfig>;
33
36
  retryTracker?: Partial<RetryTrackerConfig>;
34
37
  reconnectThrottle?: Partial<ReconnectThrottleConfig>;
38
+ lidResolver?: LidResolverConfig;
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
+ };
35
52
  /** Log warnings and blocks to console (default: true) */
36
53
  logging?: boolean;
37
54
  }
@@ -54,6 +71,9 @@ export interface AntiBanStats {
54
71
  presence?: PresenceChoreographerStats;
55
72
  retryTracker?: RetryStats | null;
56
73
  reconnectThrottle?: ReconnectThrottleStats | null;
74
+ lidResolver?: LidResolverStats | null;
75
+ jidCanonicalizer?: JidCanonicalizerStats | null;
76
+ sessionStability?: SessionHealthStats | null;
57
77
  }
58
78
  export declare class AntiBan {
59
79
  private rateLimiter;
@@ -65,6 +85,9 @@ export declare class AntiBan {
65
85
  private presenceChoreographer;
66
86
  private retryTrackerModule;
67
87
  private reconnectThrottleModule;
88
+ private lidResolverModule;
89
+ private jidCanonicalizerModule;
90
+ private sessionStabilityMonitor;
68
91
  private logging;
69
92
  private stats;
70
93
  constructor(config?: AntiBanConfig, warmUpState?: WarmUpState);
@@ -114,6 +137,12 @@ export declare class AntiBan {
114
137
  get retryTracker(): RetryReasonTracker;
115
138
  /** Get the reconnect throttle for direct access */
116
139
  get reconnectThrottle(): PostReconnectThrottle;
140
+ /** Get the LID resolver for direct access */
141
+ get lidResolver(): LidResolver | null;
142
+ /** Get the JID canonicalizer for direct access */
143
+ get jidCanonicalizer(): JidCanonicalizer | null;
144
+ /** Get the session stability monitor for direct access */
145
+ get sessionStability(): SessionHealthMonitor | null;
117
146
  /**
118
147
  * Export warm-up state for persistence between restarts
119
148
  */
package/dist/antiban.js CHANGED
@@ -22,6 +22,9 @@ import { ContactGraphWarmer } from './contactGraph.js';
22
22
  import { PresenceChoreographer } from './presenceChoreographer.js';
23
23
  import { RetryReasonTracker } from './retryTracker.js';
24
24
  import { PostReconnectThrottle } from './reconnectThrottle.js';
25
+ import { LidResolver } from './lidResolver.js';
26
+ import { JidCanonicalizer } from './jidCanonicalizer.js';
27
+ import { SessionHealthMonitor } from './sessionStability.js';
25
28
  export class AntiBan {
26
29
  rateLimiter;
27
30
  warmUp;
@@ -32,6 +35,9 @@ export class AntiBan {
32
35
  presenceChoreographer;
33
36
  retryTrackerModule;
34
37
  reconnectThrottleModule;
38
+ lidResolverModule = null;
39
+ jidCanonicalizerModule = null;
40
+ sessionStabilityMonitor = null;
35
41
  logging;
36
42
  stats = {
37
43
  messagesAllowed: 0,
@@ -86,6 +92,49 @@ export class AntiBan {
86
92
  ...config.reconnectThrottle,
87
93
  baselineRatePerMinute: () => this.rateLimiter.getStats().limits.perMinute,
88
94
  });
95
+ // Initialize LID resolver and canonicalizer if configured
96
+ // If jidCanonicalizer is enabled but no resolver provided, create standalone resolver
97
+ if (config.jidCanonicalizer?.enabled) {
98
+ // Create or use provided resolver
99
+ if (config.jidCanonicalizer.resolver) {
100
+ // User provided their own resolver
101
+ this.jidCanonicalizerModule = new JidCanonicalizer(config.jidCanonicalizer);
102
+ this.lidResolverModule = config.jidCanonicalizer.resolver;
103
+ }
104
+ else {
105
+ // Create new resolver using lidResolver config if provided
106
+ const resolverConfig = config.lidResolver || config.jidCanonicalizer.resolverConfig;
107
+ const resolver = new LidResolver(resolverConfig);
108
+ this.lidResolverModule = resolver;
109
+ this.jidCanonicalizerModule = new JidCanonicalizer({
110
+ ...config.jidCanonicalizer,
111
+ resolver,
112
+ });
113
+ }
114
+ }
115
+ else if (config.lidResolver) {
116
+ // Standalone resolver without canonicalizer
117
+ this.lidResolverModule = new LidResolver(config.lidResolver);
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
+ }
89
138
  }
90
139
  /**
91
140
  * Check if a message can be sent and get required delay.
@@ -286,6 +335,15 @@ export class AntiBan {
286
335
  if (this.reconnectThrottleModule['config']?.enabled) {
287
336
  stats.reconnectThrottle = this.reconnectThrottleModule.getStats();
288
337
  }
338
+ if (this.lidResolverModule) {
339
+ stats.lidResolver = this.lidResolverModule.getStats();
340
+ }
341
+ if (this.jidCanonicalizerModule) {
342
+ stats.jidCanonicalizer = this.jidCanonicalizerModule.getStats();
343
+ }
344
+ if (this.sessionStabilityMonitor) {
345
+ stats.sessionStability = this.sessionStabilityMonitor.getStats();
346
+ }
289
347
  return stats;
290
348
  }
291
349
  /** Get the timelock guard for direct access */
@@ -312,6 +370,18 @@ export class AntiBan {
312
370
  get reconnectThrottle() {
313
371
  return this.reconnectThrottleModule;
314
372
  }
373
+ /** Get the LID resolver for direct access */
374
+ get lidResolver() {
375
+ return this.lidResolverModule;
376
+ }
377
+ /** Get the JID canonicalizer for direct access */
378
+ get jidCanonicalizer() {
379
+ return this.jidCanonicalizerModule;
380
+ }
381
+ /** Get the session stability monitor for direct access */
382
+ get sessionStability() {
383
+ return this.sessionStabilityMonitor;
384
+ }
315
385
  /**
316
386
  * Export warm-up state for persistence between restarts
317
387
  */
@@ -364,6 +434,9 @@ export class AntiBan {
364
434
  this.presenceChoreographer.reset();
365
435
  this.retryTrackerModule.destroy();
366
436
  this.reconnectThrottleModule.destroy();
437
+ this.jidCanonicalizerModule?.destroy();
438
+ this.lidResolverModule?.destroy();
439
+ this.sessionStabilityMonitor?.reset();
367
440
  if (this.logging) {
368
441
  console.log('[baileys-antiban] 🧹 Destroyed — all timers cleared');
369
442
  }
package/dist/index.d.ts CHANGED
@@ -17,6 +17,9 @@ export { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats, ty
17
17
  export { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats } from './presenceChoreographer.js';
18
18
  export { RetryReasonTracker, type RetryTrackerConfig, type RetryStats, type RetryReason } from './retryTracker.js';
19
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';
22
+ export { SessionHealthMonitor, type SessionHealthStats, type SessionHealthConfig, wrapWithSessionStability, type SessionStabilityConfig, classifyDisconnect, type DisconnectClassification, type DisconnectCategory, } from './sessionStability.js';
20
23
  export { wrapSocket, type WrappedSocket, type WrapSocketOptions } from './wrapper.js';
21
24
  export { MessageQueue, type QueuedMessage, type MessageQueueConfig } from './messageQueue.js';
22
25
  export { ContentVariator, type VariatorConfig } from './contentVariator.js';
package/dist/index.js CHANGED
@@ -20,6 +20,11 @@ export { PresenceChoreographer } from './presenceChoreographer.js';
20
20
  // v1.5 new modules
21
21
  export { RetryReasonTracker } from './retryTracker.js';
22
22
  export { PostReconnectThrottle } from './reconnectThrottle.js';
23
+ // v1.6 new modules
24
+ export { LidResolver } from './lidResolver.js';
25
+ export { JidCanonicalizer } from './jidCanonicalizer.js';
26
+ // v2.0 new modules
27
+ export { SessionHealthMonitor, wrapWithSessionStability, classifyDisconnect, } from './sessionStability.js';
23
28
  // Socket wrapper
24
29
  export { wrapSocket } from './wrapper.js';
25
30
  // 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
+ }