in src/SimpleWebRequest.ts [302:497]
private _fire(): void {
this._xhr = new XMLHttpRequest();
this._xhrRequestHeaders = {};
// xhr.open() can throw an exception for a CSP violation.
const openError = attempt(() => {
// Apparently you're supposed to open the connection before adding events to it. If you don't, the node.js implementation
// of XHR actually calls this.abort() at the start of open()... Bad implementations, hooray.
this._xhr!!!.open(this._action, this._url, true);
});
if (openError) {
this._respond(openError.toString());
return;
}
if (this._options.timeout) {
const timeoutSupported = timeoutSupportStatus;
// Use manual timer if we don't know about timeout support
if (timeoutSupported !== FeatureSupportStatus.Supported) {
this._assertAndClean(!this._requestTimeoutTimer, 'Double-fired requestTimeoutTimer');
this._requestTimeoutTimer = SimpleWebRequestOptions.setTimeout(() => {
this._requestTimeoutTimer = undefined;
this._timedOut = true;
this.abort();
}, this._options.timeout);
}
// This is our first completed request. Use it for feature detection
if (timeoutSupported === FeatureSupportStatus.Supported || timeoutSupported <= FeatureSupportStatus.Detecting) {
// timeout and ontimeout are part of the XMLHttpRequest Level 2 spec, should be supported in most modern browsers
this._xhr.timeout = this._options.timeout;
this._xhr.ontimeout = () => {
timeoutSupportStatus = FeatureSupportStatus.Supported;
if (timeoutSupported !== FeatureSupportStatus.Supported) {
// When this request initially fired we didn't know about support, bail & let the fallback method handle this
return;
}
this._timedOut = true;
// Set aborted flag to match simple timer approach, which aborts the request and results in an _respond call
this._aborted = true;
this._respond('TimedOut');
};
}
}
const onLoadErrorSupported = onLoadErrorSupportStatus;
// Use onreadystatechange if we don't know about onload support or it onload is not supported
if (onLoadErrorSupported !== FeatureSupportStatus.Supported) {
if (onLoadErrorSupported === FeatureSupportStatus.Unknown) {
// Set global status to detecting, leave local state so we can set a timer on finish
onLoadErrorSupportStatus = FeatureSupportStatus.Detecting;
}
this._xhr.onreadystatechange = () => {
if (!this._xhr) {
return;
}
if (this._xhr.readyState === 3 && this._options.streamingDownloadProgress && !this._aborted) {
// This callback may result in cancelling the connection, so keep that in mind with any handling after it
// if we decide to stop using the return after this someday down the line. i.e. this._xhr may be undefined
// when we come back from this call.
this._options.streamingDownloadProgress(this._xhr.responseText);
return;
}
if (this._xhr.readyState !== 4) {
// Wait for it to finish
return;
}
// This is the first request completed (unknown status when fired, detecting now), use it for detection
if (onLoadErrorSupported === FeatureSupportStatus.Unknown &&
onLoadErrorSupportStatus === FeatureSupportStatus.Detecting) {
// If onload hasn't fired within 10 seconds of completion, detect as not supported
SimpleWebRequestOptions.setTimeout(() => {
if (onLoadErrorSupportStatus !== FeatureSupportStatus.Supported) {
onLoadErrorSupportStatus = FeatureSupportStatus.NotSupported;
}
}, 10000);
}
this._respond();
};
} else if (this._options.streamingDownloadProgress) {
// If we support onload and such, but have a streaming download handler, still trap the oRSC.
this._xhr.onreadystatechange = () => {
if (!this._xhr) {
return;
}
if (this._xhr.readyState === 3 && this._options.streamingDownloadProgress && !this._aborted) {
// This callback may result in cancelling the connection, so keep that in mind with any handling after it
// if we decide to stop using the return after this someday down the line. i.e. this._xhr may be undefined
// when we come back from this call.
this._options.streamingDownloadProgress(this._xhr.responseText);
}
};
}
if (onLoadErrorSupported !== FeatureSupportStatus.NotSupported) {
// onLoad and onError are part of the XMLHttpRequest Level 2 spec, should be supported in most modern browsers
this._xhr.onload = () => {
onLoadErrorSupportStatus = FeatureSupportStatus.Supported;
if (onLoadErrorSupported !== FeatureSupportStatus.Supported) {
// When this request initially fired we didn't know about support, bail & let the fallback method handle this
return;
}
this._respond();
};
this._xhr.onerror = () => {
onLoadErrorSupportStatus = FeatureSupportStatus.Supported;
if (onLoadErrorSupported !== FeatureSupportStatus.Supported) {
// When this request initially fired we didn't know about support, bail & let the fallback method handle this
return;
}
this._respond();
};
}
this._xhr.onabort = () => {
// If the browser cancels us (page navigation or whatever), it sometimes calls both the readystatechange and this,
// so make sure we know that this is an abort.
this._aborted = true;
this._respond('Aborted');
};
if (this._xhr.upload && this._options.onProgress) {
this._xhr.upload.onprogress = this._options.onProgress as (ev: ProgressEvent) => void;
}
const acceptType = this._options.acceptType || 'json';
const responseType = this._options.customResponseType || SimpleWebRequestBase._getResponseType(acceptType);
const responseTypeError = attempt(() => {
this._xhr!!!.responseType = responseType;
});
if (responseTypeError) {
// WebKit added support for the json responseType value on 09/03/2013
// https://bugs.webkit.org/show_bug.cgi?id=73648.
// Versions of Safari prior to 7 and Android 4 Samsung borwsers are
// known to throw an Error when setting the value "json" as the response type.
//
// The json response type can be ignored if not supported, because JSON payloads
// are handled by mapBody anyway
if (responseType !== 'json') {
throw responseTypeError;
}
}
SimpleWebRequest._setRequestHeader(this._xhr, this._xhrRequestHeaders, 'Accept', SimpleWebRequestBase.mapContentType(acceptType));
this._xhr.withCredentials = !!this._options.withCredentials;
const nextHeaders = this.getRequestHeaders();
// check/process headers
let headersCheck: Dictionary<boolean> = {};
Object.keys(nextHeaders).forEach(key => {
const value = nextHeaders[key];
const headerLower = key.toLowerCase();
if (headerLower === 'content-type') {
this._assertAndClean(false, `Don't set Content-Type with options.headers -- use it with the options.contentType property`);
return;
}
if (headerLower === 'accept') {
this._assertAndClean(false, `Don't set Accept with options.headers -- use it with the options.acceptType property`);
return;
}
this._assertAndClean(!headersCheck[headerLower], `Setting duplicate header key: ${ headersCheck[headerLower] } and ${ key }`);
if (value === undefined || value === null) {
console.warn(`Tried to set header "${ key }" on request with "${ value }" value, header will be dropped`);
return;
}
headersCheck[headerLower] = true;
SimpleWebRequest._setRequestHeader(this._xhr!!!, this._xhrRequestHeaders!!!, key, value);
});
if (this._options.sendData) {
const contentType = SimpleWebRequestBase.mapContentType(this._options.contentType || 'json');
SimpleWebRequest._setRequestHeader(this._xhr, this._xhrRequestHeaders, 'Content-Type', contentType);
const sendData = SimpleWebRequestBase.mapBody(this._options.sendData, contentType);
this._xhr.send(sendData as BodyInit);
} else {
this._xhr.send();
}
}