diff --git a/MIGRATING.md b/MIGRATING.md new file mode 100644 index 0000000..4cf8c9d --- /dev/null +++ b/MIGRATING.md @@ -0,0 +1,7 @@ +# Migrating from v2 to v3 + +## JSON body +If you used the `json` option to pass request body, now you need to set `body` to the object you want to send, and `json: true` + +## Old IEs +If you need to support IE8 or IE9, stay on v2. diff --git a/README.md b/README.md index 70e1d06..d11d1fd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ For webpack, add a [resolve.alias](http://webpack.github.io/docs/configuration.h } ``` -Browser support: IE8+ and everything else. +Browser support: IE10+ and everything else. + +IE8 and IE9 support was dropped as of v3.0.0 but you can still use v2 in IEs just fine. See v2 documentation for details. ## Example @@ -46,7 +48,6 @@ xhr({ ```js type XhrOptions = String | { - useXDR: Boolean?, sync: Boolean?, uri: String, url: String, @@ -64,9 +65,7 @@ type XhrOptions = String | { } xhr := (XhrOptions, Callback) => Request ``` -the returned object is either an [`XMLHttpRequest`][3] instance - or an [`XDomainRequest`][4] instance (if on IE8/IE9 && - `options.useXDR` is set to `true`) +the returned object is an [`XMLHttpRequest`][3] instance Your callback will be called once with the arguments ( [`Error`][5], `response` , `body` ) where the response is an object: @@ -83,8 +82,6 @@ Your callback will be called once with the arguments - `body`: HTTP response body - [`XMLHttpRequest.response`][6], [`XMLHttpRequest.responseText`][7] or [`XMLHttpRequest.responseXML`][8] depending on the request type. - `rawRequest`: Original [`XMLHttpRequest`][3] instance - or [`XDomainRequest`][4] instance (if on IE8/IE9 && - `options.useXDR` is set to `true`) - `headers`: A collection of headers where keys are header names converted to lowercase @@ -130,14 +127,6 @@ xhr.del('/delete-me', { headers: { my: 'auth' } }, function (err, resp) { Specify the method the [`XMLHttpRequest`][3] should be opened with. Passed to [`XMLHttpRequest.open`][2]. Defaults to "GET" -### `options.useXDR` - -Specify whether this is a cross origin (CORS) request for IE<10. - Switches IE to use [`XDomainRequest`][4] instead of `XMLHttpRequest`. - Ignored in other browsers. - -Note that headers cannot be set on an XDomainRequest instance. - ### `options.sync` Specify whether this is a synchrounous request. Note that when @@ -192,7 +181,7 @@ A function being called right before the `send` method of the `XMLHttpRequest` o ### `options.xhr` -Pass an `XMLHttpRequest` object (or something that acts like one) to use instead of constructing a new one using the `XMLHttpRequest` or `XDomainRequest` constructors. Useful for testing. +Pass an `XMLHttpRequest` object (or something that acts like one) to use instead of constructing a new one using the `XMLHttpRequest` constructor. Useful for testing. ### `options.qs` @@ -216,8 +205,6 @@ For more, see [Query string support](#query-string-support). `options.json:true` with `options.body` for convenience - then `xhr` will do the serialization and set content-type accordingly. - Where's stream API? `.pipe()` etc. - Not implemented. You can't reasonably have that in the browser. -- Why can't I send `"true"` as body by passing it as `options.json` anymore? - - Accepting `true` as a value was a bug. Despite what `JSON.stringify` does, the string `"true"` is not valid JSON. If you're sending booleans as JSON, please consider wrapping them in an object or array to save yourself from more trouble in the future. To bring back the old behavior, hardcode `options.json` to `true` and set `options.body` to your boolean value. - How do I add an `onprogress` listener? - use `beforeSend` function for non-standard things that are browser specific. In this case: ```js @@ -242,7 +229,6 @@ or you can override the constructors used to create requests at the module level ```js xhr.XMLHttpRequest = MockXMLHttpRequest -xhr.XDomainRequest = MockXDomainRequest ``` ## Query string support @@ -275,7 +261,6 @@ xhr.get('/foo', { [1]: http://xhr.spec.whatwg.org/#the-send()-method [2]: http://xhr.spec.whatwg.org/#the-open()-method [3]: http://xhr.spec.whatwg.org/#interface-xmlhttprequest - [4]: http://msdn.microsoft.com/en-us/library/ie/cc288060(v=vs.85).aspx [5]: http://es5.github.com/#x15.11 [6]: http://xhr.spec.whatwg.org/#the-response-attribute [7]: http://xhr.spec.whatwg.org/#the-responsetext-attribute diff --git a/index.js b/index.js index bd5911d..3c5654e 100644 --- a/index.js +++ b/index.js @@ -6,10 +6,10 @@ var xtend = require("xtend") module.exports = createXHR createXHR.XMLHttpRequest = window.XMLHttpRequest || noop -createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest createXHR.qsSerialize = null // Define this as a function to support the `qs` option -forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { +; +["get","put","post","patch","head","delete"].map(function(method) { createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { options = initParams(uri, options, callback) options.method = method.toUpperCase() @@ -17,19 +17,6 @@ forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) } }) -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 @@ -39,6 +26,7 @@ function initParams(uri, options, callback) { params = {uri:uri} } } else { + // xtend creates a shallow copy of options params = xtend(options, {uri: uri}) } @@ -53,7 +41,7 @@ function createXHR(uri, options, callback) { function _createXHR(options) { if(typeof options.callback === "undefined"){ - throw new Error("callback argument missing") + throw Error("callback argument missing") } var called = false @@ -91,9 +79,6 @@ function _createXHR(options) { function errorFunc(evt) { clearTimeout(timeoutTimer) - if(!(evt instanceof Error)){ - evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ) - } evt.statusCode = 0 return callback(evt, failureResponse) } @@ -103,12 +88,7 @@ function _createXHR(options) { 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) - } + status = xhr.status var response = failureResponse var err = null @@ -121,29 +101,19 @@ function _createXHR(options) { url: uri, rawRequest: xhr } - if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE - response.headers = parseHeaders(xhr.getAllResponseHeaders()) - } + response.headers = parseHeaders(xhr.getAllResponseHeaders()) } else { - err = new Error("Internal XMLHttpRequest Error") + err = 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 xhr = options.xhr || (new createXHR.XMLHttpRequest()) var qsStringifyDefined = isFunction(createXHR.qsSerialize); if (options.qs && !qsStringifyDefined) { - throw new Error("To use the 'qs' option, first define an 'xhr.qsSerialize' function.") + throw Error("To use the 'qs' option, first define an 'xhr.qsSerialize' function.") } var qs = options.qs && qsStringifyDefined ? '?' + createXHR.qsSerialize(options.qs) : '' @@ -166,26 +136,26 @@ function _createXHR(options) { rawRequest: xhr } - if ("json" in options && options.json !== false) { + if (options.json) { 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) + body = JSON.stringify(body) } } 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.onerror = errorFunc.bind({}, Error('Unknown XMLHttpRequest Error')) + xhr.onabort = function(){ aborted = true; } - xhr.ontimeout = errorFunc + + xhr.ontimeout = errorFunc.bind({}, Error('XMLHttpRequest Timeout')) + xhr.open(method, uri, !sync, options.username, options.password) //has to be after open if(!sync) { @@ -197,22 +167,18 @@ function _createXHR(options) { if (!sync && options.timeout > 0 ) { timeoutTimer = setTimeout(function(){ if (aborted) return - aborted = true//IE9 may still call readystatechange + aborted = true //browser may still call readystatechange xhr.abort("timeout") - var e = new Error("XMLHttpRequest timeout") + var e = 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]) - } + 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) { diff --git a/package.json b/package.json index e6ad29f..d0d1912 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "email": "naugtur@gmail.com" }, "dependencies": { - "browserify": "^13.3.0", "global": "~4.3.0", "is-function": "^1.0.1", "parse-headers": "^2.0.0", @@ -35,8 +34,7 @@ }, "devDependencies": { "browser-run": "3.5.0", - "browserify": "^13.1.1", - "for-each": "^0.3.2", + "browserify": "^13.3.0", "pre-commit": "1.2.2", "tap-spec": "^4.0.2", "tape": "^4.0.0" diff --git a/test/index.js b/test/index.js index 823a1ea..7ad1290 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,5 @@ var window = require("global/window") var test = require("tape") -var forEach = require("for-each") var xhr = require("../index.js") @@ -19,8 +18,7 @@ test("constructs and calls callback without throwing", { timeout: 500 }, functio test("[func] Can GET a url (cross-domain)", { timeout: 2000 }, function(assert) { xhr({ - uri: "http://www.mocky.io/v2/55a02cb72651260b1a94f024", - useXDR: true + uri: "http://www.mocky.io/v2/55a02cb72651260b1a94f024" }, function(err, resp, body) { assert.ifError(err, "no err") assert.equal(resp.statusCode, 200) @@ -33,23 +31,19 @@ test("[func] Can GET a url (cross-domain)", { timeout: 2000 }, function(assert) }) test("[func] Returns http error responses like npm's request (cross-domain)", { timeout: 2000 }, function(assert) { - if (!window.XDomainRequest) { - xhr({ - uri: "http://www.mocky.io/v2/55a02d63265126221a94f025", - }, function(err, resp, body) { - assert.ifError(err, "no err") - assert.equal(resp.statusCode, 404) - assert.equal(typeof resp.rawRequest, "object") - assert.end() - }) - } else { - assert.end(); - } + xhr({ + uri: "http://www.mocky.io/v2/55a02d63265126221a94f025" + }, function(err, resp, body) { + assert.ifError(err, "no err") + assert.equal(resp.statusCode, 404) + assert.equal(typeof resp.rawRequest, "object") + assert.end() + }) }) test("[func] Request to domain with not allowed cross-domain", { timeout: 2000 }, function(assert) { xhr({ - uri: "http://www.mocky.io/v2/57bb70c21000002f175850bd", + uri: "http://www.mocky.io/v2/57bb70c21000002f175850bd" }, function(err, resp, body) { assert.ok(err instanceof Error, "should return error") assert.equal(resp.statusCode, 0) @@ -121,57 +115,28 @@ test("[func] Times out to an error ", { timeout: 500 }, function(assert) { }) test("withCredentials option", { timeout: 500 }, function(assert) { - if (!window.XDomainRequest) { - var req = xhr({}, function() {}) - assert.ok(!req.withCredentials, - "withCredentials not true" - ) - req = xhr({ - withCredentials: true - }, function() {}) - assert.ok( - req.withCredentials, - "withCredentials set to true" - ) - } + var req = xhr({}, function() {}) + assert.ok(!req.withCredentials, + "withCredentials not true" + ) + req = xhr({ + withCredentials: true + }, function() {}) + assert.ok( + req.withCredentials, + "withCredentials set to true" + ) assert.end() }) test("withCredentials ignored when using synchronous requests", { timeout: 500 }, function(assert) { - if (!window.XDomainRequest) { - var req = xhr({ - withCredentials: true, - sync: true - }, function() {}) - assert.ok(!req.withCredentials, - "sync overrides withCredentials" - ) - } - assert.end() -}) - -test("XDR usage (run on IE8 or 9)", { timeout: 500 }, function(assert) { - var req = xhr({ - useXDR: true, - uri: window.location.href, - }, function() {}) - - assert.ok(!window.XDomainRequest || window.XDomainRequest === req.constructor, - "Uses XDR when told to" - ) - - - if (!!window.XDomainRequest) { - assert.throws(function() { - xhr({ - useXDR: true, - uri: window.location.href, - headers: { - "foo": "bar" - } - }, function() {}) - }, true, "Throws when trying to send headers with XDR") - } + var req = xhr({ + withCredentials: true, + sync: true + }, function() {}) + assert.ok(!req.withCredentials, + "sync overrides withCredentials" + ) assert.end() }) @@ -194,16 +159,12 @@ test("constructs and calls callback without throwing", { timeout: 500 }, functio assert.end() }) -if (!window.XDomainRequest) { - var methods = ["get", "put", "post", "patch"] -} else { - var methods = ["get", "post"] -} +var methods = ["get", "put", "post", "patch"] test("[func] xhr[method] get, put, post, patch", { timeout: 500 }, function(assert) { var i = 0 - forEach(methods, function(method) { + methods.forEach(function(method) { xhr[method]({ uri: "/mock/200ok" }, function(err, resp, body) { @@ -218,7 +179,7 @@ test("[func] xhr[method] get, put, post, patch", { timeout: 500 }, function(asse test("xhr[method] get, put, post, patch with url shorthands", { timeout: 500 }, function(assert) { var i = 0 - forEach(methods, function(method) { + methods.forEach(function(method) { var req = xhr[method]("/some-test", function() {}) i++ assert.equal(req.method, method.toUpperCase()) @@ -253,23 +214,21 @@ test("[func] doesn't freak out when json option is false", { timeout: 500 }, fun }) }) -test("[func] sends options.json as body when it's not a boolean", { timeout: 500 }, function(assert) { +test("[func] DO NOT send options.json as body when it's not a boolean", { timeout: 500 }, function(assert) { xhr.post("/mock/echo", { json: { foo: "bar" } }, function(err, resp, body) { assert.equal(resp.rawRequest.headers["Content-Type"], "application/json") - assert.deepEqual(body, { - foo: "bar" - }) + assert.equal(body, null) assert.end() }) }) test("xhr[method] get, put, post, patch with url shorthands and options", { timeout: 500 }, function(assert) { var i = 0 - forEach(methods, function(method) { + methods.forEach(function(method) { var req = xhr[method]("/some-test", { headers: { foo: 'bar' @@ -283,44 +242,44 @@ test("xhr[method] get, put, post, patch with url shorthands and options", { time }) }) }) -if (!window.XDomainRequest) { - test("[func] xhr.head", function(assert) { - xhr.head({ - uri: "/mock/200ok", - }, function(err, resp, body) { - assert.ifError(err, "no err") - assert.equal(resp.statusCode, 200) - assert.equal(resp.method, "HEAD") - assert.notOk(resp.body) - assert.end() - }) + +test("[func] xhr.head", function(assert) { + xhr.head({ + uri: "/mock/200ok", + }, function(err, resp, body) { + assert.ifError(err, "no err") + assert.equal(resp.statusCode, 200) + assert.equal(resp.method, "HEAD") + assert.notOk(resp.body) + assert.end() }) +}) - test("xhr.head url shorthand", { timeout: 500 }, function(assert) { - xhr.head("/mock/200ok", function(err, resp, body) { - assert.equal(resp.method, "HEAD") - assert.end() - }) +test("xhr.head url shorthand", { timeout: 500 }, function(assert) { + xhr.head("/mock/200ok", function(err, resp, body) { + assert.equal(resp.method, "HEAD") + assert.end() }) +}) - test("[func] xhr.del", { timeout: 500 }, function(assert) { - xhr.del({ - uri: "/mock/200ok" - }, function(err, resp, body) { - assert.ifError(err, "no err") - assert.equal(resp.statusCode, 200) - assert.equal(resp.method, "DELETE") - assert.end() - }) +test("[func] xhr.del", { timeout: 500 }, function(assert) { + xhr.del({ + uri: "/mock/200ok" + }, function(err, resp, body) { + assert.ifError(err, "no err") + assert.equal(resp.statusCode, 200) + assert.equal(resp.method, "DELETE") + assert.end() }) +}) - test("xhr.del url shorthand", { timeout: 500 }, function(assert) { - xhr.del("/mock/200ok", function(err, resp, body) { - assert.equal(resp.method, "DELETE") - assert.end() - }) +test("xhr.del url shorthand", { timeout: 500 }, function(assert) { + xhr.del("/mock/200ok", function(err, resp, body) { + assert.equal(resp.method, "DELETE") + assert.end() }) -} +}) + test("url signature without object", { timeout: 500 }, function(assert) { xhr("/some-test", function(err, resp, body) { assert.equal(resp.url, '/some-test') @@ -369,20 +328,11 @@ test("XHR can be overridden", { timeout: 500 }, function(assert) { xhrs++ this.open = this.send = noop } - var xdrs = 0 - var fakeXDR = function() { - xdrs++ - this.open = this.send = noop - } + xhr.XMLHttpRequest = fakeXHR xhr({}, function() {}) assert.equal(xhrs, 1, "created the custom XHR") - xhr.XDomainRequest = fakeXDR - xhr({ - useXDR: true - }, function() {}); - assert.equal(xdrs, 1, "created the custom XDR") assert.end() })