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