Spaces:
No application file
No application file
| 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; |