-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
supports.ts
179 lines (163 loc) · 5.01 KB
/
supports.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import { isDebugBuild } from './env';
import { getGlobalObject } from './global';
import { logger } from './logger';
/**
* Tells whether current environment supports ErrorEvent objects
* {@link supportsErrorEvent}.
*
* @returns Answer to the given question.
*/
export function supportsErrorEvent(): boolean {
try {
new ErrorEvent('');
return true;
} catch (e) {
return false;
}
}
/**
* Tells whether current environment supports DOMError objects
* {@link supportsDOMError}.
*
* @returns Answer to the given question.
*/
export function supportsDOMError(): boolean {
try {
// Chrome: VM89:1 Uncaught TypeError: Failed to construct 'DOMError':
// 1 argument required, but only 0 present.
// @ts-ignore It really needs 1 argument, not 0.
new DOMError('');
return true;
} catch (e) {
return false;
}
}
/**
* Tells whether current environment supports DOMException objects
* {@link supportsDOMException}.
*
* @returns Answer to the given question.
*/
export function supportsDOMException(): boolean {
try {
new DOMException('');
return true;
} catch (e) {
return false;
}
}
/**
* Tells whether current environment supports Fetch API
* {@link supportsFetch}.
*
* @returns Answer to the given question.
*/
export function supportsFetch(): boolean {
if (!('fetch' in getGlobalObject<Window>())) {
return false;
}
try {
new Headers();
new Request('');
new Response();
return true;
} catch (e) {
return false;
}
}
/**
* isNativeFetch checks if the given function is a native implementation of fetch()
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export function isNativeFetch(func: Function): boolean {
return func && /^function fetch\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
}
/**
* Tells whether current environment supports Fetch API natively
* {@link supportsNativeFetch}.
*
* @returns true if `window.fetch` is natively implemented, false otherwise
*/
export function supportsNativeFetch(): boolean {
if (!supportsFetch()) {
return false;
}
const global = getGlobalObject<Window>();
// Fast path to avoid DOM I/O
// eslint-disable-next-line @typescript-eslint/unbound-method
if (isNativeFetch(global.fetch)) {
return true;
}
// window.fetch is implemented, but is polyfilled or already wrapped (e.g: by a chrome extension)
// so create a "pure" iframe to see if that has native fetch
let result = false;
const doc = global.document;
// eslint-disable-next-line deprecation/deprecation
if (doc && typeof (doc.createElement as unknown) === 'function') {
try {
const sandbox = doc.createElement('iframe');
sandbox.hidden = true;
doc.head.appendChild(sandbox);
if (sandbox.contentWindow && sandbox.contentWindow.fetch) {
// eslint-disable-next-line @typescript-eslint/unbound-method
result = isNativeFetch(sandbox.contentWindow.fetch);
}
doc.head.removeChild(sandbox);
} catch (err) {
isDebugBuild() &&
logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err);
}
}
return result;
}
/**
* Tells whether current environment supports ReportingObserver API
* {@link supportsReportingObserver}.
*
* @returns Answer to the given question.
*/
export function supportsReportingObserver(): boolean {
return 'ReportingObserver' in getGlobalObject();
}
/**
* Tells whether current environment supports Referrer Policy API
* {@link supportsReferrerPolicy}.
*
* @returns Answer to the given question.
*/
export function supportsReferrerPolicy(): boolean {
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default'
// (see https://caniuse.com/#feat=referrer-policy),
// it doesn't. And it throws an exception instead of ignoring this parameter...
// REF: https://github.com/getsentry/raven-js/issues/1233
if (!supportsFetch()) {
return false;
}
try {
new Request('_', {
referrerPolicy: 'origin' as ReferrerPolicy,
});
return true;
} catch (e) {
return false;
}
}
/**
* Tells whether current environment supports History API
* {@link supportsHistory}.
*
* @returns Answer to the given question.
*/
export function supportsHistory(): boolean {
// NOTE: in Chrome App environment, touching history.pushState, *even inside
// a try/catch block*, will cause Chrome to output an error to console.error
// borrowed from: https://github.com/angular/angular.js/pull/13945/files
const global = getGlobalObject<Window>();
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chrome = (global as any).chrome;
const isChromePackagedApp = chrome && chrome.app && chrome.app.runtime;
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
const hasHistoryApi = 'history' in global && !!global.history.pushState && !!global.history.replaceState;
return !isChromePackagedApp && hasHistoryApi;
}