import express from 'express'; //import cors from 'cors'; import dotenv from 'dotenv'; import { WhatsAppClient } from './whatsapp/client'; import { QRSocketServer } from './sockets/qr.socket'; import { SendController } from './api/send'; import { StatusController } from './api/status'; import { SessionController } from './api/session'; import { MessagesController } from './api/messages'; import { n8nController } from './api/n8n'; import { WebhookController } from './api/webhook'; import { AuthMiddleware } from './middleware/auth'; import { logger } from './config/logger'; dotenv.config(); class WhatsAppGateway { private app: express.Application; private whatsappClient!: WhatsAppClient; private socketServer!: QRSocketServer; private sendController!: SendController; private statusController!: StatusController; private sessionController!: SessionController; private messagesController!: MessagesController; private n8nController!: n8nController; private webhookController!: WebhookController; constructor() { this.app = express(); this.setupMiddleware(); this.initializeComponents(); this.setupRoutes(); } private setupMiddleware(): void { // Configure CORS for n8n and Manager const corsOrigins = [ ...(process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:3002']), 'http://localhost:3004', // Add 3004 for current setup 'http://localhost:5678', // n8n default 'http://localhost:5679', // n8n alternative ...(process.env.N8N_ORIGINS ? process.env.N8N_ORIGINS.split(',') : []) ]; // ⚠️ SOLUCIÓN BRUTA — SOLO DEV this.app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // Intercepta preflight if (req.method === 'OPTIONS') { return res.sendStatus(204); } next(); }); /* this.app.use(cors({ origin: corsOrigins, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }));*/ this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true })); } private initializeComponents(): void { const sessionId = process.env.SESSION_ID || 'default'; this.whatsappClient = new WhatsAppClient(sessionId); this.socketServer = new QRSocketServer(3003); this.sendController = new SendController(this.whatsappClient); this.statusController = new StatusController(this.whatsappClient); this.sessionController = new SessionController(this.whatsappClient); this.messagesController = new MessagesController(); this.n8nController = new n8nController(this.whatsappClient); this.webhookController = new WebhookController(process.env.WEBHOOK_URL); this.setupWhatsAppEvents(); this.connectWhatsApp(); this.setupWebSocketCommands(); // Send current state when new clients connect this.socketServer.onClientConnect = () => { const currentState = this.whatsappClient.getConnectionState(); this.socketServer.sendStatus(currentState); }; } private setupWebSocketCommands(): void { // Handle WebSocket commands from Manager this.socketServer.onCommand = async (command: string, data?: any) => { logger.info(`Received WebSocket command: ${command}`); switch (command) { case 'restart_session': await this.sessionController.restartSession({} as any, { json: () => {} } as any); break; case 'logout_session': await this.sessionController.logoutSession({} as any, { json: () => {} } as any); break; case 'generate_token': const tokenResponse = await this.sessionController.generateToken({} as any, { json: (data: any) => { this.socketServer.broadcast({ type: 'token_generated', data: JSON.stringify(data) }); } } as any); break; case 'get_recent_messages': await this.messagesController.getRecentMessages({} as any, { json: (data: any) => { this.socketServer.broadcast({ type: 'status', data: JSON.stringify(data) }); } } as any); break; default: logger.warn(`Unknown WebSocket command: ${command}`); } }; } private setupWhatsAppEvents(): void { this.whatsappClient.onQR((qr: string) => { this.socketServer.sendQR(qr); }); this.whatsappClient.onStatus((status: string) => { this.socketServer.sendStatus(status); logger.info(`WhatsApp status: ${status}`); }); this.whatsappClient.onMessage(async (message: any) => { try { logger.info('Message received via WhatsApp'); if (!message || !message.key) { logger.warn('Invalid message structure received'); return; } // Store in recent messages this.messagesController.handleMessage(message); // Forward to webhook await this.webhookController.receiveMessage({ id: message.key.id || Date.now().toString(), from: message.key.remoteJid || 'unknown', content: message.message?.conversation || message.message?.extendedTextMessage?.text || '', type: message.message?.conversation ? 'text' : 'document', timestamp: new Date().toISOString() }); // Notify Manager this.socketServer.broadcast({ type: 'status', data: 'message_received' }); } catch (error) { logger.error(`Failed to process message: ${error}`); } }); } private async connectWhatsApp(): Promise { try { await this.whatsappClient.connect(); logger.info('WhatsApp client initialized'); } catch (error) { logger.error(`Failed to initialize WhatsApp client: ${error}`); } } private setupRoutes(): void { // Legacy API Routes (Manager) this.app.post('/api/send', this.sendController.sendMessage); this.app.post('/api/send/bulk', this.sendController.sendBulk); this.app.get('/api/status', this.statusController.getStatus); this.app.get('/api/health', this.statusController.getHealth); // Session management (Manager) this.app.post('/api/session/restart', this.sessionController.restartSession); this.app.post('/api/session/logout', this.sessionController.logoutSession); this.app.post('/api/token', this.sessionController.generateToken); // Messages (Manager) this.app.get('/api/messages', this.messagesController.getRecentMessages); this.app.delete('/api/messages', this.messagesController.clearMessages); // n8n API Core (Component 3) this.app.post('/api/messages/send', AuthMiddleware.middleware('send'), this.n8nController.sendMessage); this.app.get('/api/groups', AuthMiddleware.middleware('messages'), this.n8nController.getGroups); this.app.get('/api/status', AuthMiddleware.middleware('status'), this.n8nController.getStatus); this.app.post('/api/n8n/token', AuthMiddleware.middleware('status'), this.n8nController.validateToken); // Public n8n token generation (no auth required) this.app.post('/api/n8n/generate-token', this.n8nController.generateToken); // Webhooks for n8n (receiving messages) this.app.post('/webhook/whatsapp', (req, res) => { // This endpoint is for n8n to receive messages FROM WhatsApp res.json({ success: true, message: 'Webhook endpoint active' }); }); // Webhook configuration this.app.post('/api/webhook/configure', AuthMiddleware.middleware('messages'), this.webhookController.configureWebhook); this.app.get('/api/webhook/status', AuthMiddleware.middleware('messages'), this.webhookController.getWebhookStatus); this.app.post('/api/webhook/test', AuthMiddleware.middleware('messages'), this.webhookController.testWebhook); // Root endpoint this.app.get('/', (req, res) => { res.json({ name: 'WhatsApp Gateway', version: '1.0.0', status: 'running', endpoints: { // Manager endpoints send: 'POST /api/send', sendBulk: 'POST /api/send/bulk', status: 'GET /api/status', health: 'GET /api/health', sessionRestart: 'POST /api/session/restart', sessionLogout: 'POST /api/session/logout', token: 'POST /api/token', messages: 'GET /api/messages', websocket: 'ws://localhost:3003', // n8n API Core endpoints n8nSend: 'POST /api/messages/send (Bearer auth required)', n8nStatus: 'GET /api/status (Bearer auth required)', n8nToken: 'POST /api/n8n/generate-token', webhook: 'POST /webhook/whatsapp', webhookConfig: 'POST /api/webhook/configure', webhookTest: 'POST /api/webhook/test' }, authentication: { type: 'Bearer Token', header: 'Authorization: Bearer ', generate: 'POST /api/n8n/generate-token' } }); }); // Error handling this.app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { logger.error(`Unhandled error: ${err}`); res.status(500).json({ success: false, error: 'Internal server error', timestamp: new Date().toISOString() }); }); } async start(): Promise { const port = parseInt(process.env.PORT || '3001'); this.app.listen(port, () => { logger.info(`WhatsApp Gateway API running on port ${port}`); logger.info(`WebSocket server running on port 3003`); logger.info(`Manager Web should connect to: http://localhost:3002`); }); } async stop(): Promise { await this.whatsappClient.disconnect(); this.socketServer.close(); logger.info('WhatsApp Gateway stopped'); } } // Start the gateway const gateway = new WhatsAppGateway(); gateway.start().catch(error => { logger.error(`Failed to start gateway: ${error}`); process.exit(1); }); // Graceful shutdown process.on('SIGINT', async () => { logger.info('Received SIGINT, shutting down gracefully...'); await gateway.stop(); process.exit(0); }); process.on('SIGTERM', async () => { logger.info('Received SIGTERM, shutting down gracefully...'); await gateway.stop(); process.exit(0); });