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, URL, console: logger, crypto, axios, uuidv4, jwt, xmlBuilder, }; let globalVariableContext = {}; /** * Load all files from globalVariables/ directory into globalVariableContext * Pattern: globalVariables/filename.{json|js} -> globalVariableContext['filename'] */ function loadGlobalVariables() { const globalDir = join(__dirname, "globalVariables"); const jsonFiles = []; const jsFiles = []; // Scan and categorize files in one pass readdirSync(globalDir).forEach((file) => { if (file.includes(".example")) return; if (file.endsWith(".json")) jsonFiles.push(file); else if (file.endsWith(".js")) jsFiles.push(file); }); // Load JSON files first (data) jsonFiles.forEach((file) => { const varName = file.replace(".json", ""); const data = JSON.parse(readFileSync(join(globalDir, file), "utf-8")); globalVariableContext[varName] = data; logger.info(`Loaded global data: ${varName}`, { keys: Object.keys(data) }); }); // Load JS files second (functions can reference JSON data) jsFiles.forEach((file) => { const varName = file.replace(".js", ""); const code = readFileSync(join(globalDir, file), "utf-8"); // Wrap the literal function body in a function and execute const wrappedCode = `(function() {\n${code}\n})()`; const script = new vm.Script(wrappedCode, { filename: file }); const context = vm.createContext({ ...globalVMContext, ...globalVariableContext }); // Execute script and capture returned object const returnedObject = script.runInContext(context); globalVariableContext[varName] = returnedObject; logger.info(`Loaded global functions: ${varName}`, { type: typeof returnedObject, isObject: typeof returnedObject === 'object' && returnedObject !== null, keys: returnedObject ? Object.keys(returnedObject).length : 0 }); }); logger.info(`Loaded ${jsonFiles.length + jsFiles.length} global variables`, { json: jsonFiles.length, js: jsFiles.length } ); } /** * Load configuration from config/default.json and merge with environment variables */ function loadConfig() { const configPath = join(__dirname, "..", "config", "default.json"); const configData = readFileSync(configPath, "utf-8"); const config = JSON.parse(configData); // Merge environment variables (ENV takes precedence) return { ...config, server: { ...config.server, port: process.env.PORT ? parseInt(process.env.PORT, 10) : config.server.port, host: process.env.HOST || config.server.host, }, logging: { ...config.logging, level: process.env.LOG_LEVEL || config.logging.level, }, }; } /** * Validate configuration */ function validateConfig(config) { if (!config.server.port || config.server.port < 1 || config.server.port > 65535) { throw new Error("Invalid server.port (must be 1-65535)"); } } /** * Start the HTTP server */ async function startServer() { try { // Load configuration into global.config global.config = loadConfig(); // Load all global variables (JSON data + JS function modules) loadGlobalVariables(); 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, "proxyScripts", "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 { 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();