happy-dom 7.7.1 → 7.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of happy-dom might be problematic. Click here for more details.
- package/README.md +62 -27
- package/lib/async-task-manager/AsyncTaskManager.d.ts +3 -8
- package/lib/async-task-manager/AsyncTaskManager.js +21 -24
- package/lib/async-task-manager/AsyncTaskManager.js.map +1 -1
- package/lib/cookie/Cookie.d.ts +53 -0
- package/lib/cookie/Cookie.js +146 -0
- package/lib/cookie/Cookie.js.map +1 -0
- package/lib/cookie/CookieJar.d.ts +30 -0
- package/lib/cookie/CookieJar.js +84 -0
- package/lib/cookie/CookieJar.js.map +1 -0
- package/lib/event/IEventListener.d.ts +1 -1
- package/lib/exception/DOMExceptionNameEnum.d.ts +4 -1
- package/lib/exception/DOMExceptionNameEnum.js +3 -0
- package/lib/exception/DOMExceptionNameEnum.js.map +1 -1
- package/lib/fetch/FetchHandler.d.ts +3 -2
- package/lib/fetch/FetchHandler.js +31 -3
- package/lib/fetch/FetchHandler.js.map +1 -1
- package/lib/fetch/RequestInfo.d.ts +5 -0
- package/lib/fetch/RequestInfo.js +3 -0
- package/lib/fetch/RequestInfo.js.map +1 -0
- package/lib/fetch/ResourceFetchHandler.d.ts +2 -2
- package/lib/fetch/ResourceFetchHandler.js +9 -8
- package/lib/fetch/ResourceFetchHandler.js.map +1 -1
- package/lib/index.d.ts +1 -2
- package/lib/index.js +3 -4
- package/lib/index.js.map +1 -1
- package/lib/location/Location.d.ts +2 -1
- package/lib/location/Location.js +4 -7
- package/lib/location/Location.js.map +1 -1
- package/lib/location/RelativeURL.d.ts +3 -1
- package/lib/location/RelativeURL.js +2 -11
- package/lib/location/RelativeURL.js.map +1 -1
- package/lib/nodes/document/Document.d.ts +14 -3
- package/lib/nodes/document/Document.js +21 -6
- package/lib/nodes/document/Document.js.map +1 -1
- package/lib/nodes/document/IDocument.d.ts +2 -0
- package/lib/nodes/html-link-element/HTMLLinkElement.js +5 -2
- package/lib/nodes/html-link-element/HTMLLinkElement.js.map +1 -1
- package/lib/nodes/html-script-element/HTMLScriptElement.js +1 -1
- package/lib/nodes/html-script-element/HTMLScriptElement.js.map +1 -1
- package/lib/nodes/html-script-element/ScriptUtility.js +4 -0
- package/lib/nodes/html-script-element/ScriptUtility.js.map +1 -1
- package/lib/window/IHappyDOMSettings.d.ts +9 -0
- package/lib/window/IHappyDOMSettings.js +3 -0
- package/lib/window/IHappyDOMSettings.js.map +1 -0
- package/lib/window/IWindow.d.ts +15 -6
- package/lib/window/Window.d.ts +18 -3
- package/lib/window/Window.js +26 -4
- package/lib/window/Window.js.map +1 -1
- package/lib/xml-http-request/XMLHttpRequest.d.ts +196 -0
- package/lib/xml-http-request/XMLHttpRequest.js +777 -0
- package/lib/xml-http-request/XMLHttpRequest.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestCertificate.d.ts +5 -0
- package/lib/xml-http-request/XMLHttpRequestCertificate.js +55 -0
- package/lib/xml-http-request/XMLHttpRequestCertificate.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestEventTarget.d.ts +15 -0
- package/lib/xml-http-request/XMLHttpRequestEventTarget.js +23 -0
- package/lib/xml-http-request/XMLHttpRequestEventTarget.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestReadyStateEnum.d.ts +8 -0
- package/lib/xml-http-request/XMLHttpRequestReadyStateEnum.js +12 -0
- package/lib/xml-http-request/XMLHttpRequestReadyStateEnum.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestUpload.d.ts +6 -0
- package/lib/xml-http-request/XMLHttpRequestUpload.js +13 -0
- package/lib/xml-http-request/XMLHttpRequestUpload.js.map +1 -0
- package/lib/xml-http-request/XMLHttpResponseTypeEnum.d.ts +8 -0
- package/lib/xml-http-request/XMLHttpResponseTypeEnum.js +12 -0
- package/lib/xml-http-request/XMLHttpResponseTypeEnum.js.map +1 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.d.ts +15 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.js +55 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.js.map +1 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestURLUtility.d.ts +35 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestURLUtility.js +62 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestURLUtility.js.map +1 -0
- package/package.json +2 -2
- package/src/async-task-manager/AsyncTaskManager.ts +24 -26
- package/src/cookie/Cookie.ts +158 -0
- package/src/cookie/CookieJar.ts +82 -0
- package/src/event/IEventListener.ts +1 -1
- package/src/exception/DOMExceptionNameEnum.ts +4 -1
- package/src/fetch/FetchHandler.ts +40 -4
- package/src/fetch/RequestInfo.ts +6 -0
- package/src/fetch/ResourceFetchHandler.ts +10 -8
- package/src/index.ts +1 -2
- package/src/location/Location.ts +3 -3
- package/src/location/RelativeURL.ts +3 -14
- package/src/nodes/document/Document.ts +24 -6
- package/src/nodes/document/IDocument.ts +2 -0
- package/src/nodes/html-link-element/HTMLLinkElement.ts +7 -2
- package/src/nodes/html-script-element/HTMLScriptElement.ts +1 -1
- package/src/nodes/html-script-element/ScriptUtility.ts +8 -0
- package/src/window/IHappyDOMSettings.ts +9 -0
- package/src/window/IWindow.ts +15 -6
- package/src/window/Window.ts +36 -5
- package/src/xml-http-request/XMLHttpRequest.ts +998 -0
- package/src/xml-http-request/XMLHttpRequestCertificate.ts +52 -0
- package/src/xml-http-request/XMLHttpRequestEventTarget.ts +17 -0
- package/src/xml-http-request/XMLHttpRequestReadyStateEnum.ts +9 -0
- package/src/xml-http-request/XMLHttpRequestUpload.ts +6 -0
- package/src/xml-http-request/XMLHttpResponseTypeEnum.ts +9 -0
- package/src/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.ts +53 -0
- package/src/xml-http-request/utilities/XMLHttpRequestURLUtility.ts +64 -0
- package/lib/cookie/CookieUtility.d.ts +0 -15
- package/lib/cookie/CookieUtility.js +0 -83
- package/lib/cookie/CookieUtility.js.map +0 -1
- package/lib/location/URL.d.ts +0 -53
- package/lib/location/URL.js +0 -96
- package/lib/location/URL.js.map +0 -1
- package/src/cookie/CookieUtility.ts +0 -87
- package/src/location/URL.ts +0 -102
@@ -0,0 +1,777 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
7
|
+
const child_process_1 = __importDefault(require("child_process"));
|
8
|
+
const http_1 = __importDefault(require("http"));
|
9
|
+
const https_1 = __importDefault(require("https"));
|
10
|
+
const XMLHttpRequestEventTarget_1 = __importDefault(require("./XMLHttpRequestEventTarget"));
|
11
|
+
const XMLHttpRequestReadyStateEnum_1 = __importDefault(require("./XMLHttpRequestReadyStateEnum"));
|
12
|
+
const Event_1 = __importDefault(require("../event/Event"));
|
13
|
+
const RelativeURL_1 = __importDefault(require("../location/RelativeURL"));
|
14
|
+
const XMLHttpRequestUpload_1 = __importDefault(require("./XMLHttpRequestUpload"));
|
15
|
+
const DOMException_1 = __importDefault(require("../exception/DOMException"));
|
16
|
+
const DOMExceptionNameEnum_1 = __importDefault(require("../exception/DOMExceptionNameEnum"));
|
17
|
+
const XMLHttpRequestURLUtility_1 = __importDefault(require("./utilities/XMLHttpRequestURLUtility"));
|
18
|
+
const ProgressEvent_1 = __importDefault(require("../event/events/ProgressEvent"));
|
19
|
+
const XMLHttpResponseTypeEnum_1 = __importDefault(require("./XMLHttpResponseTypeEnum"));
|
20
|
+
const XMLHttpRequestCertificate_1 = __importDefault(require("./XMLHttpRequestCertificate"));
|
21
|
+
const XMLHttpRequestSyncRequestScriptBuilder_1 = __importDefault(require("./utilities/XMLHttpRequestSyncRequestScriptBuilder"));
|
22
|
+
// These headers are not user setable.
|
23
|
+
// The following are allowed but banned in the spec:
|
24
|
+
// * User-agent
|
25
|
+
const FORBIDDEN_REQUEST_HEADERS = [
|
26
|
+
'accept-charset',
|
27
|
+
'accept-encoding',
|
28
|
+
'access-control-request-headers',
|
29
|
+
'access-control-request-method',
|
30
|
+
'connection',
|
31
|
+
'content-length',
|
32
|
+
'content-transfer-encoding',
|
33
|
+
'cookie',
|
34
|
+
'cookie2',
|
35
|
+
'date',
|
36
|
+
'expect',
|
37
|
+
'host',
|
38
|
+
'keep-alive',
|
39
|
+
'origin',
|
40
|
+
'referer',
|
41
|
+
'te',
|
42
|
+
'trailer',
|
43
|
+
'transfer-encoding',
|
44
|
+
'upgrade',
|
45
|
+
'via'
|
46
|
+
];
|
47
|
+
// These request methods are not allowed
|
48
|
+
const FORBIDDEN_REQUEST_METHODS = ['TRACE', 'TRACK', 'CONNECT'];
|
49
|
+
/**
|
50
|
+
* XMLHttpRequest.
|
51
|
+
*
|
52
|
+
* Based on:
|
53
|
+
* https://github.com/mjwwit/node-XMLHttpRequest/blob/master/lib/XMLHttpRequest.js
|
54
|
+
*/
|
55
|
+
class XMLHttpRequest extends XMLHttpRequestEventTarget_1.default {
|
56
|
+
/**
|
57
|
+
* Constructor.
|
58
|
+
*/
|
59
|
+
constructor() {
|
60
|
+
super();
|
61
|
+
// Public properties
|
62
|
+
this.upload = new XMLHttpRequestUpload_1.default();
|
63
|
+
// Private properties
|
64
|
+
this._ownerDocument = null;
|
65
|
+
this._state = {
|
66
|
+
incommingMessage: null,
|
67
|
+
response: null,
|
68
|
+
responseType: '',
|
69
|
+
responseText: '',
|
70
|
+
responseXML: null,
|
71
|
+
responseURL: '',
|
72
|
+
readyState: XMLHttpRequestReadyStateEnum_1.default.unsent,
|
73
|
+
asyncRequest: null,
|
74
|
+
asyncTaskID: null,
|
75
|
+
requestHeaders: {},
|
76
|
+
status: null,
|
77
|
+
statusText: null,
|
78
|
+
send: false,
|
79
|
+
error: false,
|
80
|
+
aborted: false
|
81
|
+
};
|
82
|
+
this._settings = {
|
83
|
+
method: null,
|
84
|
+
url: null,
|
85
|
+
async: true,
|
86
|
+
user: null,
|
87
|
+
password: null
|
88
|
+
};
|
89
|
+
this._ownerDocument = XMLHttpRequest._ownerDocument;
|
90
|
+
}
|
91
|
+
/**
|
92
|
+
* Returns the status.
|
93
|
+
*
|
94
|
+
* @returns Status.
|
95
|
+
*/
|
96
|
+
get status() {
|
97
|
+
return this._state.status;
|
98
|
+
}
|
99
|
+
/**
|
100
|
+
* Returns the status text.
|
101
|
+
*
|
102
|
+
* @returns Status text.
|
103
|
+
*/
|
104
|
+
get statusText() {
|
105
|
+
return this._state.statusText;
|
106
|
+
}
|
107
|
+
/**
|
108
|
+
* Returns the response URL.
|
109
|
+
*
|
110
|
+
* @returns Response URL.
|
111
|
+
*/
|
112
|
+
get responseURL() {
|
113
|
+
return this._state.responseURL;
|
114
|
+
}
|
115
|
+
/**
|
116
|
+
* Returns the ready state.
|
117
|
+
*
|
118
|
+
* @returns Ready state.
|
119
|
+
*/
|
120
|
+
get readyState() {
|
121
|
+
return this._state.readyState;
|
122
|
+
}
|
123
|
+
/**
|
124
|
+
* Get the response text.
|
125
|
+
*
|
126
|
+
* @throws {DOMException} If the response type is not text or empty.
|
127
|
+
* @returns The response text.
|
128
|
+
*/
|
129
|
+
get responseText() {
|
130
|
+
if (this.responseType === XMLHttpResponseTypeEnum_1.default.text || this.responseType === '') {
|
131
|
+
return this._state.responseText;
|
132
|
+
}
|
133
|
+
throw new DOMException_1.default(`Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '${this.responseType}').`, DOMExceptionNameEnum_1.default.invalidStateError);
|
134
|
+
}
|
135
|
+
/**
|
136
|
+
* Get the responseXML.
|
137
|
+
*
|
138
|
+
* @throws {DOMException} If the response type is not text or empty.
|
139
|
+
* @returns Response XML.
|
140
|
+
*/
|
141
|
+
get responseXML() {
|
142
|
+
if (this.responseType === XMLHttpResponseTypeEnum_1.default.document || this.responseType === '') {
|
143
|
+
return this._state.responseXML;
|
144
|
+
}
|
145
|
+
throw new DOMException_1.default(`Failed to read the 'responseXML' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'document' (was '${this.responseType}').`, DOMExceptionNameEnum_1.default.invalidStateError);
|
146
|
+
}
|
147
|
+
/**
|
148
|
+
* Set response type.
|
149
|
+
*
|
150
|
+
* @param type Response type.
|
151
|
+
* @throws {DOMException} If the state is not unsent or opened.
|
152
|
+
* @throws {DOMException} If the request is synchronous.
|
153
|
+
*/
|
154
|
+
set responseType(type) {
|
155
|
+
// ResponseType can only be set when the state is unsent or opened.
|
156
|
+
if (this.readyState !== XMLHttpRequestReadyStateEnum_1.default.opened &&
|
157
|
+
this.readyState !== XMLHttpRequestReadyStateEnum_1.default.unsent) {
|
158
|
+
throw new DOMException_1.default(`Failed to set the 'responseType' property on 'XMLHttpRequest': The object's state must be OPENED or UNSENT.`, DOMExceptionNameEnum_1.default.invalidStateError);
|
159
|
+
}
|
160
|
+
// Sync requests can only have empty string or 'text' as response type.
|
161
|
+
if (!this._settings.async) {
|
162
|
+
throw new DOMException_1.default(`Failed to set the 'responseType' property on 'XMLHttpRequest': The response type cannot be changed for synchronous requests made from a document.`, DOMExceptionNameEnum_1.default.invalidStateError);
|
163
|
+
}
|
164
|
+
this._state.responseType = type;
|
165
|
+
}
|
166
|
+
/**
|
167
|
+
* Get response Type.
|
168
|
+
*
|
169
|
+
* @returns Response type.
|
170
|
+
*/
|
171
|
+
get responseType() {
|
172
|
+
return this._state.responseType;
|
173
|
+
}
|
174
|
+
/**
|
175
|
+
* Opens the connection.
|
176
|
+
*
|
177
|
+
* @param method Connection method (eg GET, POST).
|
178
|
+
* @param url URL for the connection.
|
179
|
+
* @param [async=true] Asynchronous connection.
|
180
|
+
* @param [user] Username for basic authentication (optional).
|
181
|
+
* @param [password] Password for basic authentication (optional).
|
182
|
+
*/
|
183
|
+
open(method, url, async = true, user, password) {
|
184
|
+
this.abort();
|
185
|
+
this._state.aborted = false;
|
186
|
+
this._state.error = false;
|
187
|
+
const upperMethod = method.toUpperCase();
|
188
|
+
// Check for valid request method
|
189
|
+
if (FORBIDDEN_REQUEST_METHODS.includes(upperMethod)) {
|
190
|
+
throw new DOMException_1.default('Request method not allowed', DOMExceptionNameEnum_1.default.securityError);
|
191
|
+
}
|
192
|
+
// Check responseType.
|
193
|
+
if (!async && !!this.responseType && this.responseType !== XMLHttpResponseTypeEnum_1.default.text) {
|
194
|
+
throw new DOMException_1.default(`Failed to execute 'open' on 'XMLHttpRequest': Synchronous requests from a document must not set a response type.`, DOMExceptionNameEnum_1.default.invalidAccessError);
|
195
|
+
}
|
196
|
+
this._settings = {
|
197
|
+
method: upperMethod,
|
198
|
+
url: url,
|
199
|
+
async: async,
|
200
|
+
user: user || null,
|
201
|
+
password: password || null
|
202
|
+
};
|
203
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.opened);
|
204
|
+
}
|
205
|
+
/**
|
206
|
+
* Sets a header for the request.
|
207
|
+
*
|
208
|
+
* @param header Header name
|
209
|
+
* @param value Header value
|
210
|
+
* @returns Header added.
|
211
|
+
*/
|
212
|
+
setRequestHeader(header, value) {
|
213
|
+
if (this.readyState !== XMLHttpRequestReadyStateEnum_1.default.opened) {
|
214
|
+
throw new DOMException_1.default(`Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.`, DOMExceptionNameEnum_1.default.invalidStateError);
|
215
|
+
}
|
216
|
+
const lowerHeader = header.toLowerCase();
|
217
|
+
if (FORBIDDEN_REQUEST_HEADERS.includes(lowerHeader)) {
|
218
|
+
return false;
|
219
|
+
}
|
220
|
+
if (this._state.send) {
|
221
|
+
throw new DOMException_1.default(`Failed to execute 'setRequestHeader' on 'XMLHttpRequest': Request is in progress.`, DOMExceptionNameEnum_1.default.invalidStateError);
|
222
|
+
}
|
223
|
+
this._state.requestHeaders[lowerHeader] = value;
|
224
|
+
return true;
|
225
|
+
}
|
226
|
+
/**
|
227
|
+
* Gets a header from the server response.
|
228
|
+
*
|
229
|
+
* @param header header Name of header to get.
|
230
|
+
* @returns string Text of the header or null if it doesn't exist.
|
231
|
+
*/
|
232
|
+
getResponseHeader(header) {
|
233
|
+
const lowerHeader = header.toLowerCase();
|
234
|
+
// Cookie headers are excluded for security reasons as per spec.
|
235
|
+
if (typeof header === 'string' &&
|
236
|
+
header !== 'set-cookie' &&
|
237
|
+
header !== 'set-cookie2' &&
|
238
|
+
this.readyState > XMLHttpRequestReadyStateEnum_1.default.opened &&
|
239
|
+
this._state.incommingMessage.headers[lowerHeader] &&
|
240
|
+
!this._state.error) {
|
241
|
+
return this._state.incommingMessage.headers[lowerHeader];
|
242
|
+
}
|
243
|
+
return null;
|
244
|
+
}
|
245
|
+
/**
|
246
|
+
* Gets all the response headers.
|
247
|
+
*
|
248
|
+
* @returns A string with all response headers separated by CR+LF.
|
249
|
+
*/
|
250
|
+
getAllResponseHeaders() {
|
251
|
+
if (this.readyState < XMLHttpRequestReadyStateEnum_1.default.headersRecieved || this._state.error) {
|
252
|
+
return '';
|
253
|
+
}
|
254
|
+
const result = [];
|
255
|
+
for (const name of Object.keys(this._state.incommingMessage.headers)) {
|
256
|
+
// Cookie headers are excluded for security reasons as per spec.
|
257
|
+
if (name !== 'set-cookie' && name !== 'set-cookie2') {
|
258
|
+
result.push(`${name}: ${this._state.incommingMessage.headers[name]}`);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
return result.join('\r\n');
|
262
|
+
}
|
263
|
+
/**
|
264
|
+
* Sends the request to the server.
|
265
|
+
*
|
266
|
+
* @param data Optional data to send as request body.
|
267
|
+
*/
|
268
|
+
send(data) {
|
269
|
+
var _a, _b;
|
270
|
+
if (this.readyState != XMLHttpRequestReadyStateEnum_1.default.opened) {
|
271
|
+
throw new DOMException_1.default(`Failed to execute 'send' on 'XMLHttpRequest': Connection must be opened before send() is called.`, DOMExceptionNameEnum_1.default.invalidStateError);
|
272
|
+
}
|
273
|
+
if (this._state.send) {
|
274
|
+
throw new DOMException_1.default(`Failed to execute 'send' on 'XMLHttpRequest': Send has already been called.`, DOMExceptionNameEnum_1.default.invalidStateError);
|
275
|
+
}
|
276
|
+
const { location } = this._ownerDocument.defaultView;
|
277
|
+
const url = RelativeURL_1.default.getAbsoluteURL(location, this._settings.url);
|
278
|
+
// Security check.
|
279
|
+
if (url.protocol === 'http:' && location.protocol === 'https:') {
|
280
|
+
throw new DOMException_1.default(`Mixed Content: The page at '${location.href}' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '${url.href}'. This request has been blocked; the content must be served over HTTPS.`, DOMExceptionNameEnum_1.default.securityError);
|
281
|
+
}
|
282
|
+
// Load files off the local filesystem (file://)
|
283
|
+
if (XMLHttpRequestURLUtility_1.default.isLocal(url)) {
|
284
|
+
if (!this._ownerDocument.defaultView.happyDOM.settings.enableFileSystemHttpRequests) {
|
285
|
+
throw new DOMException_1.default('File system is disabled by default for security reasons. To enable it, set the "window.happyDOM.settings.enableFileSystemHttpRequests" option to true.', DOMExceptionNameEnum_1.default.securityError);
|
286
|
+
}
|
287
|
+
if (this._settings.method !== 'GET') {
|
288
|
+
throw new DOMException_1.default('Failed to send local file system request. Only "GET" method is supported for local file system requests.', DOMExceptionNameEnum_1.default.notSupportedError);
|
289
|
+
}
|
290
|
+
if (this._settings.async) {
|
291
|
+
this._sendLocalAsyncRequest(url).catch((error) => this._onError(error));
|
292
|
+
}
|
293
|
+
else {
|
294
|
+
this._sendLocalSyncRequest(url);
|
295
|
+
}
|
296
|
+
return;
|
297
|
+
}
|
298
|
+
// TODO: CORS check.
|
299
|
+
const host = XMLHttpRequestURLUtility_1.default.getHost(url);
|
300
|
+
const ssl = XMLHttpRequestURLUtility_1.default.isSSL(url);
|
301
|
+
// Default to port 80. If accessing localhost on another port be sure
|
302
|
+
// To use http://localhost:port/path
|
303
|
+
const port = Number(url.port) || (ssl ? 443 : 80);
|
304
|
+
// Add query string if one is used
|
305
|
+
const uri = url.pathname + (url.search ? url.search : '');
|
306
|
+
// Set the Host header or the server may reject the request
|
307
|
+
this._state.requestHeaders['host'] = host;
|
308
|
+
if (!((ssl && port === 443) || port === 80)) {
|
309
|
+
this._state.requestHeaders['host'] += ':' + url.port;
|
310
|
+
}
|
311
|
+
// Set Basic Auth if necessary
|
312
|
+
if (this._settings.user) {
|
313
|
+
(_a = this._settings).password ?? (_a.password = '');
|
314
|
+
const authBuffer = Buffer.from(this._settings.user + ':' + this._settings.password);
|
315
|
+
this._state.requestHeaders['authorization'] = 'Basic ' + authBuffer.toString('base64');
|
316
|
+
}
|
317
|
+
// Set the Content-Length header if method is POST
|
318
|
+
switch (this._settings.method) {
|
319
|
+
case 'GET':
|
320
|
+
case 'HEAD':
|
321
|
+
data = null;
|
322
|
+
break;
|
323
|
+
case 'POST':
|
324
|
+
(_b = this._state.requestHeaders)['content-type'] ?? (_b['content-type'] = 'text/plain;charset=UTF-8');
|
325
|
+
if (data) {
|
326
|
+
this._state.requestHeaders['content-length'] = Buffer.isBuffer(data)
|
327
|
+
? data.length
|
328
|
+
: Buffer.byteLength(data);
|
329
|
+
}
|
330
|
+
else {
|
331
|
+
this._state.requestHeaders['content-length'] = 0;
|
332
|
+
}
|
333
|
+
break;
|
334
|
+
default:
|
335
|
+
break;
|
336
|
+
}
|
337
|
+
const options = {
|
338
|
+
host: host,
|
339
|
+
port: port,
|
340
|
+
path: uri,
|
341
|
+
method: this._settings.method,
|
342
|
+
headers: { ...this._getDefaultRequestHeaders(), ...this._state.requestHeaders },
|
343
|
+
agent: false,
|
344
|
+
rejectUnauthorized: true,
|
345
|
+
key: ssl ? XMLHttpRequestCertificate_1.default.key : null,
|
346
|
+
cert: ssl ? XMLHttpRequestCertificate_1.default.cert : null
|
347
|
+
};
|
348
|
+
// Reset error flag
|
349
|
+
this._state.error = false;
|
350
|
+
// Handle async requests
|
351
|
+
if (this._settings.async) {
|
352
|
+
this._sendAsyncRequest(options, ssl, data).catch((error) => this._onError(error));
|
353
|
+
}
|
354
|
+
else {
|
355
|
+
this._sendSyncRequest(options, ssl, data);
|
356
|
+
}
|
357
|
+
}
|
358
|
+
/**
|
359
|
+
* Aborts a request.
|
360
|
+
*/
|
361
|
+
abort() {
|
362
|
+
if (this._state.asyncRequest) {
|
363
|
+
this._state.asyncRequest.destroy();
|
364
|
+
this._state.asyncRequest = null;
|
365
|
+
}
|
366
|
+
this._state.status = null;
|
367
|
+
this._state.statusText = null;
|
368
|
+
this._state.requestHeaders = {};
|
369
|
+
this._state.responseText = '';
|
370
|
+
this._state.responseXML = null;
|
371
|
+
this._state.aborted = true;
|
372
|
+
this._state.error = true;
|
373
|
+
if (this.readyState !== XMLHttpRequestReadyStateEnum_1.default.unsent &&
|
374
|
+
(this.readyState !== XMLHttpRequestReadyStateEnum_1.default.opened || this._state.send) &&
|
375
|
+
this.readyState !== XMLHttpRequestReadyStateEnum_1.default.done) {
|
376
|
+
this._state.send = false;
|
377
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.done);
|
378
|
+
}
|
379
|
+
this._state.readyState = XMLHttpRequestReadyStateEnum_1.default.unsent;
|
380
|
+
if (this._state.asyncTaskID !== null) {
|
381
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
382
|
+
}
|
383
|
+
}
|
384
|
+
/**
|
385
|
+
* Changes readyState and calls onreadystatechange.
|
386
|
+
*
|
387
|
+
* @param state
|
388
|
+
*/
|
389
|
+
_setState(state) {
|
390
|
+
if (this.readyState === state ||
|
391
|
+
(this.readyState === XMLHttpRequestReadyStateEnum_1.default.unsent && this._state.aborted)) {
|
392
|
+
return;
|
393
|
+
}
|
394
|
+
this._state.readyState = state;
|
395
|
+
if (this._settings.async ||
|
396
|
+
this.readyState < XMLHttpRequestReadyStateEnum_1.default.opened ||
|
397
|
+
this.readyState === XMLHttpRequestReadyStateEnum_1.default.done) {
|
398
|
+
this.dispatchEvent(new Event_1.default('readystatechange'));
|
399
|
+
}
|
400
|
+
if (this.readyState === XMLHttpRequestReadyStateEnum_1.default.done) {
|
401
|
+
let fire;
|
402
|
+
if (this._state.aborted) {
|
403
|
+
fire = new Event_1.default('abort');
|
404
|
+
}
|
405
|
+
else if (this._state.error) {
|
406
|
+
fire = new Event_1.default('error');
|
407
|
+
}
|
408
|
+
else {
|
409
|
+
fire = new Event_1.default('load');
|
410
|
+
}
|
411
|
+
this.dispatchEvent(fire);
|
412
|
+
this.dispatchEvent(new Event_1.default('loadend'));
|
413
|
+
}
|
414
|
+
}
|
415
|
+
/**
|
416
|
+
* Default request headers.
|
417
|
+
*
|
418
|
+
* @returns Default request headers.
|
419
|
+
*/
|
420
|
+
_getDefaultRequestHeaders() {
|
421
|
+
const { location, navigator, document } = this._ownerDocument.defaultView;
|
422
|
+
return {
|
423
|
+
accept: '*/*',
|
424
|
+
referer: location.href,
|
425
|
+
'user-agent': navigator.userAgent,
|
426
|
+
cookie: document._cookie.getCookiesString(location, false)
|
427
|
+
};
|
428
|
+
}
|
429
|
+
/**
|
430
|
+
* Sends a synchronous request.
|
431
|
+
*
|
432
|
+
* @param options
|
433
|
+
* @param ssl
|
434
|
+
* @param data
|
435
|
+
*/
|
436
|
+
_sendSyncRequest(options, ssl, data) {
|
437
|
+
const scriptString = XMLHttpRequestSyncRequestScriptBuilder_1.default.getScript(options, ssl, data);
|
438
|
+
// Start the other Node Process, executing this string
|
439
|
+
const content = child_process_1.default.execFileSync(process.argv[0], ['-e', scriptString], {
|
440
|
+
encoding: 'buffer',
|
441
|
+
maxBuffer: 1024 * 1024 * 1024 // TODO: Consistent buffer size: 1GB.
|
442
|
+
});
|
443
|
+
// If content length is 0, then there was an error
|
444
|
+
if (!content.length) {
|
445
|
+
throw new DOMException_1.default('Synchronous request failed', DOMExceptionNameEnum_1.default.networkError);
|
446
|
+
}
|
447
|
+
const { error, data: response } = JSON.parse(content.toString());
|
448
|
+
if (error) {
|
449
|
+
this._onError(error);
|
450
|
+
}
|
451
|
+
if (response) {
|
452
|
+
this._state.incommingMessage = {
|
453
|
+
statusCode: response.statusCode,
|
454
|
+
headers: response.headers
|
455
|
+
};
|
456
|
+
this._state.status = response.statusCode;
|
457
|
+
this._state.statusText = response.statusMessage;
|
458
|
+
// Sync responseType === ''
|
459
|
+
this._state.response = response.text;
|
460
|
+
this._state.responseText = response.text;
|
461
|
+
this._state.responseXML = null;
|
462
|
+
this._state.responseURL = RelativeURL_1.default.getAbsoluteURL(this._ownerDocument.defaultView.location, this._settings.url).href;
|
463
|
+
// Set Cookies.
|
464
|
+
this._setCookies(this._state.incommingMessage.headers);
|
465
|
+
// Redirect.
|
466
|
+
if (this._state.incommingMessage.statusCode === 301 ||
|
467
|
+
this._state.incommingMessage.statusCode === 302 ||
|
468
|
+
this._state.incommingMessage.statusCode === 303 ||
|
469
|
+
this._state.incommingMessage.statusCode === 307) {
|
470
|
+
const redirectUrl = RelativeURL_1.default.getAbsoluteURL(this._ownerDocument.defaultView.location, this._state.incommingMessage.headers['location']);
|
471
|
+
ssl = redirectUrl.protocol === 'https:';
|
472
|
+
this._settings.url = redirectUrl.href;
|
473
|
+
// Recursive call.
|
474
|
+
this._sendSyncRequest(Object.assign(options, {
|
475
|
+
host: redirectUrl.host,
|
476
|
+
path: redirectUrl.pathname + (redirectUrl.search ?? ''),
|
477
|
+
port: redirectUrl.port || (ssl ? 443 : 80),
|
478
|
+
method: this._state.incommingMessage.statusCode === 303 ? 'GET' : this._settings.method,
|
479
|
+
headers: Object.assign(options.headers, {
|
480
|
+
referer: redirectUrl.origin,
|
481
|
+
host: redirectUrl.host
|
482
|
+
})
|
483
|
+
}), ssl, data);
|
484
|
+
}
|
485
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.done);
|
486
|
+
}
|
487
|
+
}
|
488
|
+
/**
|
489
|
+
* Sends an async request.
|
490
|
+
*
|
491
|
+
* @param options
|
492
|
+
* @param ssl
|
493
|
+
* @param data
|
494
|
+
*/
|
495
|
+
_sendAsyncRequest(options, ssl, data) {
|
496
|
+
return new Promise((resolve) => {
|
497
|
+
// Starts async task in Happy DOM
|
498
|
+
this._state.asyncTaskID = this._ownerDocument.defaultView.happyDOM.asyncTaskManager.startTask(this.abort.bind(this));
|
499
|
+
// Use the proper protocol
|
500
|
+
const sendRequest = ssl ? https_1.default.request : http_1.default.request;
|
501
|
+
// Request is being sent, set send flag
|
502
|
+
this._state.send = true;
|
503
|
+
// As per spec, this is called here for historical reasons.
|
504
|
+
this.dispatchEvent(new Event_1.default('readystatechange'));
|
505
|
+
// Create the request
|
506
|
+
this._state.asyncRequest = sendRequest(options, async (response) => {
|
507
|
+
await this._onAsyncResponse(response, options, ssl, data);
|
508
|
+
resolve();
|
509
|
+
// Ends async task in Happy DOM
|
510
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
511
|
+
}).on('error', (error) => {
|
512
|
+
this._onError(error);
|
513
|
+
resolve();
|
514
|
+
// Ends async task in Happy DOM
|
515
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
516
|
+
});
|
517
|
+
// Node 0.4 and later won't accept empty data. Make sure it's needed.
|
518
|
+
if (data) {
|
519
|
+
this._state.asyncRequest.write(data);
|
520
|
+
}
|
521
|
+
this._state.asyncRequest.end();
|
522
|
+
this.dispatchEvent(new Event_1.default('loadstart'));
|
523
|
+
});
|
524
|
+
}
|
525
|
+
/**
|
526
|
+
* Handles an async response.
|
527
|
+
*
|
528
|
+
* @param options Options.
|
529
|
+
* @param ssl SSL.
|
530
|
+
* @param data Data.
|
531
|
+
* @param response Response.
|
532
|
+
* @returns Promise.
|
533
|
+
*/
|
534
|
+
_onAsyncResponse(response, options, ssl, data) {
|
535
|
+
return new Promise((resolve) => {
|
536
|
+
// Set response var to the response we got back
|
537
|
+
// This is so it remains accessable outside this scope
|
538
|
+
this._state.incommingMessage = response;
|
539
|
+
// Set Cookies
|
540
|
+
this._setCookies(this._state.incommingMessage.headers);
|
541
|
+
// Check for redirect
|
542
|
+
// @TODO Prevent looped redirects
|
543
|
+
if (this._state.incommingMessage.statusCode === 301 ||
|
544
|
+
this._state.incommingMessage.statusCode === 302 ||
|
545
|
+
this._state.incommingMessage.statusCode === 303 ||
|
546
|
+
this._state.incommingMessage.statusCode === 307) {
|
547
|
+
// TODO: redirect url protocol change.
|
548
|
+
// Change URL to the redirect location
|
549
|
+
this._settings.url = this._state.incommingMessage.headers.location;
|
550
|
+
// Parse the new URL.
|
551
|
+
const redirectUrl = RelativeURL_1.default.getAbsoluteURL(this._ownerDocument.defaultView.location, this._settings.url);
|
552
|
+
this._settings.url = redirectUrl.href;
|
553
|
+
ssl = redirectUrl.protocol === 'https:';
|
554
|
+
// Issue the new request
|
555
|
+
this._sendAsyncRequest({
|
556
|
+
...options,
|
557
|
+
host: redirectUrl.hostname,
|
558
|
+
port: redirectUrl.port,
|
559
|
+
path: redirectUrl.pathname + (redirectUrl.search ?? ''),
|
560
|
+
method: this._state.incommingMessage.statusCode === 303 ? 'GET' : this._settings.method,
|
561
|
+
headers: { ...options.headers, referer: redirectUrl.origin, host: redirectUrl.host }
|
562
|
+
}, ssl, data);
|
563
|
+
// @TODO Check if an XHR event needs to be fired here
|
564
|
+
return;
|
565
|
+
}
|
566
|
+
if (this._state.incommingMessage && this._state.incommingMessage.setEncoding) {
|
567
|
+
this._state.incommingMessage.setEncoding('utf-8');
|
568
|
+
}
|
569
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.headersRecieved);
|
570
|
+
this._state.status = this._state.incommingMessage.statusCode;
|
571
|
+
this._state.statusText = this._state.incommingMessage.statusMessage;
|
572
|
+
// Initialize response.
|
573
|
+
let tempResponse = Buffer.from(new Uint8Array(0));
|
574
|
+
this._state.incommingMessage.on('data', (chunk) => {
|
575
|
+
// Make sure there's some data
|
576
|
+
if (chunk) {
|
577
|
+
tempResponse = Buffer.concat([tempResponse, Buffer.from(chunk)]);
|
578
|
+
}
|
579
|
+
// Don't emit state changes if the connection has been aborted.
|
580
|
+
if (this._state.send) {
|
581
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.loading);
|
582
|
+
}
|
583
|
+
const contentLength = Number(this._state.incommingMessage.headers['content-length']);
|
584
|
+
this.dispatchEvent(new ProgressEvent_1.default('progress', {
|
585
|
+
lengthComputable: isNaN(contentLength) ? false : true,
|
586
|
+
loaded: tempResponse.length,
|
587
|
+
total: isNaN(contentLength) ? 0 : contentLength
|
588
|
+
}));
|
589
|
+
});
|
590
|
+
this._state.incommingMessage.on('end', () => {
|
591
|
+
if (this._state.send) {
|
592
|
+
// The sendFlag needs to be set before setState is called. Otherwise, if we are chaining callbacks
|
593
|
+
// There can be a timing issue (the callback is called and a new call is made before the flag is reset).
|
594
|
+
this._state.send = false;
|
595
|
+
// Set response according to responseType.
|
596
|
+
const { response, responseXML, responseText } = this._parseResponseData(tempResponse);
|
597
|
+
this._state.response = response;
|
598
|
+
this._state.responseXML = responseXML;
|
599
|
+
this._state.responseText = responseText;
|
600
|
+
this._state.responseURL = RelativeURL_1.default.getAbsoluteURL(this._ownerDocument.defaultView.location, this._settings.url).href;
|
601
|
+
// Discard the 'end' event if the connection has been aborted
|
602
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.done);
|
603
|
+
}
|
604
|
+
resolve();
|
605
|
+
});
|
606
|
+
this._state.incommingMessage.on('error', (error) => {
|
607
|
+
this._onError(error);
|
608
|
+
resolve();
|
609
|
+
});
|
610
|
+
});
|
611
|
+
}
|
612
|
+
/**
|
613
|
+
* Sends a local file system async request.
|
614
|
+
*
|
615
|
+
* @param url URL.
|
616
|
+
* @returns Promise.
|
617
|
+
*/
|
618
|
+
async _sendLocalAsyncRequest(url) {
|
619
|
+
this._state.asyncTaskID = this._ownerDocument.defaultView.happyDOM.asyncTaskManager.startTask(this.abort.bind(this));
|
620
|
+
let data;
|
621
|
+
try {
|
622
|
+
data = await fs_1.default.promises.readFile(decodeURI(url.pathname.slice(1)));
|
623
|
+
}
|
624
|
+
catch (error) {
|
625
|
+
this._onError(error);
|
626
|
+
}
|
627
|
+
const dataLength = data.length;
|
628
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.loading);
|
629
|
+
this.dispatchEvent(new ProgressEvent_1.default('progress', {
|
630
|
+
lengthComputable: true,
|
631
|
+
loaded: dataLength,
|
632
|
+
total: dataLength
|
633
|
+
}));
|
634
|
+
if (data) {
|
635
|
+
this._parseLocalRequestData(data);
|
636
|
+
}
|
637
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
638
|
+
}
|
639
|
+
/**
|
640
|
+
* Sends a local file system synchronous request.
|
641
|
+
*
|
642
|
+
* @param url URL.
|
643
|
+
*/
|
644
|
+
_sendLocalSyncRequest(url) {
|
645
|
+
let data;
|
646
|
+
try {
|
647
|
+
data = fs_1.default.readFileSync(decodeURI(url.pathname.slice(1)));
|
648
|
+
}
|
649
|
+
catch (error) {
|
650
|
+
this._onError(error);
|
651
|
+
}
|
652
|
+
if (data) {
|
653
|
+
this._parseLocalRequestData(data);
|
654
|
+
}
|
655
|
+
}
|
656
|
+
/**
|
657
|
+
* Parses local request data.
|
658
|
+
*
|
659
|
+
* @param data Data.
|
660
|
+
*/
|
661
|
+
_parseLocalRequestData(data) {
|
662
|
+
this._state.status = 200;
|
663
|
+
this._state.statusText = 'OK';
|
664
|
+
const { response, responseXML, responseText } = this._parseResponseData(data);
|
665
|
+
this._state.response = response;
|
666
|
+
this._state.responseXML = responseXML;
|
667
|
+
this._state.responseText = responseText;
|
668
|
+
this._state.responseURL = RelativeURL_1.default.getAbsoluteURL(this._ownerDocument.defaultView.location, this._settings.url).href;
|
669
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.done);
|
670
|
+
}
|
671
|
+
/**
|
672
|
+
* Returns response based to the "responseType" property.
|
673
|
+
*
|
674
|
+
* @param data Data.
|
675
|
+
* @returns Parsed response.
|
676
|
+
*/
|
677
|
+
_parseResponseData(data) {
|
678
|
+
switch (this.responseType) {
|
679
|
+
case XMLHttpResponseTypeEnum_1.default.arraybuffer:
|
680
|
+
// See: https://github.com/jsdom/jsdom/blob/c3c421c364510e053478520500bccafd97f5fa39/lib/jsdom/living/helpers/binary-data.js
|
681
|
+
const newAB = new ArrayBuffer(data.length);
|
682
|
+
const view = new Uint8Array(newAB);
|
683
|
+
view.set(data);
|
684
|
+
return {
|
685
|
+
response: view,
|
686
|
+
responseText: null,
|
687
|
+
responseXML: null
|
688
|
+
};
|
689
|
+
case XMLHttpResponseTypeEnum_1.default.blob:
|
690
|
+
try {
|
691
|
+
return {
|
692
|
+
response: new this._ownerDocument.defaultView.Blob([new Uint8Array(data)], {
|
693
|
+
type: this.getResponseHeader('content-type') || ''
|
694
|
+
}),
|
695
|
+
responseText: null,
|
696
|
+
responseXML: null
|
697
|
+
};
|
698
|
+
}
|
699
|
+
catch (e) {
|
700
|
+
return { response: null, responseText: null, responseXML: null };
|
701
|
+
}
|
702
|
+
case XMLHttpResponseTypeEnum_1.default.document:
|
703
|
+
const window = this._ownerDocument.defaultView;
|
704
|
+
const happyDOMSettings = window.happyDOM.settings;
|
705
|
+
let response;
|
706
|
+
// Temporary disables unsecure features.
|
707
|
+
window.happyDOM.settings = {
|
708
|
+
...happyDOMSettings,
|
709
|
+
enableFileSystemHttpRequests: false,
|
710
|
+
disableJavaScriptEvaluation: true,
|
711
|
+
disableCSSFileLoading: true,
|
712
|
+
disableJavaScriptFileLoading: true
|
713
|
+
};
|
714
|
+
const domParser = new window.DOMParser();
|
715
|
+
try {
|
716
|
+
response = domParser.parseFromString(data.toString(), 'text/xml');
|
717
|
+
}
|
718
|
+
catch (e) {
|
719
|
+
return { response: null, responseText: null, responseXML: null };
|
720
|
+
}
|
721
|
+
// Restores unsecure features.
|
722
|
+
window.happyDOM.settings = happyDOMSettings;
|
723
|
+
return { response, responseText: null, responseXML: response };
|
724
|
+
case XMLHttpResponseTypeEnum_1.default.json:
|
725
|
+
try {
|
726
|
+
return {
|
727
|
+
response: JSON.parse(data.toString()),
|
728
|
+
responseText: null,
|
729
|
+
responseXML: null
|
730
|
+
};
|
731
|
+
}
|
732
|
+
catch (e) {
|
733
|
+
return { response: null, responseText: null, responseXML: null };
|
734
|
+
}
|
735
|
+
case XMLHttpResponseTypeEnum_1.default.text:
|
736
|
+
case '':
|
737
|
+
default:
|
738
|
+
return {
|
739
|
+
response: data.toString(),
|
740
|
+
responseText: data.toString(),
|
741
|
+
responseXML: null
|
742
|
+
};
|
743
|
+
}
|
744
|
+
}
|
745
|
+
/**
|
746
|
+
* Set Cookies from response headers.
|
747
|
+
*
|
748
|
+
* @param headers String array.
|
749
|
+
*/
|
750
|
+
_setCookies(headers) {
|
751
|
+
for (const cookie of [...(headers['set-cookie'] ?? []), ...(headers['set-cookie2'] ?? [])]) {
|
752
|
+
this._ownerDocument.defaultView.document._cookie.setCookiesString(cookie);
|
753
|
+
}
|
754
|
+
}
|
755
|
+
/**
|
756
|
+
* Called when an error is encountered to deal with it.
|
757
|
+
*
|
758
|
+
* @param error Error.
|
759
|
+
*/
|
760
|
+
_onError(error) {
|
761
|
+
this._state.status = 0;
|
762
|
+
this._state.statusText = error.toString();
|
763
|
+
this._state.responseText = error instanceof Error ? error.stack : '';
|
764
|
+
this._state.error = true;
|
765
|
+
this._setState(XMLHttpRequestReadyStateEnum_1.default.done);
|
766
|
+
}
|
767
|
+
}
|
768
|
+
exports.default = XMLHttpRequest;
|
769
|
+
// Owner document is set by a sub-class in the Window constructor
|
770
|
+
XMLHttpRequest._ownerDocument = null;
|
771
|
+
// Constants
|
772
|
+
XMLHttpRequest.UNSENT = XMLHttpRequestReadyStateEnum_1.default.unsent;
|
773
|
+
XMLHttpRequest.OPENED = XMLHttpRequestReadyStateEnum_1.default.opened;
|
774
|
+
XMLHttpRequest.HEADERS_RECEIVED = XMLHttpRequestReadyStateEnum_1.default.headersRecieved;
|
775
|
+
XMLHttpRequest.LOADING = XMLHttpRequestReadyStateEnum_1.default.loading;
|
776
|
+
XMLHttpRequest.DONE = XMLHttpRequestReadyStateEnum_1.default.done;
|
777
|
+
//# sourceMappingURL=XMLHttpRequest.js.map
|