Changed architecture so .js files contain literal function bodies
Changes to googleDriveAdapterHelper.js:
1. Changed from object literal (\({...}\)) to return statement (return {...})
2. File now contains LITERAL BODY of a function
3. Updated header comment to explain this pattern
4. File is NO LONGER valid standalone JavaScript (has bare return)
Changes to server.js loadGlobalVariables():
1. Wrap loaded code in function: `(function() { ${code} })()`
2. Creates IIFE that executes the function body
3. Captures returned object from the function
4. Pattern applies to ALL .js files in globalVariables/
Pattern:
```javascript
// File: googleDriveAdapterHelper.js (literal function body)
class DocumentCountExceededError extends Error {...}
function generateRequestId() {...}
return {
DocumentCountExceededError,
generateRequestId,
// ... all exports
};
```
```javascript
// server.js wraps it:
const wrappedCode = `(function() {
${code}
})()`;
const script = new vm.Script(wrappedCode, { filename: file });
const returnedObject = script.runInContext(context);
```
Benefits:
✅ Files represent pure function bodies
✅ Wrapping logic centralized in server.js
✅ Clear separation: content vs. execution wrapper
✅ Explicit return statement in function body
✅ Consistent pattern for all global variable functions
✅ Easy to understand: file = function body, server = wrapper
Testing:
✓ Server starts successfully
✓ Module loads: 'Loaded global functions: googleDriveAdapterHelper'
✓ Object captured: type=object, keys=11
✓ All functions accessible in VM context
✓ proxy.js can call googleDriveAdapterHelper.* functions
Note: googleDriveAdapterHelper.js will show syntax error if run standalone
(has bare return statement) - this is intentional, it's a function body!
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
195 lines
5.6 KiB
JavaScript
195 lines
5.6 KiB
JavaScript
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();
|