From 933a28de261f04c80b8cf2e58d9e5c666258dd36 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 16 Dec 2025 14:18:05 -0300 Subject: [PATCH] feat(baileys): enhance logout process and connection handling - Introduced a flag to prevent reconnection during instance deletion. - Improved logging for connection updates and errors during logout. - Added a delay before reconnection attempts to avoid rapid loops. - Enhanced webhook headers for better tracking and debugging. - Updated configuration to support manual Baileys version setting. --- .../whatsapp/whatsapp.baileys.service.ts | 66 ++++++++++++++++--- .../event/webhook/webhook.controller.ts | 13 +++- src/config/env.config.ts | 5 ++ src/utils/fetchLatestWaWebVersion.ts | 17 +++++ 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index b75e31a8..3b612d98 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -249,6 +249,7 @@ export class BaileysStartupService extends ChannelStartupService { private readonly msgRetryCounterCache: CacheStore = new NodeCache(); private readonly userDevicesCache: CacheStore = new NodeCache({ stdTTL: 300000, useClones: false }); private endSession = false; + private isDeleting = false; // Flag to prevent reconnection during deletion private logBaileys = this.configService.get('LOG').BAILEYS; private eventProcessingQueue: Promise = Promise.resolve(); @@ -265,10 +266,27 @@ export class BaileysStartupService extends ChannelStartupService { } public async logoutInstance() { - this.messageProcessor.onDestroy(); - await this.client?.logout('Log out instance: ' + this.instanceName); + // Mark instance as deleting to prevent reconnection attempts + this.isDeleting = true; + this.endSession = true; - this.client?.ws?.close(); + this.messageProcessor.onDestroy(); + + if (this.client) { + try { + await this.client.logout('Log out instance: ' + this.instanceName); + } catch (error) { + this.logger.error({ message: 'Error during logout', error }); + } + + // Improved socket cleanup + try { + this.client.ws?.close(); + this.client.end(new Error('Instance logout')); + } catch (error) { + this.logger.error({ message: 'Error during socket cleanup', error }); + } + } const db = this.configService.get('DATABASE'); const cache = this.configService.get('CACHE'); @@ -332,6 +350,18 @@ export class BaileysStartupService extends ChannelStartupService { } private async connectionUpdate({ qr, connection, lastDisconnect }: Partial) { + // Enhanced logging for connection updates + const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode; + this.logger.info({ + message: 'Connection update received', + connection, + hasQr: !!qr, + statusCode, + instanceName: this.instance.name, + isDeleting: this.isDeleting, + endSession: this.endSession, + }); + if (qr) { if (this.instance.qrcode.count === this.configService.get('QRCODE').LIMIT) { this.sendDataWebhook(Events.QRCODE_UPDATED, { @@ -424,11 +454,29 @@ export class BaileysStartupService extends ChannelStartupService { } if (connection === 'close') { + // Check if instance is being deleted or session is ending + if (this.isDeleting || this.endSession) { + this.logger.info('Instance is being deleted/ended, skipping reconnection attempt'); + return; + } + const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode; const codesToNotReconnect = [DisconnectReason.loggedOut, DisconnectReason.forbidden, 402, 406]; const shouldReconnect = !codesToNotReconnect.includes(statusCode); + + this.logger.info({ + message: 'Connection closed, evaluating reconnection', + statusCode, + shouldReconnect, + instanceName: this.instance.name, + }); + if (shouldReconnect) { - await this.connectToWhatsapp(this.phoneNumber); + // Add 3 second delay before reconnection to prevent rapid reconnection loops + this.logger.info('Reconnecting in 3 seconds...'); + setTimeout(async () => { + await this.connectToWhatsapp(this.phoneNumber); + }, 3000); } else { this.sendDataWebhook(Events.STATUS_INSTANCE, { instance: this.instance.name, @@ -591,10 +639,11 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.info(`Browser: ${browser}`); } + // Fetch latest WhatsApp Web version automatically const baileysVersion = await fetchLatestWaWebVersion({}); const version = baileysVersion.version; - const log = `Baileys version: ${version.join('.')}`; + const log = `Baileys version: ${version.join('.')}`; this.logger.info(log); this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`); @@ -602,7 +651,7 @@ export class BaileysStartupService extends ChannelStartupService { let options; if (this.localProxy?.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy?.host); + this.logger.verbose('Proxy enabled'); if (this.localProxy?.host?.includes('proxyscrape')) { try { @@ -611,9 +660,10 @@ export class BaileysStartupService extends ChannelStartupService { const proxyUrls = text.split('\r\n'); const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); const proxyUrl = 'http://' + proxyUrls[rand]; + this.logger.info('Proxy url: ' + proxyUrl); options = { agent: makeProxyAgent(proxyUrl), fetchAgent: makeProxyAgentUndici(proxyUrl) }; - } catch { - this.localProxy.enabled = false; + } catch (error) { + this.logger.error(error); } } else { options = { diff --git a/src/api/integrations/event/webhook/webhook.controller.ts b/src/api/integrations/event/webhook/webhook.controller.ts index 7f1dd8dc..5357554f 100644 --- a/src/api/integrations/event/webhook/webhook.controller.ts +++ b/src/api/integrations/event/webhook/webhook.controller.ts @@ -124,9 +124,20 @@ export class WebhookController extends EventController implements EventControlle try { if (instance?.enabled && regex.test(instance.url)) { + // Add custom headers for better webhook tracking and debugging + const enhancedHeaders = { + ...webhookHeaders, + 'Content-Type': 'application/json', + 'X-Instance-ID': this.monitor.waInstances[instanceName].instanceId, + 'X-Instance-Name': instanceName, + 'X-Event-Type': event, + 'X-Timestamp': Date.now().toString(), + 'User-Agent': 'EvolutionAPI-Webhook/2.3.7', + }; + const httpService = axios.create({ baseURL, - headers: webhookHeaders as Record | undefined, + headers: enhancedHeaders as Record, timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000, }); diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 7c4e382e..199ffb38 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -313,6 +313,7 @@ export type Webhook = { }; export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher }; export type ConfigSessionPhone = { CLIENT: string; NAME: string }; +export type Baileys = { VERSION?: string }; export type QrCode = { LIMIT: number; COLOR: string }; export type Typebot = { ENABLED: boolean; API_VERSION: string; SEND_MEDIA_BASE64: boolean }; export type Chatwoot = { @@ -410,6 +411,7 @@ export interface Env { WEBHOOK: Webhook; PUSHER: Pusher; CONFIG_SESSION_PHONE: ConfigSessionPhone; + BAILEYS: Baileys; QRCODE: QrCode; TYPEBOT: Typebot; CHATWOOT: Chatwoot; @@ -800,6 +802,9 @@ export class ConfigService { CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API', NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome', }, + BAILEYS: { + VERSION: process.env?.CONFIG_BAILEYS_VERSION, + }, QRCODE: { LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30, COLOR: process.env.QRCODE_COLOR || '#198754', diff --git a/src/utils/fetchLatestWaWebVersion.ts b/src/utils/fetchLatestWaWebVersion.ts index 6dcfb797..f6b0aa6d 100644 --- a/src/utils/fetchLatestWaWebVersion.ts +++ b/src/utils/fetchLatestWaWebVersion.ts @@ -1,7 +1,24 @@ import axios, { AxiosRequestConfig } from 'axios'; import { fetchLatestBaileysVersion, WAVersion } from 'baileys'; +import { Baileys, configService } from '../config/env.config'; + export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => { + // Check if manual version is set via configuration + const baileysConfig = configService.get('BAILEYS'); + const manualVersion = baileysConfig?.VERSION; + + if (manualVersion) { + const versionParts = manualVersion.split('.').map(Number); + if (versionParts.length === 3 && !versionParts.some(isNaN)) { + return { + version: versionParts as WAVersion, + isLatest: false, + isManual: true, + }; + } + } + try { const { data } = await axios.get('https://web.whatsapp.com/sw.js', { ...options,