Now working as a vm.Script passing in all the Globals the proxy script needs
This commit is contained in:
159
src/server.js
159
src/server.js
@@ -1,64 +1,67 @@
|
||||
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 axios from 'axios';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { create as xmlBuilder } from 'xmlbuilder2';
|
||||
import { logger } from './logger.js';
|
||||
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);
|
||||
|
||||
// Note: crypto is already available as globalThis.crypto (Web Crypto API)
|
||||
// No need to import or set it - Node.js provides it by default
|
||||
const globalVMContext = {
|
||||
URLSearchParams,
|
||||
console: logger,
|
||||
crypto,
|
||||
axios,
|
||||
uuidv4,
|
||||
jwt,
|
||||
xmlBuilder,
|
||||
};
|
||||
|
||||
// Replace global console with custom logger
|
||||
globalThis.console = logger;
|
||||
|
||||
// Make libraries available globally for proxy.js
|
||||
globalThis.axios = axios;
|
||||
globalThis.uuidv4 = uuidv4;
|
||||
globalThis.jwt = jwt;
|
||||
globalThis.xmlBuilder = xmlBuilder;
|
||||
let globalVariableContext = {};
|
||||
|
||||
/**
|
||||
* Load all JSON files from global/ directory and make them available as global objects
|
||||
* Pattern: global/filename.json -> globalThis['filename']
|
||||
* Pattern: global/filename.json -> globalVariableContext['filename']
|
||||
*/
|
||||
function loadGlobalObjects() {
|
||||
const globalDir = join(__dirname, '..', 'global');
|
||||
|
||||
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 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 content = readFileSync(filePath, "utf-8");
|
||||
const data = JSON.parse(content);
|
||||
globalThis[objectName] = data;
|
||||
globalVariableContext[objectName] = data;
|
||||
logger.info(`Loaded global object: ${objectName}`, {
|
||||
file: file,
|
||||
keys: Object.keys(data)
|
||||
keys: Object.keys(data),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to load global object from ${file}`, {
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
logger.info(`Loaded ${files.length} global objects from ${globalDir}`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to load global objects', {
|
||||
logger.error("Failed to load global objects", {
|
||||
directory: globalDir,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -67,16 +70,18 @@ function loadGlobalObjects() {
|
||||
/**
|
||||
* 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 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.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;
|
||||
|
||||
@@ -92,12 +97,16 @@ 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 (
|
||||
!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')}`);
|
||||
throw new Error(`Configuration validation failed:\n${errors.join("\n")}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,63 +117,77 @@ 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
|
||||
})}`);
|
||||
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');
|
||||
logger.info("Configuration validated successfully");
|
||||
|
||||
// Load proxy.js as a function wrapper (ZERO exports per constitution)
|
||||
// Import the module which sets up globalThis.handleRequest
|
||||
await import('./proxy.js');
|
||||
|
||||
// Wrap the global handleRequest function for clean invocation
|
||||
const handleRequest = (req, res) => {
|
||||
return globalThis.handleRequest(req, res);
|
||||
};
|
||||
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(handleRequest);
|
||||
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...');
|
||||
logger.info("\nShutting down gracefully...");
|
||||
server.close(() => {
|
||||
logger.info('Server closed');
|
||||
logger.info("Server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Force shutdown after 10 seconds
|
||||
setTimeout(() => {
|
||||
logger.error('Forced shutdown after timeout');
|
||||
logger.error("Forced shutdown after timeout");
|
||||
process.exit(1);
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
process.on("SIGINT", shutdown);
|
||||
|
||||
// Start listening
|
||||
server.listen(global.config.server.port, global.config.server.host, () => {
|
||||
logger.info('Server listening', {
|
||||
logger.info("Server listening", {
|
||||
port: global.config.server.port,
|
||||
host: global.config.server.host,
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to start server', {
|
||||
logger.error("Failed to start server", {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user