import express, { static as expressStatic, json, urlencoded } from 'express'; import { createServer } from 'http'; import { Server } from 'socket.io'; import cors from 'cors'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import cookieParser from 'cookie-parser'; // Get __dirname equivalent in ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Import configuration import { config, validateConfig } from './src/config/index.js'; import { initializeDatabase } from './src/config/database.js'; // Runtime detection const runtime = { isBun: typeof Bun !== 'undefined', isNode: typeof process !== 'undefined' && process.versions && process.versions.node, name: typeof Bun !== 'undefined' ? 'bun' : 'node' }; // Import services import WhatsAppService from './src/services/WhatsAppService.js'; import WebhookService from './src/services/WebhookService.js'; import SchedulerService from './src/services/SchedulerService.js'; import { getMany } from './src/services/SettingsService.js'; // Import routes import authRoutes from './src/routes/auth.js'; import appRoutes from './src/routes/app.js'; import whatsappRoutes from './src/routes/whatsapp.js'; import contactRoutes from './src/routes/contacts.js'; import autoReplyRoutes from './src/routes/autoReply.js'; import apiKeyRoutes from './src/routes/apiKeys.js'; import settingsRoutes from './src/routes/settings.js'; import userRoutes from './src/routes/users.js'; import scheduledMessagesRoutes from './src/routes/scheduledMessages.js'; // Import utilities import { info, error as _error, warn } from './src/utils/logger.js'; class Application { constructor() { this.app = express(); this.server = createServer(this.app); this.io = null; this.whatsappService = null; this.schedulerService = null; } /** * Initialize the application */ async initialize() { try { // Validate configuration validateConfig(); info(`Configuration validated successfully - Runtime: ${runtime.name}`); // Initialize database await initializeDatabase(); info('Database initialized successfully'); // Setup Express app this.setupExpress(); // Setup Socket.IO this.setupSocketIO(); // Setup WhatsApp service await this.setupWhatsAppService(); // Setup routes this.setupRoutes(); // Setup error handling this.setupErrorHandling(); info('Application initialized successfully'); } catch (error) { _error('Failed to initialize application', error); throw error; } } /** * Setup Express application */ setupExpress() { // View engine setup this.app.set('view engine', 'ejs'); this.app.set('views', join(__dirname, 'views')); // Middleware const publicDir = expressStatic(join(__dirname, 'public')); this.app.use(publicDir); this.app.use(cors()); // Custom JSON parser with better error handling this.app.use(json({ verify: (req, res, buf, encoding) => { try { // Store raw body for potential fixing req.rawBody = buf.toString(encoding); } catch (err) { // Continue with default behavior } } })); this.app.use(urlencoded({ extended: true })); this.app.use(cookieParser(config.session.secret)); // Request logging middleware this.app.use((req, res, next) => { info(`${req.method} ${req.path}`, { ip: req.ip, userAgent: req.get('User-Agent'), runtime: runtime.name }); next(); }); info('Express application configured'); } /** * Setup Socket.IO */ setupSocketIO() { this.io = new Server(this.server, { cors: { origin: "*", methods: ["GET", "POST"] } }); // Socket connection handling this.io.on('connection', (socket) => { const userId = socket.handshake.query.userId; if (userId) { socket.join(userId); info(`Socket connected for user: ${userId}`); } socket.on('disconnect', () => { info(`Socket disconnected for user: ${userId}`); }); }); // Make io available to routes this.app.set('io', this.io); info('Socket.IO configured'); } /** * Setup WhatsApp service */ async setupWhatsAppService() { this.whatsappService = new WhatsAppService(this.io); // Load settings from database await this.whatsappService.loadSettings(); // Make whatsapp service available to routes this.app.set('whatsappService', this.whatsappService); // Setup Webhook service with dynamic settings const getWebhookSettings = async () => { const map = await getMany(['webhook_url', 'webhook_secret']); return { url: map.get('webhook_url') || null, secret: map.get('webhook_secret') || null }; }; this.webhookService = new WebhookService(getWebhookSettings); this.app.set('webhookService', this.webhookService); // Attach webhooks to WhatsAppService this.whatsappService.setWebhookService(this.webhookService); // Setup Scheduler service this.schedulerService = new SchedulerService(this.whatsappService); this.app.set('schedulerService', this.schedulerService); this.schedulerService.start(); info('WhatsApp service configured'); } /** * Setup application routes */ setupRoutes() { // Authentication routes this.app.use('/', authRoutes); // Main application routes this.app.use('/', appRoutes); // WhatsApp API routes this.app.use('/', whatsappRoutes); // Contact management routes (Disabled) // this.app.use('/contacts', contactRoutes); // Auto-reply routes this.app.use('/auto-reply', autoReplyRoutes); // API key management routes this.app.use('/api-keys', apiKeyRoutes); // Settings routes this.app.use('/settings', settingsRoutes); // User management routes this.app.use('/users', userRoutes); // Scheduled messages routes this.app.use('/scheduled-messages', scheduledMessagesRoutes); info('Routes configured'); } /** * Setup error handling */ setupErrorHandling() { // 404 handler this.app.use((req, res) => { warn(`404 - Page not found: ${req.path}`); res.status(404).render('error', { error: 'Page not found', message: 'The page you are looking for does not exist.' }); }); // Global error handler this.app.use((err, req, res, next) => { _error('Unhandled error', err); // Handle JSON parsing errors specifically if (err.type === 'entity.parse.failed' && err.body && req.rawBody) { warn('JSON parse error detected, attempting to fix backticks in message'); try { // Fix backticks in the raw body const fixedBody = req.rawBody.replace(/`([^`]*)`/g, '"$1"'); const parsedBody = JSON.parse(fixedBody); // Store the fixed body for the route to use req.body = parsedBody; // Log the fix info('Successfully fixed JSON with backticks', { original: req.rawBody.substring(0, 200) + '...', fixed: fixedBody.substring(0, 200) + '...' }); // Continue to the route handler return next(); } catch (fixError) { _error('Failed to fix JSON parsing error', fixError); } } const statusCode = err.statusCode || 500; const message = config.node_env === 'production' ? 'Something went wrong' : err.message; // Return JSON error for API endpoints if (req.path.startsWith('/api/') || req.path.includes('/send-message') || req.path.includes('/send-bulk')) { return res.status(statusCode).json({ error: 'request_error', message: err.type === 'entity.parse.failed' ? 'Invalid JSON format. Please check for unescaped backticks or quotes in your message.' : message, details: config.node_env !== 'production' ? err.stack : undefined }); } res.status(statusCode).render('error', { error: 'Internal Server Error', message: message }); }); info('Error handling configured'); } /** * Start the server */ async start() { try { await this.initialize(); // Preload WhatsApp sessions for existing users if (!process.env.JEST_WORKER_ID) { await this.whatsappService.preloadSessions(); } // Start server this.server.listen(config.port, '0.0.0.0', () => { info(`WhatsApp service running on port ${config.port}`); info(`Server started successfully on ${runtime.name} runtime`, { port: config.port, runtime: runtime.name, optimizations: runtime.isBun ? 'enabled' : 'disabled' }); console.log(`🚀 Server running on http://localhost:${config.port} (${runtime.name})`); if (runtime.isBun) { console.log('⚡ Bun optimizations active'); } }); } catch (error) { _error('Failed to start server', error); process.exit(1); } } /** * Graceful shutdown */ async shutdown() { info('Shutting down server...'); // Stop scheduler if (this.schedulerService) { this.schedulerService.stop(); } // Close server this.server.close(() => { info('Server shut down successfully'); process.exit(0); }); } } // Instantiate application const appInstance = new Application(); // For production/development startup if (!process.env.JEST_WORKER_ID) { // Handle graceful shutdown process.on('SIGTERM', () => appInstance.shutdown()); process.on('SIGINT', () => appInstance.shutdown()); // Start the application appInstance.start().catch(error => { console.error('Failed to start application:', error); process.exit(1); }); } // Export app and server for testing export const app = appInstance.app; export const server = appInstance.server; export const application = appInstance; export default appInstance;