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 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.3 New Features
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
+ }