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