package.build.npm.esm.integrations.browserapierrors.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of browser Show documentation
Show all versions of browser Show documentation
Official Sentry SDK for browsers
import { defineIntegration } 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 = 'BrowserApiErrors';
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);
}
},
};
}) ;
/**
* Wrap timer functions and event targets to catch errors and provide better meta data.
*/
const browserApiErrorsIntegration = defineIntegration(_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 BrowserApiErrors, 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 { browserApiErrorsIntegration };
//# sourceMappingURL=browserapierrors.js.map