superagent 3.7.0 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/History.md CHANGED
@@ -1,3 +1,15 @@
1
+ # 3.8.1 (2017-11-08)
2
+
3
+ * Clear authorization header on cross-domain redirect
4
+
5
+ # 3.8.0
6
+
7
+ * Added support for "globally" defined headers and event handlers via `superagent.agent()`. It now remembers default settings for all its requests.
8
+ * Added optional callback to `.retry()` (Alexander Murphy)
9
+ * Unified auth args handling in node/browser (Edmundo Alvarez)
10
+ * Fixed error handling in zlib pipes (Kornel)
11
+ * Documented that 3xx status codes are errors (Mickey Reiss)
12
+
1
13
  # 3.7.0 (2017-10-17)
2
14
 
3
15
  * Limit maximum response size. Prevents zip bombs (Kornel)
package/docs/index.md CHANGED
@@ -211,13 +211,17 @@ SuperAgent will automatically serialize JSON and forms. If you want to send the
211
211
 
212
212
  ## Retrying requests
213
213
 
214
- When given the `.retry()` method, SuperAgent will automatically retry requests, if they fail in a way that is transient or could be due to a flaky Internet connection. `.retry()` takes an optional argument which is the maximum number of times to retry failed requests; the default is 3 times.
214
+ When given the `.retry()` method, SuperAgent will automatically retry requests, if they fail in a way that is transient or could be due to a flaky Internet connection.
215
+
216
+ This method has two optional arguments: number of retries (default 3) and a callback. It calls `callback(err, res)` before each retry. The callback may return `true`/`false` to control whether the request sould be retried (but the maximum number of retries is always applied).
215
217
 
216
218
  request
217
219
  .get('http://example.com/search')
218
220
  .retry(2)
219
221
  .end(callback);
220
222
 
223
+ Use `.retry()` only with requests that are *idempotent* (i.e. multiple requests reaching the server won't cause undesirable side effects like duplicate purchases).
224
+
221
225
  ## Setting Accept
222
226
 
223
227
  In a similar fashion to the `.type()` method it is also possible to set the `Accept` header via the short hand method `.accept()`. Which references `request.types` as well allowing you to specify either the full canonicalized MIME type name as `type/subtype`, or the extension suffix form as "xml", "json", "png", etc. for convenience:
@@ -443,7 +447,9 @@ By default up to 5 redirects will be followed, however you may specify this with
443
447
  .redirects(2)
444
448
  .end(callback);
445
449
 
446
- ## Preserving cookies
450
+ ## Agents for global state
451
+
452
+ ### Saving cookies
447
453
 
448
454
  In Node SuperAgent does not save cookies by default, but you can use the `.agent()` method to create a copy of SuperAgent that saves cookies. Each copy has a separate cookie jar.
449
455
 
@@ -454,7 +460,18 @@ In Node SuperAgent does not save cookies by default, but you can use the `.agent
454
460
  return agent.get('/cookied-page');
455
461
  });
456
462
 
457
- In browsers cookies are managed automatically by the browser, and there is no `.agent()` method.
463
+ In browsers cookies are managed automatically by the browser, so the `.agent()` does not isolate cookies.
464
+
465
+ ### Default options for multiple requests
466
+
467
+ Regular request methods (`.use()`, `.set()`, `.auth()`) called on the agent will be used as defaults for all requests made by that agent.
468
+
469
+ const agent = request.agent()
470
+ .use(plugin)
471
+ .auth(shared);
472
+
473
+ await agent.get('/with-plugin-and-auth');
474
+ await agent.get('/also-with-plugin-and-auth');
458
475
 
459
476
  ## Piping data
460
477
 
@@ -467,7 +484,7 @@ The Node client allows you to pipe data to and from the request. For example pip
467
484
  const req = request.post('/somewhere');
468
485
  req.type('json');
469
486
  stream.pipe(req);
470
-
487
+
471
488
  Note that when you pipe to a request, superagent sends the piped data with [chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding), which isn't supported by all servers (for instance, Python WSGI servers).
472
489
 
473
490
  Or piping the response to a file:
@@ -476,6 +493,22 @@ Or piping the response to a file:
476
493
  const req = request.get('/some.json');
477
494
  req.pipe(stream);
478
495
 
496
+ Note that you should **NOT** attempt to pipe the result of `.end()` or the `Response` object:
497
+
498
+ // Don't do either of these:
499
+ const stream = getAWritableStream();
500
+ const req = request
501
+ .get('/some.json')
502
+ // this pipes garbage to the stream and fails in unexpected ways
503
+ .end((err, response) => response.pipe(stream))
504
+ const req = request
505
+ .get('/some.json')
506
+ .end()
507
+ // this is also unsupported, .pipe calls .end for you.
508
+ .pipe(stream);
509
+
510
+ In a [future version](https://github.com/visionmedia/superagent/issues/1188) of superagent, improper calls to `pipe()` will fail.
511
+
479
512
  ## Multipart requests
480
513
 
481
514
  SuperAgent is also great for _building_ multipart requests for which it provides methods `.attach()` and `.field()`.
@@ -556,7 +589,7 @@ An "error" event is also emitted, with you can listen for:
556
589
 
557
590
  });
558
591
 
559
- Note that a 4xx or 5xx response with super agent **are** considered an error by default. For example if you get a 500 or 403 response, this status information will be available via `err.status`. Errors from such responses also contain an `err.response` field with all of the properties mentioned in "[Response properties](#response-properties)". The library behaves in this way to handle the common case of wanting success responses and treating HTTP error status codes as errors while still allowing for custom logic around specific error conditions.
592
+ Note that **superagent considers 4xx and 5xx responses (as well as unhandled 3xx responses) errors by default**. For example, if you get a `304 Not modified`, `403 Forbidden` or `500 Internal server error` response, this status information will be available via `err.status`. Errors from such responses also contain an `err.response` field with all of the properties mentioned in "[Response properties](#response-properties)". The library behaves in this way to handle the common case of wanting success responses and treating HTTP error status codes as errors while still allowing for custom logic around specific error conditions.
560
593
 
561
594
  Network failures, timeouts, and other errors that produce no response will contain no `err.status` or `err.response` fields.
562
595
 
@@ -0,0 +1,20 @@
1
+ function Agent() {
2
+ this._defaults = [];
3
+ }
4
+
5
+ ["use", "on", "once", "set", "query", "type", "accept", "auth", "withCredentials", "sortQuery", "retry", "ok", "redirects",
6
+ "timeout", "buffer", "serialize", "parse", "ca", "key", "pfx", "cert"].forEach(function(fn) {
7
+ /** Default setting for all requests from this agent */
8
+ Agent.prototype[fn] = function(/*varargs*/) {
9
+ this._defaults.push({fn:fn, arguments:arguments});
10
+ return this;
11
+ }
12
+ });
13
+
14
+ Agent.prototype._setDefaults = function(req) {
15
+ this._defaults.forEach(function(def) {
16
+ req[def.fn].apply(req, def.arguments);
17
+ });
18
+ };
19
+
20
+ module.exports = Agent;
package/lib/client.js CHANGED
@@ -16,7 +16,7 @@ var Emitter = require('component-emitter');
16
16
  var RequestBase = require('./request-base');
17
17
  var isObject = require('./is-object');
18
18
  var ResponseBase = require('./response-base');
19
- var shouldRetry = require('./should-retry');
19
+ var Agent = require('./agent-base');
20
20
 
21
21
  /**
22
22
  * Noop.
@@ -123,9 +123,9 @@ function pushEncodedKeyValuePair(pairs, key, val) {
123
123
  * Expose serialization method.
124
124
  */
125
125
 
126
- request.serializeObject = serialize;
126
+ request.serializeObject = serialize;
127
127
 
128
- /**
128
+ /**
129
129
  * Parse the given x-www-form-urlencoded `str`.
130
130
  *
131
131
  * @param {String} str
@@ -184,12 +184,12 @@ request.types = {
184
184
  *
185
185
  */
186
186
 
187
- request.serialize = {
188
- 'application/x-www-form-urlencoded': serialize,
189
- 'application/json': JSON.stringify
190
- };
187
+ request.serialize = {
188
+ 'application/x-www-form-urlencoded': serialize,
189
+ 'application/json': JSON.stringify,
190
+ };
191
191
 
192
- /**
192
+ /**
193
193
  * Default parsers.
194
194
  *
195
195
  * superagent.parse['application/xml'] = function(str){
@@ -200,7 +200,7 @@ request.types = {
200
200
 
201
201
  request.parse = {
202
202
  'application/x-www-form-urlencoded': parseString,
203
- 'application/json': JSON.parse
203
+ 'application/json': JSON.parse,
204
204
  };
205
205
 
206
206
  /**
@@ -243,7 +243,9 @@ function parseHeader(str) {
243
243
  */
244
244
 
245
245
  function isJSON(mime) {
246
- return /[\/+]json\b/.test(mime);
246
+ // should match /json or +json
247
+ // but not /json-seq
248
+ return /[\/+]json($|[^-\w])/.test(mime);
247
249
  }
248
250
 
249
251
  /**
@@ -303,7 +305,7 @@ function Response(req) {
303
305
  var status = this.xhr.status;
304
306
  // handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
305
307
  if (status === 1223) {
306
- status = 204;
308
+ status = 204;
307
309
  }
308
310
  this._setStatusProperties(status);
309
311
  this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders());
@@ -335,9 +337,9 @@ ResponseBase(Response.prototype);
335
337
  * @api private
336
338
  */
337
339
 
338
- Response.prototype._parseBody = function(str){
340
+ Response.prototype._parseBody = function(str) {
339
341
  var parse = request.parse[this.type];
340
- if(this.req._parser) {
342
+ if (this.req._parser) {
341
343
  return this.req._parser(this, str);
342
344
  }
343
345
  if (!parse && isJSON(this.type)) {
@@ -508,30 +510,25 @@ Request.prototype.accept = function(type){
508
510
  */
509
511
 
510
512
  Request.prototype.auth = function(user, pass, options){
511
- if (typeof pass === 'object' && pass !== null) { // pass is optional and can substitute for options
513
+ if (1 === arguments.length) pass = '';
514
+ if (typeof pass === 'object' && pass !== null) { // pass is optional and can be replaced with options
512
515
  options = pass;
516
+ pass = '';
513
517
  }
514
518
  if (!options) {
515
519
  options = {
516
520
  type: 'function' === typeof btoa ? 'basic' : 'auto',
517
- }
521
+ };
518
522
  }
519
523
 
520
- switch (options.type) {
521
- case 'basic':
522
- this.set('Authorization', 'Basic ' + btoa(user + ':' + pass));
523
- break;
524
-
525
- case 'auto':
526
- this.username = user;
527
- this.password = pass;
528
- break;
524
+ var encoder = function(string) {
525
+ if ('function' === typeof btoa) {
526
+ return btoa(string);
527
+ }
528
+ throw new Error('Cannot use basic auth, btoa is not a function');
529
+ };
529
530
 
530
- case 'bearer': // usage would be .auth(accessToken, { type: 'bearer' })
531
- this.set('Authorization', 'Bearer ' + user);
532
- break;
533
- }
534
- return this;
531
+ return this._auth(user, pass, options, encoder);
535
532
  };
536
533
 
537
534
  /**
@@ -599,8 +596,7 @@ Request.prototype._getFormData = function(){
599
596
  */
600
597
 
601
598
  Request.prototype.callback = function(err, res){
602
- // console.log(this._retries, this._maxRetries)
603
- if (this._maxRetries && this._retries++ < this._maxRetries && shouldRetry(err, res)) {
599
+ if (this._shouldRetry(err, res)) {
604
600
  return this._retry();
605
601
  }
606
602
 
@@ -682,7 +678,7 @@ Request.prototype.end = function(fn){
682
678
 
683
679
  Request.prototype._end = function() {
684
680
  var self = this;
685
- var xhr = this.xhr = request.getXHR();
681
+ var xhr = (this.xhr = request.getXHR());
686
682
  var data = this._formData || this._data;
687
683
 
688
684
  this._setTimeouts();
@@ -716,7 +712,7 @@ Request.prototype._end = function() {
716
712
  }
717
713
  e.direction = direction;
718
714
  self.emit('progress', e);
719
- }
715
+ };
720
716
  if (this.hasListeners('progress')) {
721
717
  try {
722
718
  xhr.onprogress = handleProgress.bind(null, 'download');
@@ -777,6 +773,23 @@ Request.prototype._end = function() {
777
773
  return this;
778
774
  };
779
775
 
776
+ request.agent = function() {
777
+ return new Agent();
778
+ };
779
+
780
+ ["GET", "POST", "OPTIONS", "PATCH", "PUT", "DELETE"].forEach(function(method) {
781
+ Agent.prototype[method.toLowerCase()] = function(url, fn) {
782
+ var req = new request.Request(method, url);
783
+ this._setDefaults(req);
784
+ if (fn) {
785
+ req.end(fn);
786
+ }
787
+ return req;
788
+ };
789
+ });
790
+
791
+ Agent.prototype.del = Agent.prototype['delete'];
792
+
780
793
  /**
781
794
  * GET `url` with optional callback `fn(res)`.
782
795
  *
@@ -787,9 +800,9 @@ Request.prototype._end = function() {
787
800
  * @api public
788
801
  */
789
802
 
790
- request.get = function(url, data, fn){
803
+ request.get = function(url, data, fn) {
791
804
  var req = request('GET', url);
792
- if ('function' == typeof data) fn = data, data = null;
805
+ if ('function' == typeof data) (fn = data), (data = null);
793
806
  if (data) req.query(data);
794
807
  if (fn) req.end(fn);
795
808
  return req;
@@ -805,9 +818,9 @@ request.get = function(url, data, fn){
805
818
  * @api public
806
819
  */
807
820
 
808
- request.head = function(url, data, fn){
821
+ request.head = function(url, data, fn) {
809
822
  var req = request('HEAD', url);
810
- if ('function' == typeof data) fn = data, data = null;
823
+ if ('function' == typeof data) (fn = data), (data = null);
811
824
  if (data) req.query(data);
812
825
  if (fn) req.end(fn);
813
826
  return req;
@@ -823,9 +836,9 @@ request.head = function(url, data, fn){
823
836
  * @api public
824
837
  */
825
838
 
826
- request.options = function(url, data, fn){
839
+ request.options = function(url, data, fn) {
827
840
  var req = request('OPTIONS', url);
828
- if ('function' == typeof data) fn = data, data = null;
841
+ if ('function' == typeof data) (fn = data), (data = null);
829
842
  if (data) req.send(data);
830
843
  if (fn) req.end(fn);
831
844
  return req;
@@ -841,13 +854,13 @@ request.options = function(url, data, fn){
841
854
  * @api public
842
855
  */
843
856
 
844
- function del(url, data, fn){
857
+ function del(url, data, fn) {
845
858
  var req = request('DELETE', url);
846
- if ('function' == typeof data) fn = data, data = null;
859
+ if ('function' == typeof data) (fn = data), (data = null);
847
860
  if (data) req.send(data);
848
861
  if (fn) req.end(fn);
849
862
  return req;
850
- };
863
+ }
851
864
 
852
865
  request['del'] = del;
853
866
  request['delete'] = del;
@@ -862,9 +875,9 @@ request['delete'] = del;
862
875
  * @api public
863
876
  */
864
877
 
865
- request.patch = function(url, data, fn){
878
+ request.patch = function(url, data, fn) {
866
879
  var req = request('PATCH', url);
867
- if ('function' == typeof data) fn = data, data = null;
880
+ if ('function' == typeof data) (fn = data), (data = null);
868
881
  if (data) req.send(data);
869
882
  if (fn) req.end(fn);
870
883
  return req;
@@ -880,9 +893,9 @@ request.patch = function(url, data, fn){
880
893
  * @api public
881
894
  */
882
895
 
883
- request.post = function(url, data, fn){
896
+ request.post = function(url, data, fn) {
884
897
  var req = request('POST', url);
885
- if ('function' == typeof data) fn = data, data = null;
898
+ if ('function' == typeof data) (fn = data), (data = null);
886
899
  if (data) req.send(data);
887
900
  if (fn) req.end(fn);
888
901
  return req;
@@ -898,9 +911,9 @@ request.post = function(url, data, fn){
898
911
  * @api public
899
912
  */
900
913
 
901
- request.put = function(url, data, fn){
914
+ request.put = function(url, data, fn) {
902
915
  var req = request('PUT', url);
903
- if ('function' == typeof data) fn = data, data = null;
916
+ if ('function' == typeof data) (fn = data), (data = null);
904
917
  if (data) req.send(data);
905
918
  if (fn) req.end(fn);
906
919
  return req;
package/lib/node/agent.js CHANGED
@@ -4,11 +4,12 @@
4
4
  * Module dependencies.
5
5
  */
6
6
 
7
- var CookieJar = require('cookiejar').CookieJar;
8
- var CookieAccess = require('cookiejar').CookieAccessInfo;
9
- var parse = require('url').parse;
10
- var request = require('../..');
11
- var methods = require('methods');
7
+ const CookieJar = require('cookiejar').CookieJar;
8
+ const CookieAccess = require('cookiejar').CookieAccessInfo;
9
+ const parse = require('url').parse;
10
+ const request = require('../..');
11
+ const AgentBase = require('../agent-base');
12
+ let methods = require('methods');
12
13
 
13
14
  /**
14
15
  * Expose `Agent`.
@@ -23,16 +24,22 @@ module.exports = Agent;
23
24
  */
24
25
 
25
26
  function Agent(options) {
26
- if (!(this instanceof Agent)) return new Agent(options);
27
+ if (!(this instanceof Agent)) {
28
+ return new Agent(options);
29
+ }
30
+ AgentBase.call(this);
31
+ this.jar = new CookieJar();
32
+
27
33
  if (options) {
28
- this._ca = options.ca;
29
- this._key = options.key;
30
- this._pfx = options.pfx;
31
- this._cert = options.cert;
34
+ if (options.ca) {this.ca(options.ca);}
35
+ if (options.key) {this.key(options.key);}
36
+ if (options.pfx) {this.pfx(options.pfx);}
37
+ if (options.cert) {this.cert(options.cert);}
32
38
  }
33
- this.jar = new CookieJar;
34
39
  }
35
40
 
41
+ Agent.prototype = Object.create(AgentBase.prototype);
42
+
36
43
  /**
37
44
  * Save the cookies in the given `res` to
38
45
  * the agent's cookie jar for persistence.
@@ -41,8 +48,8 @@ function Agent(options) {
41
48
  * @api private
42
49
  */
43
50
 
44
- Agent.prototype._saveCookies = function(res){
45
- var cookies = res.headers['set-cookie'];
51
+ Agent.prototype._saveCookies = function(res) {
52
+ const cookies = res.headers['set-cookie'];
46
53
  if (cookies) this.jar.setCookies(cookies);
47
54
  };
48
55
 
@@ -53,39 +60,33 @@ Agent.prototype._saveCookies = function(res){
53
60
  * @api private
54
61
  */
55
62
 
56
- Agent.prototype._attachCookies = function(req){
57
- var url = parse(req.url);
58
- var access = CookieAccess(url.hostname, url.pathname, 'https:' == url.protocol);
59
- var cookies = this.jar.getCookies(access).toValueString();
63
+ Agent.prototype._attachCookies = function(req) {
64
+ const url = parse(req.url);
65
+ const access = CookieAccess(
66
+ url.hostname,
67
+ url.pathname,
68
+ 'https:' == url.protocol
69
+ );
70
+ const cookies = this.jar.getCookies(access).toValueString();
60
71
  req.cookies = cookies;
61
72
  };
62
73
 
63
- // generate HTTP verb methods
64
- if (methods.indexOf('del') == -1) {
65
- // create a copy so we don't cause conflicts with
66
- // other packages using the methods package and
67
- // npm 3.x
68
- methods = methods.slice(0);
69
- methods.push('del');
70
- }
71
- methods.forEach(function(method){
72
- var name = method;
73
- method = 'del' == method ? 'delete' : method;
74
-
75
- method = method.toUpperCase();
76
- Agent.prototype[name] = function(url, fn){
77
- var req = new request.Request(method, url);
78
- req.ca(this._ca);
79
- req.key(this._key);
80
- req.pfx(this._pfx);
81
- req.cert(this._cert);
74
+ methods.forEach(name => {
75
+ const method = name.toUpperCase();
76
+ Agent.prototype[name] = function(url, fn) {
77
+ const req = new request.Request(method, url);
82
78
 
83
79
  req.on('response', this._saveCookies.bind(this));
84
80
  req.on('redirect', this._saveCookies.bind(this));
85
81
  req.on('redirect', this._attachCookies.bind(this, req));
86
82
  this._attachCookies(req);
83
+ this._setDefaults(req);
87
84
 
88
- fn && req.end(fn);
85
+ if (fn) {
86
+ req.end(fn);
87
+ }
89
88
  return req;
90
89
  };
91
90
  });
91
+
92
+ Agent.prototype.del = Agent.prototype['delete'];