When hkdf became async, mutationKeys also became async but the call
site wasn't updated. This caused Promise<Promise<Key>> instead of
Promise<Key>, breaking contact sync events (contacts.upsert).
Ref: WhiskeySockets/Baileys#2288
- Replace Function type with proper VersionCacheLogger interface
- Add documentation for cacheFilePath about container/serverless limitations
- Handle fetchInProgress in clearVersionCache to prevent cache restoration
- Add deduplication check in refreshVersionCache
- Remove unused fetchLatestWaWebVersion import
- Return success status from refreshVersionCache to detect fallback
- Fix inefficient getCachedVersion call - use cacheStatus.version directly
- Remove as any casts by using logger adapter pattern
- Don't downgrade version on transient network errors
Instead of maintaining the version number in 3 separate files
(baileys-version.json, index.ts, generics.ts), this change makes
baileys-version.json the single source of truth. Other files now
import the version from this JSON file.
This follows the DRY principle and reduces:
- Risk of version inconsistency across files
- Maintenance burden (only 1 file to update)
- PR diff size (3 files → 1 file for version updates)
- Merge conflict probability
Addresses inefficiency found in Baileys PR #2269.
Messages from Facebook/Instagram ads (Click-to-WhatsApp) don't arrive on
linked devices because Meta's ads endpoint doesn't encrypt for multi-device.
They arrive as "Message absent from node" placeholders.
This change automatically requests the message from the primary phone via
PDO (Peer Data Operation) when a CTWA placeholder is detected.
Changes:
- Add enableCTWARecovery config option (default: true)
- Trigger requestPlaceholderResend() for "Message absent from node" errors
- Add Prometheus metrics for CTWA recovery tracking
- Add comprehensive unit tests for CTWA recovery functionality
Resolves: https://github.com/WhiskeySockets/Baileys/issues/1723
Resolves: https://github.com/WhiskeySockets/Baileys/issues/1034
Merge master branch and resolve conflicts:
- Use import from '../WABinary/index.js' (master convention)
- Keep the bug fix: || instead of && in extractLidPnFromMessage
- Combine comprehensive tests from both branches
- Use Map for deduplication (master implementation)
All 55 tests passing.
Address PR #20 code review feedback:
- Fix logical bug: changed `&&` to `||` in isPersonJid validation
This ensures BOTH JIDs must be person JIDs before extracting
a LID-PN mapping, preventing "poisoned" mappings where one side
is a group/newsletter/broadcast
- Add isPersonJid() helper to validate JID types
- Add extractLidPnFromConversation() to extract mappings from
conversation lidJid/pnJid properties
- Add extractLidPnFromMessage() to extract mappings from message
remoteJidAlt/participantAlt fields
- Update processHistoryMessage() to use new extraction functions
- Add comprehensive tests covering edge cases for mixed JID types
(person + group, person + broadcast, person + newsletter)
Note: Returning `undefined` is the correct TypeScript convention
for indicating absence of value (as per TypeScript team guidelines)
Enhances LID-PN mapping extraction to handle all WhatsApp JID formats
and extract mappings from multiple sources within history sync data.
New JID validation:
- Add `isPersonJid()` helper to identify JIDs that can have LID-PN mappings
- Skip non-person JIDs: @newsletter (channels), @broadcast, @g.us (groups)
Triple-source extraction:
1. Top-level `phoneNumberToLidMappings` array (existing)
2. Conversation objects via `lidJid`/`pnJid` properties (existing)
3. Message objects via `remoteJidAlt`/`participantAlt` fields (NEW)
New `extractLidPnFromMessage()` function:
- Extracts mappings from message key alternative JID fields
- Handles both direct messages (remoteJidAlt) and group messages (participantAlt)
- Properly identifies LID vs PN based on server type
JID formats handled:
- @s.whatsapp.net - Standard phone number
- @lid - Logical ID (privacy)
- @hosted - Hosted phone number
- @hosted.lid - Hosted LID
46 comprehensive tests covering all scenarios.
Related: WhiskeySockets/Baileys#2263
WhatsApp provides LID-PN mappings in two locations within history sync:
1. Top-level `phoneNumberToLidMappings` array (already processed)
2. Individual conversation objects with `lidJid`/`pnJid` properties (new)
This change adds extraction from conversation objects, ensuring maximum
mapping coverage regardless of sync type or payload structure.
Key improvements:
- Add `extractLidPnFromConversation()` function with comprehensive JSDoc
- Use Map for O(1) deduplication of mappings across both sources
- Handle all JID formats: @lid, @hosted.lid, @s.whatsapp.net, @hosted
- Normalize JIDs using `jidNormalizedUser()` for consistency
- Skip group chats (@g.us) that don't have LID-PN mappings
Includes 22 comprehensive tests covering:
- LID chat with pnJid extraction
- PN chat with lidJid extraction
- Hosted format handling
- Deduplication between sources
- Edge cases (nulls, groups, missing data)
- All conversation sync types
Related: WhiskeySockets/Baileys#2263
See-also: WhiskeySockets/Baileys#2282
Wrap URL parsing in try/catch to prevent server crashes from malformed
requests (invalid percent-encoding, problematic Host headers, etc.).
Returns 400 Bad Request instead of crashing the process.
Prometheus Metrics:
- Add METRICS_LABELS fallback for defaultLabels env config
- Separate includeSystem from collectDefaultMetrics with own env var
- Parse URL in /metrics endpoint to support querystrings and trailing slashes
- Sort keys in labelsToKey for stable/deterministic lookups
- Update documentation with all environment variables
Retry Utils:
- Remove abort listener on sleep completion to prevent memory leak
- Apply maxDelay cap AFTER jitter to guarantee max delay limit
- Validate/normalize attempt parameter in calculateDelay
- Count retries only when attempt > 1 (actual retry, not first try)
- Use metrics.retryExhausted instead of generic errors for exhausted retries
Tests:
- Add consistency tests to verify retryConfigs.rsocket matches constants
- Re-export RETRY_BACKOFF_DELAYS and RETRY_JITTER_FACTOR from Defaults
for backwards compatibility with existing imports
- Add 'as const' to RETRY_JITTER_FACTOR for type consistency
- Improve documentation explaining why values are hardcoded in retryConfigs
(ESM initialization order issues with indirect circular imports)
Hardcode values in retryConfigs.rsocket instead of referencing
RETRY_BACKOFF_DELAYS and RETRY_JITTER_FACTOR to avoid
"Cannot access before initialization" error in ESM module loading.
- Remove duplicate RETRY_BACKOFF_DELAYS and RETRY_JITTER_FACTOR from Defaults/index.ts
- Export constants from retry-utils.ts as single source of truth
- Add 'as const' for better type inference
- Fixes "Cannot access 'RETRY_BACKOFF_DELAYS' before initialization" error
- Add registryConfigured flag to ensure single execution of configureRegistry
- Fix race condition by moving setMetricPrefix/configureRegistry before Promise
- Remove unused prefix parameter from configureRegistry function
- Consolidate prefix to single source of truth (configuredPrefix global)
- Allow clearing default labels by passing empty object
- Add resetMetricsConfiguration() and getConfiguredPrefix() for testing
- Apply configured prefix to all metric names via getFullMetricName()
- Configure customRegistry with defaultLabels at initialization
- Prevent memory leak: collectDefaultMetrics only called once via flag
- collectDefaultMetrics now uses customRegistry instead of global
- Initialize prefix/defaultLabels before creating baileysMetrics
All metrics now properly include prefix (e.g., baileys_connection_attempts)
and inherit defaultLabels configured via environment variables.
Major improvements to prometheus-metrics.ts:
- Use custom registry instead of global promClient.register (isolation)
- Remove duplicate localValues - prom-client is now single source of truth
- Fix reset(labels) to respect labels param using remove() instead of reset()
- Migrate Summary class to use prom-client internally
- getMetricsOutput() now uses customRegistry for proper isolation
Fix misleading comments in retry-utils.ts:
- Change "exponential backoff" to "custom progressive backoff"
- Fix comment claiming constants are "from Defaults" (now local)
Breaking changes to getValues()/get() - now async as they query prom-client
Define RETRY_BACKOFF_DELAYS and RETRY_JITTER_FACTOR locally in
retry-utils.ts to avoid circular dependency that caused
"Cannot access 'RETRY_BACKOFF_DELAYS' before initialization" error.
- Add prom-client ^15.1.3 as production dependency
- Migrate Counter class to use prom-client internally
- Migrate Gauge class to use prom-client internally
- Migrate Histogram class to use prom-client internally
- Update getMetricsOutput() to use prom-client's native metrics()
- Update contentType() to use prom-client's contentType
- Enable collectDefaultMetrics() for Node.js system metrics
- Maintain backward compatibility with existing API
Benefits:
- Native OpenMetrics format export
- Proper histogram bucket handling
- Default Node.js metrics (memory, CPU, event loop, GC)
- Better integration with Grafana/Prometheus ecosystem
- Integrate RETRY_BACKOFF_DELAYS and RETRY_JITTER_FACTOR into retry-utils.ts
- Add retryConfigs.rsocket using the default delays
- Add getRetryDelayWithJitter() helper function
- Add getAllRetryDelaysWithJitter() for planning
- Document DEFAULT_CACHE_MAX_KEYS with usage example
- Adjust INITIAL_PREKEY_COUNT from 30 to 200 (moderate value)
- Adjust MIN_UPLOAD_INTERVAL from 60s to 10s (balance rate limiting/responsiveness)
- Revert syncFullHistory to true (avoid breaking change)
- Integrate RETRY_BACKOFF_DELAYS and RETRY_JITTER_FACTOR into retry-utils.ts
- Add retryConfigs.rsocket using the default delays
- Add getRetryDelayWithJitter() helper function
- Add getAllRetryDelaysWithJitter() for planning
- Document DEFAULT_CACHE_MAX_KEYS with usage example
- Adjust INITIAL_PREKEY_COUNT from 30 to 200 (moderate value)
- Adjust MIN_UPLOAD_INTERVAL from 60s to 10s (balance rate limiting/responsiveness)
- Revert syncFullHistory to true (avoid breaking change)
FIX: startPromise was never cleared after stop(), causing start()
to return the old resolved promise instead of creating a new server.
This broke any flow that stops and restarts metrics (config reloads, tests).
Now stop() clears startPromise, allowing proper server restart.
Fixes based on Copilot AI and Codex code review comments:
1. Race Condition Fix (lines 1065-1069):
- BEFORE: setTimeout(100ms) was incomplete - callers could resolve
before server was actually ready
- FIX: Cache the Promise from first start() call using startPromise
- All concurrent calls now return the same Promise
- Properly handles both success and error cases
2. CPU Percentage Calculation (lines 993-996):
- BEFORE: Division by cpuCount gave artificially low values
(e.g., 25% instead of 100% on 4-core for single-threaded process)
- FIX: Removed cpuCount division
- Now follows Unix/Prometheus standard: 100% = 1 core fully used
- Values can exceed 100% on multi-core (200% = 2 cores at 100%)
- Updated metric description to clarify this behavior
Fixes based on Copilot AI and Codex code review comments:
1. CPU Usage Metric (line ~930-933):
- FIX: process.cpuUsage() now calculates delta between measurements
- Returns actual percentage (0-100) instead of cumulative seconds
- Normalized across CPU cores
2. Duplicate SystemMetricsCollector (line ~1353-1355):
- FIX: Removed duplicate instantiation from PrometheusMetricsManager
- Now uses MetricsServer's collector via getSystemCollector()
3. Collection Interval (line ~1006-1008):
- FIX: Added collectIntervalMs to MetricsConfig
- Configurable via BAILEYS_PROMETHEUS_COLLECT_INTERVAL_MS
4. Error Handling (line ~1021-1022):
- FIX: Enhanced error messages with structured JSON response
- Includes error message, timestamp, and logs to console
5. Server Binding Security (line ~1034-1038):
- FIX: Default host changed from 0.0.0.0 to 127.0.0.1
- Configurable via BAILEYS_PROMETHEUS_HOST
- Warning shown if exposed on all interfaces
6. Race Condition (line ~998-1001):
- FIX: Added isStarting flag to prevent concurrent initialization
- Multiple concurrent start() calls now handled safely
- Support BAILEYS_PROMETHEUS_ENABLED, BAILEYS_PROMETHEUS_PORT, etc.
- Add support for BAILEYS_PROMETHEUS_LABELS JSON configuration
- Maintain backward compatibility with METRICS_* prefix
- Update default port to 9092 (matching user's .env)
- Enable by opt-in (enabled=false by default)
- Improved startup log with labels info