mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-09 01:49:37 -06:00
feat(config): add telemetry and metrics configuration options
- Introduce new environment variables for telemetry and Prometheus metrics in .env.example - Create example configuration files for Prometheus and Grafana dashboards - Update main application to utilize new configuration settings for Sentry, audio converter, and proxy - Enhance channel services to support audio conversion API integration - Implement middleware for metrics IP whitelisting and basic authentication in routes
This commit is contained in:
parent
81a991a62e
commit
3ddbd6a7fb
22
.env.example
22
.env.example
@ -9,6 +9,28 @@ SSL_CONF_FULLCHAIN=/path/to/cert.crt
|
||||
|
||||
SENTRY_DSN=
|
||||
|
||||
# Telemetry - Set to false to disable telemetry
|
||||
TELEMETRY_ENABLED=true
|
||||
TELEMETRY_URL=
|
||||
|
||||
# Prometheus metrics - Set to true to enable Prometheus metrics
|
||||
PROMETHEUS_METRICS=false
|
||||
METRICS_AUTH_REQUIRED=true
|
||||
METRICS_USER=prometheus
|
||||
METRICS_PASSWORD=secure_random_password_here
|
||||
METRICS_ALLOWED_IPS=127.0.0.1,10.0.0.100,192.168.1.50
|
||||
|
||||
# Proxy configuration (optional)
|
||||
PROXY_HOST=
|
||||
PROXY_PORT=
|
||||
PROXY_PROTOCOL=
|
||||
PROXY_USERNAME=
|
||||
PROXY_PASSWORD=
|
||||
|
||||
# Audio converter API (optional)
|
||||
API_AUDIO_CONVERTER=
|
||||
API_AUDIO_CONVERTER_KEY=
|
||||
|
||||
# Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
|
||||
CORS_ORIGIN=*
|
||||
CORS_METHODS=GET,POST,PUT,DELETE
|
||||
|
||||
238
grafana-dashboard.json.example
Normal file
238
grafana-dashboard.json.example
Normal file
@ -0,0 +1,238 @@
|
||||
{
|
||||
"dashboard": {
|
||||
"id": null,
|
||||
"title": "Evolution API Monitoring",
|
||||
"tags": ["evolution-api", "whatsapp", "monitoring"],
|
||||
"style": "dark",
|
||||
"timezone": "browser",
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "API Status",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "up{job=\"evolution-api\"}",
|
||||
"legendFormat": "API Status"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"0": {
|
||||
"text": "DOWN",
|
||||
"color": "red"
|
||||
},
|
||||
"1": {
|
||||
"text": "UP",
|
||||
"color": "green"
|
||||
}
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Total Instances",
|
||||
"type": "stat",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "evolution_instances_total",
|
||||
"legendFormat": "Total Instances"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Instance Status Overview",
|
||||
"type": "piechart",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (state) (evolution_instance_state)",
|
||||
"legendFormat": "{{ state }}"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Instances by Integration Type",
|
||||
"type": "piechart",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (integration) (evolution_instance_up)",
|
||||
"legendFormat": "{{ integration }}"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Instance Uptime",
|
||||
"type": "table",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "evolution_instance_up",
|
||||
"format": "table",
|
||||
"instant": true
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Time": true,
|
||||
"__name__": true
|
||||
},
|
||||
"renameByName": {
|
||||
"instance": "Instance Name",
|
||||
"integration": "Integration Type",
|
||||
"Value": "Status"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Status"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "mappings",
|
||||
"value": [
|
||||
{
|
||||
"options": {
|
||||
"0": {
|
||||
"text": "DOWN",
|
||||
"color": "red"
|
||||
},
|
||||
"1": {
|
||||
"text": "UP",
|
||||
"color": "green"
|
||||
}
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 17
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Instance Status Timeline",
|
||||
"type": "timeseries",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "evolution_instance_up",
|
||||
"legendFormat": "{{ instance }} ({{ integration }})"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "stepAfter",
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"spanNulls": false,
|
||||
"insertNulls": false,
|
||||
"showPoints": "never",
|
||||
"pointSize": 5,
|
||||
"stacking": {
|
||||
"mode": "none",
|
||||
"group": "A"
|
||||
},
|
||||
"axisPlacement": "auto",
|
||||
"axisLabel": "",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"vis": false
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"min": 0,
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 26
|
||||
}
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 27,
|
||||
"version": 0,
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
76
prometheus.yml.example
Normal file
76
prometheus.yml.example
Normal file
@ -0,0 +1,76 @@
|
||||
# Prometheus configuration example for Evolution API
|
||||
# Copy this file to prometheus.yml and adjust the settings
|
||||
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
rule_files:
|
||||
# - "first_rules.yml"
|
||||
# - "second_rules.yml"
|
||||
|
||||
scrape_configs:
|
||||
# Evolution API metrics
|
||||
- job_name: 'evolution-api'
|
||||
static_configs:
|
||||
- targets: ['localhost:8080'] # Adjust to your Evolution API URL
|
||||
|
||||
# Metrics endpoint path
|
||||
metrics_path: '/metrics'
|
||||
|
||||
# Scrape interval for this job
|
||||
scrape_interval: 30s
|
||||
|
||||
# Basic authentication (if METRICS_AUTH_REQUIRED=true)
|
||||
basic_auth:
|
||||
username: 'prometheus' # Should match METRICS_USER
|
||||
password: 'secure_random_password_here' # Should match METRICS_PASSWORD
|
||||
|
||||
# Optional: Add custom labels
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: localhost:8080 # Evolution API address
|
||||
|
||||
# Alerting configuration (optional)
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
# - alertmanager:9093
|
||||
|
||||
# Example alert rules for Evolution API
|
||||
# Create a file called evolution_alerts.yml with these rules:
|
||||
#
|
||||
# groups:
|
||||
# - name: evolution-api
|
||||
# rules:
|
||||
# - alert: EvolutionAPIDown
|
||||
# expr: up{job="evolution-api"} == 0
|
||||
# for: 1m
|
||||
# labels:
|
||||
# severity: critical
|
||||
# annotations:
|
||||
# summary: "Evolution API is down"
|
||||
# description: "Evolution API has been down for more than 1 minute."
|
||||
#
|
||||
# - alert: EvolutionInstanceDown
|
||||
# expr: evolution_instance_up == 0
|
||||
# for: 2m
|
||||
# labels:
|
||||
# severity: warning
|
||||
# annotations:
|
||||
# summary: "Evolution instance {{ $labels.instance }} is down"
|
||||
# description: "Instance {{ $labels.instance }} has been down for more than 2 minutes."
|
||||
#
|
||||
# - alert: HighInstanceCount
|
||||
# expr: evolution_instances_total > 100
|
||||
# for: 5m
|
||||
# labels:
|
||||
# severity: warning
|
||||
# annotations:
|
||||
# summary: "High number of Evolution instances"
|
||||
# description: "Evolution API is managing {{ $value }} instances."
|
||||
@ -13,7 +13,7 @@ import { chatbotController } from '@api/server.module';
|
||||
import { CacheService } from '@api/services/cache.service';
|
||||
import { ChannelStartupService } from '@api/services/channel.service';
|
||||
import { Events, wa } from '@api/types/wa.types';
|
||||
import { Chatwoot, ConfigService, Openai, S3 } from '@config/env.config';
|
||||
import { AudioConverter, Chatwoot, ConfigService, Openai, S3 } from '@config/env.config';
|
||||
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
||||
import { createJid } from '@utils/createJid';
|
||||
import axios from 'axios';
|
||||
@ -622,7 +622,8 @@ export class EvolutionStartupService extends ChannelStartupService {
|
||||
number = number.replace(/\D/g, '');
|
||||
const hash = `${number}-${new Date().getTime()}`;
|
||||
|
||||
if (process.env.API_AUDIO_CONVERTER) {
|
||||
const audioConverterConfig = this.configService.get<AudioConverter>('AUDIO_CONVERTER');
|
||||
if (audioConverterConfig.API_URL) {
|
||||
try {
|
||||
this.logger.verbose('Using audio converter API');
|
||||
const formData = new FormData();
|
||||
@ -640,10 +641,10 @@ export class EvolutionStartupService extends ChannelStartupService {
|
||||
|
||||
formData.append('format', 'mp4');
|
||||
|
||||
const response = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
|
||||
const response = await axios.post(audioConverterConfig.API_URL, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
apikey: process.env.API_AUDIO_CONVERTER_KEY,
|
||||
apikey: audioConverterConfig.API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ import { chatbotController } from '@api/server.module';
|
||||
import { CacheService } from '@api/services/cache.service';
|
||||
import { ChannelStartupService } from '@api/services/channel.service';
|
||||
import { Events, wa } from '@api/types/wa.types';
|
||||
import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
|
||||
import { AudioConverter, Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
|
||||
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
||||
import { createJid } from '@utils/createJid';
|
||||
import { status } from '@utils/renderStatus';
|
||||
@ -1300,7 +1300,8 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
number = number.replace(/\D/g, '');
|
||||
const hash = `${number}-${new Date().getTime()}`;
|
||||
|
||||
if (process.env.API_AUDIO_CONVERTER) {
|
||||
const audioConverterConfig = this.configService.get<AudioConverter>('AUDIO_CONVERTER');
|
||||
if (audioConverterConfig.API_URL) {
|
||||
this.logger.verbose('Using audio converter API');
|
||||
const formData = new FormData();
|
||||
|
||||
@ -1317,10 +1318,10 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
|
||||
formData.append('format', 'mp3');
|
||||
|
||||
const response = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
|
||||
const response = await axios.post(audioConverterConfig.API_URL, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
apikey: process.env.API_AUDIO_CONVERTER_KEY,
|
||||
apikey: audioConverterConfig.API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -62,6 +62,7 @@ import { ChannelStartupService } from '@api/services/channel.service';
|
||||
import { Events, MessageSubtype, TypeMediaMessage, wa } from '@api/types/wa.types';
|
||||
import { CacheEngine } from '@cache/cacheengine';
|
||||
import {
|
||||
AudioConverter,
|
||||
CacheConf,
|
||||
Chatwoot,
|
||||
ConfigService,
|
||||
@ -2837,7 +2838,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
|
||||
public async processAudio(audio: string): Promise<Buffer> {
|
||||
if (process.env.API_AUDIO_CONVERTER) {
|
||||
const audioConverterConfig = this.configService.get<AudioConverter>('AUDIO_CONVERTER');
|
||||
if (audioConverterConfig.API_URL) {
|
||||
this.logger.verbose('Using audio converter API');
|
||||
const formData = new FormData();
|
||||
|
||||
@ -2847,8 +2849,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
formData.append('base64', audio);
|
||||
}
|
||||
|
||||
const { data } = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
|
||||
headers: { ...formData.getHeaders(), apikey: process.env.API_AUDIO_CONVERTER_KEY },
|
||||
const { data } = await axios.post(audioConverterConfig.API_URL, formData, {
|
||||
headers: { ...formData.getHeaders(), apikey: audioConverterConfig.API_KEY },
|
||||
});
|
||||
|
||||
if (!data.audio) {
|
||||
|
||||
@ -31,7 +31,9 @@ export class WebsocketController extends EventController implements EventControl
|
||||
const params = new URLSearchParams(url.search);
|
||||
|
||||
const { remoteAddress } = req.socket;
|
||||
const isAllowedHost = (process.env.WEBSOCKET_ALLOWED_HOSTS || '127.0.0.1,::1,::ffff:127.0.0.1')
|
||||
const websocketConfig = configService.get<Websocket>('WEBSOCKET');
|
||||
const allowedHosts = websocketConfig.ALLOWED_HOSTS || '127.0.0.1,::1,::ffff:127.0.0.1';
|
||||
const isAllowedHost = allowedHosts
|
||||
.split(',')
|
||||
.map((h) => h.trim())
|
||||
.includes(remoteAddress);
|
||||
|
||||
@ -26,7 +26,7 @@ const minioClient = (() => {
|
||||
}
|
||||
})();
|
||||
|
||||
const bucketName = process.env.S3_BUCKET;
|
||||
const bucketName = BUCKET.BUCKET_NAME;
|
||||
|
||||
const bucketExists = async () => {
|
||||
if (minioClient) {
|
||||
|
||||
@ -6,9 +6,9 @@ import { ChatbotRouter } from '@api/integrations/chatbot/chatbot.router';
|
||||
import { EventRouter } from '@api/integrations/event/event.router';
|
||||
import { StorageRouter } from '@api/integrations/storage/storage.router';
|
||||
import { waMonitor } from '@api/server.module';
|
||||
import { configService } from '@config/env.config';
|
||||
import { configService, Database, Facebook } from '@config/env.config';
|
||||
import { fetchLatestWaWebVersion } from '@utils/fetchLatestWaWebVersion';
|
||||
import { Router } from 'express';
|
||||
import { NextFunction, Request, Response, Router } from 'express';
|
||||
import fs from 'fs';
|
||||
import mimeTypes from 'mime-types';
|
||||
import path from 'path';
|
||||
@ -37,15 +37,68 @@ enum HttpStatus {
|
||||
|
||||
const router: Router = Router();
|
||||
const serverConfig = configService.get('SERVER');
|
||||
const databaseConfig = configService.get<Database>('DATABASE');
|
||||
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];
|
||||
|
||||
const telemetry = new Telemetry();
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
|
||||
// Middleware for metrics IP whitelist
|
||||
const metricsIPWhitelist = (req: Request, res: Response, next: NextFunction) => {
|
||||
const metricsConfig = configService.get('METRICS');
|
||||
const allowedIPs = metricsConfig.ALLOWED_IPS?.split(',').map((ip) => ip.trim()) || ['127.0.0.1'];
|
||||
const clientIP = req.ip || req.connection.remoteAddress || req.socket.remoteAddress;
|
||||
|
||||
if (!allowedIPs.includes(clientIP)) {
|
||||
return res.status(403).send('Forbidden: IP not allowed');
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Middleware for metrics Basic Authentication
|
||||
const metricsBasicAuth = (req: Request, res: Response, next: NextFunction) => {
|
||||
const metricsConfig = configService.get('METRICS');
|
||||
const metricsUser = metricsConfig.USER;
|
||||
const metricsPass = metricsConfig.PASSWORD;
|
||||
|
||||
if (!metricsUser || !metricsPass) {
|
||||
return res.status(500).send('Metrics authentication not configured');
|
||||
}
|
||||
|
||||
const auth = req.get('Authorization');
|
||||
if (!auth || !auth.startsWith('Basic ')) {
|
||||
res.set('WWW-Authenticate', 'Basic realm="Evolution API Metrics"');
|
||||
return res.status(401).send('Authentication required');
|
||||
}
|
||||
|
||||
const credentials = Buffer.from(auth.slice(6), 'base64').toString();
|
||||
const [user, pass] = credentials.split(':');
|
||||
|
||||
if (user !== metricsUser || pass !== metricsPass) {
|
||||
return res.status(401).send('Invalid credentials');
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Expose Prometheus metrics when enabled by env flag
|
||||
if (process.env.PROMETHEUS_METRICS === 'true') {
|
||||
router.get('/metrics', async (req, res) => {
|
||||
const metricsConfig = configService.get('METRICS');
|
||||
if (metricsConfig.ENABLED) {
|
||||
const metricsMiddleware = [];
|
||||
|
||||
// Add IP whitelist if configured
|
||||
if (metricsConfig.ALLOWED_IPS) {
|
||||
metricsMiddleware.push(metricsIPWhitelist);
|
||||
}
|
||||
|
||||
// Add Basic Auth if required
|
||||
if (metricsConfig.AUTH_REQUIRED) {
|
||||
metricsMiddleware.push(metricsBasicAuth);
|
||||
}
|
||||
|
||||
router.get('/metrics', ...metricsMiddleware, async (req, res) => {
|
||||
res.set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
|
||||
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
|
||||
@ -57,7 +110,7 @@ if (process.env.PROMETHEUS_METRICS === 'true') {
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
const clientName = process.env.DATABASE_CONNECTION_CLIENT_NAME || 'unknown';
|
||||
const clientName = databaseConfig.CONNECTION.CLIENT_NAME || 'unknown';
|
||||
const serverUrl = serverConfig.URL || '';
|
||||
|
||||
// environment info
|
||||
@ -140,19 +193,20 @@ router
|
||||
status: HttpStatus.OK,
|
||||
message: 'Welcome to the Evolution API, it is working!',
|
||||
version: packageJson.version,
|
||||
clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,
|
||||
clientName: databaseConfig.CONNECTION.CLIENT_NAME,
|
||||
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
|
||||
documentation: `https://doc.evolution-api.com`,
|
||||
whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),
|
||||
});
|
||||
})
|
||||
.post('/verify-creds', authGuard['apikey'], async (req, res) => {
|
||||
const facebookConfig = configService.get<Facebook>('FACEBOOK');
|
||||
return res.status(HttpStatus.OK).json({
|
||||
status: HttpStatus.OK,
|
||||
message: 'Credentials are valid',
|
||||
facebookAppId: process.env.FACEBOOK_APP_ID,
|
||||
facebookConfigId: process.env.FACEBOOK_CONFIG_ID,
|
||||
facebookUserToken: process.env.FACEBOOK_USER_TOKEN,
|
||||
facebookAppId: facebookConfig.APP_ID,
|
||||
facebookConfigId: facebookConfig.CONFIG_ID,
|
||||
facebookUserToken: facebookConfig.USER_TOKEN,
|
||||
});
|
||||
})
|
||||
.use('/instance', new InstanceRouter(configService, ...guards).router)
|
||||
|
||||
@ -9,7 +9,7 @@ import { TypebotService } from '@api/integrations/chatbot/typebot/services/typeb
|
||||
import { PrismaRepository, Query } from '@api/repository/repository.service';
|
||||
import { eventManager, waMonitor } from '@api/server.module';
|
||||
import { Events, wa } from '@api/types/wa.types';
|
||||
import { Auth, Chatwoot, ConfigService, HttpServer } from '@config/env.config';
|
||||
import { Auth, Chatwoot, ConfigService, HttpServer, Proxy } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { NotFoundException } from '@exceptions';
|
||||
import { Contact, Message, Prisma } from '@prisma/client';
|
||||
@ -364,13 +364,14 @@ export class ChannelStartupService {
|
||||
public async loadProxy() {
|
||||
this.localProxy.enabled = false;
|
||||
|
||||
if (process.env.PROXY_HOST) {
|
||||
const proxyConfig = this.configService.get<Proxy>('PROXY');
|
||||
if (proxyConfig.HOST) {
|
||||
this.localProxy.enabled = true;
|
||||
this.localProxy.host = process.env.PROXY_HOST;
|
||||
this.localProxy.port = process.env.PROXY_PORT || '80';
|
||||
this.localProxy.protocol = process.env.PROXY_PROTOCOL || 'http';
|
||||
this.localProxy.username = process.env.PROXY_USERNAME;
|
||||
this.localProxy.password = process.env.PROXY_PASSWORD;
|
||||
this.localProxy.host = proxyConfig.HOST;
|
||||
this.localProxy.port = proxyConfig.PORT || '80';
|
||||
this.localProxy.protocol = proxyConfig.PROTOCOL || 'http';
|
||||
this.localProxy.username = proxyConfig.USERNAME;
|
||||
this.localProxy.password = proxyConfig.PASSWORD;
|
||||
}
|
||||
|
||||
const data = await this.prismaRepository.proxy.findUnique({
|
||||
|
||||
@ -156,6 +156,7 @@ export type Sqs = {
|
||||
export type Websocket = {
|
||||
ENABLED: boolean;
|
||||
GLOBAL_EVENTS: boolean;
|
||||
ALLOWED_HOSTS?: string;
|
||||
};
|
||||
|
||||
export type WaBusiness = {
|
||||
@ -320,6 +321,46 @@ export type S3 = {
|
||||
};
|
||||
|
||||
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
|
||||
export type Metrics = {
|
||||
ENABLED: boolean;
|
||||
AUTH_REQUIRED: boolean;
|
||||
USER?: string;
|
||||
PASSWORD?: string;
|
||||
ALLOWED_IPS?: string;
|
||||
};
|
||||
|
||||
export type Telemetry = {
|
||||
ENABLED: boolean;
|
||||
URL?: string;
|
||||
};
|
||||
|
||||
export type Proxy = {
|
||||
HOST?: string;
|
||||
PORT?: string;
|
||||
PROTOCOL?: string;
|
||||
USERNAME?: string;
|
||||
PASSWORD?: string;
|
||||
};
|
||||
|
||||
export type AudioConverter = {
|
||||
API_URL?: string;
|
||||
API_KEY?: string;
|
||||
};
|
||||
|
||||
export type Facebook = {
|
||||
APP_ID?: string;
|
||||
CONFIG_ID?: string;
|
||||
USER_TOKEN?: string;
|
||||
};
|
||||
|
||||
export type Sentry = {
|
||||
DSN?: string;
|
||||
};
|
||||
|
||||
export type EventEmitter = {
|
||||
MAX_LISTENERS: number;
|
||||
};
|
||||
|
||||
export type Production = boolean;
|
||||
|
||||
export interface Env {
|
||||
@ -351,6 +392,13 @@ export interface Env {
|
||||
CACHE: CacheConf;
|
||||
S3?: S3;
|
||||
AUTHENTICATION: Auth;
|
||||
METRICS: Metrics;
|
||||
TELEMETRY: Telemetry;
|
||||
PROXY: Proxy;
|
||||
AUDIO_CONVERTER: AudioConverter;
|
||||
FACEBOOK: Facebook;
|
||||
SENTRY: Sentry;
|
||||
EVENT_EMITTER: EventEmitter;
|
||||
PRODUCTION?: Production;
|
||||
}
|
||||
|
||||
@ -542,6 +590,7 @@ export class ConfigService {
|
||||
WEBSOCKET: {
|
||||
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
|
||||
GLOBAL_EVENTS: process.env?.WEBSOCKET_GLOBAL_EVENTS === 'true',
|
||||
ALLOWED_HOSTS: process.env?.WEBSOCKET_ALLOWED_HOSTS,
|
||||
},
|
||||
PUSHER: {
|
||||
ENABLED: process.env?.PUSHER_ENABLED === 'true',
|
||||
@ -730,6 +779,39 @@ export class ConfigService {
|
||||
},
|
||||
EXPOSE_IN_FETCH_INSTANCES: process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true',
|
||||
},
|
||||
METRICS: {
|
||||
ENABLED: process.env?.PROMETHEUS_METRICS === 'true',
|
||||
AUTH_REQUIRED: process.env?.METRICS_AUTH_REQUIRED === 'true',
|
||||
USER: process.env?.METRICS_USER,
|
||||
PASSWORD: process.env?.METRICS_PASSWORD,
|
||||
ALLOWED_IPS: process.env?.METRICS_ALLOWED_IPS,
|
||||
},
|
||||
TELEMETRY: {
|
||||
ENABLED: process.env?.TELEMETRY_ENABLED === undefined || process.env?.TELEMETRY_ENABLED === 'true',
|
||||
URL: process.env?.TELEMETRY_URL,
|
||||
},
|
||||
PROXY: {
|
||||
HOST: process.env?.PROXY_HOST,
|
||||
PORT: process.env?.PROXY_PORT,
|
||||
PROTOCOL: process.env?.PROXY_PROTOCOL,
|
||||
USERNAME: process.env?.PROXY_USERNAME,
|
||||
PASSWORD: process.env?.PROXY_PASSWORD,
|
||||
},
|
||||
AUDIO_CONVERTER: {
|
||||
API_URL: process.env?.API_AUDIO_CONVERTER,
|
||||
API_KEY: process.env?.API_AUDIO_CONVERTER_KEY,
|
||||
},
|
||||
FACEBOOK: {
|
||||
APP_ID: process.env?.FACEBOOK_APP_ID,
|
||||
CONFIG_ID: process.env?.FACEBOOK_CONFIG_ID,
|
||||
USER_TOKEN: process.env?.FACEBOOK_USER_TOKEN,
|
||||
},
|
||||
SENTRY: {
|
||||
DSN: process.env?.SENTRY_DSN,
|
||||
},
|
||||
EVENT_EMITTER: {
|
||||
MAX_LISTENERS: Number.parseInt(process.env?.EVENT_EMITTER_MAX_LISTENERS) || 50,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { configService, EventEmitter as EventEmitterConfig } from '@config/env.config';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
|
||||
const maxListeners = parseInt(process.env.EVENT_EMITTER_MAX_LISTENERS, 10) || 50;
|
||||
const eventEmitterConfig = configService.get<EventEmitterConfig>('EVENT_EMITTER');
|
||||
|
||||
export const eventEmitter = new EventEmitter2({
|
||||
delimiter: '.',
|
||||
newListener: false,
|
||||
ignoreErrors: false,
|
||||
maxListeners: maxListeners,
|
||||
maxListeners: eventEmitterConfig.MAX_LISTENERS,
|
||||
});
|
||||
|
||||
13
src/main.ts
13
src/main.ts
@ -6,7 +6,15 @@ import { ProviderFiles } from '@api/provider/sessions';
|
||||
import { PrismaRepository } from '@api/repository/repository.service';
|
||||
import { HttpStatus, router } from '@api/routes/index.router';
|
||||
import { eventManager, waMonitor } from '@api/server.module';
|
||||
import { Auth, configService, Cors, HttpServer, ProviderSession, Webhook } from '@config/env.config';
|
||||
import {
|
||||
Auth,
|
||||
configService,
|
||||
Cors,
|
||||
HttpServer,
|
||||
ProviderSession,
|
||||
Sentry as SentryConfig,
|
||||
Webhook,
|
||||
} from '@config/env.config';
|
||||
import { onUnexpectedError } from '@config/error.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { ROOT_DIR } from '@config/path.config';
|
||||
@ -140,7 +148,8 @@ async function bootstrap() {
|
||||
|
||||
eventManager.init(server);
|
||||
|
||||
if (process.env.SENTRY_DSN) {
|
||||
const sentryConfig = configService.get<SentryConfig>('SENTRY');
|
||||
if (sentryConfig.DSN) {
|
||||
logger.info('Sentry - ON');
|
||||
|
||||
// Add this after all routes,
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { configService, Sentry as SentryConfig } from '@config/env.config';
|
||||
import * as Sentry from '@sentry/node';
|
||||
|
||||
const dsn = process.env.SENTRY_DSN;
|
||||
const sentryConfig = configService.get<SentryConfig>('SENTRY');
|
||||
|
||||
if (dsn) {
|
||||
if (sentryConfig.DSN) {
|
||||
Sentry.init({
|
||||
dsn: dsn,
|
||||
dsn: sentryConfig.DSN,
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
tracesSampleRate: 1.0,
|
||||
profilesSampleRate: 1.0,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { configService, Telemetry } from '@config/env.config';
|
||||
import axios from 'axios';
|
||||
import fs from 'fs';
|
||||
|
||||
@ -10,9 +11,9 @@ export interface TelemetryData {
|
||||
}
|
||||
|
||||
export const sendTelemetry = async (route: string): Promise<void> => {
|
||||
const enabled = process.env.TELEMETRY_ENABLED === undefined || process.env.TELEMETRY_ENABLED === 'true';
|
||||
const telemetryConfig = configService.get<Telemetry>('TELEMETRY');
|
||||
|
||||
if (!enabled) {
|
||||
if (!telemetryConfig.ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -27,9 +28,7 @@ export const sendTelemetry = async (route: string): Promise<void> => {
|
||||
};
|
||||
|
||||
const url =
|
||||
process.env.TELEMETRY_URL && process.env.TELEMETRY_URL !== ''
|
||||
? process.env.TELEMETRY_URL
|
||||
: 'https://log.evolution-api.com/telemetry';
|
||||
telemetryConfig.URL && telemetryConfig.URL !== '' ? telemetryConfig.URL : 'https://log.evolution-api.com/telemetry';
|
||||
|
||||
axios
|
||||
.post(url, telemetry)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { prismaRepository } from '@api/server.module';
|
||||
import { CacheService } from '@api/services/cache.service';
|
||||
import { CacheConf, configService } from '@config/env.config';
|
||||
import { INSTANCE_DIR } from '@config/path.config';
|
||||
import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from 'baileys';
|
||||
import fs from 'fs/promises';
|
||||
@ -85,9 +86,10 @@ export default async function useMultiFileAuthStatePrisma(
|
||||
|
||||
async function writeData(data: any, key: string): Promise<any> {
|
||||
const dataString = JSON.stringify(data, BufferJSON.replacer);
|
||||
const cacheConfig = configService.get<CacheConf>('CACHE');
|
||||
|
||||
if (key != 'creds') {
|
||||
if (process.env.CACHE_REDIS_ENABLED === 'true') {
|
||||
if (cacheConfig.REDIS.ENABLED) {
|
||||
return await cache.hSet(sessionId, key, data);
|
||||
} else {
|
||||
await fs.writeFile(localFile(key), dataString);
|
||||
@ -101,9 +103,10 @@ export default async function useMultiFileAuthStatePrisma(
|
||||
async function readData(key: string): Promise<any> {
|
||||
try {
|
||||
let rawData;
|
||||
const cacheConfig = configService.get<CacheConf>('CACHE');
|
||||
|
||||
if (key != 'creds') {
|
||||
if (process.env.CACHE_REDIS_ENABLED === 'true') {
|
||||
if (cacheConfig.REDIS.ENABLED) {
|
||||
return await cache.hGet(sessionId, key);
|
||||
} else {
|
||||
if (!(await fileExists(localFile(key)))) return null;
|
||||
@ -123,8 +126,10 @@ export default async function useMultiFileAuthStatePrisma(
|
||||
|
||||
async function removeData(key: string): Promise<any> {
|
||||
try {
|
||||
const cacheConfig = configService.get<CacheConf>('CACHE');
|
||||
|
||||
if (key != 'creds') {
|
||||
if (process.env.CACHE_REDIS_ENABLED === 'true') {
|
||||
if (cacheConfig.REDIS.ENABLED) {
|
||||
return await cache.hDelete(sessionId, key);
|
||||
} else {
|
||||
await fs.unlink(localFile(key));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user