You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
7.3 KiB
247 lines
7.3 KiB
"use strict"; |
|
var window = require("global/window") |
|
var isFunction = require("is-function") |
|
var parseHeaders = require("parse-headers") |
|
var xtend = require("xtend") |
|
|
|
module.exports = createXHR |
|
// Allow use of default import syntax in TypeScript |
|
module.exports.default = createXHR; |
|
createXHR.XMLHttpRequest = window.XMLHttpRequest || noop |
|
createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest |
|
|
|
forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { |
|
createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { |
|
options = initParams(uri, options, callback) |
|
options.method = method.toUpperCase() |
|
return _createXHR(options) |
|
} |
|
}) |
|
|
|
function forEachArray(array, iterator) { |
|
for (var i = 0; i < array.length; i++) { |
|
iterator(array[i]) |
|
} |
|
} |
|
|
|
function isEmpty(obj){ |
|
for(var i in obj){ |
|
if(obj.hasOwnProperty(i)) return false |
|
} |
|
return true |
|
} |
|
|
|
function initParams(uri, options, callback) { |
|
var params = uri |
|
|
|
if (isFunction(options)) { |
|
callback = options |
|
if (typeof uri === "string") { |
|
params = {uri:uri} |
|
} |
|
} else { |
|
params = xtend(options, {uri: uri}) |
|
} |
|
|
|
params.callback = callback |
|
return params |
|
} |
|
|
|
function createXHR(uri, options, callback) { |
|
options = initParams(uri, options, callback) |
|
return _createXHR(options) |
|
} |
|
|
|
function _createXHR(options) { |
|
if(typeof options.callback === "undefined"){ |
|
throw new Error("callback argument missing") |
|
} |
|
|
|
var called = false |
|
var callback = function cbOnce(err, response, body){ |
|
if(!called){ |
|
called = true |
|
options.callback(err, response, body) |
|
} |
|
} |
|
|
|
function readystatechange() { |
|
if (xhr.readyState === 4) { |
|
setTimeout(loadFunc, 0) |
|
} |
|
} |
|
|
|
function getBody() { |
|
// Chrome with requestType=blob throws errors arround when even testing access to responseText |
|
var body = undefined |
|
|
|
if (xhr.response) { |
|
body = xhr.response |
|
} else { |
|
body = xhr.responseText || getXml(xhr) |
|
} |
|
|
|
if (isJson) { |
|
try { |
|
body = JSON.parse(body) |
|
} catch (e) {} |
|
} |
|
|
|
return body |
|
} |
|
|
|
function errorFunc(evt) { |
|
clearTimeout(timeoutTimer) |
|
if(!(evt instanceof Error)){ |
|
evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ) |
|
} |
|
evt.statusCode = 0 |
|
return callback(evt, failureResponse) |
|
} |
|
|
|
// will load the data & process the response in a special response object |
|
function loadFunc() { |
|
if (aborted) return |
|
var status |
|
clearTimeout(timeoutTimer) |
|
if(options.useXDR && xhr.status===undefined) { |
|
//IE8 CORS GET successful response doesn't have a status field, but body is fine |
|
status = 200 |
|
} else { |
|
status = (xhr.status === 1223 ? 204 : xhr.status) |
|
} |
|
var response = failureResponse |
|
var err = null |
|
|
|
if (status !== 0){ |
|
response = { |
|
body: getBody(), |
|
statusCode: status, |
|
method: method, |
|
headers: {}, |
|
url: uri, |
|
rawRequest: xhr |
|
} |
|
if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE |
|
response.headers = parseHeaders(xhr.getAllResponseHeaders()) |
|
} |
|
} else { |
|
err = new Error("Internal XMLHttpRequest Error") |
|
} |
|
return callback(err, response, response.body) |
|
} |
|
|
|
var xhr = options.xhr || null |
|
|
|
if (!xhr) { |
|
if (options.cors || options.useXDR) { |
|
xhr = new createXHR.XDomainRequest() |
|
}else{ |
|
xhr = new createXHR.XMLHttpRequest() |
|
} |
|
} |
|
|
|
var key |
|
var aborted |
|
var uri = xhr.url = options.uri || options.url |
|
var method = xhr.method = options.method || "GET" |
|
var body = options.body || options.data |
|
var headers = xhr.headers = options.headers || {} |
|
var sync = !!options.sync |
|
var isJson = false |
|
var timeoutTimer |
|
var failureResponse = { |
|
body: undefined, |
|
headers: {}, |
|
statusCode: 0, |
|
method: method, |
|
url: uri, |
|
rawRequest: xhr |
|
} |
|
|
|
if ("json" in options && options.json !== false) { |
|
isJson = true |
|
headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user |
|
if (method !== "GET" && method !== "HEAD") { |
|
headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user |
|
body = JSON.stringify(options.json === true ? body : options.json) |
|
} |
|
} |
|
|
|
xhr.onreadystatechange = readystatechange |
|
xhr.onload = loadFunc |
|
xhr.onerror = errorFunc |
|
// IE9 must have onprogress be set to a unique function. |
|
xhr.onprogress = function () { |
|
// IE must die |
|
} |
|
xhr.onabort = function(){ |
|
aborted = true; |
|
} |
|
xhr.ontimeout = errorFunc |
|
xhr.open(method, uri, !sync, options.username, options.password) |
|
//has to be after open |
|
if(!sync) { |
|
xhr.withCredentials = !!options.withCredentials |
|
} |
|
// Cannot set timeout with sync request |
|
// not setting timeout on the xhr object, because of old webkits etc. not handling that correctly |
|
// both npm's request and jquery 1.x use this kind of timeout, so this is being consistent |
|
if (!sync && options.timeout > 0 ) { |
|
timeoutTimer = setTimeout(function(){ |
|
if (aborted) return |
|
aborted = true//IE9 may still call readystatechange |
|
xhr.abort("timeout") |
|
var e = new Error("XMLHttpRequest timeout") |
|
e.code = "ETIMEDOUT" |
|
errorFunc(e) |
|
}, options.timeout ) |
|
} |
|
|
|
if (xhr.setRequestHeader) { |
|
for(key in headers){ |
|
if(headers.hasOwnProperty(key)){ |
|
xhr.setRequestHeader(key, headers[key]) |
|
} |
|
} |
|
} else if (options.headers && !isEmpty(options.headers)) { |
|
throw new Error("Headers cannot be set on an XDomainRequest object") |
|
} |
|
|
|
if ("responseType" in options) { |
|
xhr.responseType = options.responseType |
|
} |
|
|
|
if ("beforeSend" in options && |
|
typeof options.beforeSend === "function" |
|
) { |
|
options.beforeSend(xhr) |
|
} |
|
|
|
// Microsoft Edge browser sends "undefined" when send is called with undefined value. |
|
// XMLHttpRequest spec says to pass null as body to indicate no body |
|
// See https://github.com/naugtur/xhr/issues/100. |
|
xhr.send(body || null) |
|
|
|
return xhr |
|
|
|
|
|
} |
|
|
|
function getXml(xhr) { |
|
// xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" |
|
// See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. |
|
try { |
|
if (xhr.responseType === "document") { |
|
return xhr.responseXML |
|
} |
|
var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror" |
|
if (xhr.responseType === "" && !firefoxBugTakenEffect) { |
|
return xhr.responseXML |
|
} |
|
} catch (e) {} |
|
|
|
return null |
|
} |
|
|
|
function noop() {}
|
|
|