got 8.3.2 → 9.0.0

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.
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+ const {PassThrough} = require('stream');
3
+ const duplexer3 = require('duplexer3');
4
+ const is = require('@sindresorhus/is');
5
+ const requestAsEventEmitter = require('./request-as-event-emitter');
6
+ const {HTTPError, ReadError} = require('./errors');
7
+
8
+ module.exports = options => {
9
+ const input = new PassThrough();
10
+ const output = new PassThrough();
11
+ const proxy = duplexer3(input, output);
12
+ const piped = new Set();
13
+ let isFinished = false;
14
+
15
+ options.gotRetry.retries = () => 0;
16
+
17
+ if (options.body) {
18
+ proxy.write = () => {
19
+ throw new Error('Got\'s stream is not writable when the `body` option is used');
20
+ };
21
+ }
22
+
23
+ const emitter = requestAsEventEmitter(options);
24
+
25
+ emitter.on('request', request => {
26
+ proxy.emit('request', request);
27
+ const uploadComplete = () => {
28
+ request.emit('upload-complete');
29
+ };
30
+
31
+ if (is.nodeStream(options.body)) {
32
+ options.body.once('end', uploadComplete);
33
+ options.body.pipe(request);
34
+ return;
35
+ }
36
+
37
+ if (options.body) {
38
+ request.end(options.body, uploadComplete);
39
+ return;
40
+ }
41
+
42
+ if (options.method === 'POST' || options.method === 'PUT' || options.method === 'PATCH') {
43
+ input.once('end', uploadComplete);
44
+ input.pipe(request);
45
+ return;
46
+ }
47
+
48
+ request.end(uploadComplete);
49
+ });
50
+
51
+ emitter.on('response', response => {
52
+ const {statusCode} = response;
53
+
54
+ response.on('error', error => {
55
+ proxy.emit('error', new ReadError(error, options));
56
+ });
57
+
58
+ if (options.throwHttpErrors && statusCode !== 304 && (statusCode < 200 || statusCode > 299)) {
59
+ proxy.emit('error', new HTTPError(statusCode, response.statusMessage, response.headers, options), null, response);
60
+ return;
61
+ }
62
+
63
+ isFinished = true;
64
+
65
+ response.pipe(output);
66
+
67
+ for (const destination of piped) {
68
+ if (destination.headersSent) {
69
+ continue;
70
+ }
71
+
72
+ for (const [key, value] of Object.entries(response.headers)) {
73
+ // Got gives *uncompressed* data. Overriding `content-encoding` header would result in an error.
74
+ // It's not possible to decompress uncompressed data, is it?
75
+ const allowed = options.decompress ? key !== 'content-encoding' : true;
76
+ if (allowed) {
77
+ destination.setHeader(key, value);
78
+ }
79
+ }
80
+
81
+ destination.statusCode = response.statusCode;
82
+ }
83
+
84
+ proxy.emit('response', response);
85
+ });
86
+
87
+ [
88
+ 'error',
89
+ 'redirect',
90
+ 'uploadProgress',
91
+ 'downloadProgress'
92
+ ].forEach(event => emitter.on(event, (...args) => proxy.emit(event, ...args)));
93
+
94
+ const pipe = proxy.pipe.bind(proxy);
95
+ const unpipe = proxy.unpipe.bind(proxy);
96
+ proxy.pipe = (destination, options) => {
97
+ if (isFinished) {
98
+ throw new Error('Failed to pipe. The response has been emitted already.');
99
+ }
100
+
101
+ const result = pipe(destination, options);
102
+
103
+ if (Reflect.has(destination, 'setHeader')) {
104
+ piped.add(destination);
105
+ }
106
+
107
+ return result;
108
+ };
109
+ proxy.unpipe = stream => {
110
+ piped.delete(stream);
111
+ return unpipe(stream);
112
+ };
113
+
114
+ return proxy;
115
+ };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+ const errors = require('./errors');
3
+ const asStream = require('./as-stream');
4
+ const asPromise = require('./as-promise');
5
+ const normalizeArguments = require('./normalize-arguments');
6
+ const merge = require('./merge');
7
+ const deepFreeze = require('./deep-freeze');
8
+
9
+ const next = options => options.stream ? asStream(options) : asPromise(options);
10
+ const mergeOptions = (defaults, options = {}) => merge({}, defaults, options);
11
+
12
+ const create = defaults => {
13
+ defaults = merge({}, defaults);
14
+ if (!defaults.handler) {
15
+ defaults.handler = next;
16
+ }
17
+
18
+ function got(url, options) {
19
+ try {
20
+ options = mergeOptions(defaults.options, options);
21
+ return defaults.handler(normalizeArguments(url, options, defaults), next);
22
+ } catch (error) {
23
+ return Promise.reject(error);
24
+ }
25
+ }
26
+
27
+ got.create = create;
28
+ got.extend = (options = {}) => create({
29
+ options: mergeOptions(defaults.options, options),
30
+ methods: defaults.methods,
31
+ handler: defaults.handler
32
+ });
33
+
34
+ got.stream = (url, options) => {
35
+ options = mergeOptions(defaults.options, options);
36
+ options.stream = true;
37
+ return defaults.handler(normalizeArguments(url, options, defaults), next);
38
+ };
39
+
40
+ for (const method of defaults.methods) {
41
+ got[method] = (url, options) => got(url, {...options, method});
42
+ got.stream[method] = (url, options) => got.stream(url, {...options, method});
43
+ }
44
+
45
+ Object.assign(got, {...errors, mergeOptions});
46
+ Object.defineProperty(got, 'defaults', {
47
+ value: deepFreeze(defaults),
48
+ writable: false,
49
+ enumerable: true,
50
+ configurable: true
51
+ });
52
+
53
+ return got;
54
+ };
55
+
56
+ module.exports = create;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+ const is = require('@sindresorhus/is');
3
+
4
+ module.exports = function deepFreeze(object) {
5
+ for (const [key, value] of Object.entries(object)) {
6
+ if (is.object(value)) {
7
+ deepFreeze(object[key]);
8
+ }
9
+ }
10
+
11
+ return Object.freeze(object);
12
+ };
@@ -19,6 +19,7 @@ class GotError extends Error {
19
19
  hostname: opts.hostname,
20
20
  method: opts.method,
21
21
  path: opts.path,
22
+ socketPath: opts.socketPath,
22
23
  protocol: opts.protocol,
23
24
  url: opts.href
24
25
  });
@@ -89,4 +90,12 @@ module.exports.UnsupportedProtocolError = class extends GotError {
89
90
  }
90
91
  };
91
92
 
93
+ module.exports.TimeoutError = class extends GotError {
94
+ constructor(threshold, event, opts) {
95
+ super(`Timeout awaiting '${event}' for ${threshold}ms`, {code: 'ETIMEDOUT'}, opts);
96
+ this.name = 'TimeoutError';
97
+ this.event = event;
98
+ }
99
+ };
100
+
92
101
  module.exports.CancelError = PCancelable.CancelError;
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const util = require('util');
4
+ const is = require('@sindresorhus/is');
5
+ const isFormData = require('./is-form-data');
6
+
7
+ module.exports = async options => {
8
+ const {body} = options;
9
+
10
+ if (options.headers['content-length']) {
11
+ return Number(options.headers['content-length']);
12
+ }
13
+
14
+ if (!body && !options.stream) {
15
+ return 0;
16
+ }
17
+
18
+ if (is.string(body)) {
19
+ return Buffer.byteLength(body);
20
+ }
21
+
22
+ if (is.buffer(body)) {
23
+ return body.length;
24
+ }
25
+
26
+ if (isFormData(body)) {
27
+ return util.promisify(body.getLength.bind(body))();
28
+ }
29
+
30
+ if (body instanceof fs.ReadStream) {
31
+ const {size} = await util.promisify(fs.stat)(body.path);
32
+ return size;
33
+ }
34
+
35
+ if (is.nodeStream(body) && is.buffer(body._buffer)) {
36
+ return body._buffer.length;
37
+ }
38
+
39
+ return null;
40
+ };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+ const {Transform} = require('stream');
3
+ const decompressResponse = require('decompress-response');
4
+ const is = require('@sindresorhus/is');
5
+ const mimicResponse = require('mimic-response');
6
+
7
+ module.exports = (response, options, emitter, redirects) => {
8
+ const downloadBodySize = Number(response.headers['content-length']) || null;
9
+ let downloaded = 0;
10
+
11
+ const progressStream = new Transform({
12
+ transform(chunk, encoding, callback) {
13
+ downloaded += chunk.length;
14
+
15
+ const percent = downloadBodySize ? downloaded / downloadBodySize : 0;
16
+
17
+ // Let `flush()` be responsible for emitting the last event
18
+ if (percent < 1) {
19
+ emitter.emit('downloadProgress', {
20
+ percent,
21
+ transferred: downloaded,
22
+ total: downloadBodySize
23
+ });
24
+ }
25
+
26
+ callback(null, chunk);
27
+ },
28
+
29
+ flush(callback) {
30
+ emitter.emit('downloadProgress', {
31
+ percent: 1,
32
+ transferred: downloaded,
33
+ total: downloadBodySize
34
+ });
35
+
36
+ callback();
37
+ }
38
+ });
39
+
40
+ mimicResponse(response, progressStream);
41
+ progressStream.redirectUrls = redirects;
42
+
43
+ const newResponse = options.decompress === true &&
44
+ is.function(decompressResponse) &&
45
+ options.method !== 'HEAD' ? decompressResponse(progressStream) : progressStream;
46
+
47
+ if (!options.decompress && ['gzip', 'deflate'].includes(response.headers['content-encoding'])) {
48
+ options.encoding = null;
49
+ }
50
+
51
+ emitter.emit('response', newResponse);
52
+
53
+ emitter.emit('downloadProgress', {
54
+ percent: 0,
55
+ transferred: 0,
56
+ total: downloadBodySize
57
+ });
58
+
59
+ response.pipe(progressStream);
60
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+ const pkg = require('../package.json');
3
+ const create = require('./create');
4
+
5
+ const defaults = {
6
+ methods: [
7
+ 'get',
8
+ 'post',
9
+ 'put',
10
+ 'patch',
11
+ 'head',
12
+ 'delete'
13
+ ],
14
+ options: {
15
+ retry: {
16
+ retries: 2,
17
+ methods: [
18
+ 'get',
19
+ 'put',
20
+ 'head',
21
+ 'delete',
22
+ 'options',
23
+ 'trace'
24
+ ],
25
+ statusCodes: [
26
+ 408,
27
+ 413,
28
+ 429,
29
+ 502,
30
+ 503,
31
+ 504
32
+ ]
33
+ },
34
+ cache: false,
35
+ decompress: true,
36
+ useElectronNet: false,
37
+ throwHttpErrors: true,
38
+ headers: {
39
+ 'user-agent': `${pkg.name}/${pkg.version} (https://github.com/sindresorhus/got)`
40
+ },
41
+ hooks: {
42
+ beforeRequest: []
43
+ }
44
+ }
45
+ };
46
+
47
+ const got = create(defaults);
48
+
49
+ module.exports = got;
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+ const is = require('@sindresorhus/is');
3
+
4
+ module.exports = body => is.nodeStream(body) && is.function(body.getBoundary);
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const WHITELIST = new Set([
4
+ 'ETIMEDOUT',
5
+ 'ECONNRESET',
6
+ 'EADDRINUSE',
7
+ 'ECONNREFUSED',
8
+ 'EPIPE'
9
+ ]);
10
+
11
+ module.exports = error => {
12
+ if (error && WHITELIST.has(error.code)) {
13
+ return true;
14
+ }
15
+
16
+ return false;
17
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+ const {URL} = require('url');
3
+ const is = require('@sindresorhus/is');
4
+
5
+ const merge = (target, ...sources) => {
6
+ for (const source of sources) {
7
+ for (const [key, sourceValue] of Object.entries(source)) {
8
+ if (is.undefined(sourceValue)) {
9
+ continue;
10
+ }
11
+
12
+ const targetValue = target[key];
13
+ if (is.urlInstance(targetValue) && (is.urlInstance(sourceValue) || is.string(sourceValue))) {
14
+ target[key] = new URL(sourceValue, targetValue);
15
+ } else if (is.plainObject(sourceValue)) {
16
+ if (is.plainObject(targetValue)) {
17
+ target[key] = merge({}, targetValue, sourceValue);
18
+ } else {
19
+ target[key] = merge({}, sourceValue);
20
+ }
21
+ } else if (is.array(sourceValue)) {
22
+ target[key] = merge([], sourceValue);
23
+ } else {
24
+ target[key] = sourceValue;
25
+ }
26
+ }
27
+ }
28
+
29
+ return target;
30
+ };
31
+
32
+ module.exports = merge;
@@ -0,0 +1,230 @@
1
+ 'use strict';
2
+ /* istanbul ignore next: compatibility reason */
3
+ const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; // TODO: Use the `URL` global when targeting Node.js 10
4
+ /* istanbul ignore next: compatibility reason */
5
+ const URLSearchParamsGlobal = typeof URLSearchParams === 'undefined' ? require('url').URLSearchParams : URLSearchParams;
6
+ const is = require('@sindresorhus/is');
7
+ const toReadableStream = require('to-readable-stream');
8
+ const urlParseLax = require('url-parse-lax');
9
+ const isRetryOnNetworkErrorAllowed = require('./is-retry-on-network-error-allowed');
10
+ const urlToOptions = require('./url-to-options');
11
+ const isFormData = require('./is-form-data');
12
+
13
+ const retryAfterStatusCodes = new Set([413, 429, 503]);
14
+ const knownHookEvents = ['beforeRequest'];
15
+
16
+ module.exports = (url, options, defaults) => {
17
+ if (Reflect.has(options, 'url') || (is.object(url) && Reflect.has(url, 'url'))) {
18
+ throw new TypeError('Parameter `url` is not an option. Use got(url, options)');
19
+ }
20
+
21
+ if (!is.string(url) && !is.object(url)) {
22
+ throw new TypeError(`Parameter \`url\` must be a string or object, not ${is(url)}`);
23
+ }
24
+
25
+ if (is.string(url)) {
26
+ if (options.baseUrl) {
27
+ url = urlToOptions(new URLGlobal(url, options.baseUrl));
28
+ } else {
29
+ url = url.replace(/^unix:/, 'http://$&');
30
+
31
+ try {
32
+ decodeURI(url);
33
+ } catch (_) {
34
+ throw new Error('Parameter `url` must contain valid UTF-8 character sequences');
35
+ }
36
+
37
+ url = urlParseLax(url);
38
+ if (url.auth) {
39
+ throw new Error('Basic authentication must be done with the `auth` option');
40
+ }
41
+ }
42
+ } else if (is(url) === 'URL') {
43
+ url = urlToOptions(url);
44
+ }
45
+
46
+ options = {
47
+ path: '',
48
+ headers: {},
49
+ ...url,
50
+ protocol: url.protocol || 'http:', // Override both null/undefined with default protocol
51
+ ...options
52
+ };
53
+
54
+ if (options.stream && options.json) {
55
+ options.json = false;
56
+ }
57
+
58
+ if (options.decompress && is.undefined(options.headers['accept-encoding'])) {
59
+ options.headers['accept-encoding'] = 'gzip, deflate';
60
+ }
61
+
62
+ const {query} = options;
63
+ if (query) {
64
+ if (!is.string(query)) {
65
+ options.query = (new URLSearchParamsGlobal(query)).toString();
66
+ }
67
+
68
+ options.path = `${options.path.split('?')[0]}?${options.query}`;
69
+ delete options.query;
70
+ }
71
+
72
+ if (options.json && is.undefined(options.headers.accept)) {
73
+ options.headers.accept = 'application/json';
74
+ }
75
+
76
+ const {headers} = options;
77
+ for (const [key, value] of Object.entries(headers)) {
78
+ if (is.nullOrUndefined(value)) {
79
+ delete headers[key];
80
+ }
81
+ }
82
+
83
+ const {body} = options;
84
+ if (is.nullOrUndefined(body)) {
85
+ options.method = (options.method || 'GET').toUpperCase();
86
+ } else {
87
+ const isObject = is.object(body) && !Buffer.isBuffer(body) && !is.nodeStream(body);
88
+ if (!is.nodeStream(body) && !is.string(body) && !is.buffer(body) && !(options.form || options.json)) {
89
+ throw new TypeError('The `body` option must be a stream.Readable, string or Buffer');
90
+ }
91
+
92
+ if (options.json && !(isObject || is.array(body))) {
93
+ throw new TypeError('The `body` option must be an Object or Array when the `json` option is used');
94
+ }
95
+
96
+ if (options.form && !isObject) {
97
+ throw new TypeError('The `body` option must be an Object when the `form` option is used');
98
+ }
99
+
100
+ if (isFormData(body)) {
101
+ // Special case for https://github.com/form-data/form-data
102
+ headers['content-type'] = headers['content-type'] || `multipart/form-data; boundary=${body.getBoundary()}`;
103
+ } else if (options.form) {
104
+ headers['content-type'] = headers['content-type'] || 'application/x-www-form-urlencoded';
105
+ options.body = (new URLSearchParamsGlobal(body)).toString();
106
+ } else if (options.json) {
107
+ headers['content-type'] = headers['content-type'] || 'application/json';
108
+ options.body = JSON.stringify(body);
109
+ }
110
+
111
+ // Convert buffer to stream to receive upload progress events (#322)
112
+ if (is.buffer(body)) {
113
+ options.body = toReadableStream(body);
114
+ options.body._buffer = body;
115
+ }
116
+
117
+ options.method = (options.method || 'POST').toUpperCase();
118
+ }
119
+
120
+ if (options.hostname === 'unix') {
121
+ const matches = /(.+?):(.+)/.exec(options.path);
122
+
123
+ if (matches) {
124
+ const [, socketPath, path] = matches;
125
+ options = {
126
+ ...options,
127
+ socketPath,
128
+ path,
129
+ host: null
130
+ };
131
+ }
132
+ }
133
+
134
+ options.gotRetry = {retries: 0, methods: [], statusCodes: []};
135
+ if (options.retry !== false) {
136
+ if (is.number(options.retry)) {
137
+ if (is.object(defaults.options.retry)) {
138
+ options.gotRetry = {...defaults.options.retry, retries: options.retry};
139
+ } else {
140
+ options.gotRetry.retries = options.retry;
141
+ }
142
+ } else {
143
+ options.gotRetry = {...options.gotRetry, ...options.retry};
144
+ }
145
+ delete options.retry;
146
+ }
147
+
148
+ options.gotRetry.methods = new Set(options.gotRetry.methods.map(method => method.toUpperCase()));
149
+ options.gotRetry.statusCodes = new Set(options.gotRetry.statusCodes);
150
+
151
+ if (!options.gotRetry.maxRetryAfter && Reflect.has(options, 'timeout')) {
152
+ if (is.number(options.timeout)) {
153
+ options.gotRetry.maxRetryAfter = options.timeout;
154
+ } else {
155
+ options.gotRetry.maxRetryAfter = Math.min(...[options.timeout.request, options.timeout.connection].filter(n => !is.nullOrUndefined(n)));
156
+ }
157
+ }
158
+
159
+ if (!is.function(options.gotRetry.retries)) {
160
+ const {retries} = options.gotRetry;
161
+
162
+ options.gotRetry.retries = (iteration, error) => {
163
+ if (iteration > retries || (!isRetryOnNetworkErrorAllowed(error) && (!options.gotRetry.methods.has(error.method) || !options.gotRetry.statusCodes.has(error.statusCode)))) {
164
+ return 0;
165
+ }
166
+
167
+ if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
168
+ let after = Number(error.headers['retry-after']);
169
+ if (is.nan(after)) {
170
+ after = Date.parse(error.headers['retry-after']) - Date.now();
171
+ } else {
172
+ after *= 1000;
173
+ }
174
+
175
+ if (after > options.gotRetry.maxRetryAfter) {
176
+ return 0;
177
+ }
178
+
179
+ return after;
180
+ }
181
+
182
+ if (error.statusCode === 413) {
183
+ return 0;
184
+ }
185
+
186
+ const noise = Math.random() * 100;
187
+
188
+ return ((1 << iteration) * 1000) + noise;
189
+ };
190
+ }
191
+
192
+ if (is.undefined(options.followRedirect)) {
193
+ options.followRedirect = true;
194
+ }
195
+
196
+ if (is.number(options.timeout) || is.object(options.timeout)) {
197
+ if (is.number(options.timeout)) {
198
+ options.gotTimeout = {request: options.timeout};
199
+ } else {
200
+ options.gotTimeout = options.timeout;
201
+ }
202
+ delete options.timeout;
203
+ }
204
+
205
+ if (is.nullOrUndefined(options.hooks)) {
206
+ options.hooks = {};
207
+ }
208
+ if (is.object(options.hooks)) {
209
+ for (const hookEvent of knownHookEvents) {
210
+ const hooks = options.hooks[hookEvent];
211
+ if (is.nullOrUndefined(hooks)) {
212
+ options.hooks[hookEvent] = [];
213
+ } else if (is.array(hooks)) {
214
+ for (const [index, hook] of hooks.entries()) {
215
+ if (!is.function(hook)) {
216
+ throw new TypeError(
217
+ `Parameter \`hooks.${hookEvent}[${index}]\` must be a function, not ${is(hook)}`
218
+ );
219
+ }
220
+ }
221
+ } else {
222
+ throw new TypeError(`Parameter \`hooks.${hookEvent}\` must be an array, not ${is(hooks)}`);
223
+ }
224
+ }
225
+ } else {
226
+ throw new TypeError(`Parameter \`hooks\` must be an object, not ${is(options.hooks)}`);
227
+ }
228
+
229
+ return options;
230
+ };