import http from "node:http"; import { join } from "node:path"; import { readFileSync, readdirSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; import vm from "node:vm"; import axios from "axios"; import { v4 as uuidv4 } from "uuid"; import jwt from "jsonwebtoken"; import { create as xmlBuilder } from "xmlbuilder2"; import { logger } from "./logger.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const globalVMContext = { URLSearchParams, console: logger, crypto, axios, uuidv4, jwt, xmlBuilder, }; let globalVariableContext = {}; /** * Load all JSON files from global/ directory and make them available as global objects * Pattern: global/filename.json -> globalVariableContext['filename'] */ function loadGlobalObjects() { const globalDir = join(__dirname, "..", "global"); try { const files = readdirSync(globalDir).filter( (f) => f.endsWith(".json") && !f.endsWith(".example"), ); files.forEach((file) => { const objectName = file.replace(".json", ""); const filePath = join(globalDir, file); try { const content = readFileSync(filePath, "utf-8"); const data = JSON.parse(content); globalVariableContext[objectName] = data; logger.info(`Loaded global object: ${objectName}`, { file: file, keys: Object.keys(data), }); } catch (error) { logger.error(`Failed to load global object from ${file}`, { error: error.message, }); throw error; } }); logger.info(`Loaded ${files.length} global objects from ${globalDir}`); } catch (error) { logger.error("Failed to load global objects", { directory: globalDir, error: error.message, }); throw error; } } /** * Load configuration from config/default.json * Merges with environment variables (ENV takes precedence) * * @returns {Object} Configuration object */ function loadConfig() { const configPath = join(__dirname, "..", "config", "default.json"); const configData = readFileSync(configPath, "utf-8"); const config = JSON.parse(configData); // Merge environment variables (ENV vars take precedence) config.server.port = process.env.PORT ? parseInt(process.env.PORT, 10) : config.server.port; config.server.host = process.env.HOST || config.server.host; config.logging.level = process.env.LOG_LEVEL || config.logging.level; return config; } /** * Validate configuration * @param {Object} config - Configuration object * @throws {Error} If configuration is invalid */ function validateConfig(config) { const errors = []; // Validate server configuration if ( !config.server.port || config.server.port < 1 || config.server.port > 65535 ) { errors.push("Invalid server.port (must be 1-65535)"); } if (errors.length > 0) { throw new Error(`Configuration validation failed:\n${errors.join("\n")}`); } } /** * Start the HTTP server */ async function startServer() { try { // Load configuration into global.config global.config = loadConfig(); // Load global objects from global/ directory (e.g., service account keys) loadGlobalObjects(); logger.info("Starting Proxy Script Server..."); logger.info( `Configuration loaded: ${JSON.stringify({ port: global.config.server.port, host: global.config.server.host, logLevel: global.config.logging.level, })}`, ); // Validate configuration validateConfig(global.config); logger.info("Configuration validated successfully"); const proxyPath = join(__dirname, "proxy.js"); const proxyCode = readFileSync(proxyPath, "utf-8"); const script = new vm.Script(proxyCode, { filename: "proxy.js" }); // Create HTTP server that delegates all requests to proxy const server = http.createServer((req, res) => { try { // Create a context with all globals that proxy.js needs const context = vm.createContext({ ...globalVMContext, ...globalVariableContext, req, res, }); script.runInContext(context); } catch (error) { logger.error("Request handling failed", { error: error.message, stack: error.stack, }); res.statusCode = 500; res.end("Internal Server Error"); } }); // Graceful shutdown const shutdown = () => { logger.info("\nShutting down gracefully..."); server.close(() => { logger.info("Server closed"); process.exit(0); }); // Force shutdown after 10 seconds setTimeout(() => { logger.error("Forced shutdown after timeout"); process.exit(1); }, 10000); }; process.on("SIGTERM", shutdown); process.on("SIGINT", shutdown); // Start listening server.listen(global.config.server.port, global.config.server.host, () => { logger.info("Server listening", { port: global.config.server.port, host: global.config.server.host, }); }); } catch (error) { logger.error("Failed to start server", { error: error.message, stack: error.stack, }); process.exit(1); } } // Start the server startServer();