--- description: Channel integration patterns for Evolution API globs: - "src/api/integrations/channel/**/*.ts" alwaysApply: false --- # Evolution API Channel Integration Rules ## Channel Controller Pattern ### Base Channel Controller ```typescript export interface ChannelControllerInterface { integrationEnabled: boolean; } export class ChannelController { public prismaRepository: PrismaRepository; public waMonitor: WAMonitoringService; constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) { this.prisma = prismaRepository; this.monitor = waMonitor; } public set prisma(prisma: PrismaRepository) { this.prismaRepository = prisma; } public get prisma() { return this.prismaRepository; } public set monitor(waMonitor: WAMonitoringService) { this.waMonitor = waMonitor; } public get monitor() { return this.waMonitor; } public init(instanceData: InstanceDto, data: ChannelDataType) { if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) { throw new BadRequestException('token is required'); } if (instanceData.integration === Integration.WHATSAPP_BUSINESS) { return new BusinessStartupService(/* dependencies */); } if (instanceData.integration === Integration.WHATSAPP_BAILEYS) { return new BaileysStartupService(/* dependencies */); } return null; } } ``` ## Channel Service Pattern ### Base Channel Service ```typescript export class ChannelStartupService { constructor( private readonly configService: ConfigService, private readonly eventEmitter: EventEmitter2, private readonly prismaRepository: PrismaRepository, public readonly cache: CacheService, public readonly chatwootCache: CacheService, ) {} public readonly logger = new Logger('ChannelStartupService'); public client: WASocket; public readonly instance: wa.Instance = {}; public readonly localChatwoot: wa.LocalChatwoot = {}; public readonly localProxy: wa.LocalProxy = {}; public readonly localSettings: wa.LocalSettings = {}; public readonly localWebhook: wa.LocalWebHook = {}; public setInstance(instance: InstanceDto) { this.logger.setInstance(instance.instanceName); this.instance.name = instance.instanceName; this.instance.id = instance.instanceId; this.instance.integration = instance.integration; this.instance.number = instance.number; this.instance.token = instance.token; this.instance.businessId = instance.businessId; if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { this.chatwootService.eventWhatsapp( Events.STATUS_INSTANCE, { instanceName: this.instance.name }, { instance: this.instance.name, status: 'created', }, ); } } public set instanceName(name: string) { this.logger.setInstance(name); this.instance.name = name; } public get instanceName() { return this.instance.name; } } ``` ## Business API Service Pattern ### Meta Business Service ```typescript export class BusinessStartupService extends ChannelStartupService { constructor( configService: ConfigService, eventEmitter: EventEmitter2, prismaRepository: PrismaRepository, cache: CacheService, chatwootCache: CacheService, baileysCache: CacheService, providerFiles: ProviderFiles, ) { super(configService, eventEmitter, prismaRepository, cache, chatwootCache); } public async sendMessage(data: SendTextDto): Promise { const businessConfig = this.configService.get('WA_BUSINESS'); const payload = { messaging_product: 'whatsapp', to: data.number, type: 'text', text: { body: data.text, }, }; try { const response = await axios.post( `${businessConfig.URL}/${businessConfig.VERSION}/${this.instance.businessId}/messages`, payload, { headers: { 'Authorization': `Bearer ${this.instance.token}`, 'Content-Type': 'application/json', }, } ); return response.data; } catch (error) { this.logger.error(`Business API call failed: ${error.message}`); throw new BadRequestException('Failed to send message via Business API'); } } public async receiveWebhook(data: any): Promise { // Process incoming webhook from Meta Business API const { entry } = data; for (const entryItem of entry) { const { changes } = entryItem; for (const change of changes) { if (change.field === 'messages') { await this.processMessage(change.value); } } } } private async processMessage(messageData: any): Promise { // Process incoming message from Business API const { messages, contacts } = messageData; if (messages) { for (const message of messages) { await this.handleIncomingMessage(message, contacts); } } } private async handleIncomingMessage(message: any, contacts: any[]): Promise { // Handle individual message const contact = contacts?.find(c => c.wa_id === message.from); // Emit event for message processing this.eventEmitter.emit(Events.MESSAGES_UPSERT, { instanceName: this.instance.name, message, contact, }); } } ``` ## Baileys Service Pattern ### Baileys Integration Service ```typescript export class BaileysStartupService extends ChannelStartupService { constructor( configService: ConfigService, eventEmitter: EventEmitter2, prismaRepository: PrismaRepository, cache: CacheService, chatwootCache: CacheService, baileysCache: CacheService, providerFiles: ProviderFiles, ) { super(configService, eventEmitter, prismaRepository, cache, chatwootCache); } public async connectToWhatsapp(): Promise { const authPath = path.join(INSTANCE_DIR, this.instance.name); const { state, saveCreds } = await useMultiFileAuthState(authPath); this.client = makeWASocket({ auth: state, logger: P({ level: 'error' }), printQRInTerminal: false, browser: ['Evolution API', 'Chrome', '4.0.0'], defaultQueryTimeoutMs: 60000, }); this.setupEventHandlers(); this.client.ev.on('creds.update', saveCreds); } private setupEventHandlers(): void { this.client.ev.on('connection.update', (update) => { this.handleConnectionUpdate(update); }); this.client.ev.on('messages.upsert', ({ messages, type }) => { this.handleIncomingMessages(messages, type); }); this.client.ev.on('messages.update', (updates) => { this.handleMessageUpdates(updates); }); this.client.ev.on('contacts.upsert', (contacts) => { this.handleContactsUpdate(contacts); }); this.client.ev.on('chats.upsert', (chats) => { this.handleChatsUpdate(chats); }); } private async handleConnectionUpdate(update: ConnectionUpdate): Promise { const { connection, lastDisconnect, qr } = update; if (qr) { this.instance.qrcode = { count: this.instance.qrcode?.count ? this.instance.qrcode.count + 1 : 1, base64: qr, }; this.eventEmitter.emit(Events.QRCODE_UPDATED, { instanceName: this.instance.name, qrcode: this.instance.qrcode, }); } if (connection === 'close') { const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; if (shouldReconnect) { this.logger.log('Connection closed, reconnecting...'); await this.connectToWhatsapp(); } else { this.logger.log('Connection closed, logged out'); this.eventEmitter.emit(Events.LOGOUT_INSTANCE, { instanceName: this.instance.name, }); } } if (connection === 'open') { this.logger.log('Connection opened successfully'); this.instance.wuid = this.client.user?.id; this.eventEmitter.emit(Events.CONNECTION_UPDATE, { instanceName: this.instance.name, state: 'open', }); } } public async sendMessage(data: SendTextDto): Promise { const jid = createJid(data.number); const message = { text: data.text, }; if (data.linkPreview !== undefined) { message.linkPreview = data.linkPreview; } if (data.mentionsEveryOne) { // Handle mentions } try { const response = await this.client.sendMessage(jid, message); return response; } catch (error) { this.logger.error(`Failed to send message: ${error.message}`); throw new BadRequestException('Failed to send message'); } } } ``` ## Channel Router Pattern ### Channel Router Structure ```typescript export class ChannelRouter { public readonly router: Router; constructor(configService: any, ...guards: any[]) { this.router = Router(); this.router.use('/', new MetaRouter(configService).router); this.router.use('/baileys', new BaileysRouter(...guards).router); } } ``` ## Integration Types ### Channel Data Types ```typescript type ChannelDataType = { configService: ConfigService; eventEmitter: EventEmitter2; prismaRepository: PrismaRepository; cache: CacheService; chatwootCache: CacheService; baileysCache: CacheService; providerFiles: ProviderFiles; }; export enum Integration { WHATSAPP_BUSINESS = 'WHATSAPP-BUSINESS', WHATSAPP_BAILEYS = 'WHATSAPP-BAILEYS', } ``` ## Error Handling in Channels ### Channel-Specific Error Handling ```typescript // CORRECT - Channel-specific error handling public async sendMessage(data: SendTextDto): Promise { try { const response = await this.channelSpecificSend(data); return response; } catch (error) { this.logger.error(`${this.constructor.name} send failed: ${error.message}`); if (error.response?.status === 401) { throw new UnauthorizedException('Invalid token for channel'); } if (error.response?.status === 429) { throw new BadRequestException('Rate limit exceeded'); } throw new InternalServerErrorException('Channel communication failed'); } } ``` ## Channel Testing Pattern Tests for channel services should mock dependencies and verify behavior for message sending, connection handling, and error cases.