293 lines
10 KiB
TypeScript
293 lines
10 KiB
TypeScript
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<void> {
|
|
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 <token>',
|
|
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<void> {
|
|
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<void> {
|
|
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);
|
|
}); |