got 9.2.1 → 9.3.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/source/merge.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
  const {URL} = require('url');
3
3
  const is = require('@sindresorhus/is');
4
+ const knownHookEvents = require('./known-hook-events');
4
5
 
5
6
  const merge = (target, ...sources) => {
6
7
  for (const source of sources) {
@@ -29,4 +30,44 @@ const merge = (target, ...sources) => {
29
30
  return target;
30
31
  };
31
32
 
33
+ const mergeOptions = (...sources) => {
34
+ sources = sources.map(source => source || {});
35
+ const merged = merge({}, ...sources);
36
+
37
+ const hooks = {};
38
+ for (const hook of knownHookEvents) {
39
+ hooks[hook] = [];
40
+ }
41
+
42
+ for (const source of sources) {
43
+ if (source.hooks) {
44
+ for (const hook of knownHookEvents) {
45
+ hooks[hook] = hooks[hook].concat(source.hooks[hook]);
46
+ }
47
+ }
48
+ }
49
+
50
+ merged.hooks = hooks;
51
+
52
+ return merged;
53
+ };
54
+
55
+ const mergeInstances = (instances, methods) => {
56
+ const handlers = instances.map(instance => instance.defaults.handler);
57
+ const size = instances.length - 1;
58
+
59
+ return {
60
+ methods,
61
+ options: mergeOptions(...instances.map(instance => instance.defaults.options)),
62
+ handler: (options, next) => {
63
+ let iteration = -1;
64
+ const iterate = options => handlers[++iteration](options, iteration === size ? next : iterate);
65
+
66
+ return iterate(options);
67
+ }
68
+ };
69
+ };
70
+
32
71
  module.exports = merge;
72
+ module.exports.options = mergeOptions;
73
+ module.exports.instances = mergeInstances;
@@ -1,64 +1,102 @@
1
1
  'use strict';
2
2
  const {URL, URLSearchParams} = require('url'); // TODO: Use the `URL` global when targeting Node.js 10
3
+ const urlLib = require('url');
3
4
  const is = require('@sindresorhus/is');
4
- const toReadableStream = require('to-readable-stream');
5
5
  const urlParseLax = require('url-parse-lax');
6
- const isRetryOnNetworkErrorAllowed = require('./is-retry-on-network-error-allowed');
7
- const urlToOptions = require('./url-to-options');
8
- const isFormData = require('./is-form-data');
9
- const knownHookEvents = require('./known-hook-events');
6
+ const lowercaseKeys = require('lowercase-keys');
7
+ const isRetryOnNetworkErrorAllowed = require('./utils/is-retry-on-network-error-allowed');
8
+ const urlToOptions = require('./utils/url-to-options');
9
+ const isFormData = require('./utils/is-form-data');
10
10
  const merge = require('./merge');
11
+ const knownHookEvents = require('./known-hook-events');
11
12
 
12
13
  const retryAfterStatusCodes = new Set([413, 429, 503]);
13
14
 
14
- // `preNormalize` handles things related to static options, like `baseUrl`, `followRedirect`, `hooks`, etc.
15
- // While `normalize` does `preNormalize` + handles things related to dynamic options, like URL, headers, body, etc.
16
- const preNormalize = options => {
17
- options = {
18
- headers: {},
19
- ...options
20
- };
15
+ // `preNormalize` handles static things (lowercasing headers; normalizing baseUrl, timeout, retry)
16
+ // While `normalize` does `preNormalize` + handles things which need to be reworked when user changes them
17
+ const preNormalize = (options, defaults) => {
18
+ if (is.nullOrUndefined(options.headers)) {
19
+ options.headers = {};
20
+ } else {
21
+ options.headers = lowercaseKeys(options.headers);
22
+ }
21
23
 
22
24
  if (options.baseUrl && !options.baseUrl.toString().endsWith('/')) {
23
25
  options.baseUrl += '/';
24
26
  }
25
27
 
26
- if (is.undefined(options.followRedirect)) {
27
- options.followRedirect = true;
28
+ if (options.stream) {
29
+ options.json = false;
28
30
  }
29
31
 
30
32
  if (is.nullOrUndefined(options.hooks)) {
31
33
  options.hooks = {};
34
+ } else if (!is.object(options.hooks)) {
35
+ throw new TypeError(`Parameter \`hooks\` must be an object, not ${is(options.hooks)}`);
32
36
  }
33
- if (is.object(options.hooks)) {
34
- for (const hookEvent of knownHookEvents) {
35
- const hooks = options.hooks[hookEvent];
36
- if (is.nullOrUndefined(hooks)) {
37
- options.hooks[hookEvent] = [];
38
- } else if (is.array(hooks)) {
39
- for (const [index, hook] of hooks.entries()) {
40
- if (!is.function(hook)) {
41
- throw new TypeError(
42
- `Parameter \`hooks.${hookEvent}[${index}]\` must be a function, not ${is(hook)}`
43
- );
44
- }
45
- }
37
+
38
+ for (const event of knownHookEvents) {
39
+ if (is.nullOrUndefined(options.hooks[event])) {
40
+ if (defaults) {
41
+ options.hooks[event] = [...defaults.hooks[event]];
46
42
  } else {
47
- throw new TypeError(`Parameter \`hooks.${hookEvent}\` must be an array, not ${is(hooks)}`);
43
+ options.hooks[event] = [];
48
44
  }
49
45
  }
50
- } else {
51
- throw new TypeError(`Parameter \`hooks\` must be an object, not ${is(options.hooks)}`);
46
+ }
47
+
48
+ if (is.number(options.timeout)) {
49
+ options.gotTimeout = {request: options.timeout};
50
+ } else if (is.object(options.timeout)) {
51
+ options.gotTimeout = options.timeout;
52
+ }
53
+ delete options.timeout;
54
+
55
+ const {retry} = options;
56
+ options.retry = {
57
+ retries: 0,
58
+ methods: [],
59
+ statusCodes: []
60
+ };
61
+
62
+ if (is.nonEmptyObject(defaults) && retry !== false) {
63
+ options.retry = {...defaults.retry};
64
+ }
65
+
66
+ if (retry !== false) {
67
+ if (is.number(retry)) {
68
+ options.retry.retries = retry;
69
+ } else {
70
+ options.retry = {...options.retry, ...retry};
71
+ }
72
+ }
73
+
74
+ if (options.gotTimeout) {
75
+ options.retry.maxRetryAfter = Math.min(...[options.gotTimeout.request, options.gotTimeout.connection].filter(n => !is.nullOrUndefined(n)));
76
+ }
77
+
78
+ if (is.array(options.retry.methods)) {
79
+ options.retry.methods = new Set(options.retry.methods.map(method => method.toUpperCase()));
80
+ }
81
+
82
+ if (is.array(options.retry.statusCodes)) {
83
+ options.retry.statusCodes = new Set(options.retry.statusCodes);
52
84
  }
53
85
 
54
86
  return options;
55
87
  };
56
88
 
57
- module.exports = (url, options, defaults) => {
58
- options = merge({}, defaults.options, options ? preNormalize(options) : {});
89
+ const normalize = (url, options, defaults) => {
90
+ if (is.plainObject(url)) {
91
+ options = {...url, ...options};
92
+ url = options.url || {};
93
+ delete options.url;
94
+ }
59
95
 
60
- if (Reflect.has(options, 'url') || (is.object(url) && Reflect.has(url, 'url'))) {
61
- throw new TypeError('Parameter `url` is not an option. Use got(url, options)');
96
+ if (defaults) {
97
+ options = merge({}, defaults.options, options ? preNormalize(options, defaults.options) : {});
98
+ } else {
99
+ options = merge({}, options ? preNormalize(options) : {});
62
100
  }
63
101
 
64
102
  if (!is.string(url) && !is.object(url)) {
@@ -75,12 +113,6 @@ module.exports = (url, options, defaults) => {
75
113
  } else {
76
114
  url = url.replace(/^unix:/, 'http://$&');
77
115
 
78
- try {
79
- decodeURI(url);
80
- } catch (_) {
81
- throw new Error('Parameter `url` must contain valid UTF-8 character sequences');
82
- }
83
-
84
116
  url = urlParseLax(url);
85
117
  if (url.auth) {
86
118
  throw new Error('Basic authentication must be done with the `auth` option');
@@ -90,12 +122,8 @@ module.exports = (url, options, defaults) => {
90
122
  url = urlToOptions(url);
91
123
  }
92
124
 
93
- options = {
94
- path: '',
95
- ...url,
96
- protocol: url.protocol || 'http:', // Override both null/undefined with default protocol
97
- ...options
98
- };
125
+ // Override both null/undefined with default protocol
126
+ options = merge({path: ''}, url, {protocol: url.protocol || 'https:'}, options);
99
127
 
100
128
  const {baseUrl} = options;
101
129
  Object.defineProperty(options, 'baseUrl', {
@@ -106,7 +134,7 @@ module.exports = (url, options, defaults) => {
106
134
  });
107
135
 
108
136
  const {query} = options;
109
- if (!is.empty(query) || query instanceof URLSearchParams) {
137
+ if (is.nonEmptyString(query) || is.nonEmptyObject(query) || query instanceof URLSearchParams) {
110
138
  if (!is.string(query)) {
111
139
  options.query = (new URLSearchParams(query)).toString();
112
140
  }
@@ -114,12 +142,18 @@ module.exports = (url, options, defaults) => {
114
142
  delete options.query;
115
143
  }
116
144
 
117
- if (options.stream && options.json) {
118
- options.json = false;
119
- }
145
+ if (options.hostname === 'unix') {
146
+ const matches = /(.+?):(.+)/.exec(options.path);
120
147
 
121
- if (options.json && is.undefined(options.headers.accept)) {
122
- options.headers.accept = 'application/json';
148
+ if (matches) {
149
+ const [, socketPath, path] = matches;
150
+ options = {
151
+ ...options,
152
+ socketPath,
153
+ path,
154
+ host: null
155
+ };
156
+ }
123
157
  }
124
158
 
125
159
  const {headers} = options;
@@ -129,11 +163,19 @@ module.exports = (url, options, defaults) => {
129
163
  }
130
164
  }
131
165
 
166
+ if (options.json && is.undefined(headers.accept)) {
167
+ headers.accept = 'application/json';
168
+ }
169
+
170
+ if (options.decompress && is.undefined(headers['accept-encoding'])) {
171
+ headers['accept-encoding'] = 'gzip, deflate';
172
+ }
173
+
132
174
  const {body} = options;
133
175
  if (is.nullOrUndefined(body)) {
134
- options.method = options.method || 'GET';
176
+ options.method = options.method ? options.method.toUpperCase() : 'GET';
135
177
  } else {
136
- const isObject = is.object(body) && !Buffer.isBuffer(body) && !is.nodeStream(body);
178
+ const isObject = is.object(body) && !is.buffer(body) && !is.nodeStream(body);
137
179
  if (!is.nodeStream(body) && !is.string(body) && !is.buffer(body) && !(options.form || options.json)) {
138
180
  throw new TypeError('The `body` option must be a stream.Readable, string or Buffer');
139
181
  }
@@ -157,103 +199,52 @@ module.exports = (url, options, defaults) => {
157
199
  options.body = JSON.stringify(body);
158
200
  }
159
201
 
160
- // Convert buffer to stream to receive upload progress events (#322)
161
- if (is.buffer(body)) {
162
- options.body = toReadableStream(body);
163
- options.body._buffer = body;
164
- }
165
-
166
- options.method = options.method || 'POST';
167
- }
168
-
169
- options.method = options.method.toUpperCase();
170
-
171
- if (options.decompress && is.undefined(options.headers['accept-encoding'])) {
172
- options.headers['accept-encoding'] = 'gzip, deflate';
202
+ options.method = options.method ? options.method.toUpperCase() : 'POST';
173
203
  }
174
204
 
175
- if (options.hostname === 'unix') {
176
- const matches = /(.+?):(.+)/.exec(options.path);
205
+ if (!is.function(options.retry.retries)) {
206
+ const {retries} = options.retry;
177
207
 
178
- if (matches) {
179
- const [, socketPath, path] = matches;
180
- options = {
181
- ...options,
182
- socketPath,
183
- path,
184
- host: null
185
- };
186
- }
187
- }
188
-
189
- options.gotRetry = {retries: 0, methods: [], statusCodes: []};
190
- if (options.retry !== false) {
191
- if (is.number(options.retry)) {
192
- if (is.object(defaults.options.retry)) {
193
- options.gotRetry = {...defaults.options.retry, retries: options.retry};
194
- } else {
195
- options.gotRetry.retries = options.retry;
208
+ options.retry.retries = (iteration, error) => {
209
+ if (iteration > retries) {
210
+ return 0;
196
211
  }
197
- } else {
198
- options.gotRetry = {...options.gotRetry, ...options.retry};
199
- }
200
- delete options.retry;
201
- }
202
-
203
- options.gotRetry.methods = new Set(options.gotRetry.methods.map(method => method.toUpperCase()));
204
- options.gotRetry.statusCodes = new Set(options.gotRetry.statusCodes);
205
-
206
- if (!options.gotRetry.maxRetryAfter && Reflect.has(options, 'timeout')) {
207
- if (is.number(options.timeout)) {
208
- options.gotRetry.maxRetryAfter = options.timeout;
209
- } else {
210
- options.gotRetry.maxRetryAfter = Math.min(...[options.timeout.request, options.timeout.connection].filter(n => !is.nullOrUndefined(n)));
211
- }
212
- }
213
212
 
214
- if (is.number(options.timeout) || is.object(options.timeout)) {
215
- if (is.number(options.timeout)) {
216
- options.gotTimeout = {request: options.timeout};
217
- } else {
218
- options.gotTimeout = options.timeout;
219
- }
220
- delete options.timeout;
221
- }
213
+ if (error !== null) {
214
+ if (!isRetryOnNetworkErrorAllowed(error) && (!options.retry.methods.has(error.method) || !options.retry.statusCodes.has(error.statusCode))) {
215
+ return 0;
216
+ }
222
217
 
223
- if (!is.function(options.gotRetry.retries)) {
224
- const {retries} = options.gotRetry;
218
+ if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
219
+ let after = Number(error.headers['retry-after']);
220
+ if (is.nan(after)) {
221
+ after = Date.parse(error.headers['retry-after']) - Date.now();
222
+ } else {
223
+ after *= 1000;
224
+ }
225
225
 
226
- options.gotRetry.retries = (iteration, error) => {
227
- if (iteration > retries || (!isRetryOnNetworkErrorAllowed(error) && (!options.gotRetry.methods.has(error.method) || !options.gotRetry.statusCodes.has(error.statusCode)))) {
228
- return 0;
229
- }
226
+ if (after > options.retry.maxRetryAfter) {
227
+ return 0;
228
+ }
230
229
 
231
- if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
232
- let after = Number(error.headers['retry-after']);
233
- if (is.nan(after)) {
234
- after = Date.parse(error.headers['retry-after']) - Date.now();
235
- } else {
236
- after *= 1000;
230
+ return after;
237
231
  }
238
232
 
239
- if (after > options.gotRetry.maxRetryAfter) {
233
+ if (error.statusCode === 413) {
240
234
  return 0;
241
235
  }
242
-
243
- return after;
244
- }
245
-
246
- if (error.statusCode === 413) {
247
- return 0;
248
236
  }
249
237
 
250
238
  const noise = Math.random() * 100;
251
-
252
- return ((1 << iteration) * 1000) + noise;
239
+ return ((2 ** (iteration - 1)) * 1000) + noise;
253
240
  };
254
241
  }
255
242
 
256
243
  return options;
257
244
  };
258
245
 
246
+ const reNormalize = options => normalize(urlLib.format(options), options);
247
+
248
+ module.exports = normalize;
259
249
  module.exports.preNormalize = preNormalize;
250
+ module.exports.reNormalize = reNormalize;
@@ -1,6 +1,40 @@
1
1
  'use strict';
2
+ const {Transform} = require('stream');
2
3
 
3
4
  module.exports = {
5
+ download(response, emitter, downloadBodySize) {
6
+ let downloaded = 0;
7
+
8
+ return new Transform({
9
+ transform(chunk, encoding, callback) {
10
+ downloaded += chunk.length;
11
+
12
+ const percent = downloadBodySize ? downloaded / downloadBodySize : 0;
13
+
14
+ // Let `flush()` be responsible for emitting the last event
15
+ if (percent < 1) {
16
+ emitter.emit('downloadProgress', {
17
+ percent,
18
+ transferred: downloaded,
19
+ total: downloadBodySize
20
+ });
21
+ }
22
+
23
+ callback(null, chunk);
24
+ },
25
+
26
+ flush(callback) {
27
+ emitter.emit('downloadProgress', {
28
+ percent: 1,
29
+ transferred: downloaded,
30
+ total: downloadBodySize
31
+ });
32
+
33
+ callback();
34
+ }
35
+ });
36
+ },
37
+
4
38
  upload(request, emitter, uploadBodySize) {
5
39
  const uploadEventFrequency = 150;
6
40
  let uploaded = 0;
@@ -34,11 +68,6 @@ module.exports = {
34
68
  const headersSize = request._header ? Buffer.byteLength(request._header) : 0;
35
69
  uploaded = socket.bytesWritten - headersSize;
36
70
 
37
- /* istanbul ignore next: see https://github.com/sindresorhus/got/pull/322#pullrequestreview-51647813 (no proof) */
38
- if (uploadBodySize && uploaded > uploadBodySize) {
39
- uploaded = uploadBodySize;
40
- }
41
-
42
71
  // Don't emit events with unchanged progress and
43
72
  // prevent last event from being emitted, because
44
73
  // it's emitted when `response` is emitted