Adding sample srcs for project
This commit is contained in:
25
src/globalVariables/helper.js
Normal file
25
src/globalVariables/helper.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Helper Functions Module for Proxy Script
|
||||||
|
*
|
||||||
|
* This module contains pure utility/helper functions extracted from proxy.js
|
||||||
|
* to improve code organization while maintaining vm.Script isolation pattern.
|
||||||
|
*
|
||||||
|
* ARCHITECTURE:
|
||||||
|
* - This file contains the LITERAL BODY of a function
|
||||||
|
* - server.js wraps this in a function: (function() { <this code> })()
|
||||||
|
* - Function returns a single object containing all helper functions
|
||||||
|
* - Injected into globalVariableContext for access by proxy.js
|
||||||
|
* - NO IMPORTS - All dependencies provided via VM context
|
||||||
|
*
|
||||||
|
* Globals expected (provided by server.js):
|
||||||
|
* - crypto: Web Crypto API (for randomUUID())
|
||||||
|
* - console: Custom logger
|
||||||
|
*
|
||||||
|
* @returns {Object} Helpers object with all utility functions
|
||||||
|
*/
|
||||||
|
// =============================================================================
|
||||||
|
// Return helpers object with all functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
return {
|
||||||
|
};
|
||||||
2
src/globalVariables/settings.json
Normal file
2
src/globalVariables/settings.json
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
||||||
93
src/logger.js
Normal file
93
src/logger.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* Structured Logging Utility
|
||||||
|
* Provides severity-based logging with JSON output
|
||||||
|
*
|
||||||
|
* @module logger
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Save reference to original console.log before it gets replaced
|
||||||
|
const originalConsoleLog = globalThis.console.log.bind(globalThis.console);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log levels (in order of severity)
|
||||||
|
*/
|
||||||
|
const LOG_LEVELS = {
|
||||||
|
DEBUG: 0,
|
||||||
|
INFO: 1,
|
||||||
|
WARN: 2,
|
||||||
|
ERROR: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configured log level from global config
|
||||||
|
* @returns {number} Log level threshold
|
||||||
|
*/
|
||||||
|
function getLogLevel() {
|
||||||
|
const configLevel = global.config?.logging?.level || 'INFO';
|
||||||
|
return LOG_LEVELS[configLevel.toUpperCase()] ?? LOG_LEVELS.INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and pretty print an object
|
||||||
|
* @param {Object} obj - Object to format
|
||||||
|
* @returns {string} Formatted object string
|
||||||
|
*/
|
||||||
|
function formatObject(obj) {
|
||||||
|
return JSON.stringify(obj, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message or object
|
||||||
|
* @param {string} level - Log level (DEBUG|INFO|WARN|ERROR)
|
||||||
|
* @param {string|Object} data - Log message (string) or structured data (object)
|
||||||
|
*/
|
||||||
|
function _log(level, data) {
|
||||||
|
const levelValue = LOG_LEVELS[level] ?? LOG_LEVELS.INFO;
|
||||||
|
const threshold = getLogLevel();
|
||||||
|
|
||||||
|
// Only log if level meets or exceeds threshold
|
||||||
|
if (levelValue >= threshold) {
|
||||||
|
let entry;
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
// String input: create structured log entry
|
||||||
|
entry = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
level,
|
||||||
|
message: data
|
||||||
|
};
|
||||||
|
originalConsoleLog(JSON.stringify(entry));
|
||||||
|
} else if (typeof data === 'object' && data !== null) {
|
||||||
|
// Object input: pretty print with timestamp and level
|
||||||
|
entry = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
level,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
originalConsoleLog(formatObject(entry));
|
||||||
|
} else {
|
||||||
|
// Fallback: convert to string
|
||||||
|
entry = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
level,
|
||||||
|
message: String(data)
|
||||||
|
};
|
||||||
|
originalConsoleLog(JSON.stringify(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console-like logging interface
|
||||||
|
* Exported as 'console' to match standard console API
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* logger.info("Simple message")
|
||||||
|
* logger.info({ message: "Structured data", requestId: "123", status: 200 })
|
||||||
|
*/
|
||||||
|
export const logger = {
|
||||||
|
log: (data) => _log('INFO', data),
|
||||||
|
debug: (data) => _log('DEBUG', data),
|
||||||
|
info: (data) => _log('INFO', data),
|
||||||
|
error: (data) => _log('ERROR', data)
|
||||||
|
};
|
||||||
43
src/proxyScripts/proxy.js
Normal file
43
src/proxyScripts/proxy.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* MONOLITHIC HTTP request handler - ALL functionality in this single file.
|
||||||
|
*
|
||||||
|
* CONSTITUTION REQUIREMENT: ZERO export statements - pure IIFE pattern
|
||||||
|
* File is loaded by server.js using Function constructor
|
||||||
|
*
|
||||||
|
* Globals provided by server.js:
|
||||||
|
* - console: Custom logger
|
||||||
|
* - crypto: Web Crypto API (provides randomUUID())
|
||||||
|
* - axios: HTTP client
|
||||||
|
* - uuidv4: UUID generator
|
||||||
|
* - jwt: JSON Web Token library
|
||||||
|
* - xmlBuilder: XML document builder
|
||||||
|
* - helper: Helper functions module (loaded from globalVariables/helper.js)
|
||||||
|
* - settings: Consolidated settings (from global/settings.json)
|
||||||
|
* - req: HTTP request object (includes req.params with routing info if proxy prefix configured)
|
||||||
|
* - res: HTTP response object
|
||||||
|
*
|
||||||
|
* Structure:
|
||||||
|
*
|
||||||
|
* @module proxy
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main HTTP request handler
|
||||||
|
*/
|
||||||
|
(async () => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
console.info({
|
||||||
|
message: "Request received",
|
||||||
|
requestId,
|
||||||
|
method: req.method,
|
||||||
|
url: req.url,
|
||||||
|
params: req.params,
|
||||||
|
query: req.query,
|
||||||
|
body: req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
res.statusCode = 501;
|
||||||
|
res.end();
|
||||||
|
})();
|
||||||
227
src/server.js
Normal file
227
src/server.js
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
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({
|
||||||
|
message: `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({
|
||||||
|
message: `Loaded global functions: ${varName}`,
|
||||||
|
type: typeof returnedObject,
|
||||||
|
isObject: typeof returnedObject === 'object' && returnedObject !== null,
|
||||||
|
keys: returnedObject ? Object.keys(returnedObject).length : 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info({
|
||||||
|
message: `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,
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
...config.proxy,
|
||||||
|
},
|
||||||
|
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({
|
||||||
|
message: "Configuration loaded",
|
||||||
|
port: global.config.server.port,
|
||||||
|
host: global.config.server.host,
|
||||||
|
logLevel: global.config.logging.level,
|
||||||
|
proxyPrefix: global.config.proxy ?
|
||||||
|
`${global.config.proxy.pathPrefix}${global.config.proxy.workspaceId}/${global.config.proxy.branch}/${global.config.proxy.routeName}` :
|
||||||
|
'(none)'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Extract proxy routing metadata from config (not parsed from URL)
|
||||||
|
// and attach to req.params if URL matches the configured prefix
|
||||||
|
if (global.config.proxy) {
|
||||||
|
const { pathPrefix, workspaceId, branch, routeName } = global.config.proxy;
|
||||||
|
const fullPrefix = `${pathPrefix.replace(/\/$/, '')}/${workspaceId}/${branch}/${routeName}`;
|
||||||
|
|
||||||
|
// Check if URL starts with proxy prefix
|
||||||
|
if (req.url.startsWith(fullPrefix)) {
|
||||||
|
// Add routing metadata to request for proxy.js
|
||||||
|
// All values come from config, not parsed from URL
|
||||||
|
req.params = {
|
||||||
|
"0": req.url, // Original full path
|
||||||
|
workspaceId, // From config.proxy.workspaceId
|
||||||
|
branch, // From config.proxy.branch
|
||||||
|
route: routeName // From config.proxy.routeName (renamed to 'route' for consistency)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = vm.createContext({
|
||||||
|
...globalVMContext,
|
||||||
|
...globalVariableContext,
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
});
|
||||||
|
script.runInContext(context);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({
|
||||||
|
message: "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({
|
||||||
|
message: "Server listening",
|
||||||
|
port: global.config.server.port,
|
||||||
|
host: global.config.server.host
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({
|
||||||
|
message: "Failed to start server",
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
startServer();
|
||||||
Reference in New Issue
Block a user