blacktraffic/blacktraffic_basic.js

//Initialise functions
{
	if (!global.Blacktraffic)
		/**
		 * The namespace for all UF/Blacktraffic utility functions, typically for static methods.
		 * 
		 * @namespace Blacktraffic
		 */
		global.Blacktraffic = {};
	
	/**
	 * Executes a shell command.
	 * 
	 * @param {string} arg0_command
	 * @param argn_arguments
	 * 
	 * @returns {{stderr: string, stdout: string}}
	 */
	Blacktraffic.execCommand = function (arg0_command, ...argn_arguments) {
		//Convert from parameters
		let command = arg0_command;
		let local_arguments = argn_arguments;
		
		//Declare local instance variables
		let exec_result = child_process.spawnSync(command, local_arguments, { encoding: "utf8" });
		let stderr = exec_result.stderr;
		let stdout = exec_result.stdout;
		
		//Return statement
		return { stderr: stderr, stdout: stdout };
	};
	
	/**
	 * Executes a shell command asynchronously.
	 * 
	 * @param {string} arg0_command
	 * @param argn_arguments
	 * 
	 * @returns {Promise<{stderr: string, stdout: string}>}
	 */
	Blacktraffic.execCommandAsync = async function (arg0_command, ...argn_arguments) {
		//Convert from parameters
		let command = arg0_command;
		let local_arguments = argn_arguments;
		
		//Return statement
		return new Promise((resolve, reject) => {
			let local_process = child_process.spawn(command, local_arguments);
			let stderr = "";
			let stdout = "";
			
			local_process.stdout.on("data", (data) => {
				data = data.toString();
				stdout += data;
			});
			local_process.stderr.on("data", (data) => {
				data = data.toString();
				stderr += data;
			});
			local_process.on("error", (e) => {
				reject(e);
			});
			local_process.on("exit", (code) => {
				if (code !== 0) {
					reject(`${command} ${local_arguments.join(" ")} exited with code ${code} and error: ${stderr}`);
				} else {
					resolve(stdout);
				}
			});
		});
	};
	
	/**
	 * Returns any available port that can be used by Puppeteer/Selenium agents, or for server work.
	 * 
	 * @returns {Promise<number>}
	 */
	Blacktraffic.getFreePort = async function () {
		//Return statement
		return new Promise((resolve, reject) => {
			let server = net.createServer();
				server.unref();
				server.on("error", reject);
				server.listen(0, () => {
					let port = server.address().port;
					server.close(() => resolve(port));
				});
		});
	};
	
	/**
	 * Returns the current operating system. Either 'lin'/'mac'/'win'.
	 *
	 * @returns {string}
	 */
	Blacktraffic.getOS = function () {
		//Declare local instance variables
		let process_platform = process.platform;
		
		//Return statement
		if (process_platform === "win32") return "win";
		if (["freebsd", "linux", "openbsd"].includes(process_platform)) return "lin";
		return "mac";
	};
	
	/**
	 * Waits a certain number of ms before continuing with the current process as a synchronous command.
	 * 
	 * @param {number} arg0_ms
	 * 
	 * @returns {Promise<unknown>}
	 */
	Blacktraffic.sleep = function (arg0_ms) {
		//Convert from parameters
		let ms = arg0_ms;
		
		//Return statement
		return new Promise(resolve => setTimeout(resolve, ms));
	};
	
	/**
	 * Sends an IPC task out from a render/worker thread to the main thread for processing, resolving the Promise once the result is received.
	 * 
	 * @param {string} arg0_channel
	 * @param {Object} [arg1_options]
	 *  @param {any[]} [arg1_options.args]
	 *  @param {function} [arg1_options.special_function] - Function to use when resolving the channel.
	 * 
	 * @returns {Promise}
	 */
	Blacktraffic.task = async function (arg0_channel, arg1_options) {
		//Convert from parameters
		let channel = arg0_channel;
		let options = arg1_options ? arg1_options : {};
		
		if (!options.args) options.args = [];
		
		//Safe environment detection
		let ipc_renderer = (global.electron) ? global.electron.ipcRenderer : null;
		let is_renderer = (ipc_renderer !== undefined && ipc_renderer !== null);
		
		if (is_renderer) {
			//Renderer logic uses the standard Electron IPC pipe
			return new Promise((resolve, reject) => {
				ipc_renderer.removeAllListeners(`${channel}:ready`);
				
				ipc_renderer.on(`${channel}:ready`, (event, ...response_args) => {
					ipc_renderer.removeAllListeners(`${channel}:ready`);
					
					try {
						if (typeof options.special_function === "function") {
							resolve(options.special_function(event, ...response_args));
						} else {
							//Return first arg or full array
							(response_args.length === 1) ?
								resolve(response_args[0]) :
								resolve(response_args);
						}
					} catch (err) {
						reject(err);
					}
				});
				
				ipc_renderer.send(channel, ...options.args);
			});
		} else {
			//Worker logic only runs if ipc_renderer is not present
			let task_id = Object.generateRandomID();
			
			try {
				//We use a generic require to avoid loader issues where possible
				let node_threads = require("worker_threads");
				let parent_port = node_threads.parentPort;
				
				if (parent_port) {
					return new Promise((resolve) => {
						let listener = (msg) => {
							if (msg.type === "worker_ipc_ready" && msg.task_id === task_id) {
								parent_port.off("message", listener);
								resolve(msg.results);
							}
						};
						parent_port.on("message", listener);
						
						parent_port.postMessage({
							type: "worker_ipc_request",
							task_id: task_id,
							channel: channel,
							args: options.args
						});
					});
				}
			} catch (e) {
				console.error("Environment detection failed: No IPC source available.");
			}
		}
	};
	/**
	 * Yields to the event loop inside of a synchronous processing sequence.
	 * @alias Blacktraffic.yield
	 * 
	 * @param {number} [arg0_delay_ms=0]
	 * 
	 * @returns {Promise<unknown>}
	 */
	Blacktraffic.yield = function (arg0_delay_ms) {
		//Convert from parameters
		let delay_ms = (arg0_delay_ms !== undefined) ? arg0_delay_ms : 0;
		
		//Return statement
		if (typeof window !== "undefined" || delay_ms > 0)
			return new Promise((resolve) => setTimeout(resolve, delay_ms));
		return new Promise((resolve) => setImmediate(resolve));
	};
}