diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 6a6910688..3c8476043 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -72,15 +72,36 @@ export class InstanceController { status: instanceData.status, }); - instance.setInstance({ - instanceName: instanceData.instanceName, - instanceId, - integration: instanceData.integration, - token: hash, - number: instanceData.number, - businessId: instanceData.businessId, + // Verifica se a Instance foi criada com sucesso antes de continuar + const createdInstance = await this.prismaRepository.instance.findUnique({ + where: { id: instanceId }, }); + if (!createdInstance) { + throw new BadRequestException('Failed to create instance in database'); + } + + // Para WhatsApp Business, setInstance é async e precisa ser aguardado + if (instanceData.integration === Integration.WHATSAPP_BUSINESS) { + await (instance as any).setInstance({ + instanceName: instanceData.instanceName, + instanceId, + integration: instanceData.integration, + token: instanceData.token || hash, // Usa o token original completo + number: instanceData.number, + businessId: instanceData.businessId, + }); + } else { + instance.setInstance({ + instanceName: instanceData.instanceName, + instanceId, + integration: instanceData.integration, + token: hash, + number: instanceData.number, + businessId: instanceData.businessId, + }); + } + this.waMonitor.waInstances[instance.instanceName] = instance; this.waMonitor.delInstanceTime(instance.instanceName); diff --git a/src/api/guards/auth.guard.ts b/src/api/guards/auth.guard.ts index 9ad20b617..87b378a64 100644 --- a/src/api/guards/auth.guard.ts +++ b/src/api/guards/auth.guard.ts @@ -1,5 +1,6 @@ import { InstanceDto } from '@api/dto/instance.dto'; -import { prismaRepository } from '@api/server.module'; +import { cache, prismaRepository, waMonitor } from '@api/server.module'; +import { Integration } from '@api/types/wa.types'; import { Auth, configService, Database } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { ForbiddenException, UnauthorizedException } from '@exceptions'; @@ -30,15 +31,69 @@ async function apikey(req: Request, _: Response, next: NextFunction) { const instance = await prismaRepository.instance.findUnique({ where: { name: param.instanceName }, }); - if (instance.token === key) { + const keyToCompare = key.length > 255 ? key.substring(0, 255) : key; + if (instance.token === keyToCompare) { + // Se o token fornecido é maior que 255 e a instância é WhatsApp Business, salva no cache + if (key.length > 255 && instance.integration === Integration.WHATSAPP_BUSINESS) { + const cacheKey = `instance:${param.instanceName}:fullToken`; + await cache.set(cacheKey, key, 0); + logger.log({ message: 'Stored full token in cache from request', instanceName: param.instanceName }); + + // Atualiza a instância em memória se existir + if (waMonitor.waInstances[param.instanceName]) { + const waInstance = waMonitor.waInstances[param.instanceName]; + if (waInstance && typeof (waInstance as any).setInstance === 'function') { + try { + await (waInstance as any).setInstance({ + instanceName: param.instanceName, + instanceId: instance.id, + integration: instance.integration, + token: key, + number: instance.number, + businessId: instance.businessId, + }); + logger.log({ message: 'Updated full token in memory', instanceName: param.instanceName }); + } catch (error) { + logger.error({ message: 'Error updating token in memory', error, instanceName: param.instanceName }); + } + } + } + } return next(); } } else { if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) { + const keyToCompare = key.length > 255 ? key.substring(0, 255) : key; const instanceByKey = await prismaRepository.instance.findFirst({ - where: { token: key }, + where: { token: keyToCompare }, }); if (instanceByKey) { + // Se o token fornecido é maior que 255 e a instância é WhatsApp Business, salva no cache + if (key.length > 255 && instanceByKey.integration === Integration.WHATSAPP_BUSINESS) { + const cacheKey = `instance:${instanceByKey.name}:fullToken`; + await cache.set(cacheKey, key, 0); + logger.log({ message: 'Stored full token in cache from request', instanceName: instanceByKey.name }); + + // Atualiza a instância em memória se existir + if (waMonitor.waInstances[instanceByKey.name]) { + const waInstance = waMonitor.waInstances[instanceByKey.name]; + if (waInstance && typeof (waInstance as any).setInstance === 'function') { + try { + await (waInstance as any).setInstance({ + instanceName: instanceByKey.name, + instanceId: instanceByKey.id, + integration: instanceByKey.integration, + token: key, + number: instanceByKey.number, + businessId: instanceByKey.businessId, + }); + logger.log({ message: 'Updated full token in memory', instanceName: instanceByKey.name }); + } catch (error) { + logger.error({ message: 'Error updating token in memory', error, instanceName: instanceByKey.name }); + } + } + } + } return next(); } } diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index 1e4808c15..26d7351c8 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -45,11 +45,44 @@ export class BusinessStartupService extends ChannelStartupService { super(configService, eventEmitter, prismaRepository, chatwootCache); } + private fullToken: string | null = null; + public stateConnection: wa.StateConnection = { state: 'open' }; public phoneNumber: string; public mobile: boolean; + // Override token getter para retornar token completo se disponível + public get token(): string { + return this.fullToken || this.instance.token || ''; + } + + // Override setInstance para armazenar/carregar token completo + public async setInstance(instance: any) { + super.setInstance(instance); + + // Se o token fornecido é maior que 255, é o token completo - armazena imediatamente + if (instance.token && instance.token.length > 255) { + this.fullToken = instance.token; + const cacheKey = `instance:${instance.instanceName}:fullToken`; + await this.cache.set(cacheKey, instance.token, 0); + this.logger.log({ message: 'Stored full token in cache', instanceName: instance.instanceName }); + } else { + // Tenta carregar token completo do cache + const cacheKey = `instance:${instance.instanceName}:fullToken`; + const fullToken = await this.cache.get(cacheKey); + if (fullToken) { + this.fullToken = fullToken; + this.logger.log({ message: 'Loaded full token from cache', instanceName: instance.instanceName }); + } else { + this.logger.warn({ + message: 'Full token not found in cache, using truncated token', + instanceName: instance.instanceName, + }); + } + } + } + public get connectionStatus() { return this.stateConnection; } diff --git a/src/api/integrations/event/websocket/websocket.controller.ts b/src/api/integrations/event/websocket/websocket.controller.ts index 3c763f08d..c96c5b2a2 100644 --- a/src/api/integrations/event/websocket/websocket.controller.ts +++ b/src/api/integrations/event/websocket/websocket.controller.ts @@ -52,7 +52,8 @@ export class WebsocketController extends EventController implements EventControl return callback('apiKey is required', false); } - const instance = await this.prismaRepository.instance.findFirst({ where: { token: apiKey } }); + const keyToCompare = apiKey.length > 255 ? apiKey.substring(0, 255) : apiKey; + const instance = await this.prismaRepository.instance.findFirst({ where: { token: keyToCompare } }); if (!instance) { const globalToken = configService.get('AUTHENTICATION').API_KEY.KEY; diff --git a/src/api/services/auth.service.ts b/src/api/services/auth.service.ts index 3a7825f80..f7d4a1853 100644 --- a/src/api/services/auth.service.ts +++ b/src/api/services/auth.service.ts @@ -9,8 +9,10 @@ export class AuthService { return true; } + // Compara apenas os primeiros 255 caracteres para verificar duplicatas + const tokenToCompare = token.length > 255 ? token.substring(0, 255) : token; const instances = await this.prismaRepository.instance.findMany({ - where: { token }, + where: { token: tokenToCompare }, }); if (instances.length > 0) { diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 56bec0802..588f6d624 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -157,41 +157,49 @@ export class ChannelStartupService { } public async setSettings(data: SettingsDto) { + const truncate = (str: string | null | undefined, maxLength: number): string | null => { + if (!str) return null; + return str.length > maxLength ? str.substring(0, maxLength) : str; + }; + + const msgCall = truncate(data.msgCall, 100); + const wavoipToken = truncate(data.wavoipToken, 100); + await this.prismaRepository.setting.upsert({ where: { instanceId: this.instanceId, }, update: { rejectCall: data.rejectCall, - msgCall: data.msgCall, + msgCall: msgCall, groupsIgnore: data.groupsIgnore, alwaysOnline: data.alwaysOnline, readMessages: data.readMessages, readStatus: data.readStatus, syncFullHistory: data.syncFullHistory, - wavoipToken: data.wavoipToken, + wavoipToken: wavoipToken, }, create: { rejectCall: data.rejectCall, - msgCall: data.msgCall, + msgCall: msgCall, groupsIgnore: data.groupsIgnore, alwaysOnline: data.alwaysOnline, readMessages: data.readMessages, readStatus: data.readStatus, syncFullHistory: data.syncFullHistory, - wavoipToken: data.wavoipToken, + wavoipToken: wavoipToken, instanceId: this.instanceId, }, }); this.localSettings.rejectCall = data?.rejectCall; - this.localSettings.msgCall = data?.msgCall; + this.localSettings.msgCall = msgCall; this.localSettings.groupsIgnore = data?.groupsIgnore; this.localSettings.alwaysOnline = data?.alwaysOnline; this.localSettings.readMessages = data?.readMessages; this.localSettings.readStatus = data?.readStatus; this.localSettings.syncFullHistory = data?.syncFullHistory; - this.localSettings.wavoipToken = data?.wavoipToken; + this.localSettings.wavoipToken = wavoipToken; if (this.localSettings.wavoipToken && this.localSettings.wavoipToken.length > 0) { this.client.ws.close(); diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index 438530b57..d5e484432 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -239,26 +239,61 @@ export class WAMonitoringService { } public async saveInstance(data: any) { + const truncate = (str: string | null | undefined, maxLength: number): string | null => { + if (!str) return null; + return str.length > maxLength ? str.substring(0, maxLength) : str; + }; + try { - const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; + const instanceName = truncate(data.instanceName, 255); + if (!instanceName || instanceName.trim().length === 0) { + throw new Error('instanceName is required and cannot be empty'); + } + + const clientName = this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; + const clientNameTruncated = truncate(clientName, 100); + + // Validação adicional para garantir que todos os campos estão dentro dos limites + const instanceData = { + id: data.instanceId, + name: instanceName, + ownerJid: truncate(data.ownerJid, 100), + profileName: truncate(data.profileName, 100), + profilePicUrl: truncate(data.profilePicUrl, 500), + connectionStatus: + data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : (data.status ?? 'open'), + number: truncate(data.number, 100), + integration: truncate(data.integration || Integration.WHATSAPP_BAILEYS, 100), + token: truncate(data.hash, 255), + clientName: clientNameTruncated, + businessId: truncate(data.businessId, 100), + }; + + this.logger.log({ + message: 'Creating instance in database', + instanceName: instanceName, + instanceId: data.instanceId, + integration: instanceData.integration, + tokenLength: instanceData.token?.length || 0, + }); + await this.prismaRepository.instance.create({ - data: { - id: data.instanceId, - name: data.instanceName, - ownerJid: data.ownerJid, - profileName: data.profileName, - profilePicUrl: data.profilePicUrl, - 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, - }, + data: instanceData, + }); + + this.logger.log({ + message: 'Instance created successfully in database', + instanceName: instanceName, + instanceId: data.instanceId, }); } catch (error) { - this.logger.error(error); + this.logger.error({ + message: 'Error creating instance in database', + error: error instanceof Error ? error.message : String(error), + instanceName: data.instanceName, + instanceId: data.instanceId, + }); + throw error; } } @@ -283,15 +318,31 @@ export class WAMonitoringService { if (!instance) return; - instance.setInstance({ - instanceId: instanceData.instanceId, - instanceName: instanceData.instanceName, - integration: instanceData.integration, - token: instanceData.token, - number: instanceData.number, - businessId: instanceData.businessId, - ownerJid: instanceData.ownerJid, - }); + // Para WhatsApp Business, setInstance é async e precisa ser aguardado + if (instanceData.integration === Integration.WHATSAPP_BUSINESS) { + const setInstanceResult = (instance as any).setInstance({ + instanceId: instanceData.instanceId, + instanceName: instanceData.instanceName, + integration: instanceData.integration, + token: instanceData.token, + number: instanceData.number, + businessId: instanceData.businessId, + ownerJid: instanceData.ownerJid, + }); + if (setInstanceResult instanceof Promise) { + await setInstanceResult; + } + } else { + instance.setInstance({ + instanceId: instanceData.instanceId, + instanceName: instanceData.instanceName, + integration: instanceData.integration, + token: instanceData.token, + number: instanceData.number, + businessId: instanceData.businessId, + ownerJid: instanceData.ownerJid, + }); + } if (instanceData.connectionStatus === 'open' || instanceData.connectionStatus === 'connecting') { this.logger.info( @@ -321,11 +372,21 @@ export class WAMonitoringService { return; } + // Para WhatsApp Business, tenta carregar token completo do cache + let token = instanceData.token; + if (instanceData.integration === Integration.WHATSAPP_BUSINESS) { + const cacheKey = `instance:${instanceData.name}:fullToken`; + const fullToken = await this.cache.get(cacheKey); + if (fullToken) { + token = fullToken; + } + } + const instance = { instanceId: k.split(':')[1], instanceName: k.split(':')[2], integration: instanceData.integration, - token: instanceData.token, + token: token, number: instanceData.number, businessId: instanceData.businessId, connectionStatus: instanceData.connectionStatus as any, // Pass connection status @@ -350,11 +411,21 @@ export class WAMonitoringService { await Promise.all( instances.map(async (instance) => { + // Para WhatsApp Business, tenta carregar token completo do cache + let token = instance.token; + if (instance.integration === Integration.WHATSAPP_BUSINESS) { + const cacheKey = `instance:${instance.name}:fullToken`; + const fullToken = await this.cache.get(cacheKey); + if (fullToken) { + token = fullToken; + } + } + this.setInstance({ instanceId: instance.id, instanceName: instance.name, integration: instance.integration, - token: instance.token, + token: token, number: instance.number, businessId: instance.businessId, ownerJid: instance.ownerJid, @@ -377,11 +448,21 @@ export class WAMonitoringService { where: { id: instanceId }, }); + // Para WhatsApp Business, tenta carregar token completo do cache + let token = instance.token; + if (instance.integration === Integration.WHATSAPP_BUSINESS) { + const cacheKey = `instance:${instance.name}:fullToken`; + const fullToken = await this.cache.get(cacheKey); + if (fullToken) { + token = fullToken; + } + } + this.setInstance({ instanceId: instance.id, instanceName: instance.name, integration: instance.integration, - token: instance.token, + token: token, businessId: instance.businessId, connectionStatus: instance.connectionStatus as any, // Pass connection status });