import { __values } from "tslib"; import { htmlTreeAsString } from './browser'; import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive, isSyntheticEvent } from './is'; import { Memo } from './memo'; import { getFunctionName } from './stacktrace'; import { truncate } from './string'; /** * Wrap a given object method with a higher-order function * * @param source An object that contains a method to be wrapped. * @param name A name of method to be wrapped. * @param replacement A function that should be used to wrap a given method. * @returns void */ export function fill(source, name, replacement) { if (!(name in source)) { return; } var original = source[name]; var wrapped = replacement(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') { try { wrapped.prototype = wrapped.prototype || {}; Object.defineProperties(wrapped, { __sentry_original__: { enumerable: false, value: original, }, }); } catch (_Oo) { // This can throw if multiple fill happens on a global object like XMLHttpRequest // Fixes https://github.com/getsentry/sentry-javascript/issues/2043 } } source[name] = wrapped; } /** * Encodes given object into url-friendly format * * @param object An object that contains serializable values * @returns string Encoded */ export function urlEncode(object) { return Object.keys(object) .map(function (key) { return encodeURIComponent(key) + "=" + encodeURIComponent(object[key]); }) .join('&'); } /** * Transforms any object into an object literal with all it's attributes * attached to it. * * @param value Initial source that we have to transform in order to be usable by the serializer */ function getWalkSource(value) { if (isError(value)) { var error = value; var err = { message: error.message, name: error.name, stack: error.stack, }; for (var i in error) { if (Object.prototype.hasOwnProperty.call(error, i)) { err[i] = error[i]; } } return err; } if (isEvent(value)) { var event_1 = value; var source = {}; source.type = event_1.type; // Accessing event.target can throw (see getsentry/raven-js#838, #768) try { source.target = isElement(event_1.target) ? htmlTreeAsString(event_1.target) : Object.prototype.toString.call(event_1.target); } catch (_oO) { source.target = ''; } try { source.currentTarget = isElement(event_1.currentTarget) ? htmlTreeAsString(event_1.currentTarget) : Object.prototype.toString.call(event_1.currentTarget); } catch (_oO) { source.currentTarget = ''; } if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) { source.detail = event_1.detail; } for (var i in event_1) { if (Object.prototype.hasOwnProperty.call(event_1, i)) { source[i] = event_1; } } return source; } return value; } /** Calculates bytes size of input string */ function utf8Length(value) { // eslint-disable-next-line no-bitwise return ~-encodeURI(value).split(/%..|./).length; } /** Calculates bytes size of input object */ function jsonSize(value) { return utf8Length(JSON.stringify(value)); } /** JSDoc */ export function normalizeToSize(object, // Default Node.js REPL depth depth, // 100kB, as 200kB is max payload size, so half sounds reasonable maxSize) { if (depth === void 0) { depth = 3; } if (maxSize === void 0) { maxSize = 100 * 1024; } var serialized = normalize(object, depth); if (jsonSize(serialized) > maxSize) { return normalizeToSize(object, depth - 1, maxSize); } return serialized; } /** Transforms any input value into a string form, either primitive value or a type of the input */ function serializeValue(value) { var type = Object.prototype.toString.call(value); // Node.js REPL notation if (typeof value === 'string') { return value; } if (type === '[object Object]') { return '[Object]'; } if (type === '[object Array]') { return '[Array]'; } var normalized = normalizeValue(value); return isPrimitive(normalized) ? normalized : type; } /** * normalizeValue() * * Takes unserializable input and make it serializable friendly * * - translates undefined/NaN values to "[undefined]"/"[NaN]" respectively, * - serializes Error objects * - filter global objects */ function normalizeValue(value, key) { if (key === 'domain' && value && typeof value === 'object' && value._events) { return '[Domain]'; } if (key === 'domainEmitter') { return '[DomainEmitter]'; } if (typeof global !== 'undefined' && value === global) { return '[Global]'; } if (typeof window !== 'undefined' && value === window) { return '[Window]'; } if (typeof document !== 'undefined' && value === document) { return '[Document]'; } // React's SyntheticEvent thingy if (isSyntheticEvent(value)) { return '[SyntheticEvent]'; } if (typeof value === 'number' && value !== value) { return '[NaN]'; } if (value === void 0) { return '[undefined]'; } if (typeof value === 'function') { return "[Function: " + getFunctionName(value) + "]"; } return value; } /** * Walks an object to perform a normalization on it * * @param key of object that's walked in current iteration * @param value object to be walked * @param depth Optional number indicating how deep should walking be performed * @param memo Optional Memo class handling decycling */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function walk(key, value, depth, memo) { if (depth === void 0) { depth = +Infinity; } if (memo === void 0) { memo = new Memo(); } // If we reach the maximum depth, serialize whatever has left if (depth === 0) { return serializeValue(value); } /* eslint-disable @typescript-eslint/no-unsafe-member-access */ // If value implements `toJSON` method, call it and return early if (value !== null && value !== undefined && typeof value.toJSON === 'function') { return value.toJSON(); } /* eslint-enable @typescript-eslint/no-unsafe-member-access */ // If normalized value is a primitive, there are no branches left to walk, so we can just bail out, as theres no point in going down that branch any further var normalized = normalizeValue(value, key); if (isPrimitive(normalized)) { return normalized; } // Create source that we will use for next itterations, either objectified error object (Error type with extracted keys:value pairs) or the input itself var source = getWalkSource(value); // Create an accumulator that will act as a parent for all future itterations of that branch var acc = Array.isArray(value) ? [] : {}; // If we already walked that branch, bail out, as it's circular reference if (memo.memoize(value)) { return '[Circular ~]'; } // Walk all keys of the source for (var innerKey in source) { // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration. if (!Object.prototype.hasOwnProperty.call(source, innerKey)) { continue; } // Recursively walk through all the child nodes acc[innerKey] = walk(innerKey, source[innerKey], depth - 1, memo); } // Once walked through all the branches, remove the parent from memo storage memo.unmemoize(value); // Return accumulated values return acc; } /** * normalize() * * - Creates a copy to prevent original input mutation * - Skip non-enumerablers * - Calls `toJSON` if implemented * - Removes circular references * - Translates non-serializeable values (undefined/NaN/Functions) to serializable format * - Translates known global objects/Classes to a string representations * - Takes care of Error objects serialization * - Optionally limit depth of final output */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function normalize(input, depth) { try { return JSON.parse(JSON.stringify(input, function (key, value) { return walk(key, value, depth); })); } catch (_oO) { return '**non-serializable**'; } } /** * 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` */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function extractExceptionKeysForMessage(exception, maxLength) { if (maxLength === void 0) { maxLength = 40; } var keys = Object.keys(getWalkSource(exception)); keys.sort(); if (!keys.length) { return '[object has no keys]'; } if (keys[0].length >= maxLength) { return truncate(keys[0], maxLength); } for (var includedKeys = keys.length; includedKeys > 0; includedKeys--) { var 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 the new object with removed keys that value was `undefined`. * Works recursively on objects and arrays. */ export function dropUndefinedKeys(val) { var e_1, _a; if (isPlainObject(val)) { var obj = val; var rv = {}; try { for (var _b = __values(Object.keys(obj)), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; if (typeof obj[key] !== 'undefined') { rv[key] = dropUndefinedKeys(obj[key]); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } return rv; } if (Array.isArray(val)) { return val.map(dropUndefinedKeys); } return val; } //# sourceMappingURL=object.js.map