follow-redirects 1.14.4 → 1.15.7

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.
Files changed (4) hide show
  1. package/README.md +8 -1
  2. package/index.js +275 -133
  3. package/nope.js +20 -0
  4. package/package.json +3 -3
package/README.md CHANGED
@@ -63,10 +63,17 @@ const { http, https } = require('follow-redirects');
63
63
 
64
64
  const options = url.parse('http://bit.ly/900913');
65
65
  options.maxRedirects = 10;
66
- options.beforeRedirect = (options, { headers }) => {
66
+ options.beforeRedirect = (options, response, request) => {
67
67
  // Use this to adjust the request options upon redirecting,
68
68
  // to inspect the latest response headers,
69
69
  // or to cancel the request by throwing an error
70
+
71
+ // response.headers = the redirect response headers
72
+ // response.statusCode = the redirect response code (eg. 301, 307, etc.)
73
+
74
+ // request.url = the requested URL that resulted in a redirect
75
+ // request.headers = the headers in the request that resulted in a redirect
76
+ // request.method = the method of the request that resulted in a redirect
70
77
  if (options.hostname === "example.com") {
71
78
  options.auth = "user:password";
72
79
  }
package/index.js CHANGED
@@ -5,6 +5,38 @@ var https = require("https");
5
5
  var Writable = require("stream").Writable;
6
6
  var assert = require("assert");
7
7
  var debug = require("./debug");
8
+ var nope = require("./nope");
9
+
10
+ // Disaster prevention
11
+ /* istanbul ignore next */
12
+ if (nope.isBrowser()) {
13
+ module.exports = nope;
14
+ return;
15
+ }
16
+
17
+ // Whether to use the native URL object or the legacy url module
18
+ var useNativeURL = false;
19
+ try {
20
+ assert(new URL());
21
+ }
22
+ catch (error) {
23
+ useNativeURL = error.code === "ERR_INVALID_URL";
24
+ }
25
+
26
+ // URL fields to preserve in copy operations
27
+ var preservedUrlFields = [
28
+ "auth",
29
+ "host",
30
+ "hostname",
31
+ "href",
32
+ "path",
33
+ "pathname",
34
+ "port",
35
+ "protocol",
36
+ "query",
37
+ "search",
38
+ "hash",
39
+ ];
8
40
 
9
41
  // Create handlers that pass events from native requests
10
42
  var events = ["abort", "aborted", "connect", "error", "socket", "timeout"];
@@ -16,13 +48,19 @@ events.forEach(function (event) {
16
48
  });
17
49
 
18
50
  // Error types with codes
51
+ var InvalidUrlError = createErrorType(
52
+ "ERR_INVALID_URL",
53
+ "Invalid URL",
54
+ TypeError
55
+ );
19
56
  var RedirectionError = createErrorType(
20
57
  "ERR_FR_REDIRECTION_FAILURE",
21
- ""
58
+ "Redirected request failed"
22
59
  );
23
60
  var TooManyRedirectsError = createErrorType(
24
61
  "ERR_FR_TOO_MANY_REDIRECTS",
25
- "Maximum number of redirects exceeded"
62
+ "Maximum number of redirects exceeded",
63
+ RedirectionError
26
64
  );
27
65
  var MaxBodyLengthExceededError = createErrorType(
28
66
  "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
@@ -33,6 +71,9 @@ var WriteAfterEndError = createErrorType(
33
71
  "write after end"
34
72
  );
35
73
 
74
+ // istanbul ignore next
75
+ var destroy = Writable.prototype.destroy || noop;
76
+
36
77
  // An HTTP(S) request that can be redirected
37
78
  function RedirectableRequest(options, responseCallback) {
38
79
  // Initialize the request
@@ -54,7 +95,13 @@ function RedirectableRequest(options, responseCallback) {
54
95
  // React to responses of native requests
55
96
  var self = this;
56
97
  this._onNativeResponse = function (response) {
57
- self._processResponse(response);
98
+ try {
99
+ self._processResponse(response);
100
+ }
101
+ catch (cause) {
102
+ self.emit("error", cause instanceof RedirectionError ?
103
+ cause : new RedirectionError({ cause: cause }));
104
+ }
58
105
  };
59
106
 
60
107
  // Perform the first request
@@ -63,10 +110,17 @@ function RedirectableRequest(options, responseCallback) {
63
110
  RedirectableRequest.prototype = Object.create(Writable.prototype);
64
111
 
65
112
  RedirectableRequest.prototype.abort = function () {
66
- abortRequest(this._currentRequest);
113
+ destroyRequest(this._currentRequest);
114
+ this._currentRequest.abort();
67
115
  this.emit("abort");
68
116
  };
69
117
 
118
+ RedirectableRequest.prototype.destroy = function (error) {
119
+ destroyRequest(this._currentRequest, error);
120
+ destroy.call(this, error);
121
+ return this;
122
+ };
123
+
70
124
  // Writes buffered data to the current native request
71
125
  RedirectableRequest.prototype.write = function (data, encoding, callback) {
72
126
  // Writing is not allowed if end has been called
@@ -75,10 +129,10 @@ RedirectableRequest.prototype.write = function (data, encoding, callback) {
75
129
  }
76
130
 
77
131
  // Validate input and shift parameters if necessary
78
- if (!(typeof data === "string" || typeof data === "object" && ("length" in data))) {
132
+ if (!isString(data) && !isBuffer(data)) {
79
133
  throw new TypeError("data should be a string, Buffer or Uint8Array");
80
134
  }
81
- if (typeof encoding === "function") {
135
+ if (isFunction(encoding)) {
82
136
  callback = encoding;
83
137
  encoding = null;
84
138
  }
@@ -107,11 +161,11 @@ RedirectableRequest.prototype.write = function (data, encoding, callback) {
107
161
  // Ends the current native request
108
162
  RedirectableRequest.prototype.end = function (data, encoding, callback) {
109
163
  // Shift parameters if necessary
110
- if (typeof data === "function") {
164
+ if (isFunction(data)) {
111
165
  callback = data;
112
166
  data = encoding = null;
113
167
  }
114
- else if (typeof encoding === "function") {
168
+ else if (isFunction(encoding)) {
115
169
  callback = encoding;
116
170
  encoding = null;
117
171
  }
@@ -169,10 +223,17 @@ RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
169
223
 
170
224
  // Stops a timeout from triggering
171
225
  function clearTimer() {
226
+ // Clear the timeout
172
227
  if (self._timeout) {
173
228
  clearTimeout(self._timeout);
174
229
  self._timeout = null;
175
230
  }
231
+
232
+ // Clean up all attached listeners
233
+ self.removeListener("abort", clearTimer);
234
+ self.removeListener("error", clearTimer);
235
+ self.removeListener("response", clearTimer);
236
+ self.removeListener("close", clearTimer);
176
237
  if (callback) {
177
238
  self.removeListener("timeout", callback);
178
239
  }
@@ -196,8 +257,10 @@ RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
196
257
 
197
258
  // Clean up on events
198
259
  this.on("socket", destroyOnTimeout);
199
- this.once("response", clearTimer);
200
- this.once("error", clearTimer);
260
+ this.on("abort", clearTimer);
261
+ this.on("error", clearTimer);
262
+ this.on("response", clearTimer);
263
+ this.on("close", clearTimer);
201
264
 
202
265
  return this;
203
266
  };
@@ -256,32 +319,36 @@ RedirectableRequest.prototype._performRequest = function () {
256
319
  var protocol = this._options.protocol;
257
320
  var nativeProtocol = this._options.nativeProtocols[protocol];
258
321
  if (!nativeProtocol) {
259
- this.emit("error", new TypeError("Unsupported protocol " + protocol));
260
- return;
322
+ throw new TypeError("Unsupported protocol " + protocol);
261
323
  }
262
324
 
263
325
  // If specified, use the agent corresponding to the protocol
264
326
  // (HTTP and HTTPS use different types of agents)
265
327
  if (this._options.agents) {
266
- var scheme = protocol.substr(0, protocol.length - 1);
328
+ var scheme = protocol.slice(0, -1);
267
329
  this._options.agent = this._options.agents[scheme];
268
330
  }
269
331
 
270
- // Create the native request
332
+ // Create the native request and set up its event handlers
271
333
  var request = this._currentRequest =
272
334
  nativeProtocol.request(this._options, this._onNativeResponse);
273
- this._currentUrl = url.format(this._options);
274
-
275
- // Set up event handlers
276
335
  request._redirectable = this;
277
- for (var e = 0; e < events.length; e++) {
278
- request.on(events[e], eventHandlers[events[e]]);
336
+ for (var event of events) {
337
+ request.on(event, eventHandlers[event]);
279
338
  }
280
339
 
340
+ // RFC7230§5.3.1: When making a request directly to an origin server, […]
341
+ // a client MUST send only the absolute path […] as the request-target.
342
+ this._currentUrl = /^\//.test(this._options.path) ?
343
+ url.format(this._options) :
344
+ // When making a request to a proxy, […]
345
+ // a client MUST send the target URI in absolute-form […].
346
+ this._options.path;
347
+
281
348
  // End a redirected request
282
349
  // (The first request must be ended explicitly with RedirectableRequest#end)
283
350
  if (this._isRedirect) {
284
- // Write the request entity and end.
351
+ // Write the request entity and end
285
352
  var i = 0;
286
353
  var self = this;
287
354
  var buffers = this._requestBodyBuffers;
@@ -329,85 +396,99 @@ RedirectableRequest.prototype._processResponse = function (response) {
329
396
  // the user agent MAY automatically redirect its request to the URI
330
397
  // referenced by the Location field value,
331
398
  // even if the specific status code is not understood.
399
+
400
+ // If the response is not a redirect; return it as-is
332
401
  var location = response.headers.location;
333
- if (location && this._options.followRedirects !== false &&
334
- statusCode >= 300 && statusCode < 400) {
335
- // Abort the current request
336
- abortRequest(this._currentRequest);
337
- // Discard the remainder of the response to avoid waiting for data
338
- response.destroy();
339
-
340
- // RFC7231§6.4: A client SHOULD detect and intervene
341
- // in cyclical redirections (i.e., "infinite" redirection loops).
342
- if (++this._redirectCount > this._options.maxRedirects) {
343
- this.emit("error", new TooManyRedirectsError());
344
- return;
345
- }
402
+ if (!location || this._options.followRedirects === false ||
403
+ statusCode < 300 || statusCode >= 400) {
404
+ response.responseUrl = this._currentUrl;
405
+ response.redirects = this._redirects;
406
+ this.emit("response", response);
346
407
 
347
- // RFC7231§6.4: Automatic redirection needs to done with
348
- // care for methods not known to be safe, []
349
- // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
350
- // the request method from POST to GET for the subsequent request.
351
- if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
352
- // RFC7231§6.4.4: The 303 (See Other) status code indicates that
353
- // the server is redirecting the user agent to a different resource […]
354
- // A user agent can perform a retrieval request targeting that URI
355
- // (a GET or HEAD request if using HTTP) […]
356
- (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
357
- this._options.method = "GET";
358
- // Drop a possible entity and headers related to it
359
- this._requestBodyBuffers = [];
360
- removeMatchingHeaders(/^content-/i, this._options.headers);
361
- }
408
+ // Clean up
409
+ this._requestBodyBuffers = [];
410
+ return;
411
+ }
362
412
 
363
- // Drop the Host header, as the redirect might lead to a different host
364
- var previousHostName = removeMatchingHeaders(/^host$/i, this._options.headers) ||
365
- url.parse(this._currentUrl).hostname;
413
+ // The response is a redirect, so abort the current request
414
+ destroyRequest(this._currentRequest);
415
+ // Discard the remainder of the response to avoid waiting for data
416
+ response.destroy();
366
417
 
367
- // Create the redirected request
368
- var redirectUrl = url.resolve(this._currentUrl, location);
369
- debug("redirecting to", redirectUrl);
370
- this._isRedirect = true;
371
- var redirectUrlParts = url.parse(redirectUrl);
372
- Object.assign(this._options, redirectUrlParts);
418
+ // RFC7231§6.4: A client SHOULD detect and intervene
419
+ // in cyclical redirections (i.e., "infinite" redirection loops).
420
+ if (++this._redirectCount > this._options.maxRedirects) {
421
+ throw new TooManyRedirectsError();
422
+ }
373
423
 
374
- // Drop the Authorization header if redirecting to another host
375
- if (redirectUrlParts.hostname !== previousHostName) {
376
- removeMatchingHeaders(/^authorization$/i, this._options.headers);
377
- }
424
+ // Store the request headers if applicable
425
+ var requestHeaders;
426
+ var beforeRedirect = this._options.beforeRedirect;
427
+ if (beforeRedirect) {
428
+ requestHeaders = Object.assign({
429
+ // The Host header was set by nativeProtocol.request
430
+ Host: response.req.getHeader("host"),
431
+ }, this._options.headers);
432
+ }
378
433
 
379
- // Evaluate the beforeRedirect callback
380
- if (typeof this._options.beforeRedirect === "function") {
381
- var responseDetails = { headers: response.headers };
382
- try {
383
- this._options.beforeRedirect.call(null, this._options, responseDetails);
384
- }
385
- catch (err) {
386
- this.emit("error", err);
387
- return;
388
- }
389
- this._sanitizeOptions(this._options);
390
- }
434
+ // RFC7231§6.4: Automatic redirection needs to done with
435
+ // care for methods not known to be safe, […]
436
+ // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
437
+ // the request method from POST to GET for the subsequent request.
438
+ var method = this._options.method;
439
+ if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
440
+ // RFC7231§6.4.4: The 303 (See Other) status code indicates that
441
+ // the server is redirecting the user agent to a different resource […]
442
+ // A user agent can perform a retrieval request targeting that URI
443
+ // (a GET or HEAD request if using HTTP) […]
444
+ (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
445
+ this._options.method = "GET";
446
+ // Drop a possible entity and headers related to it
447
+ this._requestBodyBuffers = [];
448
+ removeMatchingHeaders(/^content-/i, this._options.headers);
449
+ }
391
450
 
392
- // Perform the redirected request
393
- try {
394
- this._performRequest();
395
- }
396
- catch (cause) {
397
- var error = new RedirectionError("Redirected request failed: " + cause.message);
398
- error.cause = cause;
399
- this.emit("error", error);
400
- }
451
+ // Drop the Host header, as the redirect might lead to a different host
452
+ var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
453
+
454
+ // If the redirect is relative, carry over the host of the last request
455
+ var currentUrlParts = parseUrl(this._currentUrl);
456
+ var currentHost = currentHostHeader || currentUrlParts.host;
457
+ var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
458
+ url.format(Object.assign(currentUrlParts, { host: currentHost }));
459
+
460
+ // Create the redirected request
461
+ var redirectUrl = resolveUrl(location, currentUrl);
462
+ debug("redirecting to", redirectUrl.href);
463
+ this._isRedirect = true;
464
+ spreadUrlObject(redirectUrl, this._options);
465
+
466
+ // Drop confidential headers when redirecting to a less secure protocol
467
+ // or to a different domain that is not a superdomain
468
+ if (redirectUrl.protocol !== currentUrlParts.protocol &&
469
+ redirectUrl.protocol !== "https:" ||
470
+ redirectUrl.host !== currentHost &&
471
+ !isSubdomain(redirectUrl.host, currentHost)) {
472
+ removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
401
473
  }
402
- else {
403
- // The response is not a redirect; return it as-is
404
- response.responseUrl = this._currentUrl;
405
- response.redirects = this._redirects;
406
- this.emit("response", response);
407
474
 
408
- // Clean up
409
- this._requestBodyBuffers = [];
475
+ // Evaluate the beforeRedirect callback
476
+ if (isFunction(beforeRedirect)) {
477
+ var responseDetails = {
478
+ headers: response.headers,
479
+ statusCode: statusCode,
480
+ };
481
+ var requestDetails = {
482
+ url: currentUrl,
483
+ method: method,
484
+ headers: requestHeaders,
485
+ };
486
+ beforeRedirect(this._options, responseDetails, requestDetails);
487
+ this._sanitizeOptions(this._options);
410
488
  }
489
+
490
+ // Perform the redirected request
491
+ this._performRequest();
411
492
  };
412
493
 
413
494
  // Wraps the key/value object of protocols with redirect functionality
@@ -427,26 +508,19 @@ function wrap(protocols) {
427
508
 
428
509
  // Executes a request, following redirects
429
510
  function request(input, options, callback) {
430
- // Parse parameters
431
- if (typeof input === "string") {
432
- var urlStr = input;
433
- try {
434
- input = urlToOptions(new URL(urlStr));
435
- }
436
- catch (err) {
437
- /* istanbul ignore next */
438
- input = url.parse(urlStr);
439
- }
511
+ // Parse parameters, ensuring that input is an object
512
+ if (isURL(input)) {
513
+ input = spreadUrlObject(input);
440
514
  }
441
- else if (URL && (input instanceof URL)) {
442
- input = urlToOptions(input);
515
+ else if (isString(input)) {
516
+ input = spreadUrlObject(parseUrl(input));
443
517
  }
444
518
  else {
445
519
  callback = options;
446
- options = input;
520
+ options = validateUrl(input);
447
521
  input = { protocol: protocol };
448
522
  }
449
- if (typeof options === "function") {
523
+ if (isFunction(options)) {
450
524
  callback = options;
451
525
  options = null;
452
526
  }
@@ -457,6 +531,9 @@ function wrap(protocols) {
457
531
  maxBodyLength: exports.maxBodyLength,
458
532
  }, input, options);
459
533
  options.nativeProtocols = nativeProtocols;
534
+ if (!isString(options.host) && !isString(options.hostname)) {
535
+ options.hostname = "::1";
536
+ }
460
537
 
461
538
  assert.equal(options.protocol, protocol, "protocol mismatch");
462
539
  debug("options", options);
@@ -479,27 +556,57 @@ function wrap(protocols) {
479
556
  return exports;
480
557
  }
481
558
 
482
- /* istanbul ignore next */
483
559
  function noop() { /* empty */ }
484
560
 
485
- // from https://github.com/nodejs/node/blob/master/lib/internal/url.js
486
- function urlToOptions(urlObject) {
487
- var options = {
488
- protocol: urlObject.protocol,
489
- hostname: urlObject.hostname.startsWith("[") ?
490
- /* istanbul ignore next */
491
- urlObject.hostname.slice(1, -1) :
492
- urlObject.hostname,
493
- hash: urlObject.hash,
494
- search: urlObject.search,
495
- pathname: urlObject.pathname,
496
- path: urlObject.pathname + urlObject.search,
497
- href: urlObject.href,
498
- };
499
- if (urlObject.port !== "") {
500
- options.port = Number(urlObject.port);
561
+ function parseUrl(input) {
562
+ var parsed;
563
+ /* istanbul ignore else */
564
+ if (useNativeURL) {
565
+ parsed = new URL(input);
566
+ }
567
+ else {
568
+ // Ensure the URL is valid and absolute
569
+ parsed = validateUrl(url.parse(input));
570
+ if (!isString(parsed.protocol)) {
571
+ throw new InvalidUrlError({ input });
572
+ }
573
+ }
574
+ return parsed;
575
+ }
576
+
577
+ function resolveUrl(relative, base) {
578
+ /* istanbul ignore next */
579
+ return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));
580
+ }
581
+
582
+ function validateUrl(input) {
583
+ if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
584
+ throw new InvalidUrlError({ input: input.href || input });
585
+ }
586
+ if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
587
+ throw new InvalidUrlError({ input: input.href || input });
501
588
  }
502
- return options;
589
+ return input;
590
+ }
591
+
592
+ function spreadUrlObject(urlObject, target) {
593
+ var spread = target || {};
594
+ for (var key of preservedUrlFields) {
595
+ spread[key] = urlObject[key];
596
+ }
597
+
598
+ // Fix IPv6 hostname
599
+ if (spread.hostname.startsWith("[")) {
600
+ spread.hostname = spread.hostname.slice(1, -1);
601
+ }
602
+ // Ensure port is a number
603
+ if (spread.port !== "") {
604
+ spread.port = Number(spread.port);
605
+ }
606
+ // Concatenate path
607
+ spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;
608
+
609
+ return spread;
503
610
  }
504
611
 
505
612
  function removeMatchingHeaders(regex, headers) {
@@ -510,27 +617,62 @@ function removeMatchingHeaders(regex, headers) {
510
617
  delete headers[header];
511
618
  }
512
619
  }
513
- return lastValue;
620
+ return (lastValue === null || typeof lastValue === "undefined") ?
621
+ undefined : String(lastValue).trim();
514
622
  }
515
623
 
516
- function createErrorType(code, defaultMessage) {
517
- function CustomError(message) {
624
+ function createErrorType(code, message, baseClass) {
625
+ // Create constructor
626
+ function CustomError(properties) {
518
627
  Error.captureStackTrace(this, this.constructor);
519
- this.message = message || defaultMessage;
628
+ Object.assign(this, properties || {});
629
+ this.code = code;
630
+ this.message = this.cause ? message + ": " + this.cause.message : message;
520
631
  }
521
- CustomError.prototype = new Error();
522
- CustomError.prototype.constructor = CustomError;
523
- CustomError.prototype.name = "Error [" + code + "]";
524
- CustomError.prototype.code = code;
632
+
633
+ // Attach constructor and set default properties
634
+ CustomError.prototype = new (baseClass || Error)();
635
+ Object.defineProperties(CustomError.prototype, {
636
+ constructor: {
637
+ value: CustomError,
638
+ enumerable: false,
639
+ },
640
+ name: {
641
+ value: "Error [" + code + "]",
642
+ enumerable: false,
643
+ },
644
+ });
525
645
  return CustomError;
526
646
  }
527
647
 
528
- function abortRequest(request) {
529
- for (var e = 0; e < events.length; e++) {
530
- request.removeListener(events[e], eventHandlers[events[e]]);
648
+ function destroyRequest(request, error) {
649
+ for (var event of events) {
650
+ request.removeListener(event, eventHandlers[event]);
531
651
  }
532
652
  request.on("error", noop);
533
- request.abort();
653
+ request.destroy(error);
654
+ }
655
+
656
+ function isSubdomain(subdomain, domain) {
657
+ assert(isString(subdomain) && isString(domain));
658
+ var dot = subdomain.length - domain.length - 1;
659
+ return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
660
+ }
661
+
662
+ function isString(value) {
663
+ return typeof value === "string" || value instanceof String;
664
+ }
665
+
666
+ function isFunction(value) {
667
+ return typeof value === "function";
668
+ }
669
+
670
+ function isBuffer(value) {
671
+ return typeof value === "object" && ("length" in value);
672
+ }
673
+
674
+ function isURL(value) {
675
+ return URL && value instanceof URL;
534
676
  }
535
677
 
536
678
  // Exports
package/nope.js ADDED
@@ -0,0 +1,20 @@
1
+ // follow-redirects absolutely must not be used in the browser.
2
+ // Neither should the `http` and `http` modules it replaces, yet here we are.
3
+ var http = require("http");
4
+ var https = require("https");
5
+
6
+ /* istanbul ignore next */ // eslint-disable-next-line no-undef
7
+ var browser = typeof window !== "undefined" && typeof window.document !== "undefined";
8
+
9
+ module.exports = {
10
+ http: http,
11
+ https: https,
12
+ wrap: function () {
13
+ // Honestly looking forward to this bug report
14
+ throw new Error("Best viewed in Internet Explorer");
15
+ },
16
+ isBrowser() {
17
+ /* istanbul ignore next */ // eslint-disable-next-line
18
+ return browser && !!console.warn("Exclude follow-redirects from browser builds.");
19
+ },
20
+ };
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "1.14.4",
3
+ "version": "1.15.7",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
7
+ "browser": "nope.js",
7
8
  "files": [
8
9
  "*.js"
9
10
  ],
@@ -11,9 +12,8 @@
11
12
  "node": ">=4.0"
12
13
  },
13
14
  "scripts": {
14
- "test": "npm run lint && npm run mocha",
15
15
  "lint": "eslint *.js test",
16
- "mocha": "nyc mocha"
16
+ "test": "nyc mocha"
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",