xhr.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const is = require('../is.js');
  3. const object = require('../object.js');
  4. const worldwide = require('../worldwide.js');
  5. const _handlers = require('./_handlers.js');
  6. const WINDOW = worldwide.GLOBAL_OBJ ;
  7. const SENTRY_XHR_DATA_KEY = '__sentry_xhr_v3__';
  8. /**
  9. * Add an instrumentation handler for when an XHR request happens.
  10. * The handler function is called once when the request starts and once when it ends,
  11. * which can be identified by checking if it has an `endTimestamp`.
  12. *
  13. * Use at your own risk, this might break without changelog notice, only used internally.
  14. * @hidden
  15. */
  16. function addXhrInstrumentationHandler(handler) {
  17. const type = 'xhr';
  18. _handlers.addHandler(type, handler);
  19. _handlers.maybeInstrument(type, instrumentXHR);
  20. }
  21. /** Exported only for tests. */
  22. function instrumentXHR() {
  23. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  24. if (!(WINDOW ).XMLHttpRequest) {
  25. return;
  26. }
  27. const xhrproto = XMLHttpRequest.prototype;
  28. object.fill(xhrproto, 'open', function (originalOpen) {
  29. return function ( ...args) {
  30. const startTimestamp = Date.now();
  31. // open() should always be called with two or more arguments
  32. // But to be on the safe side, we actually validate this and bail out if we don't have a method & url
  33. const method = is.isString(args[0]) ? args[0].toUpperCase() : undefined;
  34. const url = parseUrl(args[1]);
  35. if (!method || !url) {
  36. return originalOpen.apply(this, args);
  37. }
  38. this[SENTRY_XHR_DATA_KEY] = {
  39. method,
  40. url,
  41. request_headers: {},
  42. };
  43. // if Sentry key appears in URL, don't capture it as a request
  44. if (method === 'POST' && url.match(/sentry_key/)) {
  45. this.__sentry_own_request__ = true;
  46. }
  47. const onreadystatechangeHandler = () => {
  48. // For whatever reason, this is not the same instance here as from the outer method
  49. const xhrInfo = this[SENTRY_XHR_DATA_KEY];
  50. if (!xhrInfo) {
  51. return;
  52. }
  53. if (this.readyState === 4) {
  54. try {
  55. // touching statusCode in some platforms throws
  56. // an exception
  57. xhrInfo.status_code = this.status;
  58. } catch (e) {
  59. /* do nothing */
  60. }
  61. const handlerData = {
  62. args: [method, url],
  63. endTimestamp: Date.now(),
  64. startTimestamp,
  65. xhr: this,
  66. };
  67. _handlers.triggerHandlers('xhr', handlerData);
  68. }
  69. };
  70. if ('onreadystatechange' in this && typeof this.onreadystatechange === 'function') {
  71. object.fill(this, 'onreadystatechange', function (original) {
  72. return function ( ...readyStateArgs) {
  73. onreadystatechangeHandler();
  74. return original.apply(this, readyStateArgs);
  75. };
  76. });
  77. } else {
  78. this.addEventListener('readystatechange', onreadystatechangeHandler);
  79. }
  80. // Intercepting `setRequestHeader` to access the request headers of XHR instance.
  81. // This will only work for user/library defined headers, not for the default/browser-assigned headers.
  82. // Request cookies are also unavailable for XHR, as `Cookie` header can't be defined by `setRequestHeader`.
  83. object.fill(this, 'setRequestHeader', function (original) {
  84. return function ( ...setRequestHeaderArgs) {
  85. const [header, value] = setRequestHeaderArgs;
  86. const xhrInfo = this[SENTRY_XHR_DATA_KEY];
  87. if (xhrInfo && is.isString(header) && is.isString(value)) {
  88. xhrInfo.request_headers[header.toLowerCase()] = value;
  89. }
  90. return original.apply(this, setRequestHeaderArgs);
  91. };
  92. });
  93. return originalOpen.apply(this, args);
  94. };
  95. });
  96. object.fill(xhrproto, 'send', function (originalSend) {
  97. return function ( ...args) {
  98. const sentryXhrData = this[SENTRY_XHR_DATA_KEY];
  99. if (!sentryXhrData) {
  100. return originalSend.apply(this, args);
  101. }
  102. if (args[0] !== undefined) {
  103. sentryXhrData.body = args[0];
  104. }
  105. const handlerData = {
  106. args: [sentryXhrData.method, sentryXhrData.url],
  107. startTimestamp: Date.now(),
  108. xhr: this,
  109. };
  110. _handlers.triggerHandlers('xhr', handlerData);
  111. return originalSend.apply(this, args);
  112. };
  113. });
  114. }
  115. function parseUrl(url) {
  116. if (is.isString(url)) {
  117. return url;
  118. }
  119. try {
  120. // url can be a string or URL
  121. // but since URL is not available in IE11, we do not check for it,
  122. // but simply assume it is an URL and return `toString()` from it (which returns the full URL)
  123. // If that fails, we just return undefined
  124. return (url ).toString();
  125. } catch (e2) {} // eslint-disable-line no-empty
  126. return undefined;
  127. }
  128. exports.SENTRY_XHR_DATA_KEY = SENTRY_XHR_DATA_KEY;
  129. exports.addXhrInstrumentationHandler = addXhrInstrumentationHandler;
  130. exports.instrumentXHR = instrumentXHR;
  131. //# sourceMappingURL=xhr.js.map