globalhandlers.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { defineIntegration, convertIntegrationFnToClass, getClient, captureEvent } from '@sentry/core';
  2. import { addGlobalErrorInstrumentationHandler, isString, addGlobalUnhandledRejectionInstrumentationHandler, isPrimitive, isErrorEvent, getLocationHref, logger } from '@sentry/utils';
  3. import { DEBUG_BUILD } from '../debug-build.js';
  4. import { eventFromUnknownInput } from '../eventbuilder.js';
  5. import { shouldIgnoreOnError } from '../helpers.js';
  6. /* eslint-disable @typescript-eslint/no-unsafe-member-access */
  7. const INTEGRATION_NAME = 'GlobalHandlers';
  8. const _globalHandlersIntegration = ((options = {}) => {
  9. const _options = {
  10. onerror: true,
  11. onunhandledrejection: true,
  12. ...options,
  13. };
  14. return {
  15. name: INTEGRATION_NAME,
  16. setupOnce() {
  17. Error.stackTraceLimit = 50;
  18. },
  19. setup(client) {
  20. if (_options.onerror) {
  21. _installGlobalOnErrorHandler(client);
  22. globalHandlerLog('onerror');
  23. }
  24. if (_options.onunhandledrejection) {
  25. _installGlobalOnUnhandledRejectionHandler(client);
  26. globalHandlerLog('onunhandledrejection');
  27. }
  28. },
  29. };
  30. }) ;
  31. const globalHandlersIntegration = defineIntegration(_globalHandlersIntegration);
  32. /**
  33. * Global handlers.
  34. * @deprecated Use `globalHandlersIntegration()` instead.
  35. */
  36. // eslint-disable-next-line deprecation/deprecation
  37. const GlobalHandlers = convertIntegrationFnToClass(
  38. INTEGRATION_NAME,
  39. globalHandlersIntegration,
  40. )
  41. ;
  42. function _installGlobalOnErrorHandler(client) {
  43. addGlobalErrorInstrumentationHandler(data => {
  44. const { stackParser, attachStacktrace } = getOptions();
  45. if (getClient() !== client || shouldIgnoreOnError()) {
  46. return;
  47. }
  48. const { msg, url, line, column, error } = data;
  49. const event =
  50. error === undefined && isString(msg)
  51. ? _eventFromIncompleteOnError(msg, url, line, column)
  52. : _enhanceEventWithInitialFrame(
  53. eventFromUnknownInput(stackParser, error || msg, undefined, attachStacktrace, false),
  54. url,
  55. line,
  56. column,
  57. );
  58. event.level = 'error';
  59. captureEvent(event, {
  60. originalException: error,
  61. mechanism: {
  62. handled: false,
  63. type: 'onerror',
  64. },
  65. });
  66. });
  67. }
  68. function _installGlobalOnUnhandledRejectionHandler(client) {
  69. addGlobalUnhandledRejectionInstrumentationHandler(e => {
  70. const { stackParser, attachStacktrace } = getOptions();
  71. if (getClient() !== client || shouldIgnoreOnError()) {
  72. return;
  73. }
  74. const error = _getUnhandledRejectionError(e );
  75. const event = isPrimitive(error)
  76. ? _eventFromRejectionWithPrimitive(error)
  77. : eventFromUnknownInput(stackParser, error, undefined, attachStacktrace, true);
  78. event.level = 'error';
  79. captureEvent(event, {
  80. originalException: error,
  81. mechanism: {
  82. handled: false,
  83. type: 'onunhandledrejection',
  84. },
  85. });
  86. });
  87. }
  88. function _getUnhandledRejectionError(error) {
  89. if (isPrimitive(error)) {
  90. return error;
  91. }
  92. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  93. const e = error ;
  94. // dig the object of the rejection out of known event types
  95. try {
  96. // PromiseRejectionEvents store the object of the rejection under 'reason'
  97. // see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
  98. if ('reason' in e) {
  99. return e.reason;
  100. }
  101. // something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents
  102. // to CustomEvents, moving the `promise` and `reason` attributes of the PRE into
  103. // the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec
  104. // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and
  105. // https://github.com/getsentry/sentry-javascript/issues/2380
  106. else if ('detail' in e && 'reason' in e.detail) {
  107. return e.detail.reason;
  108. }
  109. } catch (e2) {} // eslint-disable-line no-empty
  110. return error;
  111. }
  112. /**
  113. * Create an event from a promise rejection where the `reason` is a primitive.
  114. *
  115. * @param reason: The `reason` property of the promise rejection
  116. * @returns An Event object with an appropriate `exception` value
  117. */
  118. function _eventFromRejectionWithPrimitive(reason) {
  119. return {
  120. exception: {
  121. values: [
  122. {
  123. type: 'UnhandledRejection',
  124. // String() is needed because the Primitive type includes symbols (which can't be automatically stringified)
  125. value: `Non-Error promise rejection captured with value: ${String(reason)}`,
  126. },
  127. ],
  128. },
  129. };
  130. }
  131. /**
  132. * This function creates a stack from an old, error-less onerror handler.
  133. */
  134. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  135. function _eventFromIncompleteOnError(msg, url, line, column) {
  136. const ERROR_TYPES_RE =
  137. /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i;
  138. // If 'message' is ErrorEvent, get real message from inside
  139. let message = isErrorEvent(msg) ? msg.message : msg;
  140. let name = 'Error';
  141. const groups = message.match(ERROR_TYPES_RE);
  142. if (groups) {
  143. name = groups[1];
  144. message = groups[2];
  145. }
  146. const event = {
  147. exception: {
  148. values: [
  149. {
  150. type: name,
  151. value: message,
  152. },
  153. ],
  154. },
  155. };
  156. return _enhanceEventWithInitialFrame(event, url, line, column);
  157. }
  158. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  159. function _enhanceEventWithInitialFrame(event, url, line, column) {
  160. // event.exception
  161. const e = (event.exception = event.exception || {});
  162. // event.exception.values
  163. const ev = (e.values = e.values || []);
  164. // event.exception.values[0]
  165. const ev0 = (ev[0] = ev[0] || {});
  166. // event.exception.values[0].stacktrace
  167. const ev0s = (ev0.stacktrace = ev0.stacktrace || {});
  168. // event.exception.values[0].stacktrace.frames
  169. const ev0sf = (ev0s.frames = ev0s.frames || []);
  170. const colno = isNaN(parseInt(column, 10)) ? undefined : column;
  171. const lineno = isNaN(parseInt(line, 10)) ? undefined : line;
  172. const filename = isString(url) && url.length > 0 ? url : getLocationHref();
  173. // event.exception.values[0].stacktrace.frames
  174. if (ev0sf.length === 0) {
  175. ev0sf.push({
  176. colno,
  177. filename,
  178. function: '?',
  179. in_app: true,
  180. lineno,
  181. });
  182. }
  183. return event;
  184. }
  185. function globalHandlerLog(type) {
  186. DEBUG_BUILD && logger.log(`Global Handler attached: ${type}`);
  187. }
  188. function getOptions() {
  189. const client = getClient();
  190. const options = (client && client.getOptions()) || {
  191. stackParser: () => [],
  192. attachStacktrace: false,
  193. };
  194. return options;
  195. }
  196. export { GlobalHandlers, globalHandlersIntegration };
  197. //# sourceMappingURL=globalhandlers.js.map