object.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const browser = require('./browser.js');
  3. const debugBuild = require('./debug-build.js');
  4. const is = require('./is.js');
  5. const logger = require('./logger.js');
  6. const string = require('./string.js');
  7. /**
  8. * Replace a method in an object with a wrapped version of itself.
  9. *
  10. * @param source An object that contains a method to be wrapped.
  11. * @param name The name of the method to be wrapped.
  12. * @param replacementFactory A higher-order function that takes the original version of the given method and returns a
  13. * wrapped version. Note: The function returned by `replacementFactory` needs to be a non-arrow function, in order to
  14. * preserve the correct value of `this`, and the original method must be called using `origMethod.call(this, <other
  15. * args>)` or `origMethod.apply(this, [<other args>])` (rather than being called directly), again to preserve `this`.
  16. * @returns void
  17. */
  18. function fill(source, name, replacementFactory) {
  19. if (!(name in source)) {
  20. return;
  21. }
  22. const original = source[name] ;
  23. const wrapped = replacementFactory(original) ;
  24. // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work
  25. // otherwise it'll throw "TypeError: Object.defineProperties called on non-object"
  26. if (typeof wrapped === 'function') {
  27. markFunctionWrapped(wrapped, original);
  28. }
  29. source[name] = wrapped;
  30. }
  31. /**
  32. * Defines a non-enumerable property on the given object.
  33. *
  34. * @param obj The object on which to set the property
  35. * @param name The name of the property to be set
  36. * @param value The value to which to set the property
  37. */
  38. function addNonEnumerableProperty(obj, name, value) {
  39. try {
  40. Object.defineProperty(obj, name, {
  41. // enumerable: false, // the default, so we can save on bundle size by not explicitly setting it
  42. value: value,
  43. writable: true,
  44. configurable: true,
  45. });
  46. } catch (o_O) {
  47. debugBuild.DEBUG_BUILD && logger.logger.log(`Failed to add non-enumerable property "${name}" to object`, obj);
  48. }
  49. }
  50. /**
  51. * Remembers the original function on the wrapped function and
  52. * patches up the prototype.
  53. *
  54. * @param wrapped the wrapper function
  55. * @param original the original function that gets wrapped
  56. */
  57. function markFunctionWrapped(wrapped, original) {
  58. try {
  59. const proto = original.prototype || {};
  60. wrapped.prototype = original.prototype = proto;
  61. addNonEnumerableProperty(wrapped, '__sentry_original__', original);
  62. } catch (o_O) {} // eslint-disable-line no-empty
  63. }
  64. /**
  65. * This extracts the original function if available. See
  66. * `markFunctionWrapped` for more information.
  67. *
  68. * @param func the function to unwrap
  69. * @returns the unwrapped version of the function if available.
  70. */
  71. function getOriginalFunction(func) {
  72. return func.__sentry_original__;
  73. }
  74. /**
  75. * Encodes given object into url-friendly format
  76. *
  77. * @param object An object that contains serializable values
  78. * @returns string Encoded
  79. */
  80. function urlEncode(object) {
  81. return Object.keys(object)
  82. .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(object[key])}`)
  83. .join('&');
  84. }
  85. /**
  86. * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
  87. * non-enumerable properties attached.
  88. *
  89. * @param value Initial source that we have to transform in order for it to be usable by the serializer
  90. * @returns An Event or Error turned into an object - or the value argurment itself, when value is neither an Event nor
  91. * an Error.
  92. */
  93. function convertToPlainObject(
  94. value,
  95. )
  96. {
  97. if (is.isError(value)) {
  98. return {
  99. message: value.message,
  100. name: value.name,
  101. stack: value.stack,
  102. ...getOwnProperties(value),
  103. };
  104. } else if (is.isEvent(value)) {
  105. const newObj
  106. = {
  107. type: value.type,
  108. target: serializeEventTarget(value.target),
  109. currentTarget: serializeEventTarget(value.currentTarget),
  110. ...getOwnProperties(value),
  111. };
  112. if (typeof CustomEvent !== 'undefined' && is.isInstanceOf(value, CustomEvent)) {
  113. newObj.detail = value.detail;
  114. }
  115. return newObj;
  116. } else {
  117. return value;
  118. }
  119. }
  120. /** Creates a string representation of the target of an `Event` object */
  121. function serializeEventTarget(target) {
  122. try {
  123. return is.isElement(target) ? browser.htmlTreeAsString(target) : Object.prototype.toString.call(target);
  124. } catch (_oO) {
  125. return '<unknown>';
  126. }
  127. }
  128. /** Filters out all but an object's own properties */
  129. function getOwnProperties(obj) {
  130. if (typeof obj === 'object' && obj !== null) {
  131. const extractedProps = {};
  132. for (const property in obj) {
  133. if (Object.prototype.hasOwnProperty.call(obj, property)) {
  134. extractedProps[property] = (obj )[property];
  135. }
  136. }
  137. return extractedProps;
  138. } else {
  139. return {};
  140. }
  141. }
  142. /**
  143. * Given any captured exception, extract its keys and create a sorted
  144. * and truncated list that will be used inside the event message.
  145. * eg. `Non-error exception captured with keys: foo, bar, baz`
  146. */
  147. function extractExceptionKeysForMessage(exception, maxLength = 40) {
  148. const keys = Object.keys(convertToPlainObject(exception));
  149. keys.sort();
  150. if (!keys.length) {
  151. return '[object has no keys]';
  152. }
  153. if (keys[0].length >= maxLength) {
  154. return string.truncate(keys[0], maxLength);
  155. }
  156. for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) {
  157. const serialized = keys.slice(0, includedKeys).join(', ');
  158. if (serialized.length > maxLength) {
  159. continue;
  160. }
  161. if (includedKeys === keys.length) {
  162. return serialized;
  163. }
  164. return string.truncate(serialized, maxLength);
  165. }
  166. return '';
  167. }
  168. /**
  169. * Given any object, return a new object having removed all fields whose value was `undefined`.
  170. * Works recursively on objects and arrays.
  171. *
  172. * Attention: This function keeps circular references in the returned object.
  173. */
  174. function dropUndefinedKeys(inputValue) {
  175. // This map keeps track of what already visited nodes map to.
  176. // Our Set - based memoBuilder doesn't work here because we want to the output object to have the same circular
  177. // references as the input object.
  178. const memoizationMap = new Map();
  179. // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API
  180. return _dropUndefinedKeys(inputValue, memoizationMap);
  181. }
  182. function _dropUndefinedKeys(inputValue, memoizationMap) {
  183. if (isPojo(inputValue)) {
  184. // If this node has already been visited due to a circular reference, return the object it was mapped to in the new object
  185. const memoVal = memoizationMap.get(inputValue);
  186. if (memoVal !== undefined) {
  187. return memoVal ;
  188. }
  189. const returnValue = {};
  190. // Store the mapping of this value in case we visit it again, in case of circular data
  191. memoizationMap.set(inputValue, returnValue);
  192. for (const key of Object.keys(inputValue)) {
  193. if (typeof inputValue[key] !== 'undefined') {
  194. returnValue[key] = _dropUndefinedKeys(inputValue[key], memoizationMap);
  195. }
  196. }
  197. return returnValue ;
  198. }
  199. if (Array.isArray(inputValue)) {
  200. // If this node has already been visited due to a circular reference, return the array it was mapped to in the new object
  201. const memoVal = memoizationMap.get(inputValue);
  202. if (memoVal !== undefined) {
  203. return memoVal ;
  204. }
  205. const returnValue = [];
  206. // Store the mapping of this value in case we visit it again, in case of circular data
  207. memoizationMap.set(inputValue, returnValue);
  208. inputValue.forEach((item) => {
  209. returnValue.push(_dropUndefinedKeys(item, memoizationMap));
  210. });
  211. return returnValue ;
  212. }
  213. return inputValue;
  214. }
  215. function isPojo(input) {
  216. if (!is.isPlainObject(input)) {
  217. return false;
  218. }
  219. try {
  220. const name = (Object.getPrototypeOf(input) ).constructor.name;
  221. return !name || name === 'Object';
  222. } catch (e) {
  223. return true;
  224. }
  225. }
  226. /**
  227. * Ensure that something is an object.
  228. *
  229. * Turns `undefined` and `null` into `String`s and all other primitives into instances of their respective wrapper
  230. * classes (String, Boolean, Number, etc.). Acts as the identity function on non-primitives.
  231. *
  232. * @param wat The subject of the objectification
  233. * @returns A version of `wat` which can safely be used with `Object` class methods
  234. */
  235. function objectify(wat) {
  236. let objectified;
  237. switch (true) {
  238. case wat === undefined || wat === null:
  239. objectified = new String(wat);
  240. break;
  241. // Though symbols and bigints do have wrapper classes (`Symbol` and `BigInt`, respectively), for whatever reason
  242. // those classes don't have constructors which can be used with the `new` keyword. We therefore need to cast each as
  243. // an object in order to wrap it.
  244. case typeof wat === 'symbol' || typeof wat === 'bigint':
  245. objectified = Object(wat);
  246. break;
  247. // this will catch the remaining primitives: `String`, `Number`, and `Boolean`
  248. case is.isPrimitive(wat):
  249. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  250. objectified = new (wat ).constructor(wat);
  251. break;
  252. // by process of elimination, at this point we know that `wat` must already be an object
  253. default:
  254. objectified = wat;
  255. break;
  256. }
  257. return objectified;
  258. }
  259. exports.addNonEnumerableProperty = addNonEnumerableProperty;
  260. exports.convertToPlainObject = convertToPlainObject;
  261. exports.dropUndefinedKeys = dropUndefinedKeys;
  262. exports.extractExceptionKeysForMessage = extractExceptionKeysForMessage;
  263. exports.fill = fill;
  264. exports.getOriginalFunction = getOriginalFunction;
  265. exports.markFunctionWrapped = markFunctionWrapped;
  266. exports.objectify = objectify;
  267. exports.urlEncode = urlEncode;
  268. //# sourceMappingURL=object.js.map