import {NPAS_Options, NPAS_Options_Output} from "../index"; import {NextFunction, Request, Response} from "express"; export class Utils { /** * Create options from here so we can use them both in app and tests. * @param {NPAS_Options} options * @returns {NPAS_Options} */ static createOptions(options: NPAS_Options): NPAS_Options { let routerPkg = require('php-built-in-web-server-router'); let routerDefaultPath = routerPkg.lib; let routerCertificate = routerPkg.cert; let outputDefaults: NPAS_Options_Output = { startup: true, date: true, ip: true, statusCode: true, method: true, port: false, os: false, reqTime: false, browser: false, device: false, }; let opt: NPAS_Options = { host: '0.0.0.0', port: 9000, env: {}, ini_set: {}, theme: 'php', ini_config: "", secureOrigin: true, middleware: (req: Request, res: Response, next: NextFunction) => { next(); }, middlewareAfter: (req: Request, res: Response, next: NextFunction) => { next(); }, root: Utils.root('..', 'public'), indexFile: 'index.php', router: routerDefaultPath, bin: 'php', clustersSet: 'auto', phpPerCluster: 1, output: outputDefaults }; options = {...{}, ...opt, ...options}; if (typeof options.output !== "boolean") { options.output = {...{}, ...outputDefaults, ...options.output}; } /** * Add cURL certificate * https://curl.haxx.se/docs/caextract.html */ if (!options.ini_set.hasOwnProperty('curl.cainfo')) { options.ini_set['curl.cainfo'] = routerCertificate; } /** * Format boolean values to 1 | 0 */ for (let item in options) { if (options.hasOwnProperty(item)) { if (typeof options[item] === 'boolean') { options[item] = options[item] == true ? 1 : 0; } if (item === 'output') { if (typeof options.output === 'object' && !options.output.hasOwnProperty('length')) { for (let o in options[item]) { if (options[item].hasOwnProperty(o) && typeof options[item][o] === 'boolean') { options[item][o] = options[item][o] == true ? 1 : 0; } } } } } } /** * Clean paths - root, router, indexFile */ options.root = Utils.cleanInputPath(options.root); options.router = Utils.cleanInputPath(options.router); options.indexFile = Utils.cleanInputPath(options.indexFile); options.ini_config = Utils.cleanInputPath(options.ini_config); /** * Reset to index.php if root path is default */ if (options.root === Utils.cleanInputPath(Utils.root('..', 'router'))) { options.indexFile = 'index.php'; } /** * Correct phpPerCluster, defaults are based on clusterSet * clustersTotal = 1 -> phpPerCluster min value will be 2 * clustersTotal >= 2 -> phpPerCluster min value will be 1 */ let clustersTotal = Utils.getClusters(options.clustersSet); if (clustersTotal <= 1) { /** * clustersTotal <= 1 -> phpPerCluster min value will be 2 */ if (options.phpPerCluster < 2) { options.phpPerCluster = 2; } } if (clustersTotal >= 2) { /** * clustersTotal >= 2 -> phpPerCluster min value will be 1 */ if (options.phpPerCluster < 1) { options.phpPerCluster = 1; } } return options; } /** * Return the next value from array after "val" * @param {number[]} arr * @param {number} val * @returns {number} */ static getNextAfter(arr: number[], val: number): any { const searchPos = arr.indexOf(val); let newVal: number = arr[0]; if (searchPos > -1) { if (searchPos + 1 >= arr.length) { newVal = arr[0]; } else { newVal = arr[searchPos + 1]; } } return newVal; } /** * Return a random value from array */ static arrayRandom(currArray: any[]) { return currArray[Math.floor(Math.random() * currArray.length)]; } /** * Detect if we have a custom router * 1 - default * 2 - user-defined * 3 - default (fallback) * @param {string} customRouter * @param {string} defaultRouter * @returns {number} */ static detectRouterType(customRouter: string = "", defaultRouter: string = ""): number { customRouter = Utils.cleanInputPath(customRouter).toLowerCase(); defaultRouter = Utils.cleanInputPath(defaultRouter).toLowerCase(); return customRouter !== defaultRouter ? (require('fs').existsSync(customRouter) ? 2 : 3) : 1; } static cleanInputPath(path: string) { return path.replace(/[/\\]+/g, "/").replace(/[\/\\]$/, ""); } /** * Find your local network address * Exclude by default VMware and Docker networks * @param {string} matchAddress * @param {string[]} excludeNetworksLike * @returns {string} */ getLocalAddress(matchAddress: string = '127.0.0.1', excludeNetworksLike: string[] = ['vmware', 'docker']) { let address = matchAddress; let network = require('os').networkInterfaces(); let {isIPv4} = require('net'); let availableNetworks: string[] = Object.keys(network); let foundIP = false; let ipList: any[] = []; /** * Filter networks */ if (excludeNetworksLike.length) { availableNetworks = availableNetworks.filter((networkName: string) => { let keepNetwork = true; excludeNetworksLike.forEach((eth: string) => { if (new RegExp(eth.toLowerCase()).test(networkName.toLowerCase())) { keepNetwork = false; } }); return keepNetwork; }); } /** * Retrieve IPv4 addresses base on the filtered networks */ availableNetworks.forEach((networkName) => { for (let item of network[networkName]) { if (item.family === 'IPv4' && item.internal === false && ipList.indexOf(item.address) === -1) { ipList.push(item.address); } } }); if (ipList.length) { ipList.forEach((ip: string) => { if (new RegExp(matchAddress).test(ip)) { address = ip; foundIP = true; } }); if (!foundIP) { address = ipList[0]; } } if (!isIPv4(address)) { address = '127.0.0.1'; } return address; } static root(...input: any[]) { let path = require('path'); let _root = path.resolve(__dirname, '..'); input = Array.prototype.slice.call(input, 0); return path.join.apply(path, [_root].concat(input)); } static getClusters(clustersSet: string | number): number { let cpus = require('os').cpus(); let cpuCount = cpus.length; let clustersOptions = ['max', 'auto']; let clustersNr: number = 1; if (typeof clustersSet === 'string' && clustersOptions.indexOf(clustersSet.toLowerCase()) !== -1) { /** * Max CPU cores */ if (clustersSet.toLowerCase() === 'max') { clustersNr = cpuCount; } /** * Custom profile */ if (clustersSet.toLowerCase() === 'auto') { if (cpuCount === 4) { clustersNr = 2; } if (cpuCount >= 8 && cpuCount < 12) { clustersNr = 3; } if (cpuCount >= 12 && cpuCount < 16) { clustersNr = 4; } if (cpuCount >= 16) { clustersNr = 5; } } } if (!isNaN(+clustersSet) && clustersSet > 0) { clustersNr = +clustersSet; } if (clustersSet < 1) { clustersNr = 1; } return clustersNr; } static getValidAddress(host: string, port: number = 0, returnObject: boolean = false) { let net = require('net'); let fallbackHost = '0.0.0.0'; if (!net.isIP(host)) { host = fallbackHost; } if ((port < 1 || port > 65535) || isNaN(+port)) { port = 0; } if (returnObject) { return { host: host, port: port }; } else { return host + (port ? ':' + port : ''); } } }