import { InstanceDto } from '@api/dto/instance.dto'; import { ProviderFiles } from '@api/provider/sessions'; import { PrismaRepository } from '@api/repository/repository.service'; import { channelController } from '@api/server.module'; import { Events, Integration } from '@api/types/wa.types'; import { CacheConf, Chatwoot, ConfigService, Database, DelInstance, ProviderSession } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { INSTANCE_DIR, STORE_DIR } from '@config/path.config'; import { NotFoundException } from '@exceptions'; import { execSync } from 'child_process'; import EventEmitter2 from 'eventemitter2'; import { rmSync } from 'fs'; import { join } from 'path'; import { CacheService } from './cache.service'; export class WAMonitoringService { constructor( private readonly eventEmitter: EventEmitter2, private readonly configService: ConfigService, private readonly prismaRepository: PrismaRepository, private readonly providerFiles: ProviderFiles, private readonly cache: CacheService, private readonly chatwootCache: CacheService, private readonly baileysCache: CacheService, ) { this.removeInstance(); this.noConnection(); Object.assign(this.db, configService.get('DATABASE')); Object.assign(this.redis, configService.get('CACHE')); } private readonly db: Partial = {}; private readonly redis: Partial = {}; private readonly logger = new Logger('WAMonitoringService'); public readonly waInstances: Record = {}; private readonly providerSession = Object.freeze(this.configService.get('PROVIDER')); public delInstanceTime(instance: string) { const time = this.configService.get('DEL_INSTANCE'); if (typeof time === 'number' && time > 0) { setTimeout(async () => { if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') { if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) { await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); this.waInstances[instance]?.client?.ws?.close(); this.waInstances[instance]?.client?.end(undefined); } this.eventEmitter.emit('remove.instance', instance, 'inner'); } else { this.eventEmitter.emit('remove.instance', instance, 'inner'); } } }, 1000 * 60 * time); } } public async instanceInfo(instanceNames?: string[]): Promise { const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : []; if (inexistentInstances.length > 0) { throw new NotFoundException( `Instance${inexistentInstances.length > 1 ? 's' : ''} "${inexistentInstances.join(', ')}" not found`, ); } const clientName = this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; const where = instanceNames ? { name: { in: instanceNames, }, clientName, } : { clientName }; const instances = await this.prismaRepository.instance.findMany({ where, include: { Chatwoot: true, Proxy: true, Rabbitmq: true, Sqs: true, Websocket: true, Setting: true, _count: { select: { Message: true, Contact: true, Chat: true, }, }, }, }); return instances; } public async instanceInfoById(instanceId?: string, number?: string) { let instanceName: string; if (instanceId) { instanceName = await this.prismaRepository.instance.findFirst({ where: { id: instanceId } }).then((r) => r?.name); if (!instanceName) { throw new NotFoundException(`Instance "${instanceId}" not found`); } } else if (number) { instanceName = await this.prismaRepository.instance.findFirst({ where: { number } }).then((r) => r?.name); if (!instanceName) { throw new NotFoundException(`Instance "${number}" not found`); } } if (!instanceName) { throw new NotFoundException(`Instance "${instanceId}" not found`); } if (instanceName && !this.waInstances[instanceName]) { throw new NotFoundException(`Instance "${instanceName}" not found`); } return this.instanceInfo([instanceName]); } public async cleaningUp(instanceName: string) { let instanceDbId: string; if (this.db.SAVE_DATA.INSTANCE) { const findInstance = await this.prismaRepository.instance.findFirst({ where: { name: instanceName }, }); if (findInstance) { const instance = await this.prismaRepository.instance.update({ where: { name: instanceName }, data: { connectionStatus: 'close' }, }); rmSync(join(INSTANCE_DIR, instance.id), { recursive: true, force: true }); instanceDbId = instance.id; await this.prismaRepository.session.deleteMany({ where: { sessionId: instance.id } }); } } if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { await this.cache.delete(instanceName); if (instanceDbId) { await this.cache.delete(instanceDbId); } } if (this.providerSession?.ENABLED) { await this.providerFiles.removeSession(instanceName); } } public async cleaningStoreData(instanceName: string) { if (this.configService.get('CHATWOOT').ENABLED) { execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`); } const instance = await this.prismaRepository.instance.findFirst({ where: { name: instanceName }, }); if (!instance) return; rmSync(join(INSTANCE_DIR, instance.id), { recursive: true, force: true }); await this.prismaRepository.session.deleteMany({ where: { sessionId: instance.id } }); await this.prismaRepository.chat.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.contact.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.messageUpdate.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.message.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.webhook.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.chatwoot.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.proxy.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.rabbitmq.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.sqs.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.integrationSession.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.typebot.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.websocket.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.setting.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.label.deleteMany({ where: { instanceId: instance.id } }); await this.prismaRepository.instance.delete({ where: { name: instanceName } }); } public async loadInstance() { try { if (this.providerSession?.ENABLED) { await this.loadInstancesFromProvider(); } else if (this.db.SAVE_DATA.INSTANCE) { await this.loadInstancesFromDatabasePostgres(); } else if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { await this.loadInstancesFromRedis(); } } catch (error) { this.logger.error(error); } } public async saveInstance(data: any) { try { const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; await this.prismaRepository.instance.create({ data: { id: data.instanceId, name: data.instanceName, connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : data.status ?? 'open', number: data.number, integration: data.integration || Integration.WHATSAPP_BAILEYS, token: data.hash, clientName: clientName, businessId: data.businessId, }, }); } catch (error) { this.logger.error(error); } } public deleteInstance(instanceName: string) { try { this.eventEmitter.emit('remove.instance', instanceName, 'inner'); } catch (error) { this.logger.error(error); } } private async setInstance(instanceData: InstanceDto) { const instance = channelController.init(instanceData, { configService: this.configService, eventEmitter: this.eventEmitter, prismaRepository: this.prismaRepository, cache: this.cache, chatwootCache: this.chatwootCache, baileysCache: this.baileysCache, providerFiles: this.providerFiles, }); if (!instance) return; instance.setInstance({ instanceId: instanceData.instanceId, instanceName: instanceData.instanceName, integration: instanceData.integration, token: instanceData.token, number: instanceData.number, businessId: instanceData.businessId, }); await instance.connectToWhatsapp(); this.waInstances[instanceData.instanceName] = instance; } private async loadInstancesFromRedis() { const keys = await this.cache.keys(); if (keys?.length > 0) { await Promise.all( keys.map(async (k) => { const instanceData = await this.prismaRepository.instance.findUnique({ where: { id: k.split(':')[1] }, }); if (!instanceData) { return; } const instance = { instanceId: k.split(':')[1], instanceName: k.split(':')[2], integration: instanceData.integration, token: instanceData.token, number: instanceData.number, businessId: instanceData.businessId, }; this.setInstance(instance); }), ); } } private async loadInstancesFromDatabasePostgres() { const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; const instances = await this.prismaRepository.instance.findMany({ where: { clientName: clientName }, }); if (instances.length === 0) { return; } await Promise.all( instances.map(async (instance) => { this.setInstance({ instanceId: instance.id, instanceName: instance.name, integration: instance.integration, token: instance.token, number: instance.number, businessId: instance.businessId, }); }), ); } private async loadInstancesFromProvider() { const [instances] = await this.providerFiles.allInstances(); if (!instances?.data) { return; } await Promise.all( instances?.data?.map(async (instanceId: string) => { const instance = await this.prismaRepository.instance.findUnique({ where: { id: instanceId }, }); this.setInstance({ instanceId: instance.id, instanceName: instance.name, integration: instance.integration, token: instance.token, businessId: instance.businessId, }); }), ); } private removeInstance() { this.eventEmitter.on('remove.instance', async (instanceName: string) => { try { await this.waInstances[instanceName]?.sendDataWebhook(Events.REMOVE_INSTANCE, null); this.cleaningUp(instanceName); this.cleaningStoreData(instanceName); } finally { this.logger.warn(`Instance "${instanceName}" - REMOVED`); } try { delete this.waInstances[instanceName]; } catch (error) { this.logger.error(error); } }); this.eventEmitter.on('logout.instance', async (instanceName: string) => { try { await this.waInstances[instanceName]?.sendDataWebhook(Events.LOGOUT_INSTANCE, null); if (this.configService.get('CHATWOOT').ENABLED) { this.waInstances[instanceName]?.clearCacheChatwoot(); } this.cleaningUp(instanceName); } finally { this.logger.warn(`Instance "${instanceName}" - LOGOUT`); } }); } private noConnection() { this.eventEmitter.on('no.connection', async (instanceName) => { try { await this.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName); this.waInstances[instanceName]?.client?.ws?.close(); this.waInstances[instanceName].instance.qrcode = { count: 0 }; this.waInstances[instanceName].stateConnection.state = 'close'; } catch (error) { this.logger.error({ localError: 'noConnection', warn: 'Error deleting instance from memory.', error, }); } finally { this.logger.warn(`Instance "${instanceName}" - NOT CONNECTION`); } }); } }