Applied yarn lint --fix to auto-correct:
- Import spacing and sorting across multiple files
- Trailing commas
- Arrow function formatting
- Object literal formatting
- Indentation consistency
- Removed unused imports (BaileysEventType, jest from tests)
This commit addresses the linting errors reported in GitHub Actions:
- Fixed import ordering in libsignal.ts
- Fixed trailing commas in Defaults/index.ts
- Cleaned up formatting across 63 files
- Reduced line count by ~220 lines through formatting optimization
Note: Some lint errors remain (75 errors, 239 warnings) mainly:
- @typescript-eslint/no-unused-vars (variables assigned but not used)
- @typescript-eslint/no-explicit-any (type safety warnings)
These remaining issues are non-blocking and can be addressed incrementally.
https://claude.ai/code/session_015R3U3kiprQiNTTNNt31Sg6
This implements the functionality from WhiskeySockets/Baileys PR #2307
with enhanced improvements:
## Core Features
- Extract identity key from PreKeyWhisperMessage before decryption
- Detect when a contact reinstalls WhatsApp (identity key changes)
- Automatically delete old session and recreate on identity change
- Trust On First Use (TOFU) for new contacts
## Improvements over original PR
- LRU cache for identity keys (1000 keys, 30min TTL)
- Integration with existing Circuit Breaker (reset on identity change)
- New Prometheus metrics for observability:
- signal_identity_changes_total (new/changed)
- signal_mac_errors_total
- signal_session_recreations_total
- signal_identity_key_cache_hits/misses
- signal_identity_key_operations_ms (histogram)
- New 'identity.changed' event for applications to notify users
- RetryReason enum with MAC_ERROR_CODES and SESSION_ERROR_CODES
- Robust protobuf parsing with validation
- Structured logging with fingerprints
## Technical Details
- Manual protobuf parsing (compatible with any Signal implementation)
- SHA-256 fingerprints for key identification
- Atomic session deletion + identity key save
- Best-effort identity tracking (doesn't fail decryption on errors)
Resolves permanent MAC errors when contacts reinstall WhatsApp.
https://claude.ai/code/session_01SWAcNuGZQmEKyBPYkBhVHg
⚠️ EXPERIMENTAL FEATURE - Use only for testing with disposable accounts
Implements full support for WhatsApp interactive messages including:
- Simple text buttons (up to 3 buttons)
- Buttons with images/videos
- List messages (up to 10 items in sections)
- Template buttons (quick reply, URL, call actions)
- Carousel messages (up to 10 scrollable cards)
Features:
- Feature flag 'enableInteractiveMessages' (default: true for dev/testing)
- Prometheus metrics for tracking sends, successes, failures, and latency
- Comprehensive TypeScript types for all interactive message formats
- Extensive logging with warnings about potential account bans
- Automatic 'biz' node injection when feature is enabled
CRITICAL WARNINGS:
- These features may NOT work on non-business WhatsApp accounts
- Can cause temporary or permanent account BANS
- WhatsApp actively blocks this functionality since April 2022
- Messages may be rejected or fail silently
- Use ONLY in dev environment with test accounts
Architecture:
- Added ButtonInfo, Templatable, Listable, Carouselable types to Message.ts
- Extended AnyMediaMessageContent and AnyRegularMessageContent
- Implemented message generation in messages.ts
- Added getButtonType() and getButtonArgs() helpers in messages-send.ts
- Injected 'biz' node in stanza construction with metrics tracking
- Added 4 new Prometheus metrics: interactiveMessagesSent, Success, Failures, Latency
Documentation:
- Complete usage guide in INTERACTIVE_MESSAGES.md
- Examples for all interactive message types
- Metrics monitoring queries
- Troubleshooting guide
- Migration path to WhatsApp Business API
Related issues:
- https://github.com/WhiskeySockets/Baileys/issues/56
- https://github.com/WhiskeySockets/Baileys/issues/25
- https://github.com/WhiskeySockets/Baileys/pull/2291
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Metrics with labels only appear in Prometheus output after being
incremented at least once. This fix pre-initializes all labeled metrics
with zero values so they appear in Grafana dashboards immediately,
including buffer_destroyed_total which was showing "No data".
Added metrics tracking for:
- circuit_breaker_trips_total: Incremented when circuit opens
- adaptive_health_status: 1 if healthy (not in aggressive mode), 0 otherwise
- adaptive_event_rate: Current events per second
Added methods to AdaptiveTimeoutCalculator:
- getEventRate(): Returns current event rate in events/second
- isHealthy(): Returns true if not in aggressive mode
Metrics are updated on each buffer flush when adaptive timeout is enabled.
Added recordBufferDestroyed() and recordBufferFinalFlush() functions
and integrated into destroy() function in event-buffer.ts.
Tracks:
- Buffer destruction with reason and whether there was pending flush
- Final flushes that occur during buffer destruction
Added recordConnectionError() function and integrated it into socket.ts
to track connection errors by type:
- connection_closed
- connection_lost
- connection_replaced
- timed_out
- logged_out
- bad_session
- restart_required
- multidevice_mismatch
- error_{code} for other status codes
The metric was defined but never incremented when buffer overflow occurred.
Added recordBufferOverflow() function and call it from checkBufferOverflow()
when buffer exceeds maxBufferSize (default 5000 events).
The metric was defined but never incremented when LRU cleanup happened.
Added recordCacheCleanup() function and call it from cleanupHistoryCache()
when cache entries are removed due to exceeding maxHistoryCacheSize.
The buffer_cache_size metric was defined but never updated, always showing 0.
Fix:
- Add historyCacheSize parameter to recordBufferFlush function
- Pass historyCache.size from event-buffer.ts when recording flush metrics
- Update both bufferCacheSize and bufferHistoryCacheSize metrics
The buffer_flushes_total metric was registered twice with different labels:
- In main metrics object: ['type', 'reason']
- In getEventBufferMetrics(): ['forced']
This caused recordBufferFlush() to silently fail because it tried to
increment with { forced } labels but the registered metric expected
{ type, reason } labels.
Fix:
- Remove duplicate bufferFlushes from getEventBufferMetrics()
- Update recordBufferFlush() to use metrics.bufferFlushes with correct labels
- Update recordEventBuffered() to use main metrics.eventsBuffered
- Rename local variable to avoid shadowing global metrics object
Added new metrics:
- active_connections: Number of active WhatsApp connections
- connection_errors_total: Total connection errors by type
- buffer_destroyed_total: Total buffers destroyed
- buffer_final_flush_total: Final flushes during destruction
- buffer_cache_cleanup_total: Cache cleanup operations
- buffer_cache_size: Current buffer cache size
- adaptive_health_status: System health (0=unhealthy, 1=healthy)
- adaptive_event_rate: Current event rate per second
These metrics are required for the Grafana dashboard to display all panels correctly.
Check if nodejs_eventloop_lag_seconds metric already exists before creating
it in SystemMetricsCollector. This prevents conflicts when collectDefaultMetrics
from prom-client has already registered metrics with the same name.
Added helper functions metricExists() and getExistingMetric() to safely
check and retrieve existing metrics from the custom registry.
The Prometheus metrics server was implemented but never started because
initializeMetrics() was never called. This fix adds automatic initialization
when the module is loaded and BAILEYS_PROMETHEUS_ENABLED=true.
This ensures the /metrics endpoint is available without requiring manual
initialization in the application code.
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
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
- 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
- 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
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
- baileys-event-stream.ts: Add null check for buffer item in priority insertion
- baileys-logger.ts: Handle 'silent' log level and fix JID sanitization
- cache-utils.ts: Fix type incompatibility in global cache with any type
- logger-adapter.ts: Add nullish coalescing for level mapping and fix console type cast
- prometheus-metrics.ts: Rename private 'metrics' property to 'metricsMap' to avoid conflict with method
- trace-context.ts: Store baggage header in variable before accessing to fix undefined check
- Translate all Portuguese comments to English in utility modules
- Enhance circuit-breaker.ts with sliding window failure tracking
- Add factory functions for WhatsApp-specific circuit breakers
(createPreKeyCircuitBreaker, createConnectionCircuitBreaker, createMessageCircuitBreaker)
- Improve volume threshold support for circuit breaker decisions
- Standardize documentation across all utility files
Files updated:
- structured-logger.ts: English comments
- baileys-logger.ts: English comments
- trace-context.ts: English comments
- prometheus-metrics.ts: English comments
- cache-utils.ts: English comments
- circuit-breaker.ts: Enhanced with sliding window + English comments
- retry-utils.ts: English comments
- baileys-event-stream.ts: English comments
- index.ts: English comments