Fixes "Permission denied (publickey)" error when Yarn tries to fetch
libsignal dependency via SSH in GitHub Actions.
Added git config to force HTTPS instead of SSH for all GitHub URLs
in all workflows that run yarn install.
https://claude.ai/code/session_01QPt4WssG6jjEciQKdVpFtK
Implements automatic detection of OS versions at runtime instead of
hardcoded values. This ensures the library reports accurate platform
versions to WhatsApp without manual updates.
Version Detection:
- Linux: Reads /etc/os-release for distribution version (e.g., '24.04.1')
- macOS: Converts Darwin kernel version to macOS version (e.g., Darwin 24.x → macOS 15.x)
- Windows: Uses os.release() directly (already returns correct format)
Security Fixes (from PR #62 review):
- Fix isValidBrowserPreset() to use Object.prototype.hasOwnProperty.call()
to prevent matching inherited properties like 'toString' or 'constructor'
- Fix getPlatformId() signature to accept 'unknown' type for runtime safety
Other Improvements:
- Add FALLBACK_VERSIONS constant with updated 2025 versions
- Add DARWIN_TO_MACOS mapping for accurate macOS version conversion
- Export detectedOSVersions for debugging and logging
- Improve JSDoc documentation with accurate examples
- Add 36 comprehensive unit tests (up from 25)
The version is detected once at module load and cached for consistent
behavior throughout the application lifecycle.
https://claude.ai/code/session_018XbFWEYwCfeeXioFDLvSXV
- Add pre-computed BROWSER_TO_PLATFORM_ID map for better performance
- Implement getPlatformName() helper with try-catch for error handling
- Add normalizeBrowserKey() for input validation (handles non-string inputs)
- Add safeRelease() wrapper with fallback for OS release detection
- Export isValidBrowserPreset() type guard for external validation
- Add comprehensive JSDoc documentation with examples
- Add 25 unit tests covering all edge cases and robustness scenarios
Improvements over original code:
- Handles undefined/null/invalid input types gracefully
- Returns default values instead of crashing on invalid input
- Platform map is alphabetically sorted and deduplicated
- Constants use 'as const' for better type inference
- Module-level caching avoids repeated proto.DeviceProps access
Inspired by PR #2303 from WhiskeySockets/Baileys
https://claude.ai/code/session_018XbFWEYwCfeeXioFDLvSXV
Low Priority Fixes based on Copilot code review:
1. Clarified IdentitySaveResult.changed comment
- Explicitly documents that false means "new OR unchanged"
- Explains to use isNew to distinguish between cases
- Documents that previousFingerprint only present when changed
2. Improved fingerprint documentation
- Documents that fingerprint is 64 hex characters (full SHA-256)
3. Added comments to varint parsing
- Documents that varint too long could indicate malformed/malicious message
- Added comment for incomplete varint case
https://claude.ai/code/session_01SWAcNuGZQmEKyBPYkBhVHg
Medium Priority Fixes based on Copilot code review:
1. Use full SHA-256 fingerprint (64 characters)
- Previously truncated to 32 characters (128 bits)
- Now uses full 256-bit hash for cryptographic consistency
- Aligns with standard security practices
2. Add bounds checking in protobuf parsing
- Added bounds check after offset += length for length-delimited fields
- Added bounds check for 64-bit and 32-bit fixed fields
- Prevents reading beyond buffer bounds with malformed messages
- Defense in depth against malformed/malicious PreKeyWhisperMessages
https://claude.ai/code/session_01SWAcNuGZQmEKyBPYkBhVHg
High Priority Fixes based on Copilot/Codex code review:
1. Propagate errorCode to shouldRecreateSession (CRITICAL)
- Extract error attribute from retry node
- Pass errorCode to shouldRecreateSession in both sendRetryRequest and sendMessagesAgain
- Without this fix, MAC error detection was NOT working
- Now immediate session recreation works for MAC errors
2. Use .unref() on metrics interval
- Prevents blocking process exit in short-lived scripts/tests
- Interval no longer keeps event loop alive unnecessarily
3. Reset circuit breaker regardless of state
- Previously only reset when isOpen()
- Now resets in any state (open, half-open, or closed with accumulated failures)
- Ensures clean slate after identity change detection
https://claude.ai/code/session_01SWAcNuGZQmEKyBPYkBhVHg
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
Adicionada função isCatalogMessage() para detectar mensagens de
catálogo (catalog_message, single_product, product_list).
Mensagens de catálogo funcionam apenas com o formato proto
viewOnceMessage > interactiveMessage > nativeFlowMessage,
sem necessidade de injeção de biz node.
Isso corrige o error 405 ao enviar lista de produtos.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mensagens de lista de produtos do catálogo WhatsApp Business (PRODUCT_LIST)
não precisam de injeção de biz node. O biz node estava causando erro 405.
- Retorna undefined em vez de 'native_flow' para PRODUCT_LIST
- Afeta apenas productList, não afeta carrossel de mídia
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added detailed logging to capture the full button structure including
parsed buttonParamsJson to help diagnose why list menus don't open.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Testing showed that using type='list' for list messages (even those
using nativeFlowMessage with single_select) causes error 479 rejection.
All interactive message types (buttons, lists, carousels) now use
type='native_flow' in the biz node structure.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add detailed logging to diagnose list message detection issues.
Shows buttonType, hasListMessage, hasNativeFlow, and button names.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Lists sent as nativeFlowMessage (with single_select/multi_select buttons)
still need type='list' in the biz node to work properly.
Added isListNativeFlow() function to detect list-type messages by checking
for single_select or multi_select button names.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For list messages, use type='list' instead of 'native_flow' in the
biz node structure. This allows the list button to open properly.
- native_flow messages: biz > interactive(type=native_flow) > native_flow
- list messages: biz > interactive(type=list) > list
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes the biz node from flat structure (biz > native_flow) to nested
structure (biz > interactive > native_flow) as used by Pastorini/Astra-Api.
Structure:
- biz: {}
- interactive: { type: 'native_flow', v: '1' }
- native_flow: { v: '9', name: 'mixed' }
This fixes error 479 that was breaking all interactive messages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix carousel detection in getButtonType to return native_flow
- Add isCarouselMessage helper function
- Skip bot node injection for carousel messages (fixes error 479)
- Restore ProductCarouselCard and ProductCarouselMessageOptions types
- Restore generateProductCarouselMessage function
- Add productCarousel handler in generateWAMessageContent
The bot node with biz_bot='1' was causing error 479 for media carousels
because carousels are regular interactive messages, not bot messages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix ProductCarouselMessageOptions import indentation
- Fix productList comment indentation to align with else-if chain
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace catalogId with businessOwnerJid (required for catalog reference)
- Fix cards structure to use proper IInteractiveMessage[] format
- Each card now uses collectionMessage with bizJid and id
- Fix body reading from productCarousel.body (was reading from message.body)
- Remove 'as any' type casting by using correct proto types
- Update examples in types and function documentation
Addresses:
- Schema mismatch in carousel cards
- Body text being silently ignored when nested in productCarousel
- Improper type casting masking validation errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous condition `!isJidGroup()` was too broad and would incorrectly
inject bot nodes for status broadcasts, newsletters, and Meta AI bots.
Now uses explicit checks for private 1:1 user conversations:
- isPnUser() for @s.whatsapp.net JIDs
- isLidUser() for @lid JIDs
- @c.us suffix for legacy format
- Excludes bot JIDs with isJidBot()
This prevents potential issues with message delivery to non-user destinations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update getButtonType() to return 'native_flow' for modern nativeFlowMessage format
- Add product list detection (PRODUCT_LIST type returns native_flow)
- Implement getButtonArgs() with proper v:4 attributes for native_flow
- Add special attribute handling for payment flows (review_and_pay, mpm, review_order)
- Add bot node injection for private chats (required for interactive messages to render)
- Based on Itsukichan/Baileys and baileys_helpers implementation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes based on code review:
1. Fix sections vs productSections mismatch
- Changed `productMsg.productList.sections` to `productMsg.productList.productSections`
- Ensures consistency with ProductListMessageOptions type
2. Add section title validation
- Each section must have a non-empty title string
3. Add productId validation for each product
- Each product in a section must have a non-empty productId string
4. Add headerImage.productId validation
- When headerImage is provided, productId must be a non-empty string
5. Remove fallback values (|| '')
- Removed fallbacks for title and description
- Let generateProductListMessage handle validation consistently
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address PR review comment: the viewOnceMessage detection now checks
all 7 message types for consistency with direct message detection:
- buttonsMessage
- templateMessage
- listMessage
- buttonsResponseMessage
- listResponseMessage
- templateButtonReplyMessage
- interactiveMessage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The getButtonType function was not detecting interactive messages
when wrapped inside viewOnceMessage (the modern nativeFlowMessage format).
This fix adds detection for:
- viewOnceMessage > interactiveMessage
- viewOnceMessage > listMessage
- viewOnceMessage > buttonsMessage
When enableInteractiveMessages is true, the 'biz' node will now be
correctly injected for messages using the viewOnceMessage wrapper.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add generateProductListMessage function for sending multiple products
from the WhatsApp Business catalog in a single message.
Features:
- ProductListMessageOptions type with full validation
- Support for product sections (categories)
- Optional header image from catalog
- Integration with sendMessage via 'productList' property
- Maximum 30 products limit per WhatsApp specs
Usage:
```typescript
const msg = generateProductListMessage({
title: 'Our Best Sellers',
description: 'Check out our products!',
buttonText: 'View Products',
businessOwnerJid: '5511999999999@s.whatsapp.net',
productSections: [
{ title: 'Electronics', products: [{ productId: 'prod_001' }] }
]
})
await sock.sendMessage(jid, msg)
```
Note: Requires WhatsApp Business account with catalog configured.
Does NOT require Meta Business Manager integration.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes all 9 issues identified in PR #49 review:
## 1. Input Validation (formatNativeFlowButton)
- Added validateNonEmptyString helper function
- Validates required fields: text, url, copyText, id, phoneNumber
- Throws Boom error with descriptive message for empty/whitespace values
## 2-3. Async Media Processing (generateButtonMessage)
- Function is now async, returns Promise<WAMessageContent>
- Accepts optional MessageContentGenerationOptions parameter
- Calls prepareWAMessageMedia() for headerImage/headerVideo
- Throws error if media provided without mediaOptions
## 4-7. Async Media Processing (generateCarouselMessage)
- Function is now async, returns Promise<WAMessageContent>
- Uses Promise.all to process all card media in parallel
- Properly converts WAMediaUpload to IImageMessage/IVideoMessage
## 5. Mutual Exclusivity Validation
- generateButtonMessage: Throws if both headerImage AND headerVideo provided
- generateCarouselMessage: Throws if card has both image AND video
## 6. Empty Button Array Validation
- Each carousel card must have at least one button
- Throws descriptive error with card index
## 8. Updated Async Calls
- generateWAMessageContent now awaits generateButtonMessage
- generateWAMessageContent now awaits generateCarouselMessage
- Both pass MessageContentGenerationOptions for media processing
## 9. Type Support
- Functions accept MessageContentGenerationOptions as optional param
- Enables access to upload, mediaCache, logger options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extends the Native Flow implementation with additional features:
## New Button Type
- `CallButton` - `cta_call` type for initiating phone calls
```typescript
{ type: 'call', text: 'Call Us', phoneNumber: '+5511999999999' }
```
## New List Message Support
- `generateListMessage()` - Creates interactive list with single_select
- `nativeList` type for sendMessage integration
```typescript
await sock.sendMessage(jid, {
text: 'Choose:',
nativeList: {
buttonText: 'View Options',
sections: [{ title: 'Section', rows: [...] }]
}
})
```
## Legacy Functions (for backward compatibility)
- `generateButtonMessageLegacy()` - Old buttonsMessage format
- `generateListMessageLegacy()` - Old listMessage format
⚠️ These are deprecated and may not work on all devices
## Other Improvements
- Added `merchantUrl` support for URL buttons
- Added `messageVersion` parameter (default: 2)
- Added `messageParamsJson` to nativeFlowMessage
- Created `NativeListSection` type to avoid conflict with legacy `ListSection`
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The import of TimeMs from Defaults/index.ts caused a circular
dependency error in ESM:
"ReferenceError: Cannot access 'TimeMs' before initialization"
Solution: Define TimeMs constants locally in unified-session.ts
instead of importing from Defaults. This avoids the ESM module
initialization order issue.
Adds backward compatibility alias:
- isJidUser -> isPersonJid
This allows zpro.io and other consumers that depend on the old
isJidUser function name to continue working without changes.
1. Add logger to writeCacheFile (version-cache.ts)
- Now logs warnings when file write fails
- Helps debugging in production environments
2. Sanitize logging in parseGroupResult (communities.ts)
- Changed from info to debug level
- Removed full node/groupNode dumps (sensitive data)
- Now only logs nodeTag and groupId
3. Add input validation in parseNewsletterCreateResponse (newsletter.ts)
- Validates response structure before destructuring
- Adds fallback values for parseInt (prevents NaN)
- Adds null checks for optional fields
4. Add health-status.ts module
- getHealthStatus(): Full health check with circuit breakers, cache, memory
- isHealthy(): Simple boolean for liveness probes
- getSimpleHealthStatus(): Returns 'ok', 'degraded', or 'error'
- Useful for k8s probes and monitoring dashboards