utils.js 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { isNativeFetch, logger } from '@sentry/utils';
  2. import { DEBUG_BUILD } from '../debug-build.js';
  3. import { WINDOW } from '../helpers.js';
  4. let cachedFetchImpl = undefined;
  5. /**
  6. * A special usecase for incorrectly wrapped Fetch APIs in conjunction with ad-blockers.
  7. * Whenever someone wraps the Fetch API and returns the wrong promise chain,
  8. * this chain becomes orphaned and there is no possible way to capture it's rejections
  9. * other than allowing it bubble up to this very handler. eg.
  10. *
  11. * const f = window.fetch;
  12. * window.fetch = function () {
  13. * const p = f.apply(this, arguments);
  14. *
  15. * p.then(function() {
  16. * console.log('hi.');
  17. * });
  18. *
  19. * return p;
  20. * }
  21. *
  22. * `p.then(function () { ... })` is producing a completely separate promise chain,
  23. * however, what's returned is `p` - the result of original `fetch` call.
  24. *
  25. * This mean, that whenever we use the Fetch API to send our own requests, _and_
  26. * some ad-blocker blocks it, this orphaned chain will _always_ reject,
  27. * effectively causing another event to be captured.
  28. * This makes a whole process become an infinite loop, which we need to somehow
  29. * deal with, and break it in one way or another.
  30. *
  31. * To deal with this issue, we are making sure that we _always_ use the real
  32. * browser Fetch API, instead of relying on what `window.fetch` exposes.
  33. * The only downside to this would be missing our own requests as breadcrumbs,
  34. * but because we are already not doing this, it should be just fine.
  35. *
  36. * Possible failed fetch error messages per-browser:
  37. *
  38. * Chrome: Failed to fetch
  39. * Edge: Failed to Fetch
  40. * Firefox: NetworkError when attempting to fetch resource
  41. * Safari: resource blocked by content blocker
  42. */
  43. function getNativeFetchImplementation() {
  44. if (cachedFetchImpl) {
  45. return cachedFetchImpl;
  46. }
  47. /* eslint-disable @typescript-eslint/unbound-method */
  48. // Fast path to avoid DOM I/O
  49. if (isNativeFetch(WINDOW.fetch)) {
  50. return (cachedFetchImpl = WINDOW.fetch.bind(WINDOW));
  51. }
  52. const document = WINDOW.document;
  53. let fetchImpl = WINDOW.fetch;
  54. // eslint-disable-next-line deprecation/deprecation
  55. if (document && typeof document.createElement === 'function') {
  56. try {
  57. const sandbox = document.createElement('iframe');
  58. sandbox.hidden = true;
  59. document.head.appendChild(sandbox);
  60. const contentWindow = sandbox.contentWindow;
  61. if (contentWindow && contentWindow.fetch) {
  62. fetchImpl = contentWindow.fetch;
  63. }
  64. document.head.removeChild(sandbox);
  65. } catch (e) {
  66. DEBUG_BUILD && logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e);
  67. }
  68. }
  69. return (cachedFetchImpl = fetchImpl.bind(WINDOW));
  70. /* eslint-enable @typescript-eslint/unbound-method */
  71. }
  72. /** Clears cached fetch impl */
  73. function clearCachedFetchImplementation() {
  74. cachedFetchImpl = undefined;
  75. }
  76. export { clearCachedFetchImplementation, getNativeFetchImplementation };
  77. //# sourceMappingURL=utils.js.map