Files
apps.apple.com/shared/logger/node_modules/@sentry/browser/esm/integrations/trycatch.js
2025-11-04 05:03:50 +08:00

282 lines
8.5 KiB
JavaScript

import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils';
import { WINDOW, wrap } from '../helpers.js';
const DEFAULT_EVENT_TARGET = [
'EventTarget',
'Window',
'Node',
'ApplicationCache',
'AudioTrackList',
'ChannelMergerNode',
'CryptoOperation',
'EventSource',
'FileReader',
'HTMLUnknownElement',
'IDBDatabase',
'IDBRequest',
'IDBTransaction',
'KeyOperation',
'MediaController',
'MessagePort',
'ModalWindow',
'Notification',
'SVGElementInstance',
'Screen',
'TextTrack',
'TextTrackCue',
'TextTrackList',
'WebSocket',
'WebSocketWorker',
'Worker',
'XMLHttpRequest',
'XMLHttpRequestEventTarget',
'XMLHttpRequestUpload',
];
/** Wrap timer functions and event targets to catch errors and provide better meta data */
class TryCatch {
/**
* @inheritDoc
*/
static __initStatic() {this.id = 'TryCatch';}
/**
* @inheritDoc
*/
__init() {this.name = TryCatch.id;}
/** JSDoc */
/**
* @inheritDoc
*/
constructor(options) {TryCatch.prototype.__init.call(this);
this._options = {
XMLHttpRequest: true,
eventTarget: true,
requestAnimationFrame: true,
setInterval: true,
setTimeout: true,
...options,
};
}
/**
* Wrap timer functions and event targets to catch errors
* and provide better metadata.
*/
setupOnce() {
if (this._options.setTimeout) {
fill(WINDOW, 'setTimeout', _wrapTimeFunction);
}
if (this._options.setInterval) {
fill(WINDOW, 'setInterval', _wrapTimeFunction);
}
if (this._options.requestAnimationFrame) {
fill(WINDOW, 'requestAnimationFrame', _wrapRAF);
}
if (this._options.XMLHttpRequest && 'XMLHttpRequest' in WINDOW) {
fill(XMLHttpRequest.prototype, 'send', _wrapXHR);
}
const eventTargetOption = this._options.eventTarget;
if (eventTargetOption) {
const eventTarget = Array.isArray(eventTargetOption) ? eventTargetOption : DEFAULT_EVENT_TARGET;
eventTarget.forEach(_wrapEventTarget);
}
}
} TryCatch.__initStatic();
/** JSDoc */
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: true,
type: 'instrument',
},
});
return original.apply(this, args);
};
}
/** JSDoc */
// 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: true,
type: 'instrument',
},
}),
]);
};
}
/** JSDoc */
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: true,
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);
};
}
/** JSDoc */
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: true,
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: true,
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 };
//# sourceMappingURL=trycatch.js.map