import { htmlTreeAsString } from './browser.js'; import { DEBUG_BUILD } from './debug-build.js'; import { isError, isEvent, isInstanceOf, isElement, isPlainObject, isPrimitive } from './is.js'; import { logger } from './logger.js'; import { truncate } from './string.js'; /** * Replace a method in an object with a wrapped version of itself. * * @param source An object that contains a method to be wrapped. * @param name The name of the method to be wrapped. * @param replacementFactory A higher-order function that takes the original version of the given method and returns a * wrapped version. Note: The function returned by `replacementFactory` needs to be a non-arrow function, in order to * preserve the correct value of `this`, and the original method must be called using `origMethod.call(this, )` or `origMethod.apply(this, [])` (rather than being called directly), again to preserve `this`. * @returns void */ function fill(source, name, replacementFactory) { if (!(name in source)) { return; } const original = source[name] ; const wrapped = replacementFactory(original) ; // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work // otherwise it'll throw "TypeError: Object.defineProperties called on non-object" if (typeof wrapped === 'function') { markFunctionWrapped(wrapped, original); } source[name] = wrapped; } /** * Defines a non-enumerable property on the given object. * * @param obj The object on which to set the property * @param name The name of the property to be set * @param value The value to which to set the property */ function addNonEnumerableProperty(obj, name, value) { try { Object.defineProperty(obj, name, { // enumerable: false, // the default, so we can save on bundle size by not explicitly setting it value: value, writable: true, configurable: true, }); } catch (o_O) { DEBUG_BUILD && logger.log(`Failed to add non-enumerable property "${name}" to object`, obj); } } /** * Remembers the original function on the wrapped function and * patches up the prototype. * * @param wrapped the wrapper function * @param original the original function that gets wrapped */ function markFunctionWrapped(wrapped, original) { try { const proto = original.prototype || {}; wrapped.prototype = original.prototype = proto; addNonEnumerableProperty(wrapped, '__sentry_original__', original); } catch (o_O) {} // eslint-disable-line no-empty } /** * This extracts the original function if available. See * `markFunctionWrapped` for more information. * * @param func the function to unwrap * @returns the unwrapped version of the function if available. */ function getOriginalFunction(func) { return func.__sentry_original__; } /** * Encodes given object into url-friendly format * * @param object An object that contains serializable values * @returns string Encoded */ function urlEncode(object) { return Object.keys(object) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(object[key])}`) .join('&'); } /** * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their * non-enumerable properties attached. * * @param value Initial source that we have to transform in order for it to be usable by the serializer * @returns An Event or Error turned into an object - or the value argurment itself, when value is neither an Event nor * an Error. */ function convertToPlainObject( value, ) { if (isError(value)) { return { message: value.message, name: value.name, stack: value.stack, ...getOwnProperties(value), }; } else if (isEvent(value)) { const newObj = { type: value.type, target: serializeEventTarget(value.target), currentTarget: serializeEventTarget(value.currentTarget), ...getOwnProperties(value), }; if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) { newObj.detail = value.detail; } return newObj; } else { return value; } } /** Creates a string representation of the target of an `Event` object */ function serializeEventTarget(target) { try { return isElement(target) ? htmlTreeAsString(target) : Object.prototype.toString.call(target); } catch (_oO) { return ''; } } /** Filters out all but an object's own properties */ function getOwnProperties(obj) { if (typeof obj === 'object' && obj !== null) { const extractedProps = {}; for (const property in obj) { if (Object.prototype.hasOwnProperty.call(obj, property)) { extractedProps[property] = (obj )[property]; } } return extractedProps; } else { return {}; } } /** * Given any captured exception, extract its keys and create a sorted * and truncated list that will be used inside the event message. * eg. `Non-error exception captured with keys: foo, bar, baz` */ function extractExceptionKeysForMessage(exception, maxLength = 40) { const keys = Object.keys(convertToPlainObject(exception)); keys.sort(); if (!keys.length) { return '[object has no keys]'; } if (keys[0].length >= maxLength) { return truncate(keys[0], maxLength); } for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) { const serialized = keys.slice(0, includedKeys).join(', '); if (serialized.length > maxLength) { continue; } if (includedKeys === keys.length) { return serialized; } return truncate(serialized, maxLength); } return ''; } /** * Given any object, return a new object having removed all fields whose value was `undefined`. * Works recursively on objects and arrays. * * Attention: This function keeps circular references in the returned object. */ function dropUndefinedKeys(inputValue) { // This map keeps track of what already visited nodes map to. // Our Set - based memoBuilder doesn't work here because we want to the output object to have the same circular // references as the input object. const memoizationMap = new Map(); // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API return _dropUndefinedKeys(inputValue, memoizationMap); } function _dropUndefinedKeys(inputValue, memoizationMap) { if (isPojo(inputValue)) { // If this node has already been visited due to a circular reference, return the object it was mapped to in the new object const memoVal = memoizationMap.get(inputValue); if (memoVal !== undefined) { return memoVal ; } const returnValue = {}; // Store the mapping of this value in case we visit it again, in case of circular data memoizationMap.set(inputValue, returnValue); for (const key of Object.keys(inputValue)) { if (typeof inputValue[key] !== 'undefined') { returnValue[key] = _dropUndefinedKeys(inputValue[key], memoizationMap); } } return returnValue ; } if (Array.isArray(inputValue)) { // If this node has already been visited due to a circular reference, return the array it was mapped to in the new object const memoVal = memoizationMap.get(inputValue); if (memoVal !== undefined) { return memoVal ; } const returnValue = []; // Store the mapping of this value in case we visit it again, in case of circular data memoizationMap.set(inputValue, returnValue); inputValue.forEach((item) => { returnValue.push(_dropUndefinedKeys(item, memoizationMap)); }); return returnValue ; } return inputValue; } function isPojo(input) { if (!isPlainObject(input)) { return false; } try { const name = (Object.getPrototypeOf(input) ).constructor.name; return !name || name === 'Object'; } catch (e) { return true; } } /** * Ensure that something is an object. * * Turns `undefined` and `null` into `String`s and all other primitives into instances of their respective wrapper * classes (String, Boolean, Number, etc.). Acts as the identity function on non-primitives. * * @param wat The subject of the objectification * @returns A version of `wat` which can safely be used with `Object` class methods */ function objectify(wat) { let objectified; switch (true) { case wat === undefined || wat === null: objectified = new String(wat); break; // Though symbols and bigints do have wrapper classes (`Symbol` and `BigInt`, respectively), for whatever reason // those classes don't have constructors which can be used with the `new` keyword. We therefore need to cast each as // an object in order to wrap it. case typeof wat === 'symbol' || typeof wat === 'bigint': objectified = Object(wat); break; // this will catch the remaining primitives: `String`, `Number`, and `Boolean` case isPrimitive(wat): // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access objectified = new (wat ).constructor(wat); break; // by process of elimination, at this point we know that `wat` must already be an object default: objectified = wat; break; } return objectified; } export { addNonEnumerableProperty, convertToPlainObject, dropUndefinedKeys, extractExceptionKeysForMessage, fill, getOriginalFunction, markFunctionWrapped, objectify, urlEncode }; //# sourceMappingURL=object.js.map