UNPKG

amazon-modern-widgets

Version:

Amazon Modern Widgets for Amazon affiliate websites based on Amazon PAAPI v5

302 lines 12.3 kB
"use strict"; /** * Main Server Class * ---------------------------------------------- * Amazon Modern Widgets (AMW). * * @author : Ludovic Toinel <ludovic@toinel.com> * @src : https://github.com/ltoinel/amw */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AmwServer = void 0; // Lets import our required libraries const config_1 = __importDefault(require("config")); const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const ioredis_1 = __importDefault(require("ioredis")); const express_cache_controller_1 = __importDefault(require("express-cache-controller")); const ConfigLog4j_1 = require("../utils/ConfigLog4j"); const AmwApi_1 = require("./AmwApi"); /** * AMW Server Class */ class AmwServer { /** * Main AmwServer constructor. */ constructor() { try { this.validateConfiguration(); this.initializeLogger(); this.initializeExpress(); this.initializeRedis(); this.initializeApi(); this.setupRoutes(); this.setupErrorHandling(); this.setupGracefulShutdown(); } catch (error) { this.log.error('Failed to initialize AMW Server:', error); throw error; } } /** * Validate server configuration */ validateConfiguration() { if (AmwServer.PORT < 1 || AmwServer.PORT > 65535) { throw new Error(`Invalid port configuration: ${AmwServer.PORT}. Must be between 1 and 65535.`); } if (!AmwServer.RELATIVE_PATH || !AmwServer.RELATIVE_PATH.startsWith('/')) { throw new Error(`Invalid relative path: ${AmwServer.RELATIVE_PATH}. Must start with '/'.`); } if (AmwServer.HTTP_CACHE < 0) { throw new Error(`Invalid HTTP cache configuration: ${AmwServer.HTTP_CACHE}. Must be >= 0.`); } } /** * Initialize logger */ initializeLogger() { this.log = (0, ConfigLog4j_1.getLogger)("AmwServer"); this.log.info("Logger initialized successfully"); } /** * Initialize Express application */ initializeExpress() { this.app = (0, express_1.default)(); // Enable CORS if configured if (AmwServer.CORS_ENABLED) { this.app.use((0, cors_1.default)()); this.log.info("CORS enabled"); } // Configure cache control this.app.use((0, express_cache_controller_1.default)({ maxAge: AmwServer.HTTP_CACHE })); this.log.info("Express application initialized"); } /** * Initialize Redis cache connection */ initializeRedis() { if (AmwServer.CACHE_ENABLED) { try { this.cache = new ioredis_1.default({ host: config_1.default.get('Redis.host'), port: config_1.default.get('Redis.port'), username: config_1.default.get('Redis.username'), password: config_1.default.get('Redis.password'), maxRetriesPerRequest: 3, lazyConnect: true, }); this.cache.on('connect', () => { this.log.info('Redis connection established'); }); this.cache.on('error', (error) => { this.log.error('Redis connection error:', error); }); this.cache.on('close', () => { this.log.warn('Redis connection closed'); }); } catch (error) { this.log.error('Failed to initialize Redis:', error); throw new Error(`Redis initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } else { this.log.info("Redis cache is disabled"); } } /** * Initialize API instance */ initializeApi() { this.api = new AmwApi_1.AmwApi(this.cache); this.log.info("API instance created"); } /** * Setup application routes */ setupRoutes() { const basePath = AmwServer.RELATIVE_PATH; // Product endpoint this.app.get(`${basePath}/product`, (req, res) => { this.api.setProductEndpoint(req, res); }); // Widget JavaScript endpoint this.app.get(`${basePath}/widget.js`, (req, res) => { this.api.setWidgetEndpoint(req, res); }); // Health check endpoint this.app.get(`${basePath}/health`, (req, res) => { const healthStatus = { status: 'ok', version: AmwServer.RELEASE, timestamp: new Date().toISOString(), uptime: process.uptime(), redis: AmwServer.CACHE_ENABLED ? (this.cache && this.cache.status === 'ready' ? 'connected' : 'disconnected') : 'disabled' }; res.status(200).json(healthStatus); }); // Server info endpoint this.app.get(`${basePath}/info`, (req, res) => { const serverInfo = { name: 'Amazon Modern Widgets (AMW)', version: AmwServer.RELEASE, author: 'Ludovic Toinel', source: 'https://github.com/ltoinel/amw', endpoints: [ `${basePath}/product`, `${basePath}/widget.js`, `${basePath}/health`, `${basePath}/info` ] }; res.status(200).json(serverInfo); }); this.log.info(`Routes configured with base path: ${basePath}`); } /** * Setup error handling middleware */ setupErrorHandling() { // Global error handler this.app.use((error, req, res, _next) => { this.log.error(`Unhandled error: ${error.message}`, error); if (!res.headersSent) { res.status(500).json({ error: 'Internal Server Error', message: AmwServer.DEBUG ? error.message : 'Something went wrong', timestamp: new Date().toISOString() }); } }); // 404 handler this.app.use((req, res) => { res.status(404).json({ error: 'Not Found', message: `Route ${req.originalUrl} not found`, timestamp: new Date().toISOString() }); }); this.log.info("Error handling middleware configured"); } /** * Setup graceful shutdown handlers */ setupGracefulShutdown() { const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; signals.forEach(signal => { process.on(signal, () => { this.log.info(`Received ${signal}, starting graceful shutdown...`); this.gracefulShutdown(); }); }); } /** * Graceful shutdown handler */ gracefulShutdown() { return __awaiter(this, void 0, void 0, function* () { try { this.log.info('Starting graceful shutdown...'); // Close HTTP server if (this.server) { yield new Promise((resolve) => { this.server.close(() => { this.log.info('HTTP server closed'); resolve(); }); }); } // Close Redis connection if exists if (this.cache) { try { yield this.cache.quit(); this.log.info('Redis connection closed'); } catch (redisError) { this.log.warn('Error closing Redis connection:', redisError); } } this.log.info('AMW Server shutdown completed successfully'); // eslint-disable-next-line no-process-exit process.exit(0); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.log.error(`Error during graceful shutdown: ${errorMessage}`, error); // eslint-disable-next-line no-process-exit process.exit(1); } }); } /** * Start the Express Webserver. */ start() { return __awaiter(this, void 0, void 0, function* () { try { // Connect to Redis if enabled if (AmwServer.CACHE_ENABLED && this.cache) { yield this.cache.connect(); this.log.info('Redis connection established'); } // Start the HTTP server const server = this.app.listen(AmwServer.PORT, () => { this.log.info(`AMW ${AmwServer.RELEASE} is Starting ...`); this.log.info(`Loading ${process.env.NODE_ENV || 'production'}.yaml settings`); this.log.info(`------------------------------------`); this.log.info(` |- Port = ${AmwServer.PORT}`); this.log.info(` |- Relative path = ${AmwServer.RELATIVE_PATH}`); this.log.info(` |- Redis cache = ${AmwServer.CACHE_ENABLED}`); this.log.info(` |- CORS = ${AmwServer.CORS_ENABLED}`); this.log.info(` |- Debug = ${AmwServer.DEBUG}`); this.log.info(` |- HTTP Cache = ${AmwServer.HTTP_CACHE}s`); this.log.info(`------------------------------------`); this.log.info(`>>> AMW Server Ready: http://localhost:${AmwServer.PORT}${AmwServer.RELATIVE_PATH}`); this.log.info(`>>> Health Check: http://localhost:${AmwServer.PORT}${AmwServer.RELATIVE_PATH}/health`); }); // Handle server errors server.on('error', (error) => { this.log.error(`Server error: ${error.message}`, error); // eslint-disable-next-line no-process-exit process.exit(1); }); // Store server reference for graceful shutdown this.server = server; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.log.error(`Failed to start server: ${errorMessage}`, error); throw error; } }); } } exports.AmwServer = AmwServer; // Static attributes AmwServer.RELEASE = "3.0.0"; AmwServer.DEFAULT_PORT = 8080; AmwServer.PORT = Number(config_1.default.get('Server.port')) || AmwServer.DEFAULT_PORT; AmwServer.RELATIVE_PATH = config_1.default.get('Server.path') || '/amw'; AmwServer.DEBUG = config_1.default.get('Server.debug') === 'true' || config_1.default.get('Server.debug') === true; AmwServer.CORS_ENABLED = config_1.default.get('Server.cors') === 'true' || config_1.default.get('Server.cors') === true; AmwServer.CACHE_ENABLED = config_1.default.get('Redis.enabled') === true; AmwServer.HTTP_CACHE = Number(config_1.default.get('Server.httpCache')) || 300; //# sourceMappingURL=AmwServer.js.map