(function () { 'use strict'; /** * Stores original versions of native functions before patching. * @type {Map} * @description Map structure: * - Key: Original method's name property (e.g., 'log', 'error') * - Value: Original native function implementation */ const savedOriginalMethods = new Map(); /** * Stores the string conversion of the modified local function together with the original local function. * @type {Map} * @description Map structure: * - Key: String conversion of the modified native function * - Value: Original native function implementation */ const methodRemapDictionary = new Map(); /** * Converts a function to shorthand method syntax while preserving closure variables * @param {Function} functionToConvert - Function to convert to shorthand method syntax * @param {string} functionName - Name to use for the method property * @returns {Function} Generated function with access to closure variables * @description * 1. Extracts function body by removing 'function' declaration * 2. Creates new method-style function using dynamic evaluation * 3. Injects closure references (savedOriginalMethods, methodRemapDictionary) * through immediately-invoked arguments */ function convertFunctionToShorthand(standardFunction, functionName) { const functionBody = standardFunction.toString().replace(new RegExp(`^function([^\\(]*)`), '') const shorthandFunction = new Function( `const [savedOriginalMethods, methodRemapDictionary, name]=arguments;return { ${functionName}${functionBody} }.${functionName};` )(savedOriginalMethods, methodRemapDictionary, functionName); return shorthandFunction; } /** * Replaces a native function with a modified proxy function * @param {string} name - The name of the native function to be modified * @param {Function} nativeFunction - The original native function implementation * @param {Function} modifyFunction - The function to modify the native function * @returns {Function} The shorthand function that replaces the native function * @description This function stores the original native function in a map, * converts the modifying function to a shorthand method, * and returns a shorthand function that maintains a reference * to the original implementation. */ function modifyNativeFunction(name, nativeFunction, modifyFunction) { methodRemapDictionary.set(name, nativeFunction); const shorthandModifyFunction = convertFunctionToShorthand(modifyFunction, name); savedOriginalMethods.set(methodRemapDictionary.get('toString').call(shorthandModifyFunction), nativeFunction); return shorthandModifyFunction; } /** * A function that handles toString operations with method remapping support. * It attempts to apply toString on the original method if available in the savedOriginalMethods map. * If an error occurs, it modifies the error stack trace by removing the third line before rethrowing. * * @function toString * @param {...*} args - Arguments to be passed to the toString method * @returns {string} The string representation of the object * @throws {Error} Rethrows any error that occurs during execution with modified stack trace */ const toString = function (...args) { try { const ret = Reflect.apply(methodRemapDictionary.get("toString"), this, args); if (savedOriginalMethods.has(ret)) { return Reflect.apply(methodRemapDictionary.get("toString"), savedOriginalMethods.get(ret), args); } return ret; } catch (error) { const stackArray = error.stack.split('\n'); stackArray.splice(2, 1); const modifiedStack = stackArray.join('\n'); error.stack = modifiedStack; throw error; } }; Function.prototype.toString = modifyNativeFunction('toString', Function.prototype.toString, toString); /** * Patches console methods to intercept and handle potential errors during argument access. * * Iterates through a list of console methods (log, error, info, warn, trace, dir, debug, dirxml, table ...) * and replaces each with a modified version. The modified version attempts to access specific properties * of each argument passed to the console method (toString, name, message). If any error occurs during * this access, the error's stack trace is modified by removing the third line before re-throwing. * The original console methods are stored and used for the actual logging. */ const consoleMethods = ['log','error','info','warn','trace','dir','debug','dirxml','table']; for (const name of consoleMethods) { const fn = function (...args) { try { for (const arg of args) { const descriptors=Object.getOwnPropertyDescriptors(arg); if(arg?.hasOwnProperty('toString')){ arg.toString(); } if(descriptors?.name?.get){ descriptors?.name?.get(); } if(descriptors?.message?.get){ descriptors?.message?.get(); } } //Reflect.apply(orginalMethodsMap.get(name), this, args); } catch (error) { const stackArray = error.stack.split('\n'); stackArray.splice(2, 1); const modifiedStack = stackArray.join('\n'); error.stack = modifiedStack; throw error; } }; console[name] = modifyNativeFunction(name, console[name], fn); } })();