123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- Object.defineProperty(exports, '__esModule', { value: true });
- const is = require('./is.js');
- const memo = require('./memo.js');
- const object = require('./object.js');
- const stacktrace = require('./stacktrace.js');
- /**
- * Recursively normalizes the given object.
- *
- * - Creates a copy to prevent original input mutation
- * - Skips non-enumerable properties
- * - When stringifying, calls `toJSON` if implemented
- * - Removes circular references
- * - Translates non-serializable values (`undefined`/`NaN`/functions) to serializable format
- * - Translates known global objects/classes to a string representations
- * - Takes care of `Error` object serialization
- * - Optionally limits depth of final output
- * - Optionally limits number of properties/elements included in any single object/array
- *
- * @param input The object to be normalized.
- * @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.)
- * @param maxProperties The max number of elements or properties to be included in any single array or
- * object in the normallized output.
- * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
- */
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- function normalize(input, depth = 100, maxProperties = +Infinity) {
- try {
- // since we're at the outermost level, we don't provide a key
- return visit('', input, depth, maxProperties);
- } catch (err) {
- return { ERROR: `**non-serializable** (${err})` };
- }
- }
- /** JSDoc */
- function normalizeToSize(
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- object,
- // Default Node.js REPL depth
- depth = 3,
- // 100kB, as 200kB is max payload size, so half sounds reasonable
- maxSize = 100 * 1024,
- ) {
- const normalized = normalize(object, depth);
- if (jsonSize(normalized) > maxSize) {
- return normalizeToSize(object, depth - 1, maxSize);
- }
- return normalized ;
- }
- /**
- * Visits a node to perform normalization on it
- *
- * @param key The key corresponding to the given node
- * @param value The node to be visited
- * @param depth Optional number indicating the maximum recursion depth
- * @param maxProperties Optional maximum number of properties/elements included in any single object/array
- * @param memo Optional Memo class handling decycling
- */
- function visit(
- key,
- value,
- depth = +Infinity,
- maxProperties = +Infinity,
- memo$1 = memo.memoBuilder(),
- ) {
- const [memoize, unmemoize] = memo$1;
- // Get the simple cases out of the way first
- if (
- value == null || // this matches null and undefined -> eqeq not eqeqeq
- (['number', 'boolean', 'string'].includes(typeof value) && !is.isNaN(value))
- ) {
- return value ;
- }
- const stringified = stringifyValue(key, value);
- // Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`.
- // Everything else will have already been serialized, so if we don't see that pattern, we're done.
- if (!stringified.startsWith('[object ')) {
- return stringified;
- }
- // From here on, we can assert that `value` is either an object or an array.
- // Do not normalize objects that we know have already been normalized. As a general rule, the
- // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
- // have already been normalized.
- if ((value )['__sentry_skip_normalization__']) {
- return value ;
- }
- // We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
- // We keep a certain amount of depth.
- // This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
- const remainingDepth =
- typeof (value )['__sentry_override_normalization_depth__'] === 'number'
- ? ((value )['__sentry_override_normalization_depth__'] )
- : depth;
- // We're also done if we've reached the max depth
- if (remainingDepth === 0) {
- // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
- return stringified.replace('object ', '');
- }
- // If we've already visited this branch, bail out, as it's circular reference. If not, note that we're seeing it now.
- if (memoize(value)) {
- return '[Circular ~]';
- }
- // If the value has a `toJSON` method, we call it to extract more information
- const valueWithToJSON = value ;
- if (valueWithToJSON && typeof valueWithToJSON.toJSON === 'function') {
- try {
- const jsonValue = valueWithToJSON.toJSON();
- // We need to normalize the return value of `.toJSON()` in case it has circular references
- return visit('', jsonValue, remainingDepth - 1, maxProperties, memo$1);
- } catch (err) {
- // pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
- }
- }
- // At this point we know we either have an object or an array, we haven't seen it before, and we're going to recurse
- // because we haven't yet reached the max depth. Create an accumulator to hold the results of visiting each
- // property/entry, and keep track of the number of items we add to it.
- const normalized = (Array.isArray(value) ? [] : {}) ;
- let numAdded = 0;
- // Before we begin, convert`Error` and`Event` instances into plain objects, since some of each of their relevant
- // properties are non-enumerable and otherwise would get missed.
- const visitable = object.convertToPlainObject(value );
- for (const visitKey in visitable) {
- // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
- if (!Object.prototype.hasOwnProperty.call(visitable, visitKey)) {
- continue;
- }
- if (numAdded >= maxProperties) {
- normalized[visitKey] = '[MaxProperties ~]';
- break;
- }
- // Recursively visit all the child nodes
- const visitValue = visitable[visitKey];
- normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo$1);
- numAdded++;
- }
- // Once we've visited all the branches, remove the parent from memo storage
- unmemoize(value);
- // Return accumulated values
- return normalized;
- }
- /* eslint-disable complexity */
- /**
- * Stringify the given value. Handles various known special values and types.
- *
- * Not meant to be used on simple primitives which already have a string representation, as it will, for example, turn
- * the number 1231 into "[Object Number]", nor on `null`, as it will throw.
- *
- * @param value The value to stringify
- * @returns A stringified representation of the given value
- */
- function stringifyValue(
- key,
- // this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for
- // our internal use, it'll do
- value,
- ) {
- try {
- if (key === 'domain' && value && typeof value === 'object' && (value )._events) {
- return '[Domain]';
- }
- if (key === 'domainEmitter') {
- return '[DomainEmitter]';
- }
- // It's safe to use `global`, `window`, and `document` here in this manner, as we are asserting using `typeof` first
- // which won't throw if they are not present.
- if (typeof global !== 'undefined' && value === global) {
- return '[Global]';
- }
- // eslint-disable-next-line no-restricted-globals
- if (typeof window !== 'undefined' && value === window) {
- return '[Window]';
- }
- // eslint-disable-next-line no-restricted-globals
- if (typeof document !== 'undefined' && value === document) {
- return '[Document]';
- }
- if (is.isVueViewModel(value)) {
- return '[VueViewModel]';
- }
- // React's SyntheticEvent thingy
- if (is.isSyntheticEvent(value)) {
- return '[SyntheticEvent]';
- }
- if (typeof value === 'number' && value !== value) {
- return '[NaN]';
- }
- if (typeof value === 'function') {
- return `[Function: ${stacktrace.getFunctionName(value)}]`;
- }
- if (typeof value === 'symbol') {
- return `[${String(value)}]`;
- }
- // stringified BigInts are indistinguishable from regular numbers, so we need to label them to avoid confusion
- if (typeof value === 'bigint') {
- return `[BigInt: ${String(value)}]`;
- }
- // Now that we've knocked out all the special cases and the primitives, all we have left are objects. Simply casting
- // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as
- // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class),
- // we can make sure that only plain objects come out that way.
- const objName = getConstructorName(value);
- // Handle HTML Elements
- if (/^HTML(\w*)Element$/.test(objName)) {
- return `[HTMLElement: ${objName}]`;
- }
- return `[object ${objName}]`;
- } catch (err) {
- return `**non-serializable** (${err})`;
- }
- }
- /* eslint-enable complexity */
- function getConstructorName(value) {
- const prototype = Object.getPrototypeOf(value);
- return prototype ? prototype.constructor.name : 'null prototype';
- }
- /** 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 */
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- function jsonSize(value) {
- return utf8Length(JSON.stringify(value));
- }
- /**
- * Normalizes URLs in exceptions and stacktraces to a base path so Sentry can fingerprint
- * across platforms and working directory.
- *
- * @param url The URL to be normalized.
- * @param basePath The application base path.
- * @returns The normalized URL.
- */
- function normalizeUrlToBase(url, basePath) {
- const escapedBase = basePath
- // Backslash to forward
- .replace(/\\/g, '/')
- // Escape RegExp special characters
- .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
- let newUrl = url;
- try {
- newUrl = decodeURI(url);
- } catch (_Oo) {
- // Sometime this breaks
- }
- return (
- newUrl
- .replace(/\\/g, '/')
- .replace(/webpack:\/?/g, '') // Remove intermediate base path
- // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
- .replace(new RegExp(`(file://)?/*${escapedBase}/*`, 'ig'), 'app:///')
- );
- }
- exports.normalize = normalize;
- exports.normalizeToSize = normalizeToSize;
- exports.normalizeUrlToBase = normalizeUrlToBase;
- exports.walk = visit;
- //# sourceMappingURL=normalize.js.map
|