trycatch.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const core = require('@sentry/core');
  3. const utils = require('@sentry/utils');
  4. const helpers = require('../helpers.js');
  5. const DEFAULT_EVENT_TARGET = [
  6. 'EventTarget',
  7. 'Window',
  8. 'Node',
  9. 'ApplicationCache',
  10. 'AudioTrackList',
  11. 'BroadcastChannel',
  12. 'ChannelMergerNode',
  13. 'CryptoOperation',
  14. 'EventSource',
  15. 'FileReader',
  16. 'HTMLUnknownElement',
  17. 'IDBDatabase',
  18. 'IDBRequest',
  19. 'IDBTransaction',
  20. 'KeyOperation',
  21. 'MediaController',
  22. 'MessagePort',
  23. 'ModalWindow',
  24. 'Notification',
  25. 'SVGElementInstance',
  26. 'Screen',
  27. 'SharedWorker',
  28. 'TextTrack',
  29. 'TextTrackCue',
  30. 'TextTrackList',
  31. 'WebSocket',
  32. 'WebSocketWorker',
  33. 'Worker',
  34. 'XMLHttpRequest',
  35. 'XMLHttpRequestEventTarget',
  36. 'XMLHttpRequestUpload',
  37. ];
  38. const INTEGRATION_NAME = 'TryCatch';
  39. const _browserApiErrorsIntegration = ((options = {}) => {
  40. const _options = {
  41. XMLHttpRequest: true,
  42. eventTarget: true,
  43. requestAnimationFrame: true,
  44. setInterval: true,
  45. setTimeout: true,
  46. ...options,
  47. };
  48. return {
  49. name: INTEGRATION_NAME,
  50. // TODO: This currently only works for the first client this is setup
  51. // We may want to adjust this to check for client etc.
  52. setupOnce() {
  53. if (_options.setTimeout) {
  54. utils.fill(helpers.WINDOW, 'setTimeout', _wrapTimeFunction);
  55. }
  56. if (_options.setInterval) {
  57. utils.fill(helpers.WINDOW, 'setInterval', _wrapTimeFunction);
  58. }
  59. if (_options.requestAnimationFrame) {
  60. utils.fill(helpers.WINDOW, 'requestAnimationFrame', _wrapRAF);
  61. }
  62. if (_options.XMLHttpRequest && 'XMLHttpRequest' in helpers.WINDOW) {
  63. utils.fill(XMLHttpRequest.prototype, 'send', _wrapXHR);
  64. }
  65. const eventTargetOption = _options.eventTarget;
  66. if (eventTargetOption) {
  67. const eventTarget = Array.isArray(eventTargetOption) ? eventTargetOption : DEFAULT_EVENT_TARGET;
  68. eventTarget.forEach(_wrapEventTarget);
  69. }
  70. },
  71. };
  72. }) ;
  73. const browserApiErrorsIntegration = core.defineIntegration(_browserApiErrorsIntegration);
  74. /**
  75. * Wrap timer functions and event targets to catch errors and provide better meta data.
  76. * @deprecated Use `browserApiErrorsIntegration()` instead.
  77. */
  78. // eslint-disable-next-line deprecation/deprecation
  79. const TryCatch = core.convertIntegrationFnToClass(
  80. INTEGRATION_NAME,
  81. browserApiErrorsIntegration,
  82. )
  83. ;
  84. function _wrapTimeFunction(original) {
  85. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  86. return function ( ...args) {
  87. const originalCallback = args[0];
  88. args[0] = helpers.wrap(originalCallback, {
  89. mechanism: {
  90. data: { function: utils.getFunctionName(original) },
  91. handled: false,
  92. type: 'instrument',
  93. },
  94. });
  95. return original.apply(this, args);
  96. };
  97. }
  98. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  99. function _wrapRAF(original) {
  100. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  101. return function ( callback) {
  102. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  103. return original.apply(this, [
  104. helpers.wrap(callback, {
  105. mechanism: {
  106. data: {
  107. function: 'requestAnimationFrame',
  108. handler: utils.getFunctionName(original),
  109. },
  110. handled: false,
  111. type: 'instrument',
  112. },
  113. }),
  114. ]);
  115. };
  116. }
  117. function _wrapXHR(originalSend) {
  118. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  119. return function ( ...args) {
  120. // eslint-disable-next-line @typescript-eslint/no-this-alias
  121. const xhr = this;
  122. const xmlHttpRequestProps = ['onload', 'onerror', 'onprogress', 'onreadystatechange'];
  123. xmlHttpRequestProps.forEach(prop => {
  124. if (prop in xhr && typeof xhr[prop] === 'function') {
  125. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  126. utils.fill(xhr, prop, function (original) {
  127. const wrapOptions = {
  128. mechanism: {
  129. data: {
  130. function: prop,
  131. handler: utils.getFunctionName(original),
  132. },
  133. handled: false,
  134. type: 'instrument',
  135. },
  136. };
  137. // If Instrument integration has been called before TryCatch, get the name of original function
  138. const originalFunction = utils.getOriginalFunction(original);
  139. if (originalFunction) {
  140. wrapOptions.mechanism.data.handler = utils.getFunctionName(originalFunction);
  141. }
  142. // Otherwise wrap directly
  143. return helpers.wrap(original, wrapOptions);
  144. });
  145. }
  146. });
  147. return originalSend.apply(this, args);
  148. };
  149. }
  150. function _wrapEventTarget(target) {
  151. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  152. const globalObject = helpers.WINDOW ;
  153. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  154. const proto = globalObject[target] && globalObject[target].prototype;
  155. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
  156. if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
  157. return;
  158. }
  159. utils.fill(proto, 'addEventListener', function (original,)
  160. {
  161. return function (
  162. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  163. eventName,
  164. fn,
  165. options,
  166. ) {
  167. try {
  168. if (typeof fn.handleEvent === 'function') {
  169. // ESlint disable explanation:
  170. // First, it is generally safe to call `wrap` with an unbound function. Furthermore, using `.bind()` would
  171. // introduce a bug here, because bind returns a new function that doesn't have our
  172. // flags(like __sentry_original__) attached. `wrap` checks for those flags to avoid unnecessary wrapping.
  173. // Without those flags, every call to addEventListener wraps the function again, causing a memory leak.
  174. // eslint-disable-next-line @typescript-eslint/unbound-method
  175. fn.handleEvent = helpers.wrap(fn.handleEvent, {
  176. mechanism: {
  177. data: {
  178. function: 'handleEvent',
  179. handler: utils.getFunctionName(fn),
  180. target,
  181. },
  182. handled: false,
  183. type: 'instrument',
  184. },
  185. });
  186. }
  187. } catch (err) {
  188. // can sometimes get 'Permission denied to access property "handle Event'
  189. }
  190. return original.apply(this, [
  191. eventName,
  192. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  193. helpers.wrap(fn , {
  194. mechanism: {
  195. data: {
  196. function: 'addEventListener',
  197. handler: utils.getFunctionName(fn),
  198. target,
  199. },
  200. handled: false,
  201. type: 'instrument',
  202. },
  203. }),
  204. options,
  205. ]);
  206. };
  207. });
  208. utils.fill(
  209. proto,
  210. 'removeEventListener',
  211. function (
  212. originalRemoveEventListener,
  213. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  214. ) {
  215. return function (
  216. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  217. eventName,
  218. fn,
  219. options,
  220. ) {
  221. /**
  222. * There are 2 possible scenarios here:
  223. *
  224. * 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
  225. * method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
  226. * as a pass-through, and call original `removeEventListener` with it.
  227. *
  228. * 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
  229. * our wrapped version of `addEventListener`, which internally calls `wrap` helper.
  230. * This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
  231. * in order for us to make a distinction between wrapped/non-wrapped functions possible.
  232. * If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
  233. *
  234. * When someone adds a handler prior to initialization, and then do it again, but after,
  235. * then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
  236. * to get rid of the initial handler and it'd stick there forever.
  237. */
  238. const wrappedEventHandler = fn ;
  239. try {
  240. const originalEventHandler = wrappedEventHandler && wrappedEventHandler.__sentry_wrapped__;
  241. if (originalEventHandler) {
  242. originalRemoveEventListener.call(this, eventName, originalEventHandler, options);
  243. }
  244. } catch (e) {
  245. // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
  246. }
  247. return originalRemoveEventListener.call(this, eventName, wrappedEventHandler, options);
  248. };
  249. },
  250. );
  251. }
  252. exports.TryCatch = TryCatch;
  253. exports.browserApiErrorsIntegration = browserApiErrorsIntegration;
  254. //# sourceMappingURL=trycatch.js.map