amazon-modern-widgets
Version:
Amazon Modern Widgets for Amazon affiliate websites based on Amazon PAAPI v5
302 lines • 12.3 kB
JavaScript
;
/**
* 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