123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- import { defineIntegration, convertIntegrationFnToClass } from '@sentry/core';
- import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils';
- import { WINDOW, wrap } from '../helpers.js';
- const DEFAULT_EVENT_TARGET = [
- 'EventTarget',
- 'Window',
- 'Node',
- 'ApplicationCache',
- 'AudioTrackList',
- 'BroadcastChannel',
- 'ChannelMergerNode',
- 'CryptoOperation',
- 'EventSource',
- 'FileReader',
- 'HTMLUnknownElement',
- 'IDBDatabase',
- 'IDBRequest',
- 'IDBTransaction',
- 'KeyOperation',
- 'MediaController',
- 'MessagePort',
- 'ModalWindow',
- 'Notification',
- 'SVGElementInstance',
- 'Screen',
- 'SharedWorker',
- 'TextTrack',
- 'TextTrackCue',
- 'TextTrackList',
- 'WebSocket',
- 'WebSocketWorker',
- 'Worker',
- 'XMLHttpRequest',
- 'XMLHttpRequestEventTarget',
- 'XMLHttpRequestUpload',
- ];
- const INTEGRATION_NAME = 'TryCatch';
- const _browserApiErrorsIntegration = ((options = {}) => {
- const _options = {
- XMLHttpRequest: true,
- eventTarget: true,
- requestAnimationFrame: true,
- setInterval: true,
- setTimeout: true,
- ...options,
- };
- return {
- name: INTEGRATION_NAME,
- // TODO: This currently only works for the first client this is setup
- // We may want to adjust this to check for client etc.
- setupOnce() {
- if (_options.setTimeout) {
- fill(WINDOW, 'setTimeout', _wrapTimeFunction);
- }
- if (_options.setInterval) {
- fill(WINDOW, 'setInterval', _wrapTimeFunction);
- }
- if (_options.requestAnimationFrame) {
- fill(WINDOW, 'requestAnimationFrame', _wrapRAF);
- }
- if (_options.XMLHttpRequest && 'XMLHttpRequest' in WINDOW) {
- fill(XMLHttpRequest.prototype, 'send', _wrapXHR);
- }
- const eventTargetOption = _options.eventTarget;
- if (eventTargetOption) {
- const eventTarget = Array.isArray(eventTargetOption) ? eventTargetOption : DEFAULT_EVENT_TARGET;
- eventTarget.forEach(_wrapEventTarget);
- }
- },
- };
- }) ;
- const browserApiErrorsIntegration = defineIntegration(_browserApiErrorsIntegration);
- /**
- * Wrap timer functions and event targets to catch errors and provide better meta data.
- * @deprecated Use `browserApiErrorsIntegration()` instead.
- */
- // eslint-disable-next-line deprecation/deprecation
- const TryCatch = convertIntegrationFnToClass(
- INTEGRATION_NAME,
- browserApiErrorsIntegration,
- )
- ;
- function _wrapTimeFunction(original) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return function ( ...args) {
- const originalCallback = args[0];
- args[0] = wrap(originalCallback, {
- mechanism: {
- data: { function: getFunctionName(original) },
- handled: false,
- type: 'instrument',
- },
- });
- return original.apply(this, args);
- };
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- function _wrapRAF(original) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return function ( callback) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- return original.apply(this, [
- wrap(callback, {
- mechanism: {
- data: {
- function: 'requestAnimationFrame',
- handler: getFunctionName(original),
- },
- handled: false,
- type: 'instrument',
- },
- }),
- ]);
- };
- }
- function _wrapXHR(originalSend) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return function ( ...args) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const xhr = this;
- const xmlHttpRequestProps = ['onload', 'onerror', 'onprogress', 'onreadystatechange'];
- xmlHttpRequestProps.forEach(prop => {
- if (prop in xhr && typeof xhr[prop] === 'function') {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- fill(xhr, prop, function (original) {
- const wrapOptions = {
- mechanism: {
- data: {
- function: prop,
- handler: getFunctionName(original),
- },
- handled: false,
- type: 'instrument',
- },
- };
- // If Instrument integration has been called before TryCatch, get the name of original function
- const originalFunction = getOriginalFunction(original);
- if (originalFunction) {
- wrapOptions.mechanism.data.handler = getFunctionName(originalFunction);
- }
- // Otherwise wrap directly
- return wrap(original, wrapOptions);
- });
- }
- });
- return originalSend.apply(this, args);
- };
- }
- function _wrapEventTarget(target) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const globalObject = WINDOW ;
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- const proto = globalObject[target] && globalObject[target].prototype;
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
- if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
- return;
- }
- fill(proto, 'addEventListener', function (original,)
- {
- return function (
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- eventName,
- fn,
- options,
- ) {
- try {
- if (typeof fn.handleEvent === 'function') {
- // ESlint disable explanation:
- // First, it is generally safe to call `wrap` with an unbound function. Furthermore, using `.bind()` would
- // introduce a bug here, because bind returns a new function that doesn't have our
- // flags(like __sentry_original__) attached. `wrap` checks for those flags to avoid unnecessary wrapping.
- // Without those flags, every call to addEventListener wraps the function again, causing a memory leak.
- // eslint-disable-next-line @typescript-eslint/unbound-method
- fn.handleEvent = wrap(fn.handleEvent, {
- mechanism: {
- data: {
- function: 'handleEvent',
- handler: getFunctionName(fn),
- target,
- },
- handled: false,
- type: 'instrument',
- },
- });
- }
- } catch (err) {
- // can sometimes get 'Permission denied to access property "handle Event'
- }
- return original.apply(this, [
- eventName,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- wrap(fn , {
- mechanism: {
- data: {
- function: 'addEventListener',
- handler: getFunctionName(fn),
- target,
- },
- handled: false,
- type: 'instrument',
- },
- }),
- options,
- ]);
- };
- });
- fill(
- proto,
- 'removeEventListener',
- function (
- originalRemoveEventListener,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ) {
- return function (
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- eventName,
- fn,
- options,
- ) {
- /**
- * There are 2 possible scenarios here:
- *
- * 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
- * method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
- * as a pass-through, and call original `removeEventListener` with it.
- *
- * 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
- * our wrapped version of `addEventListener`, which internally calls `wrap` helper.
- * This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
- * in order for us to make a distinction between wrapped/non-wrapped functions possible.
- * If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
- *
- * When someone adds a handler prior to initialization, and then do it again, but after,
- * then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
- * to get rid of the initial handler and it'd stick there forever.
- */
- const wrappedEventHandler = fn ;
- try {
- const originalEventHandler = wrappedEventHandler && wrappedEventHandler.__sentry_wrapped__;
- if (originalEventHandler) {
- originalRemoveEventListener.call(this, eventName, originalEventHandler, options);
- }
- } catch (e) {
- // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
- }
- return originalRemoveEventListener.call(this, eventName, wrappedEventHandler, options);
- };
- },
- );
- }
- export { TryCatch, browserApiErrorsIntegration };
- //# sourceMappingURL=trycatch.js.map
|