Util.js

import fs from 'fs';
import { dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));

/**
 * A module for utility functions.
 * @module Util
 */
export default {

  /**
   * Implementation of a conditional counter.
   * @param {Boolean} condition Either true or false. Note, the condition is not evaluated in the function. 
   * @param {Number} value The current count (of a certain feature).  
   * @returns {Number} Returns ``value + 1`` in case ``condition == true`` or ``value`` otherwise.
   */
  count: (condition, value) => {
    return (condition)
      ? value + 1
      : value;
  },

  /**
   * A function to encode boolean values as number (1 for ``true``, 0 for ``false``).
   * The encoding is later needed, when the data is fed into the classifier.
   * @param {Boolean} condition Either ``true`` or ``false``. Note, the condition is not evaluated in the function.
   * @returns {Number} Returns 1 in case ``true`` is passed, 0 otherwise.
   */
  zeroOrOne: (condition) =>
    (condition) ? 1 : 0,

  /**
   * A function to compute the ratio of two numbers.
   * **NOTE:** Returns 0 in case that the divisor is 0.
   * @param {Number} dividend The dividend of the ratio.
   * @param {Number} divisor The divisor of the ratio.
   * @returns {Number} The ratio between two numbers.
   */
  ratio: (dividend, divisor) => {
    return (divisor > 0) 
      ? dividend / divisor
      : 0;
  },

  /**
   * A wrapper function for ``Math.max()``.
   * @param {Number} x The first number.
   * @param {Number} y The second number.
   * @returns {Number} Returns the greater number.
   */
  max: (x, y) => {
    return Math.max(x, y);
  },

  /**
   * A function to extract a header field from the HTTP/S header of a request.
   * **NOTE:** The matching is case insensitive.
   * @param {Array} headers An array of HTTP/S header fields in the format 
   * ```json
   * { name: "key", value: "value" }
   * ```
   * @param {String} key An HTTP/S header key like ``Referer`` or ``Origin``.
   * @returns {Object} The requested HTTP/S header in the format: 
   * ```json
   * { name: "key", value: "value" }
   * ```
   * Returns ``undefined`` in case header with ``name === key`` is not present.
   */
  header(headers, key) {
    return headers.find((h) => h.name.toLowerCase() === key.toLowerCase())
  },

  /**
   * A function to retrieve all query parameters of an HTTP/S request.
   * @param {Object} r An HTTP/S request retrieved from the ``webRequest`` interface. 
   * See {@link https://developer.chrome.com/docs/extensions/reference/webRequest/#event-onBeforeRequest|webRequest.onBeforeRequest()}
   * for more details.
   * @returns {Array} Returns an array of all query parameters in the format:
   * ```json
   * [ [param_1, value_1], [param_2, value_2], ..., [param_n, value_n] ]
   * ```
   * Returns an empty array in case no query parameters are present.
   */
  params(r) {
    let u = new URL(this.target(r));
    return (u.searchParams)
      ? [...u.searchParams.entries()]
      : []
  },

  /**
   * A function to retrieve all cookie fields of an HTTP/S request.
   * @param {Object} r An HTTP/S request retrieved from the ``webRequest`` interface. 
   * See {@link https://developer.chrome.com/docs/extensions/reference/webRequest/#event-onBeforeRequest|webRequest.onBeforeRequest()}
   * for more details.
   * @returns Returns an array of all cookie fields in the format:
   * ```json
   * [ [field_1, value_1], [field_2, value_2], ..., [field_n, value_n] ]
   * ```
   * Returns an empty array in case no cookie is present.
   */
  cookie(r) {
    let c = this.header(r.requestHeaders, 'cookie');
    return (c) 
      ? c.value.split(";").map((el) => el.trim().split("="))
      : [];
  },

  /**
   * A function to extract the target URL of an HTTP/S request.
   * The function will extract the ``url`` property of the request
   * and falls back to the ``url`` property of the response in case
   * no ``url`` property is present in the request.
   * @param {Object} r An HTTP/S request retrieved from the ``webRequest`` interface. 
   * See {@link https://developer.chrome.com/docs/extensions/reference/webRequest/#event-onBeforeRequest|webRequest.onBeforeRequest()}
   * for more details.
   * @returns {String} Returns the full target URL as string. Returns ``undefined`` 
   * in case the ``url`` property is neither present in the request nor the response.
   */
  target(r) {
    if (r.url) {
      return r.url;
    } else if (r.response) {
      if (r.response.url) {
        return r.response.url;
      }
    }    
  },
  
  /**
   * A function to extract the source of an HTTP/S request.
   * The function will use the ``initiator`` property of the request.
   * If this property is absent, the function tries
   * to extract the source from the ``referer`` or ``origin`` HTTP/S header.
   * @param {Object} r An HTTP/S request retrieved from the ``webRequest`` interface. 
   * See {@link https://developer.chrome.com/docs/extensions/reference/webRequest/#event-onBeforeRequest|webRequest.onBeforeRequest()}
   * for more details.
   * @returns {String} Returns the full source URL as string. Returns ``undefined``
   * in case no source could be extracted.
   */
  source(r) {
    if (r.initiator) {
      return r.initiator;
    } else if (r.requestHeaders) {
      let referer = this.header(r.requestHeaders, 'referer');
      if (referer) {
        return referer.value;
      } else {
        let origin = this.header(r.requestHeaders, 'origin');
        if (origin) {
          return origin.value;
        }
      }
    }    
  },

  /**
   * A function to check whether an absolute or relative path exists.
   * @param {String} dir A given path as string.
   * @returns {String} Returns the path in case it exists. Returns ``null`` otherwise.
   */
  path(dir) {
    return (fs.existsSync(dir)) 
      ? dir
      : (fs.existsSync(__dirname + '/' + dir))
        ? rel // TODO: not defined
        : null;    
  },

  /**
   * A function to check whether a path is a directory.
   * This is a wrapper function for ``fs.lstatSync(path).isDirectory()``.
   * @param {String} path  A given path as string.
   * @returns {Boolean} 
   */
  isDir(path) {
    return fs
      .lstatSync(path)
      .isDirectory();
  },

  /**
   * A function to read the contents of a directory and to
   * extract all files with the file extension ``*.json``.
   * @param {String} path A given path as string.
   * @returns {Array} Returns all JSON files contained in ``path``.
   */
  batches(path) {
    return fs
      .readdirSync(path)
      .filter((file) => file.endsWith('.json'));
  },

};