diff --git a/CHANGELOG.md b/CHANGELOG.md index 383282ce..b8f25542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ -# 2.3.1 (develop) +# 2.3.2 (2025-09-02) + +### Features + +* Add support to socks proxy + +### Fixed + +* Added key id into webhook payload in n8n service +* Enhance RabbitMQ controller with improved connection management and shutdown procedures +* Convert outgoing images to JPEG before sending with Chatwoot +* Update baileys dependency to version 6.7.19 + +# 2.3.1 (2025-07-29) ### Feature diff --git a/README.md b/README.md index 9ad5fa0a..6d9b3344 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-[![Docker Image (https://img.shields.io/badge/Docker-Image-blue)](https://hub.docker.com/r/evoapicloud/evolution-api)] +[![Docker Image](https://img.shields.io/badge/Docker-image-blue)](https://hub.docker.com/r/evoapicloud/evolution-api) [![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp) [![Discord Community](https://img.shields.io/badge/Discord-Community-blue)](https://evolution-api.com/discord) [![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman) diff --git a/docker-compose.yaml b/docker-compose.yaml index 1bdd557c..b049f00f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,83 +1,75 @@ +version: "3.8" + services: - api: container_name: evolution_api image: evoapicloud/evolution-api:latest restart: always depends_on: - redis - - postgres + - evolution-postgres ports: - - 8080:8080 + - "127.0.0.1:8080:8080" volumes: - evolution_instances:/evolution/instances networks: - evolution-net + - dokploy-network env_file: - .env expose: - - 8080 + - "8080" redis: + container_name: evolution_redis image: redis:latest - networks: - - evolution-net - container_name: redis + restart: always command: > redis-server --port 6379 --appendonly yes volumes: - evolution_redis:/data - ports: - - 6379:6379 + networks: + evolution-net: + aliases: + - evolution-redis + dokploy-network: + aliases: + - evolution-redis + expose: + - "6379" - postgres: - container_name: postgres + evolution-postgres: + container_name: evolution_postgres image: postgres:15 + restart: always + env_file: + - .env + command: + - postgres + - -c + - max_connections=1000 + - -c + - listen_addresses=* + environment: + - POSTGRES_DB=${POSTGRES_DATABASE} + - POSTGRES_USER=${POSTGRES_USERNAME} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data networks: - evolution-net - command: [ - "postgres", - "-c", "max_connections=200", - "-c", "listen_addresses=*", - "-c", "shared_buffers=256MB", - "-c", "effective_cache_size=1GB", - "-c", "work_mem=4MB" - ] - restart: always - ports: - - 5432:5432 - environment: - - POSTGRES_USER=user - - POSTGRES_PASSWORD=pass - - POSTGRES_DB=evolution_db - - POSTGRES_HOST_AUTH_METHOD=trust - volumes: - - postgres_data:/var/lib/postgresql/data - - # pgbouncer: - # image: edoburu/pgbouncer:latest - # environment: - # DB_HOST: postgres - # DB_USER: user - # DB_PASSWORD: pass - # POOL_MODE: transaction - # AUTH_TYPE: trust - # MAX_CLIENT_CONN: 1000 - # DEFAULT_POOL_SIZE: 25 - # depends_on: - # - postgres - # ports: - # - "6543:5432" - # networks: - # - evolution-net + - dokploy-network + expose: + - "5432" volumes: evolution_instances: evolution_redis: postgres_data: - networks: evolution-net: name: evolution-net driver: bridge + dokploy-network: + external: true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bb9f0c48..fd6942a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "sharp": "^0.34.2", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", + "socks-proxy-agent": "^8.0.5", "swagger-ui-express": "^5.0.1", "tsup": "^8.3.5" }, @@ -4985,8 +4986,8 @@ } }, "node_modules/baileys": { - "version": "6.7.18", - "resolved": "git+ssh://git@github.com/WhiskeySockets/Baileys.git#b7876da2e5d8d4d4b391e215b48b668517b86f3e", + "version": "6.7.19", + "resolved": "git+ssh://git@github.com/WhiskeySockets/Baileys.git#9e04cce8d3eeb16025283a57849cc83aa26c6dd1", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -7739,6 +7740,19 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -8249,6 +8263,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -10761,6 +10781,16 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/socket.io": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", @@ -10897,6 +10927,34 @@ } } }, + "node_modules/socks": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", + "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", @@ -10921,6 +10979,12 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index 9de9d07c..107cca49 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "sharp": "^0.34.2", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", + "socks-proxy-agent": "^8.0.5", "swagger-ui-express": "^5.0.1", "tsup": "^8.3.5" }, diff --git a/prisma/mysql-schema.prisma b/prisma/mysql-schema.prisma index 33d09d2b..70efea47 100644 --- a/prisma/mysql-schema.prisma +++ b/prisma/mysql-schema.prisma @@ -647,22 +647,22 @@ model IsOnWhatsapp { model N8n { id String @id @default(cuid()) - enabled Boolean @default(true) @db.Boolean + enabled Boolean @default(true) @db.TinyInt(1) description String? @db.VarChar(255) webhookUrl String? @db.VarChar(255) basicAuthUser String? @db.VarChar(255) basicAuthPass String? @db.VarChar(255) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int triggerType TriggerType? triggerOperator TriggerOperator? triggerValue String? @@ -677,15 +677,15 @@ model N8nSetting { id String @id @default(cuid()) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp Fallback N8n? @relation(fields: [n8nIdFallback], references: [id]) @@ -696,21 +696,21 @@ model N8nSetting { model Evoai { id String @id @default(cuid()) - enabled Boolean @default(true) @db.Boolean + enabled Boolean @default(true) @db.TinyInt(1) description String? @db.VarChar(255) agentUrl String? @db.VarChar(255) apiKey String? @db.VarChar(255) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int triggerType TriggerType? triggerOperator TriggerOperator? triggerValue String? @@ -725,15 +725,15 @@ model EvoaiSetting { id String @id @default(cuid()) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp Fallback Evoai? @relation(fields: [evoaiIdFallback], references: [id]) diff --git a/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql b/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql index 26898a08..00f9bc7d 100644 --- a/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql +++ b/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql @@ -6,14 +6,4 @@ Warnings: */ -- AlterTable -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = 'Setting' - AND column_name = 'wavoipToken' - ) THEN - ALTER TABLE "Setting" ADD COLUMN "wavoipToken" VARCHAR(100); - END IF; -END $$; \ No newline at end of file +ALTER TABLE "Setting" ADD COLUMN IF NOT EXISTS "wavoipToken" VARCHAR(100); diff --git a/src/api/controllers/proxy.controller.ts b/src/api/controllers/proxy.controller.ts index fac00375..b6e7d153 100644 --- a/src/api/controllers/proxy.controller.ts +++ b/src/api/controllers/proxy.controller.ts @@ -53,15 +53,21 @@ export class ProxyController { httpsAgent: makeProxyAgent(proxy), }); - return response?.data !== serverIp?.data; - } catch (error) { - if (axios.isAxiosError(error) && error.response?.data) { - logger.error('testProxy error: ' + error.response.data); - } else if (axios.isAxiosError(error)) { - logger.error('testProxy error: '); + const result = response?.data !== serverIp?.data; + if (result) { + logger.info('testProxy: proxy connection successful'); } else { - logger.error('testProxy error: '); + logger.warn("testProxy: proxy connection doesn't change the origin IP"); } + + return result; + } catch (error) { + if (axios.isAxiosError(error)) { + logger.error('testProxy error: axios error: ' + error.message); + } else { + logger.error('testProxy error: unexpected error: ' + error); + } + return false; } } diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 758a5bf9..c70ab6f6 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1446,16 +1446,7 @@ export class BaileysStartupService extends ChannelStartupService { } } - const findMessage = await this.prismaRepository.message.findFirst({ - where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } }, - }); - - if (!findMessage) { - continue; - } - const message: any = { - messageId: findMessage.id, keyId: key.id, remoteJid: key?.remoteJid, fromMe: key.fromMe, @@ -1465,6 +1456,16 @@ export class BaileysStartupService extends ChannelStartupService { instanceId: this.instanceId, }; + let findMessage: any; + const configDatabaseData = this.configService.get('DATABASE').SAVE_DATA; + if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) { + findMessage = await this.prismaRepository.message.findFirst({ + where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } }, + }); + + if (findMessage) message.messageId = findMessage.id; + } + if (update.message === null && update.status === undefined) { this.sendDataWebhook(Events.MESSAGES_DELETE, key); @@ -1480,7 +1481,9 @@ export class BaileysStartupService extends ChannelStartupService { } continue; - } else if (update.status !== undefined && status[update.status] !== findMessage.status) { + } + + if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) { if (!key.fromMe && key.remoteJid) { readChatToUpdate[key.remoteJid] = true; @@ -2445,9 +2448,43 @@ export class BaileysStartupService extends ChannelStartupService { try { const type = mediaMessage.mediatype === 'ptv' ? 'video' : mediaMessage.mediatype; + let mediaInput: any; + if (mediaMessage.mediatype === 'image') { + let imageBuffer: Buffer; + if (isURL(mediaMessage.media)) { + let config: any = { responseType: 'arraybuffer' }; + + if (this.localProxy?.enabled) { + config = { + ...config, + httpsAgent: makeProxyAgent({ + host: this.localProxy.host, + port: this.localProxy.port, + protocol: this.localProxy.protocol, + username: this.localProxy.username, + password: this.localProxy.password, + }), + }; + } + + const response = await axios.get(mediaMessage.media, config); + imageBuffer = Buffer.from(response.data, 'binary'); + } else { + imageBuffer = Buffer.from(mediaMessage.media, 'base64'); + } + + mediaInput = await sharp(imageBuffer).jpeg().toBuffer(); + mediaMessage.fileName ??= 'image.jpg'; + mediaMessage.mimetype = 'image/jpeg'; + } else { + mediaInput = isURL(mediaMessage.media) + ? { url: mediaMessage.media } + : Buffer.from(mediaMessage.media, 'base64'); + } + const prepareMedia = await prepareWAMessageMedia( { - [type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'), + [type]: mediaInput, } as any, { upload: this.client.waUploadToServer }, ); @@ -2461,7 +2498,7 @@ export class BaileysStartupService extends ChannelStartupService { } if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) { - mediaMessage.fileName = 'image.png'; + mediaMessage.fileName = 'image.jpg'; } if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) { @@ -3438,17 +3475,20 @@ export class BaileysStartupService extends ChannelStartupService { where: { id: message.id }, data: { key: { ...existingKey, deleted: true }, status: 'DELETED' }, }); - const messageUpdate: any = { - messageId: message.id, - keyId: messageId, - remoteJid: response.key.remoteJid, - fromMe: response.key.fromMe, - participant: response.key?.remoteJid, - status: 'DELETED', - instanceId: this.instanceId, - }; - await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: response.key.remoteJid, + fromMe: response.key.fromMe, + participant: response.key?.remoteJid, + status: 'DELETED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + } } else { + if (!message) return response; await this.prismaRepository.message.deleteMany({ where: { id: message.id } }); } this.sendDataWebhook(Events.MESSAGES_DELETE, { @@ -3780,6 +3820,10 @@ export class BaileysStartupService extends ChannelStartupService { private async formatUpdateMessage(data: UpdateMessageDto) { try { + if (!this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { + return data; + } + const msg: any = await this.getMessage(data.key, true); if (msg?.messageType === 'conversation' || msg?.messageType === 'extendedTextMessage') { @@ -3813,13 +3857,15 @@ export class BaileysStartupService extends ChannelStartupService { try { const oldMessage: any = await this.getMessage(data.key, true); - if (!oldMessage) throw new NotFoundException('Message not found'); - if (oldMessage?.key?.remoteJid !== jid) { - throw new BadRequestException('RemoteJid does not match'); - } - if (oldMessage?.messageTimestamp > Date.now() + 900000) { - // 15 minutes in milliseconds - throw new BadRequestException('Message is older than 15 minutes'); + if (this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { + if (!oldMessage) throw new NotFoundException('Message not found'); + if (oldMessage?.key?.remoteJid !== jid) { + throw new BadRequestException('RemoteJid does not match'); + } + if (oldMessage?.messageTimestamp > Date.now() + 900000) { + // 15 minutes in milliseconds + throw new BadRequestException('Message is older than 15 minutes'); + } } const messageSent = await this.client.sendMessage(jid, { ...(options as any), edit: data.key }); @@ -3837,7 +3883,7 @@ export class BaileysStartupService extends ChannelStartupService { ); const messageId = messageSent.message?.protocolMessage?.key?.id; - if (messageId) { + if (messageId && this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { let message = await this.prismaRepository.message.findFirst({ where: { key: { path: ['id'], equals: messageId } }, }); @@ -3849,6 +3895,7 @@ export class BaileysStartupService extends ChannelStartupService { if ((message.key.valueOf() as any)?.deleted) { new BadRequestException('You cannot edit deleted messages'); } + if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') { oldMessage.message.conversation = data.text; } else { @@ -3862,16 +3909,19 @@ export class BaileysStartupService extends ChannelStartupService { messageTimestamp: Math.floor(Date.now() / 1000), // Convert to int32 by dividing by 1000 to get seconds }, }); - const messageUpdate: any = { - messageId: message.id, - keyId: messageId, - remoteJid: messageSent.key.remoteJid, - fromMe: messageSent.key.fromMe, - participant: messageSent.key?.remoteJid, - status: 'EDITED', - instanceId: this.instanceId, - }; - await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: messageSent.key.remoteJid, + fromMe: messageSent.key.fromMe, + participant: messageSent.key?.remoteJid, + status: 'EDITED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + } } } } diff --git a/src/api/integrations/chatbot/n8n/services/n8n.service.ts b/src/api/integrations/chatbot/n8n/services/n8n.service.ts index 2d0802b9..5bb40890 100644 --- a/src/api/integrations/chatbot/n8n/services/n8n.service.ts +++ b/src/api/integrations/chatbot/n8n/services/n8n.service.ts @@ -49,6 +49,7 @@ export class N8nService extends BaseChatbotService { sessionId: session.sessionId, remoteJid: remoteJid, pushName: pushName, + keyId: msg?.key?.id, fromMe: msg?.key?.fromMe, instanceName: instance.instanceName, serverUrl: this.configService.get('SERVER').URL, diff --git a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts index be73b157..3295b12d 100644 --- a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts +++ b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts @@ -45,7 +45,7 @@ export class RabbitmqController extends EventController implements EventControll heartbeat: 30, // Add heartbeat of 30 seconds }; - amqp.connect(connectionOptions, (error, connection) => { + amqp.connect(connectionOptions, (error: Error, connection: amqp.Connection) => { if (error) { this.logger.error({ local: 'RabbitmqController.connect', @@ -57,7 +57,7 @@ export class RabbitmqController extends EventController implements EventControll } // Connection event handlers - connection.on('error', (err) => { + connection.on('error', (err: Error) => { this.logger.error({ local: 'RabbitmqController.connectionError', message: 'RabbitMQ connection error', @@ -71,7 +71,7 @@ export class RabbitmqController extends EventController implements EventControll this.handleConnectionLoss(); }); - connection.createChannel((channelError, channel) => { + connection.createChannel((channelError: Error, channel: amqp.Channel) => { if (channelError) { this.logger.error({ local: 'RabbitmqController.createChannel', @@ -83,7 +83,7 @@ export class RabbitmqController extends EventController implements EventControll } // Channel event handlers - channel.on('error', (err) => { + channel.on('error', (err: Error) => { this.logger.error({ local: 'RabbitmqController.channelError', message: 'RabbitMQ channel error', @@ -136,8 +136,7 @@ export class RabbitmqController extends EventController implements EventControll return; // Already attempting to reconnect } - this.amqpChannel = null; - this.amqpConnection = null; + this.cleanup(); this.scheduleReconnect(); } @@ -406,4 +405,25 @@ export class RabbitmqController extends EventController implements EventControll } } } + + public async cleanup(): Promise { + try { + if (this.amqpChannel) { + await this.amqpChannel.close(); + this.amqpChannel = null; + } + if (this.amqpConnection) { + await this.amqpConnection.close(); + this.amqpConnection = null; + } + } catch (error) { + this.logger.warn({ + local: 'RabbitmqController.cleanup', + message: 'Error during cleanup', + error: error.message || error, + }); + this.amqpChannel = null; + this.amqpConnection = null; + } + } } diff --git a/src/api/integrations/event/websocket/websocket.controller.ts b/src/api/integrations/event/websocket/websocket.controller.ts index a1cef2db..3f4afd9b 100644 --- a/src/api/integrations/event/websocket/websocket.controller.ts +++ b/src/api/integrations/event/websocket/websocket.controller.ts @@ -30,8 +30,12 @@ export class WebsocketController extends EventController implements EventControl const url = new URL(req.url || '', 'http://localhost'); const params = new URLSearchParams(url.search); + const { remoteAddress } = req.socket; + const isLocalhost = + remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1'; + // Permite conexões internas do Socket.IO (EIO=4 é o Engine.IO v4) - if (params.has('EIO')) { + if (params.has('EIO') && isLocalhost) { return callback(null, true); } diff --git a/src/utils/makeProxyAgent.ts b/src/utils/makeProxyAgent.ts index dcf560f6..3b1379bd 100644 --- a/src/utils/makeProxyAgent.ts +++ b/src/utils/makeProxyAgent.ts @@ -1,4 +1,5 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; +import { SocksProxyAgent } from 'socks-proxy-agent'; type Proxy = { host: string; @@ -8,9 +9,28 @@ type Proxy = { username?: string; }; -export function makeProxyAgent(proxy: Proxy | string) { +function selectProxyAgent(proxyUrl: string): HttpsProxyAgent | SocksProxyAgent { + const url = new URL(proxyUrl); + + // NOTE: The following constants are not used in the function but are defined for clarity. + // When a proxy URL is used to build the URL object, the protocol returned by procotol's property contains a `:` at + // the end so, we add the protocol constants without the `:` to avoid confusion. + const PROXY_HTTP_PROTOCOL = 'http:'; + const PROXY_SOCKS_PROTOCOL = 'socks:'; + + switch (url.protocol) { + case PROXY_HTTP_PROTOCOL: + return new HttpsProxyAgent(url); + case PROXY_SOCKS_PROTOCOL: + return new SocksProxyAgent(url); + default: + throw new Error(`Unsupported proxy protocol: ${url.protocol}`); + } +} + +export function makeProxyAgent(proxy: Proxy | string): HttpsProxyAgent | SocksProxyAgent { if (typeof proxy === 'string') { - return new HttpsProxyAgent(proxy); + return selectProxyAgent(proxy); } const { host, password, port, protocol, username } = proxy; @@ -19,5 +39,6 @@ export function makeProxyAgent(proxy: Proxy | string) { if (username && password) { proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`; } - return new HttpsProxyAgent(proxyUrl); + + return selectProxyAgent(proxyUrl); }