2 var window = require("global/window")
3 var isFunction = require("is-function")
4 var parseHeaders = require("parse-headers")
5 var xtend = require("xtend")
7 module.exports = createXHR
8 createXHR.XMLHttpRequest = window.XMLHttpRequest || noop
9 createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest
11 forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) {
12 createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) {
13 options = initParams(uri, options, callback)
14 options.method = method.toUpperCase()
15 return _createXHR(options)
19 function forEachArray(array, iterator) {
20 for (var i = 0; i < array.length; i++) {
25 function isEmpty(obj){
27 if(obj.hasOwnProperty(i)) return false
32 function initParams(uri, options, callback) {
35 if (isFunction(options)) {
37 if (typeof uri === "string") {
41 params = xtend(options, {uri: uri})
44 params.callback = callback
48 function createXHR(uri, options, callback) {
49 options = initParams(uri, options, callback)
50 return _createXHR(options)
53 function _createXHR(options) {
54 if(typeof options.callback === "undefined"){
55 throw new Error("callback argument missing")
59 var callback = function cbOnce(err, response, body){
62 options.callback(err, response, body)
66 function readystatechange() {
67 if (xhr.readyState === 4) {
73 // Chrome with requestType=blob throws errors arround when even testing access to responseText
79 body = xhr.responseText || getXml(xhr)
84 body = JSON.parse(body)
91 var failureResponse = {
100 function errorFunc(evt) {
101 clearTimeout(timeoutTimer)
102 if(!(evt instanceof Error)){
103 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") )
106 return callback(evt, failureResponse)
109 // will load the data & process the response in a special response object
110 function loadFunc() {
113 clearTimeout(timeoutTimer)
114 if(options.useXDR && xhr.status===undefined) {
115 //IE8 CORS GET successful response doesn't have a status field, but body is fine
118 status = (xhr.status === 1223 ? 204 : xhr.status)
120 var response = failureResponse
132 if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
133 response.headers = parseHeaders(xhr.getAllResponseHeaders())
136 err = new Error("Internal XMLHttpRequest Error")
138 return callback(err, response, response.body)
141 var xhr = options.xhr || null
144 if (options.cors || options.useXDR) {
145 xhr = new createXHR.XDomainRequest()
147 xhr = new createXHR.XMLHttpRequest()
153 var uri = xhr.url = options.uri || options.url
154 var method = xhr.method = options.method || "GET"
155 var body = options.body || options.data || null
156 var headers = xhr.headers = options.headers || {}
157 var sync = !!options.sync
161 if ("json" in options) {
163 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user
164 if (method !== "GET" && method !== "HEAD") {
165 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user
166 body = JSON.stringify(options.json)
170 xhr.onreadystatechange = readystatechange
171 xhr.onload = loadFunc
172 xhr.onerror = errorFunc
173 // IE9 must have onprogress be set to a unique function.
174 xhr.onprogress = function () {
177 xhr.ontimeout = errorFunc
178 xhr.open(method, uri, !sync, options.username, options.password)
179 //has to be after open
181 xhr.withCredentials = !!options.withCredentials
183 // Cannot set timeout with sync request
184 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
185 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
186 if (!sync && options.timeout > 0 ) {
187 timeoutTimer = setTimeout(function(){
188 aborted=true//IE9 may still call readystatechange
190 var e = new Error("XMLHttpRequest timeout")
196 if (xhr.setRequestHeader) {
198 if(headers.hasOwnProperty(key)){
199 xhr.setRequestHeader(key, headers[key])
202 } else if (options.headers && !isEmpty(options.headers)) {
203 throw new Error("Headers cannot be set on an XDomainRequest object")
206 if ("responseType" in options) {
207 xhr.responseType = options.responseType
210 if ("beforeSend" in options &&
211 typeof options.beforeSend === "function"
213 options.beforeSend(xhr)
223 function getXml(xhr) {
224 if (xhr.responseType === "document") {
225 return xhr.responseXML
227 var firefoxBugTakenEffect = xhr.status === 204 && xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"
228 if (xhr.responseType === "" && !firefoxBugTakenEffect) {
229 return xhr.responseXML