Files
google-drive-content-adapter/src/server.js

198 lines
5.2 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,
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();