Files
evolution-api/.cursor/rules/specialized-rules/integration-channel-rules.mdc
Claude f7f8bf1182 refactor: remove Evolution Channel provider integration
Remove the Evolution Channel provider from WhatsApp integrations as it is no longer needed.
Only Baileys and Meta Business API providers are now supported.

Changes:
- Deleted Evolution Channel service, controller, and router files
- Updated ChannelController factory to remove Evolution provider case
- Removed Integration.EVOLUTION from type definitions
- Updated channel router to remove Evolution routes
- Cleaned up imports and exports in server.module.ts
- Updated documentation (CLAUDE.md, AGENTS.md, integration-channel-rules.mdc)

Note: EvolutionBot chatbot integration remains unchanged (different component)
2025-11-09 06:24:09 +00:00

388 lines
10 KiB
Plaintext

---
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>('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<any> {
const businessConfig = this.configService.get<WaBusiness>('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<void> {
// 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<void> {
// 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<void> {
// 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<void> {
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<void> {
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<any> {
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<any> {
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.