tiny-electron-essentials
Version: 
A lightweight and modular utility library for Electron apps, offering simplified window management, tray support, IPC channels, and custom frameless window styling.
191 lines (173 loc) • 6.69 kB
JavaScript
;
var tinyEssentials = require('tiny-essentials');
/**
 * An extended error object that may contain additional metadata.
 *
 * @typedef {Error & {
 *   code?: any,   // Optional error code, any type.
 *   data?: any    // Optional additional error data.
 * }} ErrorParsed
 */
/**
 * Converts an `Error` object into a plain JSON-serializable object.
 * Useful for sending errors between processes or over the network.
 *
 * @param {ErrorParsed} error - The error object to serialize.
 * @returns {{
 *   name: string,          // Error name (e.g., 'TypeError')
 *   message: string,       // Error message
 *   stack?: string,        // Optional stack trace
 *   code?: any,            // Optional error code
 *   data?: any             // Optional additional error data
 * }}
 * @throws {Error} Throws if the input is not a valid error object.
 */
function serializeError(error) {
  if (typeof error !== 'object' || error === null)
    throw new Error('Expected error to be a non-null object');
  if (typeof error.name !== 'string') throw new Error('Expected error.name to be a string');
  if (typeof error.message !== 'string') throw new Error('Expected error.message to be a string');
  if (error.stack !== undefined && typeof error.stack !== 'string')
    throw new Error('Expected error.stack to be a string if defined');
  return {
    name: error.name,
    message: error.message,
    stack: error.stack,
    ...(error.code !== undefined && { code: error.code }),
    ...(error.data !== undefined && { data: error.data }),
  };
}
/**
 * Converts a plain JSON object back into an `Error` instance.
 * Useful for reconstructing errors received over IPC or network.
 *
 * @param {{
 *   name?: string,        // Optional error name (defaults to 'Error')
 *   message: string,      // Error message
 *   stack?: string,       // Optional stack trace
 *   code?: any,           // Optional error code
 *   data?: any            // Optional additional error data
 * }} json - The JSON object representing the error.
 * @returns {ErrorParsed} The reconstructed error object.
 * @throws {Error} Throws if the input JSON does not match the expected structure.
 */
function deserializeError(json) {
  if (typeof json !== 'object' || json === null)
    throw new Error('Expected json to be a non-null object');
  if (typeof json.message !== 'string') throw new Error('Expected json.message to be a string');
  if (json.name !== undefined && typeof json.name !== 'string')
    throw new Error('Expected json.name to be a string if defined');
  if (json.stack !== undefined && typeof json.stack !== 'string')
    throw new Error('Expected json.stack to be a string if defined');
  /** @type {ErrorParsed} */
  const error = new Error(json.message);
  error.name = json.name || 'Error';
  error.stack = json.stack || '';
  if (json.code !== undefined) error.code = json.code;
  if (json.data !== undefined) error.data = json.data;
  return error;
}
/**
 * Performs a deep clone of an object, array, or date.
 * Useful for creating fully independent copies of nested structures.
 *
 * @param {*} obj - The object to clone. Supports plain objects, arrays, and Date instances.
 * @returns {*} A deep-cloned copy of the input.
 * @throws {Error} Throws if the input type is not supported (e.g., functions, Map, Set).
 */
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof Array) return obj.map(deepClone);
  if (obj instanceof Object) {
    const copy = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        // @ts-ignore
        copy[key] = deepClone(obj[key]);
      }
    }
    return copy;
  }
  throw new Error('Unsupported type');
}
/**
 * Validates and synchronizes event name constants between two objects.
 *
 * This function checks if the `eventNames` object contains string values
 * for keys present in the `list` object. If valid, it copies the values
 * from `eventNames` into `list`. If a value in `eventNames` is not a string,
 * it throws an error. Additionally, it ensures that `eventNames` is a valid object.
 *
 * @param {Record<string, string>} eventNames
 * An object containing event name keys and their corresponding string values.
 *
 * @param {Record<string, string>} list
 * The target object where valid event names from `eventNames` will be copied to.
 * This object is modified in-place.
 *
 * @throws {TypeError} If `eventNames` is not a valid JSON object.
 * @throws {Error} If any value in `eventNames` (for keys that exist in `list`) is not a string.
 */
const checkEventsList = (eventNames, list) => {
  if (!tinyEssentials.isJsonObject(eventNames)) throw new TypeError('Expected "eventNames" to be an object.');
  for (const key in list) {
    // @ts-ignore
    if (typeof eventNames[key] !== 'undefined' && typeof eventNames[key] !== 'string')
      throw new Error(
        // @ts-ignore
        `[Events] Value of key "${eventNames[key]}" must be a string. Got: ${typeof eventNames[key]}`,
      );
  }
  for (const key in eventNames) {
    // @ts-ignore
    if (typeof eventNames[key] === 'string')
      // @ts-ignore
      list[key] = eventNames[key];
  }
};
/**
 * Move all elements from document.body into a target container,
 * except for <style> and <script> tags.
 *
 * @param {HTMLElement} targetElement - The destination container.
 */
function moveBodyContentTo(targetElement) {
  if (!(targetElement instanceof HTMLElement)) {
    throw new TypeError('targetElement must be an HTMLElement');
  }
  const nodes = Array.from(document.body.childNodes);
  for (const node of nodes) {
    if (
      node.nodeType === Node.ELEMENT_NODE &&
      // @ts-ignore
      (node.tagName === 'SCRIPT' || node.tagName === 'STYLE')
    ) {
      continue; // Skip <script> and <style>
    }
    targetElement.appendChild(node);
  }
}
/**
 * Checks if two DOM elements are colliding on the screen.
 *
 * @param {Element} elem1 - First DOM element.
 * @param {Element} elem2 - Second DOM element.
 * @returns {boolean} - Returns true if the elements are colliding.
 */
function areElementsColliding(elem1, elem2) {
  const rect1 = elem1.getBoundingClientRect();
  const rect2 = elem2.getBoundingClientRect();
  return !(
    rect1.right < rect2.left ||
    rect1.left > rect2.right ||
    rect1.bottom < rect2.top ||
    rect1.top > rect2.bottom
  );
}
exports.areElementsColliding = areElementsColliding;
exports.checkEventsList = checkEventsList;
exports.deepClone = deepClone;
exports.deserializeError = deserializeError;
exports.moveBodyContentTo = moveBodyContentTo;
exports.serializeError = serializeError;