fix: address PR review comments

Fixes from code review:

1. Fix #1,6: Use connection.update event instead of overriding sock.end()
   - Listens for 'close' event to cleanup interval
   - Handles both explicit close and internal disconnections

2. Fix #3: Exit code 2 when fetch fails (not 0)
   - Allows CI to distinguish success/error/fetch-failed
   - Properly signals fetch failures to workflows

3. Fix #4: Document revision bounds + add env vars
   - Added detailed comments explaining min/max revision values
   - Made configurable via WA_MIN_REVISION/WA_MAX_REVISION env vars

4. Fix #5,9: Remove unused fetchLatestVersion option
   - Removed from SocketConfig and defaults
   - Updated versionCheckIntervalMs docs to clarify it's only for makeWASocketAutoVersion

5. Fix #7: Use separate variable for version tracking
   - trackedVersion instead of mutating mergedConfig
   - Prevents unexpected side effects

6. Fix #8: Check socket state before emitting events
   - isSocketClosed flag to prevent race conditions
   - Double-check after async operations

7. Fix #10: Implement force parameter in workflow
   - Creates PR even without version changes when force=true
   - Useful for re-triggering updates manually

Note: Test coverage (Fix #2) deferred to separate PR due to
ESM mocking complexity with Jest.
This commit is contained in:
Claude
2026-01-21 17:21:49 +00:00
parent 805fdd3525
commit 46ffaf027c
5 changed files with 172 additions and 52 deletions
+67 -10
View File
@@ -7,9 +7,10 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
force: force:
description: 'Force update even if version is the same' description: 'Force create PR even if version unchanged (useful for re-triggering)'
required: false required: false
default: 'false' default: 'false'
type: boolean
permissions: permissions:
contents: write contents: write
@@ -47,23 +48,52 @@ jobs:
- name: Update WhatsApp Web Version - name: Update WhatsApp Web Version
id: update_version id: update_version
run: yarn update:version run: |
# Run the update script, capture exit code
# Exit codes: 0=success, 1=error, 2=fetch failed (fallback used)
set +e
yarn update:version
EXIT_CODE=$?
set -e
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
# Only fail on fatal errors (exit code 1)
if [ $EXIT_CODE -eq 1 ]; then
echo "::error::Script failed with fatal error"
exit 1
fi
# Exit code 2 means fetch failed but fallback was used - this is a warning
if [ $EXIT_CODE -eq 2 ]; then
echo "::warning::Could not fetch latest version from WhatsApp servers"
fi
- name: Check for changes - name: Check for changes
id: check_changes id: check_changes
run: | run: |
FORCE="${{ inputs.force }}"
if git diff --quiet; then if git diff --quiet; then
echo "has_changes=false" >> $GITHUB_OUTPUT echo "has_changes=false" >> $GITHUB_OUTPUT
if [ "$FORCE" == "true" ]; then
echo "force_mode=true" >> $GITHUB_OUTPUT
echo "::notice::Force mode enabled - will create PR even without changes"
else
echo "force_mode=false" >> $GITHUB_OUTPUT
fi
else else
echo "has_changes=true" >> $GITHUB_OUTPUT echo "has_changes=true" >> $GITHUB_OUTPUT
echo "force_mode=false" >> $GITHUB_OUTPUT
fi fi
- name: Create Pull Request - name: Create Pull Request
if: steps.check_changes.outputs.has_changes == 'true' if: steps.check_changes.outputs.has_changes == 'true' || steps.check_changes.outputs.force_mode == 'true'
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
BRANCH_NAME="update-version/stable" BRANCH_NAME="update-version/stable"
FORCE="${{ inputs.force }}"
# Configure git # Configure git
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
@@ -72,25 +102,40 @@ jobs:
# Check if branch exists and delete it # Check if branch exists and delete it
git push origin --delete "$BRANCH_NAME" 2>/dev/null || true git push origin --delete "$BRANCH_NAME" 2>/dev/null || true
# Create new branch, commit, and push # Create new branch
git checkout -b "$BRANCH_NAME" git checkout -b "$BRANCH_NAME"
# Only add the single source of truth file # Only add the single source of truth file
git add src/Defaults/baileys-version.json git add src/Defaults/baileys-version.json
git commit -m "chore: update WhatsApp Web version"
# Create commit (allow empty if force mode)
if [ "$FORCE" == "true" ] && git diff --cached --quiet; then
git commit --allow-empty -m "chore: force WhatsApp Web version check"
else
git commit -m "chore: update WhatsApp Web version"
fi
git push origin "$BRANCH_NAME" git push origin "$BRANCH_NAME"
# Create PR using GitHub CLI # Create PR using GitHub CLI
gh pr create \ PR_BODY="Automated WhatsApp Web version update.
--title "chore: update WhatsApp Web version" \
--body "Automated WhatsApp Web version update.
This PR updates the WhatsApp Web client revision to the latest version fetched from \`web.whatsapp.com\`. This PR updates the WhatsApp Web client revision to the latest version fetched from \`web.whatsapp.com\`.
**Single source of truth:** **Single source of truth:**
- \`src/Defaults/baileys-version.json\` (other files import from here) - \`src/Defaults/baileys-version.json\` (other files import from here)
**Update frequency:** Daily at 06:00 UTC" \ **Update frequency:** Daily at 06:00 UTC"
if [ "$FORCE" == "true" ]; then
PR_BODY="$PR_BODY
> **Note:** This PR was created with force mode enabled."
fi
gh pr create \
--title "chore: update WhatsApp Web version" \
--body "$PR_BODY" \
--base master \ --base master \
--head "$BRANCH_NAME" || echo "PR already exists or could not be created" --head "$BRANCH_NAME" || echo "PR already exists or could not be created"
@@ -98,8 +143,20 @@ jobs:
run: | run: |
echo "### WhatsApp Version Update" >> $GITHUB_STEP_SUMMARY echo "### WhatsApp Version Update" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.check_changes.outputs.has_changes }}" == "true" ]; then
EXIT_CODE="${{ steps.update_version.outputs.exit_code }}"
HAS_CHANGES="${{ steps.check_changes.outputs.has_changes }}"
FORCE_MODE="${{ steps.check_changes.outputs.force_mode }}"
if [ "$EXIT_CODE" == "2" ]; then
echo "⚠️ Warning: Could not fetch latest version from WhatsApp servers" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
if [ "$HAS_CHANGES" == "true" ]; then
echo "✅ New version detected and PR created" >> $GITHUB_STEP_SUMMARY echo "✅ New version detected and PR created" >> $GITHUB_STEP_SUMMARY
elif [ "$FORCE_MODE" == "true" ]; then
echo "🔄 Force mode: PR created (no version change)" >> $GITHUB_STEP_SUMMARY
else else
echo "️ Already up to date" >> $GITHUB_STEP_SUMMARY echo "️ Already up to date" >> $GITHUB_STEP_SUMMARY
fi fi
+47 -7
View File
@@ -9,6 +9,15 @@
* Updates: src/Defaults/baileys-version.json (SINGLE SOURCE OF TRUTH) * Updates: src/Defaults/baileys-version.json (SINGLE SOURCE OF TRUTH)
* *
* Usage: yarn update:version * Usage: yarn update:version
*
* Environment variables:
* - WA_MIN_REVISION: Minimum valid revision number (default: 1000000000)
* - WA_MAX_REVISION: Maximum valid revision number (default: 9999999999)
*
* Exit codes:
* - 0: Success (version updated or already up to date)
* - 1: Fatal error (script failure)
* - 2: Fetch failed (all sources failed, fallback version used)
*/ */
import { readFileSync, writeFileSync, appendFileSync } from 'fs' import { readFileSync, writeFileSync, appendFileSync } from 'fs'
@@ -29,14 +38,26 @@ interface VersionResult {
error?: unknown error?: unknown
} }
// Configuration /**
* Configuration for the version update script.
*
* Revision bounds explanation:
* - WhatsApp Web uses a numeric "client_revision" that increments with each release
* - As of 2024, revisions are in the 10-digit range (e.g., 1032141294)
* - minRevision (1000000000) ensures we don't accept clearly invalid small numbers
* that could result from parsing errors
* - maxRevision (9999999999) is the maximum 10-digit number, providing an upper sanity check
*
* These bounds can be overridden via environment variables if WhatsApp changes their
* versioning scheme in the future.
*/
const CONFIG = { const CONFIG = {
maxRetries: 3, maxRetries: 3,
retryDelayMs: 2000, // Base delay, will be multiplied by attempt number retryDelayMs: 2000, // Base delay, will be multiplied by attempt number
requestTimeoutMs: 10000, requestTimeoutMs: 10000,
// Version sanity checks // Version sanity checks - can be overridden via environment variables
minRevision: 1000000000, // Minimum expected revision (to catch parsing errors) minRevision: parseInt(process.env.WA_MIN_REVISION || '1000000000', 10),
maxRevision: 9999999999, // Maximum expected revision maxRevision: parseInt(process.env.WA_MAX_REVISION || '9999999999', 10),
} }
/** /**
@@ -65,7 +86,11 @@ async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs: nu
} }
/** /**
* Validates that a revision number looks reasonable * Validates that a revision number looks reasonable.
* WhatsApp Web revisions are typically 10-digit numbers.
*
* @param revision - The revision number to validate
* @returns true if the revision is within expected bounds
*/ */
function isValidRevision(revision: number): boolean { function isValidRevision(revision: number): boolean {
return ( return (
@@ -240,6 +265,13 @@ async function main() {
console.log('║ WhatsApp Web Version Update Script ║') console.log('║ WhatsApp Web Version Update Script ║')
console.log('╚════════════════════════════════════════════════╝\n') console.log('╚════════════════════════════════════════════════╝\n')
// Log configuration
console.log('Configuration:')
console.log(` Min revision: ${CONFIG.minRevision}`)
console.log(` Max revision: ${CONFIG.maxRevision}`)
console.log(` Max retries: ${CONFIG.maxRetries}`)
console.log(` Request timeout: ${CONFIG.requestTimeoutMs}ms\n`)
console.log('Fetching latest WhatsApp Web version...\n') console.log('Fetching latest WhatsApp Web version...\n')
const result = await fetchLatestWaWebVersion() const result = await fetchLatestWaWebVersion()
@@ -274,10 +306,18 @@ async function main() {
} }
} }
// Exit with error if we couldn't fetch latest (so CI knows) // Exit with code 2 if fetch failed (Fix #3)
// This allows CI to distinguish between:
// - 0: Success
// - 1: Script error
// - 2: Fetch failed but fallback used
if (!result.isLatest) { if (!result.isLatest) {
console.log('\n⚠ Warning: Could not fetch latest version from WhatsApp servers') console.log('\n⚠ Warning: Could not fetch latest version from WhatsApp servers')
process.exit(0) // Don't fail the workflow, just warn console.log('Exit code 2: Fetch failed, fallback version used')
if (process.env.GITHUB_OUTPUT) {
appendFileSync(process.env.GITHUB_OUTPUT, `fetch_failed=true\n`)
}
process.exit(2)
} }
} }
-1
View File
@@ -52,7 +52,6 @@ const SIX_HOURS_MS = 6 * 60 * 60 * 1000
export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
version: version as WAVersion, version: version as WAVersion,
fetchLatestVersion: false,
versionCheckIntervalMs: SIX_HOURS_MS, versionCheckIntervalMs: SIX_HOURS_MS,
browser: Browsers.macOS('Chrome'), browser: Browsers.macOS('Chrome'),
waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat', waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
+54 -27
View File
@@ -67,8 +67,22 @@ export const makeWASocketAutoVersion = async (config: UserFacingSocketConfig) =>
} }
const logger = mergedConfig.logger const logger = mergedConfig.logger
let currentVersion = mergedConfig.version
// Track version separately to avoid mutating config (Fix #7)
let trackedVersion: WAVersion = [...mergedConfig.version] as WAVersion
let versionCheckInterval: ReturnType<typeof setInterval> | null = null let versionCheckInterval: ReturnType<typeof setInterval> | null = null
let isSocketClosed = false
/**
* Cleans up the version check interval
*/
const cleanupInterval = () => {
if (versionCheckInterval) {
clearInterval(versionCheckInterval)
versionCheckInterval = null
logger?.debug('Stopped periodic version check')
}
}
// Fetch latest version before connecting // Fetch latest version before connecting
try { try {
@@ -78,7 +92,7 @@ export const makeWASocketAutoVersion = async (config: UserFacingSocketConfig) =>
if (result.isLatest) { if (result.isLatest) {
logger?.info({ version: result.version }, 'Using latest WhatsApp Web version') logger?.info({ version: result.version }, 'Using latest WhatsApp Web version')
mergedConfig.version = result.version mergedConfig.version = result.version
currentVersion = result.version trackedVersion = [...result.version] as WAVersion
} else { } else {
logger?.warn( logger?.warn(
{ error: result.error, fallbackVersion: mergedConfig.version }, { error: result.error, fallbackVersion: mergedConfig.version },
@@ -95,6 +109,17 @@ export const makeWASocketAutoVersion = async (config: UserFacingSocketConfig) =>
// Create the socket // Create the socket
const sock = makeWASocket(mergedConfig) const sock = makeWASocket(mergedConfig)
// Listen for connection close to cleanup interval (Fix #1, #6)
// This handles both explicit sock.end() and internal disconnections
sock.ev.on('connection.update', (update) => {
if (update.connection === 'close') {
isSocketClosed = true
cleanupInterval()
} else if (update.connection === 'open') {
isSocketClosed = false
}
})
// Setup periodic version check if interval > 0 // Setup periodic version check if interval > 0
const checkIntervalMs = mergedConfig.versionCheckIntervalMs const checkIntervalMs = mergedConfig.versionCheckIntervalMs
if (checkIntervalMs > 0) { if (checkIntervalMs > 0) {
@@ -104,35 +129,48 @@ export const makeWASocketAutoVersion = async (config: UserFacingSocketConfig) =>
) )
versionCheckInterval = setInterval(async () => { versionCheckInterval = setInterval(async () => {
// Skip if socket is closed (Fix #8 - race condition)
if (isSocketClosed) {
cleanupInterval()
return
}
try { try {
logger?.debug('Checking for WhatsApp Web version update...') logger?.debug('Checking for WhatsApp Web version update...')
const result = await fetchLatestWaWebVersion() const result = await fetchLatestWaWebVersion()
if (result.isLatest && versionsAreDifferent(currentVersion, result.version)) { // Double-check socket is still open after async operation (Fix #8)
const isCritical = isCriticalVersionChange(currentVersion, result.version) if (isSocketClosed) {
cleanupInterval()
return
}
if (result.isLatest && versionsAreDifferent(trackedVersion, result.version)) {
const isCritical = isCriticalVersionChange(trackedVersion, result.version)
const previousVersion = trackedVersion
logger?.info( logger?.info(
{ {
currentVersion, currentVersion: previousVersion,
newVersion: result.version, newVersion: result.version,
isCritical isCritical
}, },
'New WhatsApp Web version detected! Will use on next reconnection.' 'New WhatsApp Web version detected! Will use on next reconnection.'
) )
// Emit event for user to handle // Update tracked version for next reconnection (Fix #7)
sock.ev.emit('version.update', { trackedVersion = [...result.version] as WAVersion
currentVersion,
newVersion: result.version,
isCritical
})
// Update the version for next reconnection // Emit event for user to handle (only if socket still open)
// This is the "soft" update - it will be used when WhatsApp naturally reconnects if (!isSocketClosed) {
currentVersion = result.version sock.ev.emit('version.update', {
mergedConfig.version = result.version currentVersion: previousVersion,
newVersion: result.version,
isCritical
})
}
} else if (result.isLatest) { } else if (result.isLatest) {
logger?.debug({ version: currentVersion }, 'Version is up to date') logger?.debug({ version: trackedVersion }, 'Version is up to date')
} else { } else {
logger?.warn({ error: result.error }, 'Failed to check for version update') logger?.warn({ error: result.error }, 'Failed to check for version update')
} }
@@ -140,17 +178,6 @@ export const makeWASocketAutoVersion = async (config: UserFacingSocketConfig) =>
logger?.warn({ error }, 'Error checking for version update') logger?.warn({ error }, 'Error checking for version update')
} }
}, checkIntervalMs) }, checkIntervalMs)
// Clean up interval when socket closes
const originalEnd = sock.end.bind(sock)
sock.end = (error) => {
if (versionCheckInterval) {
clearInterval(versionCheckInterval)
versionCheckInterval = null
logger?.debug('Stopped periodic version check')
}
return originalEnd(error)
}
} }
return sock return sock
+4 -7
View File
@@ -49,17 +49,14 @@ export type SocketConfig = {
logger: ILogger logger: ILogger
/** version to connect with */ /** version to connect with */
version: WAVersion version: WAVersion
/**
* Automatically fetch the latest WhatsApp Web version on connect.
* When enabled, fetches from web.whatsapp.com before connecting.
* Falls back to bundled version if fetch fails.
* @default false
*/
fetchLatestVersion: boolean
/** /**
* Interval in milliseconds to check for new WhatsApp Web versions. * Interval in milliseconds to check for new WhatsApp Web versions.
* When a new version is detected, it will be used on the next reconnection. * When a new version is detected, it will be used on the next reconnection.
* Set to 0 to disable periodic checks. * Set to 0 to disable periodic checks.
*
* Note: This option is only used by `makeWASocketAutoVersion()`.
* The standard `makeWASocket()` does not perform automatic version checks.
*
* @default 21600000 (6 hours) * @default 21600000 (6 hours)
*/ */
versionCheckIntervalMs: number versionCheckIntervalMs: number