mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-18 11:22:21 -06:00
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.
This commit is contained in:
@@ -249,6 +249,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
|
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
|
||||||
private readonly userDevicesCache: CacheStore = new NodeCache({ stdTTL: 300000, useClones: false });
|
private readonly userDevicesCache: CacheStore = new NodeCache({ stdTTL: 300000, useClones: false });
|
||||||
private endSession = false;
|
private endSession = false;
|
||||||
|
private isDeleting = false; // Flag to prevent reconnection during deletion
|
||||||
private logBaileys = this.configService.get<Log>('LOG').BAILEYS;
|
private logBaileys = this.configService.get<Log>('LOG').BAILEYS;
|
||||||
private eventProcessingQueue: Promise<void> = Promise.resolve();
|
private eventProcessingQueue: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
@@ -265,10 +266,27 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async logoutInstance() {
|
public async logoutInstance() {
|
||||||
this.messageProcessor.onDestroy();
|
// Mark instance as deleting to prevent reconnection attempts
|
||||||
await this.client?.logout('Log out instance: ' + this.instanceName);
|
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>('DATABASE');
|
const db = this.configService.get<Database>('DATABASE');
|
||||||
const cache = this.configService.get<CacheConf>('CACHE');
|
const cache = this.configService.get<CacheConf>('CACHE');
|
||||||
@@ -332,6 +350,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async connectionUpdate({ qr, connection, lastDisconnect }: Partial<ConnectionState>) {
|
private async connectionUpdate({ qr, connection, lastDisconnect }: Partial<ConnectionState>) {
|
||||||
|
// 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 (qr) {
|
||||||
if (this.instance.qrcode.count === this.configService.get<QrCode>('QRCODE').LIMIT) {
|
if (this.instance.qrcode.count === this.configService.get<QrCode>('QRCODE').LIMIT) {
|
||||||
this.sendDataWebhook(Events.QRCODE_UPDATED, {
|
this.sendDataWebhook(Events.QRCODE_UPDATED, {
|
||||||
@@ -424,11 +454,29 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connection === 'close') {
|
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 statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode;
|
||||||
const codesToNotReconnect = [DisconnectReason.loggedOut, DisconnectReason.forbidden, 402, 406];
|
const codesToNotReconnect = [DisconnectReason.loggedOut, DisconnectReason.forbidden, 402, 406];
|
||||||
const shouldReconnect = !codesToNotReconnect.includes(statusCode);
|
const shouldReconnect = !codesToNotReconnect.includes(statusCode);
|
||||||
|
|
||||||
|
this.logger.info({
|
||||||
|
message: 'Connection closed, evaluating reconnection',
|
||||||
|
statusCode,
|
||||||
|
shouldReconnect,
|
||||||
|
instanceName: this.instance.name,
|
||||||
|
});
|
||||||
|
|
||||||
if (shouldReconnect) {
|
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 {
|
} else {
|
||||||
this.sendDataWebhook(Events.STATUS_INSTANCE, {
|
this.sendDataWebhook(Events.STATUS_INSTANCE, {
|
||||||
instance: this.instance.name,
|
instance: this.instance.name,
|
||||||
@@ -591,10 +639,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
this.logger.info(`Browser: ${browser}`);
|
this.logger.info(`Browser: ${browser}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch latest WhatsApp Web version automatically
|
||||||
const baileysVersion = await fetchLatestWaWebVersion({});
|
const baileysVersion = await fetchLatestWaWebVersion({});
|
||||||
const version = baileysVersion.version;
|
const version = baileysVersion.version;
|
||||||
const log = `Baileys version: ${version.join('.')}`;
|
|
||||||
|
|
||||||
|
const log = `Baileys version: ${version.join('.')}`;
|
||||||
this.logger.info(log);
|
this.logger.info(log);
|
||||||
|
|
||||||
this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`);
|
this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`);
|
||||||
@@ -602,7 +651,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
let options;
|
let options;
|
||||||
|
|
||||||
if (this.localProxy?.enabled) {
|
if (this.localProxy?.enabled) {
|
||||||
this.logger.info('Proxy enabled: ' + this.localProxy?.host);
|
this.logger.verbose('Proxy enabled');
|
||||||
|
|
||||||
if (this.localProxy?.host?.includes('proxyscrape')) {
|
if (this.localProxy?.host?.includes('proxyscrape')) {
|
||||||
try {
|
try {
|
||||||
@@ -611,9 +660,10 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
const proxyUrls = text.split('\r\n');
|
const proxyUrls = text.split('\r\n');
|
||||||
const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
|
const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
|
||||||
const proxyUrl = 'http://' + proxyUrls[rand];
|
const proxyUrl = 'http://' + proxyUrls[rand];
|
||||||
|
this.logger.info('Proxy url: ' + proxyUrl);
|
||||||
options = { agent: makeProxyAgent(proxyUrl), fetchAgent: makeProxyAgentUndici(proxyUrl) };
|
options = { agent: makeProxyAgent(proxyUrl), fetchAgent: makeProxyAgentUndici(proxyUrl) };
|
||||||
} catch {
|
} catch (error) {
|
||||||
this.localProxy.enabled = false;
|
this.logger.error(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
options = {
|
options = {
|
||||||
|
|||||||
@@ -124,9 +124,20 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (instance?.enabled && regex.test(instance.url)) {
|
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({
|
const httpService = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
headers: webhookHeaders as Record<string, string> | undefined,
|
headers: enhancedHeaders as Record<string, string>,
|
||||||
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
|
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ export type Webhook = {
|
|||||||
};
|
};
|
||||||
export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher };
|
export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher };
|
||||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||||
|
export type Baileys = { VERSION?: string };
|
||||||
export type QrCode = { LIMIT: number; COLOR: string };
|
export type QrCode = { LIMIT: number; COLOR: string };
|
||||||
export type Typebot = { ENABLED: boolean; API_VERSION: string; SEND_MEDIA_BASE64: boolean };
|
export type Typebot = { ENABLED: boolean; API_VERSION: string; SEND_MEDIA_BASE64: boolean };
|
||||||
export type Chatwoot = {
|
export type Chatwoot = {
|
||||||
@@ -410,6 +411,7 @@ export interface Env {
|
|||||||
WEBHOOK: Webhook;
|
WEBHOOK: Webhook;
|
||||||
PUSHER: Pusher;
|
PUSHER: Pusher;
|
||||||
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
||||||
|
BAILEYS: Baileys;
|
||||||
QRCODE: QrCode;
|
QRCODE: QrCode;
|
||||||
TYPEBOT: Typebot;
|
TYPEBOT: Typebot;
|
||||||
CHATWOOT: Chatwoot;
|
CHATWOOT: Chatwoot;
|
||||||
@@ -800,6 +802,9 @@ export class ConfigService {
|
|||||||
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
|
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
|
||||||
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
|
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
|
||||||
},
|
},
|
||||||
|
BAILEYS: {
|
||||||
|
VERSION: process.env?.CONFIG_BAILEYS_VERSION,
|
||||||
|
},
|
||||||
QRCODE: {
|
QRCODE: {
|
||||||
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
|
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
|
||||||
COLOR: process.env.QRCODE_COLOR || '#198754',
|
COLOR: process.env.QRCODE_COLOR || '#198754',
|
||||||
|
|||||||
@@ -1,7 +1,24 @@
|
|||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import { fetchLatestBaileysVersion, WAVersion } from 'baileys';
|
import { fetchLatestBaileysVersion, WAVersion } from 'baileys';
|
||||||
|
|
||||||
|
import { Baileys, configService } from '../config/env.config';
|
||||||
|
|
||||||
export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => {
|
export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => {
|
||||||
|
// Check if manual version is set via configuration
|
||||||
|
const baileysConfig = configService.get<Baileys>('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 {
|
try {
|
||||||
const { data } = await axios.get('https://web.whatsapp.com/sw.js', {
|
const { data } = await axios.get('https://web.whatsapp.com/sw.js', {
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
Reference in New Issue
Block a user