mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-19 03:42:23 -06:00
feat: pusher event integration
This commit is contained in:
@@ -7,7 +7,7 @@ export type EmitData = {
|
||||
instanceName: string;
|
||||
origin: string;
|
||||
event: string;
|
||||
data: Object;
|
||||
data: any;
|
||||
serverUrl: string;
|
||||
dateTime: string;
|
||||
sender: string;
|
||||
@@ -22,7 +22,7 @@ export interface EventControllerInterface {
|
||||
}
|
||||
|
||||
export class EventController {
|
||||
private prismaRepository: PrismaRepository;
|
||||
public prismaRepository: PrismaRepository;
|
||||
private waMonitor: WAMonitoringService;
|
||||
private integrationStatus: boolean;
|
||||
private integrationName: string;
|
||||
|
||||
@@ -25,6 +25,16 @@ export class EventDto {
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
pusher?: {
|
||||
enabled?: boolean;
|
||||
appId?: string;
|
||||
key?: string;
|
||||
secret?: string;
|
||||
cluster?: string;
|
||||
useTLS?: boolean;
|
||||
events?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export function EventInstanceMixin<TBase extends Constructor>(Base: TBase) {
|
||||
@@ -52,5 +62,15 @@ export function EventInstanceMixin<TBase extends Constructor>(Base: TBase) {
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
pusher?: {
|
||||
enabled?: boolean;
|
||||
appId?: string;
|
||||
key?: string;
|
||||
secret?: string;
|
||||
cluster?: string;
|
||||
useTLS?: boolean;
|
||||
events?: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PusherController } from '@api/integrations/event/pusher/pusher.controller';
|
||||
import { RabbitmqController } from '@api/integrations/event/rabbitmq/rabbitmq.controller';
|
||||
import { SqsController } from '@api/integrations/event/sqs/sqs.controller';
|
||||
import { WebhookController } from '@api/integrations/event/webhook/webhook.controller';
|
||||
@@ -13,6 +14,7 @@ export class EventManager {
|
||||
private webhookController: WebhookController;
|
||||
private rabbitmqController: RabbitmqController;
|
||||
private sqsController: SqsController;
|
||||
private pusherController: PusherController;
|
||||
|
||||
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
|
||||
this.prisma = prismaRepository;
|
||||
@@ -22,6 +24,7 @@ export class EventManager {
|
||||
this.webhook = new WebhookController(prismaRepository, waMonitor);
|
||||
this.rabbitmq = new RabbitmqController(prismaRepository, waMonitor);
|
||||
this.sqs = new SqsController(prismaRepository, waMonitor);
|
||||
this.pusher = new PusherController(prismaRepository, waMonitor);
|
||||
}
|
||||
|
||||
public set prisma(prisma: PrismaRepository) {
|
||||
@@ -72,10 +75,18 @@ export class EventManager {
|
||||
return this.sqsController;
|
||||
}
|
||||
|
||||
public set pusher(pusher: PusherController) {
|
||||
this.pusherController = pusher;
|
||||
}
|
||||
public get pusher() {
|
||||
return this.pusherController;
|
||||
}
|
||||
|
||||
public init(httpServer: Server): void {
|
||||
this.websocket.init(httpServer);
|
||||
this.rabbitmq.init();
|
||||
this.sqs.init();
|
||||
this.pusher.init();
|
||||
}
|
||||
|
||||
public async emit(eventData: {
|
||||
@@ -93,6 +104,7 @@ export class EventManager {
|
||||
await this.rabbitmq.emit(eventData);
|
||||
await this.sqs.emit(eventData);
|
||||
await this.webhook.emit(eventData);
|
||||
await this.pusher.emit(eventData);
|
||||
}
|
||||
|
||||
public async setInstance(instanceName: string, data: any): Promise<any> {
|
||||
@@ -131,5 +143,18 @@ export class EventManager {
|
||||
byEvents: data.webhook?.byEvents,
|
||||
},
|
||||
});
|
||||
|
||||
if (data.pusher)
|
||||
await this.pusher.set(instanceName, {
|
||||
pusher: {
|
||||
enabled: true,
|
||||
events: data.pusher?.events,
|
||||
appId: data.pusher?.appId,
|
||||
key: data.pusher?.key,
|
||||
secret: data.pusher?.secret,
|
||||
cluster: data.pusher?.cluster,
|
||||
useTLS: data.pusher?.useTLS,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PusherRouter } from '@api/integrations/event/pusher/pusher.router';
|
||||
import { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';
|
||||
import { SqsRouter } from '@api/integrations/event/sqs/sqs.router';
|
||||
import { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';
|
||||
@@ -13,6 +14,7 @@ export class EventRouter {
|
||||
this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);
|
||||
this.router.use('/websocket', new WebsocketRouter(...guards).router);
|
||||
this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);
|
||||
this.router.use('/pusher', new PusherRouter(...guards).router);
|
||||
this.router.use('/sqs', new SqsRouter(...guards).router);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { v4 } from 'uuid';
|
||||
|
||||
import { EventController } from './event.controller';
|
||||
|
||||
export * from '@api/integrations/event/pusher/pusher.schema';
|
||||
export * from '@api/integrations/event/webhook/webhook.schema';
|
||||
|
||||
export const eventSchema: JSONSchema7 = {
|
||||
|
||||
209
src/api/integrations/event/pusher/pusher.controller.ts
Normal file
209
src/api/integrations/event/pusher/pusher.controller.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
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, Pusher as ConfigPusher } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import Pusher from 'pusher';
|
||||
|
||||
import { EmitData, EventController, EventControllerInterface } from '../event.controller';
|
||||
export class PusherController extends EventController implements EventControllerInterface {
|
||||
private readonly logger = new Logger('PusherController');
|
||||
private pusherClients: { [instanceName: string]: Pusher } = {};
|
||||
private globalPusherClient: Pusher | null = null;
|
||||
private pusherConfig: ConfigPusher = configService.get<ConfigPusher>('PUSHER');
|
||||
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
|
||||
super(prismaRepository, waMonitor, configService.get<ConfigPusher>('PUSHER')?.ENABLED, 'pusher');
|
||||
this.init();
|
||||
}
|
||||
public async init(): Promise<void> {
|
||||
if (!this.status) {
|
||||
return;
|
||||
}
|
||||
if (this.pusherConfig.GLOBAL?.ENABLED) {
|
||||
const { APP_ID, KEY, SECRET, CLUSTER, USE_TLS } = this.pusherConfig.GLOBAL;
|
||||
if (APP_ID && KEY && SECRET && CLUSTER) {
|
||||
this.globalPusherClient = new Pusher({
|
||||
appId: APP_ID,
|
||||
key: KEY,
|
||||
secret: SECRET,
|
||||
cluster: CLUSTER,
|
||||
useTLS: USE_TLS,
|
||||
});
|
||||
this.logger.info('Pusher global client initialized');
|
||||
}
|
||||
}
|
||||
const instances = await this.prismaRepository.instance.findMany({
|
||||
where: {
|
||||
Pusher: {
|
||||
isNot: null,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Pusher: true,
|
||||
},
|
||||
});
|
||||
instances.forEach((instance) => {
|
||||
if (
|
||||
instance.Pusher.enabled &&
|
||||
instance.Pusher.appId &&
|
||||
instance.Pusher.key &&
|
||||
instance.Pusher.secret &&
|
||||
instance.Pusher.cluster
|
||||
) {
|
||||
this.pusherClients[instance.name] = new Pusher({
|
||||
appId: instance.Pusher.appId,
|
||||
key: instance.Pusher.key,
|
||||
secret: instance.Pusher.secret,
|
||||
cluster: instance.Pusher.cluster,
|
||||
useTLS: instance.Pusher.useTLS,
|
||||
});
|
||||
this.logger.info(`Pusher client initialized for instance ${instance.name}`);
|
||||
} else {
|
||||
delete this.pusherClients[instance.name];
|
||||
this.logger.warn(`Pusher client disabled or misconfigured for instance ${instance.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
override async set(instanceName: string, data: EventDto): Promise<wa.LocalPusher> {
|
||||
if (!data.pusher?.enabled) {
|
||||
data.pusher.events = [];
|
||||
} else if (data.pusher.events.length === 0) {
|
||||
data.pusher.events = EventController.events;
|
||||
}
|
||||
const instance = await this.prisma.pusher.upsert({
|
||||
where: {
|
||||
instanceId: this.monitor.waInstances[instanceName].instanceId,
|
||||
},
|
||||
update: {
|
||||
enabled: data.pusher.enabled,
|
||||
events: data.pusher.events,
|
||||
appId: data.pusher.appId,
|
||||
key: data.pusher.key,
|
||||
secret: data.pusher.secret,
|
||||
cluster: data.pusher.cluster,
|
||||
useTLS: data.pusher.useTLS,
|
||||
},
|
||||
create: {
|
||||
enabled: data.pusher.enabled,
|
||||
events: data.pusher.events,
|
||||
instanceId: this.monitor.waInstances[instanceName].instanceId,
|
||||
appId: data.pusher.appId,
|
||||
key: data.pusher.key,
|
||||
secret: data.pusher.secret,
|
||||
cluster: data.pusher.cluster,
|
||||
useTLS: data.pusher.useTLS,
|
||||
},
|
||||
});
|
||||
if (instance.enabled && instance.appId && instance.key && instance.secret && instance.cluster) {
|
||||
this.pusherClients[instanceName] = new Pusher({
|
||||
appId: instance.appId,
|
||||
key: instance.key,
|
||||
secret: instance.secret,
|
||||
cluster: instance.cluster,
|
||||
useTLS: instance.useTLS,
|
||||
});
|
||||
this.logger.info(`Pusher client initialized for instance ${instanceName}`);
|
||||
} else {
|
||||
delete this.pusherClients[instanceName];
|
||||
this.logger.warn(`Pusher client disabled or misconfigured for instance ${instanceName}`);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public async emit({
|
||||
instanceName,
|
||||
origin,
|
||||
event,
|
||||
data,
|
||||
serverUrl,
|
||||
dateTime,
|
||||
sender,
|
||||
apiKey,
|
||||
local,
|
||||
}: EmitData): Promise<void> {
|
||||
if (!this.status) {
|
||||
return;
|
||||
}
|
||||
const instance = (await this.get(instanceName)) as wa.LocalPusher;
|
||||
const we = event.replace(/[.-]/gm, '_').toUpperCase();
|
||||
const enabledLog = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||
const eventName = event.replace(/_/g, '.').toLowerCase();
|
||||
const pusherData = {
|
||||
event,
|
||||
instance: instanceName,
|
||||
data,
|
||||
destination: instance?.appId || this.pusherConfig.GLOBAL?.APP_ID,
|
||||
date_time: dateTime,
|
||||
sender,
|
||||
server_url: serverUrl,
|
||||
apikey: apiKey,
|
||||
};
|
||||
if (event == 'qrcode.updated') {
|
||||
delete pusherData.data.qrcode.base64;
|
||||
}
|
||||
const payload = JSON.stringify(pusherData);
|
||||
const payloadSize = Buffer.byteLength(payload, 'utf8');
|
||||
const MAX_SIZE = 10240;
|
||||
if (payloadSize > MAX_SIZE) {
|
||||
this.logger.error({
|
||||
local: `${origin}.sendData-Pusher`,
|
||||
message: 'Payload size exceeds Pusher limit',
|
||||
event,
|
||||
instanceName,
|
||||
payloadSize,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (local && instance && instance.enabled) {
|
||||
const pusherLocalEvents = instance.events;
|
||||
if (Array.isArray(pusherLocalEvents) && pusherLocalEvents.includes(we)) {
|
||||
if (enabledLog) {
|
||||
this.logger.log({
|
||||
local: `${origin}.sendData-Pusher`,
|
||||
appId: instance.appId,
|
||||
...pusherData,
|
||||
});
|
||||
}
|
||||
try {
|
||||
const pusher = this.pusherClients[instanceName];
|
||||
if (pusher) {
|
||||
pusher.trigger(instanceName, eventName, pusherData);
|
||||
} else {
|
||||
this.logger.error(`Pusher client not found for instance ${instanceName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
local: `${origin}.sendData-Pusher`,
|
||||
message: error?.message,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.pusherConfig.GLOBAL?.ENABLED) {
|
||||
const globalEvents = this.pusherConfig.EVENTS;
|
||||
if (globalEvents[we]) {
|
||||
if (enabledLog) {
|
||||
this.logger.log({
|
||||
local: `${origin}.sendData-Pusher-Global`,
|
||||
appId: this.pusherConfig.GLOBAL?.APP_ID,
|
||||
...pusherData,
|
||||
});
|
||||
}
|
||||
try {
|
||||
if (this.globalPusherClient) {
|
||||
this.globalPusherClient.trigger(instanceName, eventName, pusherData);
|
||||
} else {
|
||||
this.logger.error('Global Pusher client not initialized');
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
local: `${origin}.sendData-Pusher-Global`,
|
||||
message: error?.message,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/api/integrations/event/pusher/pusher.router.ts
Normal file
32
src/api/integrations/event/pusher/pusher.router.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||
import { InstanceDto } from '@api/dto/instance.dto';
|
||||
import { EventDto } from '@api/integrations/event/event.dto';
|
||||
import { HttpStatus } from '@api/routes/index.router';
|
||||
import { eventManager } from '@api/server.module';
|
||||
import { instanceSchema, pusherSchema } from '@validate/validate.schema';
|
||||
import { RequestHandler, Router } from 'express';
|
||||
export class PusherRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<EventDto>({
|
||||
request: req,
|
||||
schema: pusherSchema,
|
||||
ClassRef: EventDto,
|
||||
execute: (instance, data) => eventManager.pusher.set(instance.instanceName, data),
|
||||
});
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => eventManager.pusher.get(instance.instanceName),
|
||||
});
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
public readonly router: Router = Router();
|
||||
}
|
||||
50
src/api/integrations/event/pusher/pusher.schema.ts
Normal file
50
src/api/integrations/event/pusher/pusher.schema.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { EventController } from '../event.controller';
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
export const pusherSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
pusher: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean' },
|
||||
appId: { type: 'string' },
|
||||
key: { type: 'string' },
|
||||
secret: { type: 'string' },
|
||||
cluster: { type: 'string' },
|
||||
useTLS: { type: 'boolean' },
|
||||
events: {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: EventController.events,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['enabled', 'appId', 'key', 'secret', 'cluster', 'useTLS'],
|
||||
...isNotEmpty('enabled', 'appId', 'key', 'secret', 'cluster', 'useTLS'),
|
||||
},
|
||||
},
|
||||
required: ['pusher'],
|
||||
};
|
||||
Reference in New Issue
Block a user