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