superagent
Advanced tools
Comparing version 0.21.0 to 1.0.0
@@ -5,3 +5,3 @@ { | ||
"description": "awesome http requests", | ||
"version": "0.20.0", | ||
"version": "1.0.0", | ||
"keywords": [ | ||
@@ -8,0 +8,0 @@ "http", |
@@ -0,2 +1,15 @@ | ||
1.0.0 / 2015-03-08 | ||
================== | ||
* keep timeouts intact across redirects (hopkinsth) | ||
* handle falsy json values (themaarten) | ||
* fire response events in browser version (Schoonology) | ||
* getXHR exported in client version (KidsKilla) | ||
* remove arity check on `.end()` callbacks (defunctzombie) | ||
* avoid setting content-type for host objects (rexxars) | ||
* don't index array strings in querystring (travisjeffery) | ||
* fix pipe() with redirects (cyrilis) | ||
* add xhr2 file download (vstirbu) | ||
* set default response type to text/plain if not specified (warrenseine) | ||
0.21.0 / 2014-11-11 | ||
@@ -3,0 +16,0 @@ ================== |
@@ -50,3 +50,3 @@ /** | ||
function getXHR() { | ||
request.getXHR = function () { | ||
if (root.XMLHttpRequest | ||
@@ -62,3 +62,3 @@ && ('file:' != root.location.protocol || !root.ActiveXObject)) { | ||
return false; | ||
} | ||
}; | ||
@@ -299,5 +299,7 @@ /** | ||
this.xhr = this.req.xhr; | ||
this.text = this.req.method !='HEAD' | ||
? this.xhr.responseText | ||
// responseText is accessible only if responseType is '' or 'text' | ||
this.text = (this.req.method !='HEAD' && (this.xhr.responseType === '' || this.xhr.responseType === 'text')) | ||
? this.xhr.responseText | ||
: null; | ||
this.statusText = this.req.xhr.statusText; | ||
this.setStatusProperties(this.xhr.status); | ||
@@ -311,3 +313,3 @@ this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders()); | ||
this.body = this.req.method != 'HEAD' | ||
? this.parseBody(this.text) | ||
? this.parseBody(this.text ? this.text : this.xhr.response) | ||
: null; | ||
@@ -363,3 +365,3 @@ } | ||
var parse = request.parse[this.type]; | ||
return parse && str && str.length | ||
return parse && str && (str.length || str instanceof Object) | ||
? parse(str) | ||
@@ -464,3 +466,3 @@ : null; | ||
try { | ||
res = new Response(self); | ||
res = new Response(self); | ||
} catch(e) { | ||
@@ -472,3 +474,18 @@ err = new Error('Parser is unable to parse the response'); | ||
self.callback(err, res); | ||
if (res) { | ||
self.emit('response', res); | ||
} | ||
if (res && res.status >= 200 && res.status < 300) { | ||
return self.callback(err, res); | ||
} | ||
var msg = 'Unsuccessful HTTP response'; | ||
if (res) { | ||
msg = res.statusText || msg; | ||
} | ||
var new_err = new Error(msg); | ||
new_err.original = err; | ||
self.callback(err || new_err, res); | ||
}); | ||
@@ -702,3 +719,3 @@ } | ||
Request.prototype.field = function(name, val){ | ||
if (!this._formData) this._formData = new FormData(); | ||
if (!this._formData) this._formData = new root.FormData(); | ||
this._formData.append(name, val); | ||
@@ -726,3 +743,3 @@ return this; | ||
Request.prototype.attach = function(field, file, filename){ | ||
if (!this._formData) this._formData = new FormData(); | ||
if (!this._formData) this._formData = new root.FormData(); | ||
this._formData.append(field, file, filename); | ||
@@ -806,3 +823,3 @@ return this; | ||
if (!obj) return this; | ||
if (!obj || isHost(data)) return this; | ||
if (!type) this.type('json'); | ||
@@ -824,5 +841,3 @@ return this; | ||
this.clearTimeout(); | ||
if (2 == fn.length) return fn(err, res); | ||
if (err) return this.emit('error', err); | ||
fn(res); | ||
fn(err, res); | ||
}; | ||
@@ -882,3 +897,3 @@ | ||
var self = this; | ||
var xhr = this.xhr = getXHR(); | ||
var xhr = this.xhr = request.getXHR(); | ||
var query = this._query.join('&'); | ||
@@ -894,4 +909,11 @@ var timeout = this._timeout; | ||
if (4 != xhr.readyState) return; | ||
if (0 == xhr.status) { | ||
if (self.aborted) return self.timeoutError(); | ||
// In IE9, reads to any property (e.g. status) off of an aborted XHR will | ||
// result in the error "Could not complete the operation due to error c00c023f" | ||
var status; | ||
try { status = xhr.status } catch(e) { status = 0; } | ||
if (0 == status) { | ||
if (self.timedout) return self.timeoutError(); | ||
if (self.aborted) return; | ||
return self.crossDomainError(); | ||
@@ -903,7 +925,13 @@ } | ||
// progress | ||
if (xhr.upload) { | ||
xhr.upload.onprogress = function(e){ | ||
e.percent = e.loaded / e.total * 100; | ||
self.emit('progress', e); | ||
}; | ||
try { | ||
if (xhr.upload && this.hasListeners('progress')) { | ||
xhr.upload.onprogress = function(e){ | ||
e.percent = e.loaded / e.total * 100; | ||
self.emit('progress', e); | ||
}; | ||
} | ||
} catch(e) { | ||
// Accessing xhr.upload fails in IE from a web worker, so just pretend it doesn't exist. | ||
// Reported here: | ||
// https://connect.microsoft.com/IE/feedback/details/837245/xmlhttprequest-upload-throws-invalid-argument-when-used-from-web-worker-context | ||
} | ||
@@ -914,2 +942,3 @@ | ||
this._timer = setTimeout(function(){ | ||
self.timedout = true; | ||
self.abort(); | ||
@@ -916,0 +945,0 @@ }, timeout); |
@@ -12,2 +12,3 @@ | ||
var format = require('url').format; | ||
var resolve = require('url').resolve; | ||
var methods = require('methods'); | ||
@@ -133,2 +134,3 @@ var Stream = require('stream'); | ||
this.qs = {}; | ||
this.qsRaw = []; | ||
this._redirectList = []; | ||
@@ -198,7 +200,7 @@ this.on('end', this.clearTimeout.bind(this)); | ||
if ('string' == typeof file) { | ||
filename = file; | ||
debug('creating `fs.ReadStream` instance for file: %s', filename); | ||
file = fs.createReadStream(filename); | ||
if (!filename) filename = file; | ||
debug('creating `fs.ReadStream` instance for file: %s', file); | ||
file = fs.createReadStream(file); | ||
} | ||
this._formData.append(field, file, filename); | ||
this._formData.append(field, file, { filename: filename }); | ||
return this; | ||
@@ -386,11 +388,5 @@ }; | ||
Request.prototype.query = function(val){ | ||
var obj = {}; | ||
if ('string' == typeof val) { | ||
var elements = val.split('&'); | ||
for (var i = 0; i < elements.length; i++) { | ||
var parts = elements[i].split('='); | ||
obj[parts[0]] = parts[1]; | ||
} | ||
return this.query(obj); | ||
this.qsRaw.push(val); | ||
return this; | ||
} | ||
@@ -503,3 +499,10 @@ | ||
this.buffer(false); | ||
var self = this; | ||
this.end().req.on('response', function(res){ | ||
// redirect | ||
var redirect = isRedirect(res.statusCode); | ||
if (redirect && self._redirects++ != self._maxRedirects) { | ||
return self.redirect(res).pipe(stream, options); | ||
} | ||
if (/^(deflate|gzip)$/.test(res.headers['content-encoding'])) { | ||
@@ -510,2 +513,5 @@ res.pipe(zlib.createUnzip()).pipe(stream, options); | ||
} | ||
res.on('end', function(){ | ||
self.emit('end'); | ||
}); | ||
}); | ||
@@ -594,11 +600,10 @@ return stream; | ||
var url = res.headers.location; | ||
if (!url) { | ||
return this.callback(new Error('No location header for redirect'), res); | ||
} | ||
debug('redirect %s -> %s', this.url, url); | ||
// location | ||
if (!~url.indexOf('://')) { | ||
if (0 != url.indexOf('//')) { | ||
url = '//' + this.host + url; | ||
} | ||
url = this.protocol + url; | ||
} | ||
url = resolve(this.url, url); | ||
@@ -609,19 +614,35 @@ // ensure the response is being consumed | ||
// strip Content-* related fields | ||
// in case of POST etc | ||
var header = utils.cleanHeader(this.req._headers); | ||
var headers = this.req._headers; | ||
// implementation of 302 following defacto standard | ||
if (res.statusCode == 301 || res.statusCode == 302){ | ||
// strip Content-* related fields | ||
// in case of POST etc | ||
headers = utils.cleanHeader(this.req._headers); | ||
// force GET | ||
this.method = 'HEAD' == this.method | ||
? 'HEAD' | ||
: 'GET'; | ||
// clear data | ||
this._data = null; | ||
} | ||
// 303 is always GET | ||
if (res.statusCode == 303) { | ||
// force method | ||
this.method = 'GET'; | ||
// clear data | ||
this._data = null; | ||
} | ||
// 307 preserves method | ||
delete this.req; | ||
// force GET | ||
this.method = 'HEAD' == this.method | ||
? 'HEAD' | ||
: 'GET'; | ||
// redirect | ||
this._data = null; | ||
this.url = url; | ||
this._redirectList.push(url); | ||
this.clearTimeout(); | ||
this.emit('redirect', res); | ||
this.set(header); | ||
this.qs = {}; | ||
this.set(headers); | ||
this.end(this._callback); | ||
@@ -754,5 +775,18 @@ return this; | ||
this.called = true; | ||
if (2 == fn.length) return fn(err, res); | ||
if (err) return this.emit('error', err); | ||
fn(res); | ||
// only emit error event if there is a listener | ||
// otherwise we assume the callback to `.end()` will get the error | ||
if (err && this.listeners('error').length > 0) this.emit('error', err); | ||
if (res && res.status >= 200 && res.status < 300) { | ||
return fn(err, res); | ||
} | ||
var msg = 'Unsuccessful HTTP response'; | ||
if (res) { | ||
msg = http.STATUS_CODES[res.status] || msg; | ||
} | ||
var new_err = new Error(msg); | ||
new_err.original = err; | ||
fn(err || new_err, res); | ||
}; | ||
@@ -783,3 +817,4 @@ | ||
try { | ||
var querystring = qs.stringify(this.qs); | ||
var querystring = qs.stringify(this.qs, { indices: false }); | ||
querystring += ((querystring.length && this.qsRaw.length) ? '&' : '') + this.qsRaw.join('&'); | ||
req.path += querystring.length | ||
@@ -798,2 +833,3 @@ ? (~req.path.indexOf('?') ? '&' : '?') + querystring | ||
err.timeout = timeout; | ||
err.code = 'ECONNABORTED'; | ||
self.abort(); | ||
@@ -825,3 +861,3 @@ self.callback(err); | ||
var max = self._maxRedirects; | ||
var mime = utils.type(res.headers['content-type'] || ''); | ||
var mime = utils.type(res.headers['content-type'] || '') || 'text/plain'; | ||
var len = res.headers['content-length']; | ||
@@ -847,5 +883,2 @@ var type = mime.split('/'); | ||
if (self.piped) { | ||
res.on('end', function(){ | ||
self.emit('end'); | ||
}); | ||
return; | ||
@@ -898,6 +931,4 @@ } | ||
// by default only buffer text/*, json | ||
// and messed up thing from hell | ||
var text = isText(mime); | ||
if (null == buffer && text) buffer = true; | ||
// by default only buffer text/*, json and messed up thing from hell | ||
if (null == buffer && isText(mime) || isJSON(mime)) buffer = true; | ||
@@ -909,2 +940,5 @@ // parser | ||
// everyone wants their own white-labeled json | ||
if (isJSON(mime)) parse = exports.parse['application/json']; | ||
// buffered response | ||
@@ -923,3 +957,3 @@ if (buffer) parse = parse || exports.parse.text; | ||
}); | ||
} catch(err) { | ||
} catch (err) { | ||
self.callback(err); | ||
@@ -1045,4 +1079,6 @@ return; | ||
method = method.toUpperCase(); | ||
request[name] = function(url, fn){ | ||
request[name] = function(url, data, fn){ | ||
var req = request(method, url); | ||
if ('function' == typeof data) fn = data, data = null; | ||
if (data) req.send(data); | ||
fn && req.end(fn); | ||
@@ -1067,3 +1103,2 @@ return req; | ||
return 'text' == type | ||
|| 'json' == subtype | ||
|| 'x-www-form-urlencoded' == subtype; | ||
@@ -1089,2 +1124,18 @@ } | ||
/** | ||
* Check if `mime` is json or has +json structured syntax suffix. | ||
* | ||
* @param {String} mime | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
function isJSON(mime) { | ||
if (!mime) return false; | ||
var parts = mime.split('/'); | ||
var type = parts[0]; | ||
var subtype = parts[1]; | ||
return subtype && subtype.match(/json/i); | ||
} | ||
/** | ||
* Check if we should follow the redirect `code`. | ||
@@ -1091,0 +1142,0 @@ * |
module.exports = function(res, fn){ | ||
module.exports = function parseJSON(res, fn){ | ||
res.text = ''; | ||
res.setEncoding('utf8'); | ||
res.on('data', function(chunk){ res.text += chunk; }); | ||
res.on('data', function(chunk){ res.text += chunk;}); | ||
res.on('end', function(){ | ||
@@ -7,0 +7,0 @@ try { |
@@ -38,3 +38,3 @@ | ||
this.text = res.text; | ||
this.body = res.body || {}; | ||
this.body = res.body !== undefined ? res.body : {}; | ||
this.files = res.files || {}; | ||
@@ -41,0 +41,0 @@ this.buffered = 'string' == typeof this.text; |
@@ -19,18 +19,2 @@ | ||
/** | ||
* Generate a UID with the given `len`. | ||
* | ||
* @param {Number} len | ||
* @return {String} | ||
* @api private | ||
*/ | ||
exports.uid = function(len){ | ||
var buf = ''; | ||
var chars = 'abcdefghijklmnopqrstuvwxyz123456789'; | ||
var nchars = chars.length; | ||
while (len--) buf += chars[Math.random() * nchars | 0]; | ||
return buf; | ||
}; | ||
/** | ||
* Return the mime type for the given `str`. | ||
@@ -37,0 +21,0 @@ * |
{ | ||
"name": "superagent", | ||
"version": "0.21.0", | ||
"version": "1.0.0", | ||
"description": "elegant & feature rich browser / node HTTP with a fluent API", | ||
@@ -23,3 +23,3 @@ "scripts": { | ||
"dependencies": { | ||
"qs": "1.2.0", | ||
"qs": "2.3.3", | ||
"formidable": "1.0.14", | ||
@@ -45,3 +45,5 @@ "mime": "1.2.11", | ||
"should": "3.1.3", | ||
"zuul": "~1.6.0" | ||
"zuul": "~1.19.0", | ||
"browserify": "~6.3.2", | ||
"Base64": "0.3.0" | ||
}, | ||
@@ -51,3 +53,4 @@ "browser": { | ||
"emitter": "component-emitter", | ||
"reduce": "reduce-component" | ||
"reduce": "reduce-component", | ||
"./test/support/server.js": "./test/support/blank.js" | ||
}, | ||
@@ -54,0 +57,0 @@ "component": { |
111
Readme.md
@@ -1,5 +0,7 @@ | ||
# SuperAgent | ||
# SuperAgent [![Build Status](https://travis-ci.org/visionmedia/superagent.svg?branch=master)](https://travis-ci.org/visionmedia/superagent) | ||
SuperAgent is a small progressive __client-side__ HTTP request library, and __Node.js__ module with the same API, sporting many high-level HTTP client features. View the [docs](http://visionmedia.github.com/superagent/). | ||
[![Sauce Test Status](https://saucelabs.com/browser-matrix/shtylman-superagent.svg)](https://saucelabs.com/u/shtylman-superagent) | ||
SuperAgent is a small progressive __client-side__ HTTP request library, and __Node.js__ module with the same API, sporting many high-level HTTP client features. View the [docs](http://visionmedia.github.com/superagent/). | ||
![super agent](http://f.cl.ly/items/3d282n3A0h0Z0K2w0q2a/Screenshot.png) | ||
@@ -9,3 +11,3 @@ | ||
node: | ||
node: | ||
@@ -16,3 +18,3 @@ ``` | ||
component: | ||
component: | ||
@@ -23,44 +25,5 @@ ``` | ||
with script tags use ./superagent.js | ||
Works with [browserify](https://github.com/substack/node-browserify) and should work with [webpack](https://github.com/visionmedia/superagent/wiki/Superagent-for-Webpack) | ||
## Motivation | ||
This library spawned from my frustration with jQuery's weak & inconsistent Ajax support. jQuery's API, while having recently added some promise-like support, is largely static, forcing you to build up big objects containing all the header fields and options, not to mention most of the options are awkwardly named "type" instead of "method", etc. Onto examples! | ||
The following is what you might typically do for a simple __GET__ with jQuery: | ||
```js | ||
$.get('/user/1', function(data, textStatus, xhr){ | ||
}); | ||
``` | ||
Great, it's ok, but it's kinda lame having 3 arguments just to access something on the `xhr`. Our equivalent would be: | ||
```js | ||
request.get('/user/1', function(res){ | ||
}); | ||
``` | ||
The response object is an instanceof `request.Response`, encapsulating all of this information instead of throwing a bunch of arguments at you. For example, we can check `res.status`, `res.header` for header fields, `res.text`, `res.body` etc. | ||
An example of a JSON POST with jQuery typically might use `$.post()`, however once you need to start defining header fields you have to then re-write it using `$.ajax()`... so that might look like: | ||
```js | ||
$.ajax({ | ||
url: '/api/pet', | ||
type: 'POST', | ||
data: { name: 'Manny', species: 'cat' }, | ||
headers: { 'X-API-Key': 'foobar' } | ||
}).success(function(res){ | ||
}).error(function(){ | ||
}); | ||
``` | ||
Not only is it ugly, but it's pretty opinionated. jQuery likes to special-case {4,5}xx- for example, you cannot (easily at least) receive a parsed JSON response for say "400 Bad Request". This same request would look like this: | ||
```js | ||
request | ||
@@ -71,3 +34,3 @@ .post('/api/pet') | ||
.set('Accept', 'application/json') | ||
.end(function(error, res){ | ||
.end(function(err, res){ | ||
@@ -77,13 +40,18 @@ }); | ||
Building on the existing API internally, we also provide something similar to `$.post()` for those times in life where your interactions are very basic: | ||
## Supported browsers | ||
```js | ||
request.post('/api/pet', cat, function(error, res){ | ||
Tested browsers: | ||
}); | ||
``` | ||
- Latest Android | ||
- Latest Firefox | ||
- Latest Chrome | ||
- IE9 through latest | ||
- Latest iPhone | ||
- Latest Safari | ||
Even though IE9 is supported, a polyfill `window.btoa` is needed to use basic auth. | ||
# Plugins | ||
Usage: | ||
Superagent is easily extended via plugins. | ||
@@ -109,3 +77,3 @@ ```js | ||
Please prefix your plugin component with `superagent-*` | ||
Please prefix your plugin with `superagent-*` so that it can easily be found by others. | ||
@@ -116,34 +84,33 @@ For superagent extensions such as couchdb and oauth visit the [wiki](https://github.com/visionmedia/superagent/wiki). | ||
Install dependencies: | ||
Install dependencies: | ||
$ npm install | ||
```shell | ||
$ npm install | ||
``` | ||
Run em! | ||
Run em! | ||
```shell | ||
$ make test | ||
``` | ||
$ make test | ||
## Running browser tests | ||
Install the test server deps (nodejs / express): | ||
Install dependencies: | ||
$ npm install | ||
```shell | ||
$ npm install | ||
``` | ||
Start the test server: | ||
Start the test runner: | ||
$ make test-server | ||
```shell | ||
$ make test-browser-local | ||
``` | ||
Visit `localhost:4000/` in the browser. | ||
Visit `http://localhost:4000/__zuul` in your browser. | ||
## Browser build | ||
Edit tests and refresh your browser. You do not have to restart the test runner. | ||
The browser build of superagent is located in ./superagent.js | ||
## Examples: | ||
- [agency tests](https://github.com/visionmedia/superagent/blob/master/test/node/agency.js) | ||
- [express demo app](https://github.com/hunterloftis/component-test/blob/master/lib/users/test/controller.test.js) | ||
## License | ||
MIT | ||
MIT |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
1
79785
11
21
2497
110
3