@bs-core/shell
Version:
The Bamboo Shell
1 lines • 289 kB
Source Map (JSON)
{"version":3,"file":"shell.mjs","sources":["../out/config-man.js","../out/logger.js","../out/http-req.js","../out/http-server/req-res.js","../out/http-server/content-types.js","../out/http-server/sse-server.js","../out/http-server/middleware.js","../../node_modules/.pnpm/path-to-regexp@6.3.0/node_modules/path-to-regexp/dist.es2015/index.js","../out/http-server/router.js","../out/http-server/static-file-server.js","../out/http-server/main.js","../out/bs-plugin.js","../out/main.js"],"sourcesContent":["/**\n * Config manager module. Provides functions to retrieve config values from various sources like\n * CLI, environment variables, and env file. Includes utility functions to handle config value\n * lookup, type conversion, error handling etc.\n */\n// imports here\nimport * as fs from \"node:fs\";\n// Config consts here\n// The env var that contains the name of the .env file\nconst CFG_ENV_FILE = \"ENV_FILE\";\n// The env var that contains the name of the cfg file\nconst CFG_CFG_FILE = \"CFG_FILE\";\n// Private variables here\n// Stores the parsed contents of the .env file as key-value pairs\nlet _envFileStore;\n// Stores the parsed contents of the cfg file as an object\nlet _cfgFileStore;\n// Stores the messages generated during configuration.\n// NOTE: This is used because the configuration manager is used by Logger\n// and it becomes a chicken/egg situation when initialising the Logger\nlet _messageStore;\n/**\n * Enumeration of supported configuration value types.\n * Can be used when retrieving a config value to specify the expected type.\n */\nexport var ConfigType;\n(function (ConfigType) {\n ConfigType[\"String\"] = \"String\";\n ConfigType[\"Number\"] = \"Number\";\n ConfigType[\"Boolean\"] = \"Boolean\";\n ConfigType[\"Object\"] = \"Object\";\n ConfigType[\"Array\"] = \"Array\";\n})(ConfigType || (ConfigType = {}));\n/**\n * Represents an error that occurred while retrieving a config value\n */\nexport class ConfigError {\n message;\n constructor(message) {\n this.message = message;\n }\n}\n// Private methods here\n/**\n * Converts a string value to the specified configuration type.\n *\n * NOTE: This is not used for the config files so we dont check\n * for Object or Array types.\n *\n * @param value - The string value to convert.\n * @param type - The expected configuration type.\n * @returns The converted value as a number, string, or boolean.\n */\nfunction convertValue(value, type) {\n //Check the type\n switch (type) {\n case ConfigType.Number:\n return parseInt(value);\n case ConfigType.Boolean:\n // Only accept Y or TRUE (case insensitive) to mean true\n if (value.toUpperCase() === \"Y\" || value.toUpperCase() === \"TRUE\") {\n return true;\n }\n // Everything else is false\n return false;\n default:\n // All that is left is String and this is already a string!\n return value;\n }\n}\n/**\n * Checks the command line arguments for a configuration value matching the\n * specified configuration key.\n *\n * @param config - The configuration key to look for in the command line arguments.\n * @param type - The expected type of the configuration value.\n * @param options - Additional options for configuring the behavior of the function.\n * @returns The configuration value from the command line arguments, converted to the specified type, or `null` if the configuration value is not found.\n */\nfunction checkCli(config, type, options) {\n // Ignore the first 2 params (node bin and executable file)\n let cliParams = process.argv.slice(2);\n // The convention used for config params on the command line is:\n // Convert to lowercase, replace '_' with '-' and prepend \"--\"\n let cliParam = `--${config.toLowerCase().replaceAll(\"_\", \"-\")}`;\n // Command line flags are just prepended with a '-'\n let cmdLineFlag = options.cmdLineFlag !== undefined ? `-${options.cmdLineFlag}` : \"\";\n // If the param is assigned a value on the cli it has the format:\n // --param=value\n // otherwise the format is and it implies true:\n // --parm or -flag\n let regExp;\n if (options.cmdLineFlag === undefined) {\n // No flag specified so only look for the param and an assigned value\n regExp = new RegExp(`^${cliParam}=(.+)$`);\n }\n else {\n // Look for param and an assigned value or cmd line flag\n regExp = new RegExp(`^${cliParam}=(.+)$|^${cliParam}$|^${cmdLineFlag}$`);\n }\n let value;\n // Step through each cli params until you find a match\n for (let i = 0; i < cliParams.length; i++) {\n let match = cliParams[i].match(regExp);\n let paramOrFlag;\n if (match === null) {\n // There was no match so look at the next param\n continue;\n }\n // If a value was supplied then match[1] will contain a value\n if (match[1] !== undefined) {\n paramOrFlag = match[0];\n value = match[1];\n }\n else {\n paramOrFlag = match[0];\n // The presence of the flag/param without a value implies a true value\n value = \"Y\";\n }\n // Check if we can or should log that we found it\n // NOTE: If we log it we want to indicate is was found on the CLI\n if (!options.silent) {\n _messageStore.add(`CLI parameter/flag (${paramOrFlag}) = (${options.redact ? \"redacted\" : value})`);\n }\n // We are done so break out of the loop\n break;\n }\n // Return null if we have no value\n if (value === undefined) {\n return null;\n }\n return convertValue(value, type);\n}\n/**\n * Retrieves a configuration value from an environment variable.\n *\n * @param config - The configuration key to retrieve from the environment.\n * @param type - The expected type of the configuration value.\n * @param options - Additional options for configuring the behavior of the function.\n * @returns The configuration value converted to the specified type, or `null` if the environment variable is not set.\n */\nfunction checkEnvVar(config, type, options) {\n // NOTE: Always convert to upper case for env vars\n let evar = config.toUpperCase();\n let value = process.env[evar];\n // Return null if we have no value\n if (value === undefined) {\n return null;\n }\n // If we are here then we found it, now lets check if we can or should\n // log that we found it\n // NOTE: If we log it we want to indicate is was found in an env var\n if (!options.silent) {\n _messageStore.add(`Env var (${evar}) = (${options.redact ? \"redacted\" : value})`);\n }\n return convertValue(value, type);\n}\n/**\n * Retrieves a configuration value from an environment file store.\n *\n * @param config - The configuration key to retrieve from the environment file.\n * @param type - The expected type of the configuration value.\n * @param options - Additional options for configuring the behavior of the function.\n * @returns The configuration value converted to the specified type, or `null` if the configuration is not found in the environment file.\n */\nfunction checkEnvFile(config, type, options) {\n // NOTE: Always convert to upper case when checking the env file store\n let evar = config.toUpperCase();\n let value = _envFileStore.get(evar);\n // Return null if we have no value\n if (value === undefined) {\n return null;\n }\n // If we are here then we found it, now lets check if we can or should\n // log that we found it\n // NOTE: If we log it we want to indicate it was found in the env file\n if (!options.silent) {\n _messageStore.add(`Env var from env file (${evar}) = (${options.redact ? \"redacted\" : value})`);\n }\n return convertValue(value, type);\n}\n/**\n * Retrieves a configuration value from a configuration file store.\n *\n * @param config - The configuration key to retrieve from the configuration file.\n * @param options - Additional options for configuring the behavior of the function.\n * @returns The configuration value, or `null` if the configuration is not found in the configuration file.\n */\nfunction checkCfgFile(config, options) {\n let value = _cfgFileStore.get(config);\n // Return null if we have no value\n if (value === undefined) {\n return null;\n }\n // If we are here then we found it, now lets check if we can or should\n // log that we found it\n // NOTE: If we log it we want to indicate it was found in the cfg file\n if (!options.silent) {\n _messageStore.add(`Config from cfg file (${config}) = (${options.redact ? \"redacted\" : JSON.stringify(value)})`);\n }\n // No need for any convertions, just return the value\n return value;\n}\n/**\n * Retrieves a configuration value from various sources, with the following precedence:\n * 1. Command-line arguments\n * 2. Environment variables\n * 3. Environment file (.env)\n * 4. Configuration file\n *\n * If the configuration value is not found in any of these sources, a default value can be provided.\n *\n * @param config - The configuration key to retrieve.\n * @param type - The expected type of the configuration value.\n * @param defaultVal - The default value to use if the configuration is not found.\n * @param configOptions - Additional options for configuring the behavior of the function.\n * @returns The configuration value, or the default value if the configuration is not found.\n * @throws {ConfigError} If the configuration is required and not found.\n */\nfunction get(config, type, defaultVal, configOptions) {\n // Set up the defaults if not provided\n let options = {\n silent: false,\n redact: false,\n ...configOptions,\n };\n // Check the CLI first, i.e. CLI has higher precedence then env vars\n // of the cfg file\n let value = checkCli(config, type, options);\n if (value !== null) {\n return value;\n }\n // OK it's not in the CLI so lets check the env vars, env var has higher\n // precedence then the .env file\n value = checkEnvVar(config, type, options);\n if (value !== null) {\n return value;\n }\n // OK it's not in the env vars either so check the env file store\n value = checkEnvFile(config, type, options);\n if (value !== null) {\n return value;\n }\n // OK it's not in the env file store either so check the cfg store\n value = checkCfgFile(config, options);\n if (value !== null) {\n return value;\n }\n // If we are here then the value was not found - use default provided\n // NOTE: The default SHOULD have the correct type so do not do a conversion\n if (defaultVal === undefined) {\n // If the default was not provided then the config WAS required. In this\n // scenario we need to throw an error\n throw new ConfigError(`Config parameter (${config}) not found!`);\n }\n // Lets check if we can or should log the default value\n // NOTE: If we log it we want to indicate is the default value\n if (!options.silent) {\n _messageStore.add(`Default value used for (${config}) = (${options.redact ? \"redacted\" : defaultVal})`);\n }\n return defaultVal;\n}\n/**\n * Reads the contents of the specified .env file and adds the key-value\n * pairs to the _envFileStore.\n *\n * @param envFile - The path to the .env file to read.\n * @throws {ConfigError} If an error occurs while reading the .env file.\n */\nfunction parseEnvFile(envFile) {\n let lines = [];\n try {\n _messageStore.add(`Reading config info from .env file (${envFile})`);\n // Read env file and split it into lines ...\n let contents = fs.readFileSync(envFile, \"utf8\");\n // ... makes sure if works for DOS and linux files!\n lines = contents.split(/\\r?\\n/);\n }\n catch (e) {\n throw new ConfigError(`The following error occured when trying to open the .env file (${envFile}) - (${e})`);\n }\n // Iterate through each line\n for (let line of lines) {\n // If the line is commented out or blank then skip it\n if (line.length === 0 || line.startsWith(\"#\")) {\n continue;\n }\n // Don't use split() here because the value may contain an \"=\"\n let index = line.indexOf(\"=\");\n // Check if there was an equal in the line - if not then skip this line\n if (index === -1) {\n continue;\n }\n // Get the key/value pair - make sure to trim them as well\n let key = line.slice(0, index).trim();\n let value = line.slice(index + 1).trim();\n // Check if the value is delimited with single or double quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n // Strip them away\n value = value.slice(1, value.length - 1);\n }\n // Stick it in the env file store\n // NOTE: Make key upper case to match env vars conventions\n _envFileStore.set(key.toUpperCase(), value);\n _messageStore.add(`Added (${key.toUpperCase()}) to the env file store`);\n }\n}\n/**\n * Reads the contents of a cfg file and adds the key-value pairs to the\n * configuration store.\n *\n * @param cfgFile - The path to the configuration file to read.\n * @throws {ConfigError} If an error occurs while reading or parsing the configuration file.\n */\nfunction readCfgFile(cfgFile) {\n let contents;\n try {\n _messageStore.add(`Reading config info from cfg file (${cfgFile})`);\n // Read the cfg file\n contents = fs.readFileSync(cfgFile, \"utf8\");\n }\n catch (e) {\n throw new ConfigError(`The following error occured when trying to open the cfg file (${cfgFile}) - (${e})`);\n }\n try {\n _messageStore.add(\"Adding the cfg file contents to the cfg store\");\n _cfgFileStore = new Map(Object.entries(JSON.parse(contents)));\n }\n catch (e) {\n throw new ConfigError(`The following error occured when trying to add (${contents}) to the cfg store - (${e})`);\n }\n}\n/**\n * Initializes the configuration manager by setting up the necessary stores and\n * parsing any specified environment and configuration files.\n *\n * The function first initializes the `_envFileStore`, `_cfgFileStore`, and\n * `_messageStore` stores. It then checks if a `.env` file has been specified\n * in the configuration and, if so, calls the `parseEnvFile` function to parse\n * the contents of the file and add the key-value pairs to the `_envFileStore`.\n *\n * Next, the function checks if a configuration file has been specified in the\n * configuration and, if so, calls the `readCfgFile` function to read the\n * contents of the file and add the key-value pairs to the `_cfgFileStore`.\n *\n * This function is typically called during the initialization of the\n * application to ensure that the configuration manager is properly set up and\n * ready to use.\n */\nfunction init() {\n // Initialise the stores\n _envFileStore = new Map();\n _cfgFileStore = new Map();\n _messageStore = new Set();\n // Check if the user has specified a .env file\n let envFile = configMan.getStr(CFG_ENV_FILE, \"\");\n if (envFile.length > 0) {\n parseEnvFile(envFile);\n }\n else {\n _messageStore.add(\"No .env file specified\");\n }\n // Check if the user has specified a cfg file\n // NOTE: The cfg file config CAN be specified in the .env file since it\n // has already been parsed\n let cfgFile = configMan.getStr(CFG_CFG_FILE, \"\");\n if (cfgFile.length > 0) {\n readCfgFile(cfgFile);\n }\n else {\n _messageStore.add(\"No cfg file specified\");\n }\n}\n// Public methods here\n/**\n * Provides a set of functions for retrieving configuration values from various\n * sources, including environment variables and configuration files.\n *\n * The `configMan` object is a frozen object that contains the following methods:\n *\n * - `getStr(config: string, defaultVal?: string, options?: ConfigOptions): string`\n * - Retrieves a string configuration value with a default value if not set.\n * - `getBool(config: string, defaultVal?: boolean, options?: ConfigOptions): boolean`\n * - Retrieves a boolean configuration value with a default value if not set.\n * - `getNum(config: string, defaultVal?: number, options?: ConfigOptions): number`\n * - Retrieves a number configuration value with a default value if not set.\n * - `getMessages(): IterableIterator<[string, string]>`\n * - Retrieves an iterator over the key-value pairs of the message store.\n * - `clearMessages(): void`\n * - Clears all messages stored in the message store.`\n *\n * NOTE: Freezing the object prevents modifications to the exported API.\n */\nexport const configMan = Object.freeze({\n /**\n * Retrieves a string configuration value with a default value if not set.\n *\n * @param config - The name of the config parameter to retrieve.\n * @param defaultVal - A default value if the config is not set. NOTE: This must be of the correct type.\n * @param configOptions - The config options.\n * @returns The string configuration value, or the default value if not set.\n */\n getStr: (config, defaultVal, options) => {\n return get(config, ConfigType.String, defaultVal, options);\n },\n /**\n * Retrieves a boolean configuration value with a default value if not set.\n *\n * @param config - The name of the config parameter to retrieve.\n * @param defaultVal - A default value if the config is not set. NOTE: This must be of the correct type.\n * @param configOptions - The config options.\n * @returns The boolean configuration value, or the default value if not set.\n */\n getBool: (config, defaultVal, options) => {\n return get(config, ConfigType.Boolean, defaultVal, options);\n },\n /**\n * Retrieves a number configuration value with a default value if not set.\n *\n * @param config - The name of the config parameter to retrieve.\n * @param defaultVal - A default value if the config is not set. NOTE: This must be of the correct type.\n * @param configOptions - The config options.\n * @returns The number configuration value, or the default value if not set.\n */\n getNum: (config, defaultVal, options) => {\n return get(config, ConfigType.Number, defaultVal, options);\n },\n /**\n * Retrieves an object configuration value with a default value if not set.\n *\n * @param config - The name of the config parameter to retrieve.\n * @param defaultVal - A default value if the config is not set. NOTE: This must be of the correct type.\n * @param configOptions - The config options.\n * @returns The object configuration value, or the default value if not set.\n */\n getObject: (config, defaultVal, options) => {\n return get(config, ConfigType.Object, defaultVal, options);\n },\n /**\n * Retrieves an array configuration value with a default value if not set.\n *\n * @param config - The name of the config parameter to retrieve.\n * @param defaultVal - A default value if the config is not set. NOTE: This must be of the correct type.\n * @param configOptions - The config options.\n * @returns The array configuration value, or the default value if not set.\n */\n getArray: (config, defaultVal, options) => {\n return get(config, ConfigType.Array, defaultVal, options);\n },\n /**\n * Retrieves an iterator over the key-value pairs of the message store.\n *\n * @returns An iterator over the key-value pairs of the message store.\n */\n getMessages: () => {\n return _messageStore.entries();\n },\n /**\n * Clears all messages stored in the message store.\n */\n clearMessages: () => {\n _messageStore.clear();\n },\n});\n// Time to kick this puppy!\ninit();\n//# sourceMappingURL=config-man.js.map","// imports here\nimport { configMan } from \"./config-man.js\";\nimport * as util from \"node:util\";\n// Config consts here\nconst CFG_LOG_LEVEL = \"LOG_LEVEL\";\nconst CFG_LOG_TIMESTAMP = \"LOG_TIMESTAMP\";\nconst CFG_LOG_TIMESTAMP_LOCALE = \"LOG_TIMESTAMP_LOCALE\";\nconst CFG_LOG_TIMESTAMP_TZ = \"LOG_TIMESTAMP_TZ\";\n// Types here\nexport var LogLevel;\n(function (LogLevel) {\n LogLevel[LogLevel[\"COMPLETE_SILENCE\"] = 0] = \"COMPLETE_SILENCE\";\n LogLevel[LogLevel[\"QUIET\"] = 100] = \"QUIET\";\n LogLevel[LogLevel[\"INFO\"] = 200] = \"INFO\";\n LogLevel[LogLevel[\"START_UP\"] = 250] = \"START_UP\";\n LogLevel[LogLevel[\"DEBUG\"] = 300] = \"DEBUG\";\n LogLevel[LogLevel[\"TRACE\"] = 400] = \"TRACE\";\n})(LogLevel || (LogLevel = {}));\n// Logger class here\nexport class Logger {\n // Private properties here\n _name;\n _timestamp;\n _timestampLocale;\n _timestampTz;\n _logLevel;\n // Private methods here\n /**\n * Generates a timestamp string to prefix log messages.\n * Returns an empty string if timestamps are disabled. Otherwise returns\n * the formatted timestamp string.\n */\n timestamp() {\n // If we are not supposed to generate timestamps then return nothing\n if (!this._timestamp) {\n return \"\";\n }\n let now = new Date();\n if (this._timestampLocale === \"ISO\") {\n // Make sure to add a trailing space!\n return `${now.toISOString()} `;\n }\n // Make sure to add a trailing space!\n return `${now.toLocaleString(this._timestampLocale, {\n timeZone: this._timestampTz,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n fractionalSecondDigits: 3,\n })} `;\n }\n convertLevel(level) {\n let logLevel;\n switch (level.toUpperCase()) {\n case \"\": // This is in case it is not set\n logLevel = LogLevel.INFO;\n break;\n case \"SILENT\":\n logLevel = LogLevel.COMPLETE_SILENCE;\n break;\n case \"QUIET\":\n logLevel = LogLevel.QUIET;\n break;\n case \"INFO\":\n logLevel = LogLevel.INFO;\n break;\n case \"STARTUP\":\n logLevel = LogLevel.START_UP;\n break;\n case \"DEBUG\":\n logLevel = LogLevel.DEBUG;\n break;\n case \"TRACE\":\n logLevel = LogLevel.TRACE;\n break;\n default:\n throw new Error(`Log Level (${level}) is unknown.`);\n }\n return logLevel;\n }\n // constructor here\n constructor(name) {\n this._name = name;\n this._timestamp = configMan.getBool(CFG_LOG_TIMESTAMP, false);\n this._timestampLocale = configMan.getStr(CFG_LOG_TIMESTAMP_LOCALE, \"ISO\");\n this._timestampTz = configMan.getStr(CFG_LOG_TIMESTAMP_TZ, \"UTC\");\n this._logLevel = this.convertLevel(configMan.getStr(CFG_LOG_LEVEL, \"\"));\n // Now get the messages from the confgiMan for display\n let messages = configMan.getMessages();\n for (const message of messages) {\n this.startupMsg(\"Logger\", message[0]);\n }\n configMan.clearMessages();\n }\n fatal(...args) {\n // fatals are always logged\n let msg = util.format(`${this.timestamp()}FATAL: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.error(msg);\n }\n error(...args) {\n // errors are always logged unless level = LOG_COMPLETE_SILENCE\n if (this._logLevel > LogLevel.COMPLETE_SILENCE) {\n let msg = util.format(`${this.timestamp()}ERROR: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.error(msg);\n }\n }\n warn(...args) {\n // warnings are always logged unless level = LOG_COMPLETE_SILENCE\n if (this._logLevel > LogLevel.COMPLETE_SILENCE) {\n let msg = util.format(`${this.timestamp()}WARN: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.warn(msg);\n }\n }\n info(...args) {\n if (this._logLevel >= LogLevel.INFO) {\n let msg = util.format(`${this.timestamp()}INFO: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.info(msg);\n }\n }\n startupMsg(...args) {\n if (this._logLevel >= LogLevel.START_UP) {\n let msg = util.format(`${this.timestamp()}STARTUP: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.info(msg);\n }\n }\n shutdownMsg(...args) {\n if (this._logLevel >= LogLevel.START_UP) {\n let msg = util.format(`${this.timestamp()}SHUTDOWN: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.info(msg);\n }\n }\n debug(...args) {\n if (this._logLevel >= LogLevel.DEBUG) {\n let msg = util.format(`${this.timestamp()}DEBUG: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.info(msg);\n }\n }\n trace(...args) {\n if (this._logLevel >= LogLevel.TRACE) {\n let msg = util.format(`${this.timestamp()}TRACE: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.info(msg);\n }\n }\n force(...args) {\n // forces are always logged even if level == LOG_COMPLETE_SILENCE\n let msg = util.format(`${this.timestamp()}FORCED: ${this._name}: ${args[0]}`, ...args.slice(1));\n console.error(msg);\n }\n setLevel(level) {\n this._logLevel = level;\n }\n}\n//# sourceMappingURL=logger.js.map","// NOTE: To use this with endpoints using self signed certs add this env var\n// NODE_TLS_REJECT_UNAUTHORIZED=0\n// imports here\nimport { Logger } from \"./logger.js\";\nimport { performance } from \"node:perf_hooks\";\n// Misc consts here\nconst LOG_TAG = \"request\";\n// Module private variables here\nconst _logger = new Logger(LOG_TAG);\n// Error classes here\nexport class ReqAborted {\n timedOut;\n message;\n constructor(timedOut, message) {\n this.timedOut = timedOut;\n this.message = message;\n }\n}\nexport class ReqError {\n status;\n message;\n constructor(status, message) {\n this.status = status;\n this.message = message;\n }\n}\n// Private methods here\nasync function callFetch(origin, path, options, body) {\n // Build the url\n let url = `${origin}${path}`;\n // And add the query string if one has been provided\n if (options.searchParams !== undefined) {\n url += `?${new URLSearchParams(options.searchParams)}`;\n }\n let timeoutTimer;\n // Create an AbortController if a timeout has been provided\n if (options.timeout) {\n const controller = new AbortController();\n // NOTE: this will overwrite a signal if one has been provided\n options.signal = controller.signal;\n timeoutTimer = setTimeout(() => {\n controller.abort();\n }, options.timeout * 1000);\n }\n let results = await fetch(url, {\n method: options.method,\n headers: options.headers,\n body,\n keepalive: options.keepalive,\n cache: options.cache,\n credentials: options.credentials,\n mode: options.mode,\n redirect: options.redirect,\n referrer: options.referrer,\n referrerPolicy: options.referrerPolicy,\n signal: options.signal,\n }).catch((e) => {\n // Check if the request was aborted\n if (e.name === \"AbortError\") {\n // If timeout was set then the req must have timed out\n if (options.timeout) {\n throw new ReqAborted(true, `Request timeout out after ${options.timeout} seconds`);\n }\n throw new ReqAborted(false, \"Request aborted\");\n }\n // Need to check if we started a timeout\n if (timeoutTimer !== undefined) {\n clearTimeout(timeoutTimer);\n }\n // We don't know what the error is so pass it back\n throw e;\n });\n // Need to check if we started a timeout\n if (timeoutTimer !== undefined) {\n clearTimeout(timeoutTimer);\n }\n // We will throw an error if the response is not 2XX\n if (!results.ok) {\n let message = await results.text();\n throw new ReqError(results.status, message.length === 0 ? results.statusText : message);\n }\n return results;\n}\nasync function handleResponseData(results) {\n // No point worrying if the body is JSON at first, because we know its text\n const body = await results.text();\n // If the body exists then check if it is JSON\n if (body.length > 0) {\n // Check if the content type is JSON\n const contentType = results.headers.get(\"content-type\");\n if (contentType?.startsWith(\"application/json\")) {\n return JSON.parse(body);\n }\n }\n // If we are here, the body wasnt JSON so just return the text\n return body;\n}\n// Public methods here\nexport let request = async (origin, path, reqOptions) => {\n // We need to remember the start time\n const startTime = performance.now();\n _logger.trace(\"Request for origin (%s) path (%s)\", origin, path);\n // Set the default values\n let options = {\n method: \"GET\",\n timeout: 0,\n keepalive: true,\n handleResponse: true,\n cache: \"no-store\",\n mode: \"cors\",\n credentials: \"include\",\n redirect: \"follow\",\n referrerPolicy: \"no-referrer\",\n ...reqOptions,\n };\n // Make sure the headers is set to something for later\n if (options.headers === undefined) {\n options.headers = {};\n }\n // If a bearer token is provided then add a Bearer auth header\n if (options.bearerToken !== undefined) {\n options.headers.Authorization = `Bearer ${options.bearerToken}`;\n }\n // If the basic auth creds are provided add a Basic auth header\n if (options.auth !== undefined) {\n let token = Buffer.from(`${options.auth.username}:${options.auth.password}`).toString(\"base64\");\n options.headers.Authorization = `Basic ${token}`;\n }\n let payloadBody;\n // Automatically stringify and set the header if this is a JSON payload\n // BUT dont do it for GETs and DELETE since they can have no body\n if (options.body !== undefined &&\n options.method !== \"GET\" &&\n options.method !== \"DELETE\") {\n // Rem an array is an object to!\n if (typeof options.body === \"object\") {\n // Add the content-type if it hasn't been provided\n if (options.headers?.[\"content-type\"] === undefined) {\n options.headers[\"content-type\"] = \"application/json; charset=utf-8\";\n }\n payloadBody = JSON.stringify(options.body);\n }\n else {\n payloadBody = options.body;\n }\n }\n // Call fetch\n let response = await callFetch(origin, path, options, payloadBody);\n // Build the response\n let res = {\n statusCode: response.status,\n headers: response.headers,\n body: undefined, // set to undefined for now\n responseTime: 0,\n };\n // Check if we should handle the response for the user\n if (options.handleResponse) {\n // Yes, so handle and set the body\n res.body = await handleResponseData(response).catch((e) => {\n const msg = `Error handling response data for (${origin}) (${path}) - (${e}))`;\n throw new Error(msg);\n });\n }\n else {\n // No, so set the response\n res.response = response;\n }\n // Don't forget to set the response time\n res.responseTime = Math.round(performance.now() - startTime);\n return res;\n};\n//# sourceMappingURL=http-req.js.map","import * as http from \"node:http\";\nimport { performance } from \"node:perf_hooks\";\n// Classes here\nexport class HttpError {\n status;\n message;\n constructor(status, message = \"Achtung Baby!\") {\n this.status = status;\n this.message = message;\n }\n}\nexport class HttpRedirect {\n statusCode;\n location;\n message;\n constructor(statusCode = 302, location, message = \"\") {\n this.statusCode = statusCode;\n this.location = location;\n this.message = message;\n }\n}\nexport class ServerRequest extends http.IncomingMessage {\n // Properties here\n urlObj;\n params;\n middlewareProps;\n sseServer;\n json;\n body;\n matchedInfo;\n dontCompressResponse;\n // Constructor here\n constructor(socket) {\n super(socket);\n // When this object is instantiated the body of the req has not yet been\n // received so the details, such as the URL, will not be known until later\n this.urlObj = new URL(\"http://localhost/\");\n this.params = {};\n this.middlewareProps = {};\n this.dontCompressResponse = false;\n }\n getCookie = (cookieName) => {\n // Get the cookie header and spilt it up by cookies -\n // NOTE: cookies are separated by semi colons\n let cookies = this.headers.cookie?.split(\";\");\n if (cookies === undefined) {\n // Nothing to do so just return\n return null;\n }\n // Loop through the cookies\n for (let cookie of cookies) {\n // Split the cookie up into a key value pair\n // NOTE: key/value is separated by an equals sign and has leading spaces\n let [name, value] = cookie.trim().split(\"=\");\n // Make sure it was a validly formatted cookie\n if (value === undefined) {\n // It is not a valid cookie so skip it\n continue;\n }\n // Check if we found the cookie\n if (name === cookieName) {\n // Return the cookie value\n return value;\n }\n }\n return null;\n };\n setServerTimingHeader = (value) => {\n this.headers[\"Server-Timing\"] = value;\n };\n}\nexport class ServerResponse extends http.ServerResponse {\n // Properties here\n _receiveTime;\n _redirected;\n _latencyMetricName;\n _serverTimingsMetrics;\n json;\n body;\n proxied;\n // constructor here\n constructor(req) {\n super(req);\n // NOTE: This will be created at the same time as ServerRequest\n this._receiveTime = performance.now();\n this._redirected = false;\n this._latencyMetricName = \"latency\";\n this._serverTimingsMetrics = [];\n this.proxied = false;\n }\n // Getter methods here\n get redirected() {\n return this._redirected;\n }\n // Setter methods here\n set latencyMetricName(name) {\n this._latencyMetricName = name;\n }\n // Public functions here\n redirect(location, statusCode = 302, message = \"\") {\n this._redirected = true;\n let htmlMessage = message.length > 0\n ? message\n : `Redirected to <a href=\"${location}\">here</a>`;\n // Write a little something something for good measure\n this.body = `\n <html>\n <body>\n <p>${htmlMessage}</p>\n </body>\n </html>`;\n this.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n this.setHeader(\"Location\", location);\n this.statusCode = statusCode;\n }\n setCookies = (cookies) => {\n let setCookiesValue = [];\n // Check for exiting cookies and add them to the setCookiesValue array\n let existing = this.getHeader(\"Set-Cookie\");\n if (typeof existing === \"string\") {\n setCookiesValue.push(existing);\n }\n else if (Array.isArray(existing)) {\n setCookiesValue = existing;\n }\n // Loop through each cookie and build the cookie values\n for (let cookie of cookies) {\n // Set the cookie value first\n let value = `${cookie.name}=${cookie.value}`;\n // if there is a maxAge then set it - NOTE: put \";\" first\n if (cookie.maxAge !== undefined) {\n value += `; Max-Age=${cookie.maxAge}`;\n }\n // If there is a path then set it or use default path of \"/\" - NOTE: put \";\" first\n if (cookie.path !== undefined) {\n value += `; Path=${cookie.path}`;\n }\n else {\n value += `; Path=/`;\n }\n // If httpOnly is indicated then add it - NOTE: put \";\" first\n if (cookie.httpOnly === true) {\n value += \"; HttpOnly\";\n }\n // If secure is indicated set then add it - NOTE: put \";\" first\n if (cookie.secure === true) {\n value += \"; Secure\";\n }\n // If sameSite has been provided then add it - NOTE: put \";\" first\n if (cookie.sameSite !== undefined) {\n value += `; SameSite=${cookie.sameSite}`;\n }\n // If domain has been provided then add it - NOTE: put \";\" first\n if (cookie.domain !== undefined) {\n value += `; Domain=${cookie.domain}`;\n }\n // Save the cookie\n setCookiesValue.push(value);\n }\n // Finally set the cookie/s in the response header\n this.setHeader(\"Set-Cookie\", setCookiesValue);\n };\n clearCookies = (cookies) => {\n let httpCookies = [];\n for (let cookie of cookies) {\n // To clear a cookie - set value to empty string and max age to -1\n httpCookies.push({ name: cookie, value: \"\", maxAge: -1 });\n }\n this.setCookies(httpCookies);\n };\n setServerTimingHeader = () => {\n let serverTimingHeaders = [];\n // Check if the req has a Server-Timing header. This is not normal but I\n // want to something like a forwardAuth server to be able to add it's\n // metrics to the response header\n if (this?.req?.headers[\"server-timing\"] !== undefined) {\n const reqTimings = this.req.headers[\"server-timing\"];\n // Check if there are multiple headers\n if (Array.isArray(reqTimings)) {\n // If so then since this is the first just use it as the headers array\n serverTimingHeaders = reqTimings;\n }\n else {\n // If not then just add it to the array\n serverTimingHeaders.push(reqTimings);\n }\n }\n let serverTimingValue = \"\";\n // Add each additional metric added to the res next so they are in\n // the order they were added\n for (let metric of this._serverTimingsMetrics) {\n // Check if we have a string or a metric object\n if (typeof metric === \"string\") {\n // The string version is already formatted so just add to the array\n serverTimingHeaders.push(metric);\n continue;\n }\n // If we are here then we have a metric object so add the name\n serverTimingValue += metric.name;\n // Check if there is an optional duration\n if (metric.duration !== undefined) {\n serverTimingValue += `;dur=${metric.duration}`;\n }\n // Check if there is an optional description\n if (metric.description !== undefined) {\n serverTimingValue += `;desc=\"${metric.description}\"`;\n }\n serverTimingValue += \", \";\n }\n // Finally add the total latency for the endpoint to the array\n const latency = Math.round(performance.now() - this._receiveTime);\n serverTimingValue += `${this._latencyMetricName};dur=${latency}`;\n serverTimingHeaders.push(serverTimingValue);\n // Of course don't forget to set the header!!\n this.setHeader(\"Server-Timing\", serverTimingHeaders);\n };\n addServerTimingMetric = (name, duration, description) => {\n // This adds a metric to the Server-Timing header for this response\n this._serverTimingsMetrics.push({ name, duration, description });\n };\n addServerTimingHeader = (header) => {\n // This adds a complete Server-Timing header to this response\n this._serverTimingsMetrics.push(header);\n };\n}\n//# sourceMappingURL=req-res.js.map","export const contentTypes = {\n // \"123\": \"application/vnd.lotus-1-2-3\",\n // \"1km\": \"application/vnd.1000minds.decision-model+xml\",\n // \"3dml\": \"text/vnd.in3d.3dml\",\n // \"3ds\": \"image/x-3ds\",\n // \"3g2\": \"video/3gpp2\",\n // \"3gp\": \"video/3gpp\",\n // \"3gpp\": \"video/3gpp\",\n // \"3mf\": \"model/3mf\",\n \"7z\": \"application/x-7z-compressed\",\n // \"disposition-notification\": \"message/disposition-notification\",\n // \"n-gage\": \"application/vnd.nokia.n-gage.symbian.install\",\n // \"sfd-hdstx\": \"application/vnd.hydrostatix.sof-data\",\n // \"vbox-extpack\": \"application/x-virtualbox-vbox-extpack\",\n // aab: \"application/x-authorware-bin\",\n // aac: \"audio/x-aac\",\n // aam: \"application/x-authorware-map\",\n // aas: \"application/x-authorware-seg\",\n // abw: \"application/x-abiword\",\n // ac: \"application/vnd.nokia.n-gage.ac+xml\",\n // acc: \"application/vnd.americandynamics.acc\",\n // ace: \"application/x-ace-compressed\",\n // acu: \"application/vnd.acucobol\",\n // acutc: \"application/vnd.acucorp\",\n // adp: \"audio/adpcm\",\n // adts: \"audio/aac\",\n // aep: \"application/vnd.audiograph\",\n // afm: \"application/x-font-type1\",\n // afp: \"application/vnd.ibm.modcap\",\n // age: \"application/vnd.age\",\n // ahead: \"application/vnd.ahead.space\",\n // ai: \"application/postscript\",\n // aif: \"audio/x-aiff\",\n // aifc: \"audio/x-aiff\",\n // aiff: \"audio/x-aiff\",\n // air: \"application/vnd.adobe.air-application-installer-package+zip\",\n // ait: \"application/vnd.dvb.ait\",\n // ami: \"application/vnd.amiga.ami\",\n // aml: \"application/automationml-aml+xml\",\n // amlx: \"application/automationml-amlx+zip\",\n // amr: \"audio/amr\",\n // apk: \"application/vnd.android.package-archive\",\n // apng: \"image/apng\",\n // appcache: \"text/cache-manifest\",\n // appinstaller: \"application/appinstaller\",\n // application: \"application/x-ms-application\",\n // appx: \"application/appx\",\n // appxbundle: \"application/appxbundle\",\n // apr: \"application/vnd.lotus-approach\",\n // arc: \"application/x-freearc\",\n // arj: \"application/x-arj\",\n // asc: \"application/pgp-signature\",\n // asf: \"video/x-ms-asf\",\n // asm: \"text/x-asm\",\n // aso: \"application/vnd.accpac.simply.aso\",\n // asx: \"video/x-ms-asf\",\n // atc: \"application/vnd.acucorp\",\n // atom: \"application/atom+xml\",\n // atomcat: \"application/atomcat+xml\",\n // atomdeleted: \"application/atomdeleted+xml\",\n // atomsvc: \"application/atomsvc+xml\",\n // atx: \"application/vnd.antix.game-component\",\n // au: \"audio/basic\",\n // avci: \"image/avci\",\n // avcs: \"image/avcs\",\n // avi: \"video/x-msvideo\",\n // avif: \"image/avif\",\n // aw: \"application/applixware\",\n // azf: \"application/vnd.airzip.filesecure.azf\",\n // azs: \"application/vnd.airzip.filesecure.azs\",\n // azv: \"image/vnd.airzip.accelerator.azv\",\n // azw: \"application/vnd.amazon.ebook\",\n // b16: \"image/vnd.pco.b16\",\n // bat: \"application/x-msdownload\",\n // bcpio: \"application/x-bcpio\",\n // bdf: \"application/x-font-bdf\",\n // bdm: \"application/vnd.syncml.dm+wbxml\",\n // bdoc: \"application/x-bdoc\",\n // bed: \"application/vnd.realvnc.bed\",\n // bh2: \"application/vnd.fujitsu.oasysprs\",\n // bin: \"application/octet-stream\",\n // blb: \"application/x-blorb\",\n // blorb: \"application/x-blorb\",\n // bmi: \"application/vnd.bmi\",\n // bmml: \"application/vnd.balsamiq.bmml+xml\",\n bmp: \"image/x-ms-bmp\",\n // book: \"application/vnd.framemaker\",\n // box: \"application/vnd.previewsystems.box\",\n // boz: \"application/x-bzip2\",\n // bpk: \"application/octet-stream\",\n // bsp: \"model/vnd.valve.source.compiled-map\",\n // btf: \"image/prs.btif\",\n // btif: \"image/prs.btif\",\n // buffer: \"application/octet-stream\",\n // bz2: \"application/x-bzip2\",\n // bz: \"application/x-bzip\",\n // c11amc: \"application/vnd.cluetrust.cartomobile-config\",\n // c11amz: \"application/vnd.cluetrust.cartomobile-config-pkg\",\n // c4d: \"application/vnd.clonk.c4group\",\n // c4f: \"application/vnd.clonk.c4group\",\n // c4g: \"application/vnd.clonk.c4group\",\n // c4p: \"application/vnd.clonk.c4group\",\n // c4u: \"application/vnd.clonk.c4group\",\n // c: \"text/x-c\",\n // cab: \"application/vnd.ms-cab-compressed\",\n // caf: \"audio/x-caf\",\n // cap: \"application/vnd.tcpdump.pcap\",\n // car: \"application/vnd.curl.car\",\n // cat: \"application/vnd.ms-pki.seccat\",\n // cb7: \"application/x-cbr\",\n // cba: \"application/x-cbr\",\n // cbr: \"application/x-cbr\",\n // cbt: \"application/x-cbr\",\n // cbz: \"application/x-cbr\",\n // cc: \"text/x-c\",\n // cco: \"application/x-cocoa\",\n // cct: \"application/x-director\",\n // ccxml: \"application/ccxml+xml\",\n // cdbcmsg: \"application/vnd.contact.cmsg\",\n // cdf: \"application/x-netcdf\",\n // cdfx: \"application/cdfx+xml\",\n // cdkey: \"application/vnd.mediastation.cdkey\",\n // cdmia: \"application/cdmi-capability\",\n // cdmic: \"application/cdmi-container\",\n // cdmid: \"application/cdmi-domain\",\n // cdmio: \"application/cdmi-object\",\n // cdmiq: \"application/cdmi-queue\",\n // cdx: \"chemical/x-cdx\",\n // cdxml: \"application/vnd.chemdraw+xml\",\n // cdy: \"application/vnd.cinderella\",\n // cer: \"application/pkix-cert\",\n // cfs: \"application/x-cfs-compressed\",\n // cgm: \"image/cgm\",\n // chat: \"application/x-chat\",\n // chm: \"application/vnd.ms-htmlhelp\",\n // chrt: \"application/vnd.kde.kchart\",\n // cif: \"chemical/x-cif\",\n // cii: \"application/vnd.anser-web-certificate-issue-initiation\",\n // cil: \"application/vnd.ms-artgalry\",\n // cjs: \"application/node\",\n // cla: \"application/vnd.claymore\",\n // class: \"application/java-vm\",\n // cld: \"model/vnd.cld\",\n // clkk: \"application/vnd.crick.clicker.keyboard\",\n // clkp: \"application/vnd.crick.clicker.palette\",\n // clkt: \"application/vnd.crick.clicker.template\",\n // clkw: \"application/vnd.crick.clicker.wordbank\",\n // clkx: \"application/vnd.crick.clicker\",\n // clp: \"application/x-msclip\",\n // cmc: \"application/vnd.cosmocaller\",\n // cmdf: \"chemical/x-cmdf\",\n // cml: \"chemical/x-cml\",\n // cmp: \"application/vnd.yellowriver-custom-menu\",\n // cmx: \"image/x-cmx\",\n // cod: \"application/vnd.rim.cod\",\n // coffee: \"text/coffeescript\",\n // com: \"application/x-msdownload\",\n // conf: \"text/plain\",\n // cpio: \"application/x-cpio\",\n // cpl: \"application/cpl+xml\",\n // cpp: \"text/x-c\",\n // cpt: \"application/mac-compactpro\",\n // crd: \"application/x-mscardfile\",\n // crl: \"application/pkix-crl\",\n // crt: \"application/x-x509-ca-cert\",\n // crx: \"application/x-chrome-extension\",\n // cryptonote: \"application/vnd.rig.cryptonote\",\n // csh: \"application/x-csh\",\n // csl: \"application/vnd.citationstyles.style+xml\",\n // csml: \"chemical/x-csml\",\n // csp: \"application/vnd.commonspace\",\n css: \"text/css\",\n // cst: \"application/x-director\",\n csv: \"text/csv\",\n // cu: \"application/cu-seeme\",\n // curl: \"text/vnd.curl\",\n // cwl: \"application/cwl\",\n // cww: \"application/prs.cww\",\n // cxt: \"application/x-director\",\n // cxx: \"text/x-c\",\n // dae: \"model/vnd.collada+xml\",\n // daf: \"application/vnd.mobius.daf\",\n // dart: \"application/vnd.dart\",\n // dataless: \"application/vnd.fdsn.seed\",\n // davmount: \"application/davmount+xml\",\n // dbf: \"application/vnd.dbf\",\n // dbk: \"application/docbook+xml\",\n // dcr: \"application/x-director\",\n // dcurl: \"text/vnd.curl.dcurl\",\n // dd2: \"application/vnd.oma.dd2+xml\",\n // ddd: \"application/vnd.fujixerox.ddd\",\n // ddf: \"application/vnd.syncml.dmddf+xml\",\n // dds: \"image/vnd.ms-dds\",\n //