mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-16 04:02:54 -06:00
264 lines
7.6 KiB
TypeScript
264 lines
7.6 KiB
TypeScript
import { EventDto } from '@api/integrations/event/event.dto';
|
|
import { PrismaRepository } from '@api/repository/repository.service';
|
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
|
import { wa } from '@api/types/wa.types';
|
|
import { configService, Log, Webhook } from '@config/env.config';
|
|
import { Logger } from '@config/logger.config';
|
|
import { BadRequestException } from '@exceptions';
|
|
import axios, { AxiosInstance } from 'axios';
|
|
import * as jwt from 'jsonwebtoken';
|
|
|
|
import { EmitData, EventController, EventControllerInterface } from '../event.controller';
|
|
|
|
export class WebhookController extends EventController implements EventControllerInterface {
|
|
private readonly logger = new Logger('WebhookController');
|
|
|
|
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
|
|
super(prismaRepository, waMonitor, true, 'webhook');
|
|
}
|
|
|
|
override async set(instanceName: string, data: EventDto): Promise<wa.LocalWebHook> {
|
|
if (!/^(https?:\/\/)/.test(data.webhook.url)) {
|
|
throw new BadRequestException('Invalid "url" property');
|
|
}
|
|
|
|
if (!data.webhook?.enabled) {
|
|
data.webhook.events = [];
|
|
} else {
|
|
if (0 === data.webhook.events.length) {
|
|
data.webhook.events = EventController.events;
|
|
}
|
|
}
|
|
|
|
return this.prisma.webhook.upsert({
|
|
where: {
|
|
instanceId: this.monitor.waInstances[instanceName].instanceId,
|
|
},
|
|
update: {
|
|
enabled: data.webhook?.enabled,
|
|
events: data.webhook?.events,
|
|
url: data.webhook?.url,
|
|
headers: data.webhook?.headers,
|
|
webhookBase64: data.webhook.base64,
|
|
webhookByEvents: data.webhook.byEvents,
|
|
},
|
|
create: {
|
|
enabled: data.webhook?.enabled,
|
|
events: data.webhook?.events,
|
|
instanceId: this.monitor.waInstances[instanceName].instanceId,
|
|
url: data.webhook?.url,
|
|
headers: data.webhook?.headers,
|
|
webhookBase64: data.webhook.base64,
|
|
webhookByEvents: data.webhook.byEvents,
|
|
},
|
|
});
|
|
}
|
|
|
|
public async emit({
|
|
instanceName,
|
|
origin,
|
|
event,
|
|
data,
|
|
serverUrl,
|
|
dateTime,
|
|
sender,
|
|
apiKey,
|
|
local,
|
|
integration,
|
|
}: EmitData): Promise<void> {
|
|
if (integration && !integration.includes('webhook')) {
|
|
return;
|
|
}
|
|
|
|
const instance = (await this.get(instanceName)) as wa.LocalWebHook;
|
|
|
|
const webhookConfig = configService.get<Webhook>('WEBHOOK');
|
|
const webhookLocal = instance?.events;
|
|
const webhookHeaders = { ...((instance?.headers as Record<string, string>) || {}) };
|
|
|
|
if (webhookHeaders && 'jwt_key' in webhookHeaders) {
|
|
const jwtKey = webhookHeaders['jwt_key'];
|
|
const jwtToken = this.generateJwtToken(jwtKey);
|
|
webhookHeaders['Authorization'] = `Bearer ${jwtToken}`;
|
|
|
|
delete webhookHeaders['jwt_key'];
|
|
}
|
|
|
|
const we = event.replace(/[.-]/gm, '_').toUpperCase();
|
|
const transformedWe = we.replace(/_/gm, '-').toLowerCase();
|
|
const enabledLog = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
|
const regex = /^(https?:\/\/)/;
|
|
|
|
const webhookData = {
|
|
event,
|
|
instance: instanceName,
|
|
data,
|
|
destination: instance?.url || `${webhookConfig.GLOBAL.URL}/${transformedWe}`,
|
|
date_time: dateTime,
|
|
sender,
|
|
server_url: serverUrl,
|
|
apikey: apiKey,
|
|
};
|
|
|
|
if (local && instance?.enabled) {
|
|
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
|
|
let baseURL: string;
|
|
|
|
if (instance?.webhookByEvents) {
|
|
baseURL = `${instance?.url}/${transformedWe}`;
|
|
} else {
|
|
baseURL = instance?.url;
|
|
}
|
|
|
|
if (enabledLog) {
|
|
const logData = {
|
|
local: `${origin}.sendData-Webhook`,
|
|
url: baseURL,
|
|
...webhookData,
|
|
};
|
|
|
|
this.logger.log(logData);
|
|
}
|
|
|
|
try {
|
|
if (instance?.enabled && regex.test(instance.url)) {
|
|
const httpService = axios.create({
|
|
baseURL,
|
|
headers: webhookHeaders as Record<string, string> | undefined,
|
|
});
|
|
|
|
await this.retryWebhookRequest(httpService, webhookData, `${origin}.sendData-Webhook`, baseURL, serverUrl);
|
|
}
|
|
} catch (error) {
|
|
this.logger.error({
|
|
local: `${origin}.sendData-Webhook`,
|
|
message: `Todas as tentativas falharam: ${error?.message}`,
|
|
hostName: error?.hostname,
|
|
syscall: error?.syscall,
|
|
code: error?.code,
|
|
error: error?.errno,
|
|
stack: error?.stack,
|
|
name: error?.name,
|
|
url: baseURL,
|
|
server_url: serverUrl,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (webhookConfig.GLOBAL?.ENABLED) {
|
|
if (webhookConfig.EVENTS[we]) {
|
|
let globalURL = webhookConfig.GLOBAL.URL;
|
|
|
|
if (webhookConfig.GLOBAL.WEBHOOK_BY_EVENTS) {
|
|
globalURL = `${globalURL}/${transformedWe}`;
|
|
}
|
|
|
|
if (enabledLog) {
|
|
const logData = {
|
|
local: `${origin}.sendData-Webhook-Global`,
|
|
url: globalURL,
|
|
...webhookData,
|
|
};
|
|
|
|
this.logger.log(logData);
|
|
}
|
|
|
|
try {
|
|
if (regex.test(globalURL)) {
|
|
const httpService = axios.create({ baseURL: globalURL });
|
|
|
|
await this.retryWebhookRequest(
|
|
httpService,
|
|
webhookData,
|
|
`${origin}.sendData-Webhook-Global`,
|
|
globalURL,
|
|
serverUrl,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
this.logger.error({
|
|
local: `${origin}.sendData-Webhook-Global`,
|
|
message: `Todas as tentativas falharam: ${error?.message}`,
|
|
hostName: error?.hostname,
|
|
syscall: error?.syscall,
|
|
code: error?.code,
|
|
error: error?.errno,
|
|
stack: error?.stack,
|
|
name: error?.name,
|
|
url: globalURL,
|
|
server_url: serverUrl,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async retryWebhookRequest(
|
|
httpService: AxiosInstance,
|
|
webhookData: any,
|
|
origin: string,
|
|
baseURL: string,
|
|
serverUrl: string,
|
|
maxRetries = 10,
|
|
delaySeconds = 30,
|
|
): Promise<void> {
|
|
let attempts = 0;
|
|
|
|
while (attempts < maxRetries) {
|
|
try {
|
|
await httpService.post('', webhookData);
|
|
if (attempts > 0) {
|
|
this.logger.log({
|
|
local: `${origin}`,
|
|
message: `Sucesso no envio após ${attempts + 1} tentativas`,
|
|
url: baseURL,
|
|
});
|
|
}
|
|
return;
|
|
} catch (error) {
|
|
attempts++;
|
|
|
|
this.logger.error({
|
|
local: `${origin}`,
|
|
message: `Tentativa ${attempts}/${maxRetries} falhou: ${error?.message}`,
|
|
hostName: error?.hostname,
|
|
syscall: error?.syscall,
|
|
code: error?.code,
|
|
error: error?.errno,
|
|
stack: error?.stack,
|
|
name: error?.name,
|
|
url: baseURL,
|
|
server_url: serverUrl,
|
|
});
|
|
|
|
if (attempts === maxRetries) {
|
|
throw error;
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
|
|
}
|
|
}
|
|
}
|
|
|
|
private generateJwtToken(authToken: string): string {
|
|
try {
|
|
const payload = {
|
|
iat: Math.floor(Date.now() / 1000),
|
|
exp: Math.floor(Date.now() / 1000) + 600, // 10 min expiration
|
|
app: 'evolution',
|
|
action: 'webhook',
|
|
};
|
|
|
|
const token = jwt.sign(payload, authToken, { algorithm: 'HS256' });
|
|
return token;
|
|
} catch (error) {
|
|
this.logger.error({
|
|
local: 'WebhookController.generateJwtToken',
|
|
message: `JWT generation failed: ${error?.message}`,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
}
|