From f0bcdc013f7ee79959cde0cbce5cd6baec866096 Mon Sep 17 00:00:00 2001 From: niftylettuce Date: Thu, 28 Mar 2019 02:32:44 -0500 Subject: [PATCH] fix: removed core-js, fixed docs with async/await + then/catch usage --- README.md | 27 +- lib/agent-base.js | 25 - lib/client.js | 1021 ------------------------ lib/is-object.js | 14 - lib/node/agent.js | 108 --- lib/node/http2wrapper.js | 203 ----- lib/node/index.js | 1331 -------------------------------- lib/node/parsers/image.js | 12 - lib/node/parsers/index.js | 11 - lib/node/parsers/json.js | 25 - lib/node/parsers/text.js | 10 - lib/node/parsers/urlencoded.js | 21 - lib/node/response.js | 125 --- lib/node/unzip.js | 71 -- lib/request-base.js | 744 ------------------ lib/response-base.js | 130 ---- lib/utils.js | 64 -- package.json | 1 - src/client.js | 16 - yarn.lock | 2 +- 20 files changed, 26 insertions(+), 3935 deletions(-) delete mode 100644 lib/agent-base.js delete mode 100644 lib/client.js delete mode 100644 lib/is-object.js delete mode 100644 lib/node/agent.js delete mode 100644 lib/node/http2wrapper.js delete mode 100644 lib/node/index.js delete mode 100644 lib/node/parsers/image.js delete mode 100644 lib/node/parsers/index.js delete mode 100644 lib/node/parsers/json.js delete mode 100644 lib/node/parsers/text.js delete mode 100644 lib/node/parsers/urlencoded.js delete mode 100644 lib/node/response.js delete mode 100644 lib/node/unzip.js delete mode 100644 lib/request-base.js delete mode 100644 lib/response-base.js delete mode 100644 lib/utils.js diff --git a/README.md b/README.md index 8967119a1..0b75e9b5b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ yarn add superagent ```js const superagent = require('superagent'); +// callback superagent .post('/api/pet') .send({ name: 'Manny', species: 'cat' }) // sends a JSON post body @@ -54,6 +55,19 @@ superagent .end((err, res) => { // Calling the end function will send the request }); + +// promise with then/catch +superagent.post('/api/pet').then(console.log).catch(console.error); + +// promise with async/await +(async () => { + try { + const res = await superagent.post('/api/pet'); + console.log(res); + } catch (err) { + console.error(err); + } +})(); ``` ### Browser @@ -69,6 +83,8 @@ Browser-ready versions of this module are available via [jsdelivr][], [unpkg][], This is the solution for you if you're just using ` @@ -141,7 +157,14 @@ If you are using [browserify][], [webpack][], [rollup][], or another bundler, th ### Required Browser Features -* IE9 requires a polyfill for `window.FormData` (we recommend [formdata-polyfill][]) +We recommend using (specifically with the bundle mentioned in [VanillaJS](#vanillajs) above): + +```html + +``` + +* IE 9-10 requires a polyfill for `Promise`, `Array.from`, `Symbol`, `Object.getOwnPropertySymbols`, and `Object.setPrototypeOf` +* IE 9 requires a polyfill for `window.FormData` (we recommend [formdata-polyfill][]) ## Plugins @@ -223,7 +246,7 @@ Our breaking changes are mostly in rarely used functionality and from stricter e [MIT](LICENSE) © TJ Holowaychuk -## +## [npm]: https://www.npmjs.com/ diff --git a/lib/agent-base.js b/lib/agent-base.js deleted file mode 100644 index cfda373e7..000000000 --- a/lib/agent-base.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; - -function Agent() { - this._defaults = []; -} - -['use', 'on', 'once', 'set', 'query', 'type', 'accept', 'auth', 'withCredentials', 'sortQuery', 'retry', 'ok', 'redirects', 'timeout', 'buffer', 'serialize', 'parse', 'ca', 'key', 'pfx', 'cert'].forEach(fn => { - // Default setting for all requests from this agent - Agent.prototype[fn] = function (...args) { - this._defaults.push({ - fn, - args - }); - - return this; - }; -}); - -Agent.prototype._setDefaults = function (req) { - this._defaults.forEach(def => { - req[def.fn](...def.args); - }); -}; - -module.exports = Agent; \ No newline at end of file diff --git a/lib/client.js b/lib/client.js deleted file mode 100644 index 30788219c..000000000 --- a/lib/client.js +++ /dev/null @@ -1,1021 +0,0 @@ -"use strict"; - -/** - * Root reference for iframes. - */ -let root; - -if (typeof window !== 'undefined') { - // Browser window - root = window; -} else if (typeof self === 'undefined') { - // Other environments - console.warn('Using browser-only version of superagent in non-browser environment'); - root = void 0; -} else { - // Web Worker - root = self; -} // Array.from() is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import - - -require('core-js/features/array/from'); // Symbol is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import - - -require('core-js/features/symbol'); // Object.getOwnPropertySymbols() is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import - - -require('core-js/features/object/get-own-property-symbols'); // Object.setPrototypeOf() is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import - - -require('core-js/features/object/set-prototype-of'); - -const Emitter = require('component-emitter'); - -const RequestBase = require('./request-base'); - -const isObject = require('./is-object'); - -const ResponseBase = require('./response-base'); - -const Agent = require('./agent-base'); -/** - * Noop. - */ - - -function noop() {} -/** - * Expose `request`. - */ - - -module.exports = function (method, url) { - // callback - if (typeof url === 'function') { - return new exports.Request('GET', method).end(url); - } // url first - - - if (arguments.length === 1) { - return new exports.Request('GET', method); - } - - return new exports.Request(method, url); -}; - -exports = module.exports; -const request = exports; -exports.Request = Request; -/** - * Determine XHR. - */ - -request.getXHR = () => { - if (root.XMLHttpRequest && (!root.location || root.location.protocol !== 'file:' || !root.ActiveXObject)) { - return new XMLHttpRequest(); - } - - try { - return new ActiveXObject('Microsoft.XMLHTTP'); - } catch (err) {} - - try { - return new ActiveXObject('Msxml2.XMLHTTP.6.0'); - } catch (err) {} - - try { - return new ActiveXObject('Msxml2.XMLHTTP.3.0'); - } catch (err) {} - - try { - return new ActiveXObject('Msxml2.XMLHTTP'); - } catch (err) {} - - throw new Error('Browser-only version of superagent could not find XHR'); -}; -/** - * Removes leading and trailing whitespace, added to support IE. - * - * @param {String} s - * @return {String} - * @api private - */ - - -const trim = ''.trim ? s => s.trim() : s => s.replace(/(^\s*|\s*$)/g, ''); -/** - * Serialize the given `obj`. - * - * @param {Object} obj - * @return {String} - * @api private - */ - -function serialize(obj) { - if (!isObject(obj)) return obj; - const pairs = []; - - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) pushEncodedKeyValuePair(pairs, key, obj[key]); - } - - return pairs.join('&'); -} -/** - * Helps 'serialize' with serializing arrays. - * Mutates the pairs array. - * - * @param {Array} pairs - * @param {String} key - * @param {Mixed} val - */ - - -function pushEncodedKeyValuePair(pairs, key, val) { - if (val !== null) { - if (Array.isArray(val)) { - val.forEach(v => { - pushEncodedKeyValuePair(pairs, key, v); - }); - } else if (isObject(val)) { - for (const subkey in val) { - if (Object.prototype.hasOwnProperty.call(val, subkey)) pushEncodedKeyValuePair(pairs, `${key}[${subkey}]`, val[subkey]); - } - } else { - pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(val)); - } - } else if (val === null) { - pairs.push(encodeURIComponent(key)); - } -} -/** - * Expose serialization method. - */ - - -request.serializeObject = serialize; -/** - * Parse the given x-www-form-urlencoded `str`. - * - * @param {String} str - * @return {Object} - * @api private - */ - -function parseString(str) { - const obj = {}; - const pairs = str.split('&'); - let pair; - let pos; - - for (let i = 0, len = pairs.length; i < len; ++i) { - pair = pairs[i]; - pos = pair.indexOf('='); - - if (pos === -1) { - obj[decodeURIComponent(pair)] = ''; - } else { - obj[decodeURIComponent(pair.slice(0, pos))] = decodeURIComponent(pair.slice(pos + 1)); - } - } - - return obj; -} -/** - * Expose parser. - */ - - -request.parseString = parseString; -/** - * Default MIME type map. - * - * superagent.types.xml = 'application/xml'; - * - */ - -request.types = { - html: 'text/html', - json: 'application/json', - xml: 'text/xml', - urlencoded: 'application/x-www-form-urlencoded', - form: 'application/x-www-form-urlencoded', - 'form-data': 'application/x-www-form-urlencoded' -}; -/** - * Default serialization map. - * - * superagent.serialize['application/xml'] = function(obj){ - * return 'generated xml here'; - * }; - * - */ - -request.serialize = { - 'application/x-www-form-urlencoded': serialize, - 'application/json': JSON.stringify -}; -/** - * Default parsers. - * - * superagent.parse['application/xml'] = function(str){ - * return { object parsed from str }; - * }; - * - */ - -request.parse = { - 'application/x-www-form-urlencoded': parseString, - 'application/json': JSON.parse -}; -/** - * Parse the given header `str` into - * an object containing the mapped fields. - * - * @param {String} str - * @return {Object} - * @api private - */ - -function parseHeader(str) { - const lines = str.split(/\r?\n/); - const fields = {}; - let index; - let line; - let field; - let val; - - for (let i = 0, len = lines.length; i < len; ++i) { - line = lines[i]; - index = line.indexOf(':'); - - if (index === -1) { - // could be empty line, just skip it - continue; - } - - field = line.slice(0, index).toLowerCase(); - val = trim(line.slice(index + 1)); - fields[field] = val; - } - - return fields; -} -/** - * Check if `mime` is json or has +json structured syntax suffix. - * - * @param {String} mime - * @return {Boolean} - * @api private - */ - - -function isJSON(mime) { - // should match /json or +json - // but not /json-seq - return /[/+]json($|[^-\w])/.test(mime); -} -/** - * Initialize a new `Response` with the given `xhr`. - * - * - set flags (.ok, .error, etc) - * - parse header - * - * Examples: - * - * Aliasing `superagent` as `request` is nice: - * - * request = superagent; - * - * We can use the promise-like API, or pass callbacks: - * - * request.get('/').end(function(res){}); - * request.get('/', function(res){}); - * - * Sending data can be chained: - * - * request - * .post('/user') - * .send({ name: 'tj' }) - * .end(function(res){}); - * - * Or passed to `.send()`: - * - * request - * .post('/user') - * .send({ name: 'tj' }, function(res){}); - * - * Or passed to `.post()`: - * - * request - * .post('/user', { name: 'tj' }) - * .end(function(res){}); - * - * Or further reduced to a single call for simple cases: - * - * request - * .post('/user', { name: 'tj' }, function(res){}); - * - * @param {XMLHTTPRequest} xhr - * @param {Object} options - * @api private - */ - - -function Response(req) { - this.req = req; - this.xhr = this.req.xhr; // responseText is accessible only if responseType is '' or 'text' and on older browsers - - this.text = this.req.method !== 'HEAD' && (this.xhr.responseType === '' || this.xhr.responseType === 'text') || typeof this.xhr.responseType === 'undefined' ? this.xhr.responseText : null; - this.statusText = this.req.xhr.statusText; - let status = this.xhr.status; // handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request - - if (status === 1223) { - status = 204; - } - - this._setStatusProperties(status); - - this.headers = parseHeader(this.xhr.getAllResponseHeaders()); - this.header = this.headers; // getAllResponseHeaders sometimes falsely returns "" for CORS requests, but - // getResponseHeader still works. so we get content-type even if getting - // other headers fails. - - this.header['content-type'] = this.xhr.getResponseHeader('content-type'); - - this._setHeaderProperties(this.header); - - if (this.text === null && req._responseType) { - this.body = this.xhr.response; - } else { - this.body = this.req.method === 'HEAD' ? null : this._parseBody(this.text ? this.text : this.xhr.response); - } -} // eslint-disable-next-line new-cap - - -ResponseBase(Response.prototype); -/** - * Parse the given body `str`. - * - * Used for auto-parsing of bodies. Parsers - * are defined on the `superagent.parse` object. - * - * @param {String} str - * @return {Mixed} - * @api private - */ - -Response.prototype._parseBody = function (str) { - let parse = request.parse[this.type]; - - if (this.req._parser) { - return this.req._parser(this, str); - } - - if (!parse && isJSON(this.type)) { - parse = request.parse['application/json']; - } - - return parse && str && (str.length > 0 || str instanceof Object) ? parse(str) : null; -}; -/** - * Return an `Error` representative of this response. - * - * @return {Error} - * @api public - */ - - -Response.prototype.toError = function () { - const req = this.req; - const method = req.method; - const url = req.url; - const msg = `cannot ${method} ${url} (${this.status})`; - const err = new Error(msg); - err.status = this.status; - err.method = method; - err.url = url; - return err; -}; -/** - * Expose `Response`. - */ - - -request.Response = Response; -/** - * Initialize a new `Request` with the given `method` and `url`. - * - * @param {String} method - * @param {String} url - * @api public - */ - -function Request(method, url) { - const self = this; - this._query = this._query || []; - this.method = method; - this.url = url; - this.header = {}; // preserves header name case - - this._header = {}; // coerces header names to lowercase - - this.on('end', () => { - let err = null; - let res = null; - - try { - res = new Response(self); - } catch (err2) { - err = new Error('Parser is unable to parse the response'); - err.parse = true; - err.original = err2; // issue #675: return the raw response if the response parsing fails - - if (self.xhr) { - // ie9 doesn't have 'response' property - err.rawResponse = typeof self.xhr.responseType === 'undefined' ? self.xhr.responseText : self.xhr.response; // issue #876: return the http status code if the response parsing fails - - err.status = self.xhr.status ? self.xhr.status : null; - err.statusCode = err.status; // backwards-compat only - } else { - err.rawResponse = null; - err.status = null; - } - - return self.callback(err); - } - - self.emit('response', res); - let new_err; - - try { - if (!self._isResponseOK(res)) { - new_err = new Error(res.statusText || 'Unsuccessful HTTP response'); - } - } catch (err2) { - new_err = err2; // ok() callback can throw - } // #1000 don't catch errors from the callback to avoid double calling it - - - if (new_err) { - new_err.original = err; - new_err.response = res; - new_err.status = res.status; - self.callback(new_err, res); - } else { - self.callback(null, res); - } - }); -} -/** - * Mixin `Emitter` and `RequestBase`. - */ -// eslint-disable-next-line new-cap - - -Emitter(Request.prototype); // eslint-disable-next-line new-cap - -RequestBase(Request.prototype); -/** - * Set Content-Type to `type`, mapping values from `request.types`. - * - * Examples: - * - * superagent.types.xml = 'application/xml'; - * - * request.post('/') - * .type('xml') - * .send(xmlstring) - * .end(callback); - * - * request.post('/') - * .type('application/xml') - * .send(xmlstring) - * .end(callback); - * - * @param {String} type - * @return {Request} for chaining - * @api public - */ - -Request.prototype.type = function (type) { - this.set('Content-Type', request.types[type] || type); - return this; -}; -/** - * Set Accept to `type`, mapping values from `request.types`. - * - * Examples: - * - * superagent.types.json = 'application/json'; - * - * request.get('/agent') - * .accept('json') - * .end(callback); - * - * request.get('/agent') - * .accept('application/json') - * .end(callback); - * - * @param {String} accept - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.accept = function (type) { - this.set('Accept', request.types[type] || type); - return this; -}; -/** - * Set Authorization field value with `user` and `pass`. - * - * @param {String} user - * @param {String} [pass] optional in case of using 'bearer' as type - * @param {Object} options with 'type' property 'auto', 'basic' or 'bearer' (default 'basic') - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.auth = function (user, pass, options) { - if (arguments.length === 1) pass = ''; - - if (typeof pass === 'object' && pass !== null) { - // pass is optional and can be replaced with options - options = pass; - pass = ''; - } - - if (!options) { - options = { - type: typeof btoa === 'function' ? 'basic' : 'auto' - }; - } - - const encoder = string => { - if (typeof btoa === 'function') { - return btoa(string); - } - - throw new Error('Cannot use basic auth, btoa is not a function'); - }; - - return this._auth(user, pass, options, encoder); -}; -/** - * Add query-string `val`. - * - * Examples: - * - * request.get('/shoes') - * .query('size=10') - * .query({ color: 'blue' }) - * - * @param {Object|String} val - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.query = function (val) { - if (typeof val !== 'string') val = serialize(val); - if (val) this._query.push(val); - return this; -}; -/** - * Queue the given `file` as an attachment to the specified `field`, - * with optional `options` (or filename). - * - * ``` js - * request.post('/upload') - * .attach('content', new Blob(['hey!'], { type: "text/html"})) - * .end(callback); - * ``` - * - * @param {String} field - * @param {Blob|File} file - * @param {String|Object} options - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.attach = function (field, file, options) { - if (file) { - if (this._data) { - throw new Error("superagent can't mix .send() and .attach()"); - } - - this._getFormData().append(field, file, options || file.name); - } - - return this; -}; - -Request.prototype._getFormData = function () { - if (!this._formData) { - this._formData = new root.FormData(); - } - - return this._formData; -}; -/** - * Invoke the callback with `err` and `res` - * and handle arity check. - * - * @param {Error} err - * @param {Response} res - * @api private - */ - - -Request.prototype.callback = function (err, res) { - if (this._shouldRetry(err, res)) { - return this._retry(); - } - - const fn = this._callback; - this.clearTimeout(); - - if (err) { - if (this._maxRetries) err.retries = this._retries - 1; - this.emit('error', err); - } - - fn(err, res); -}; -/** - * Invoke callback with x-domain error. - * - * @api private - */ - - -Request.prototype.crossDomainError = function () { - const err = new Error('Request has been terminated\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.'); - err.crossDomain = true; - err.status = this.status; - err.method = this.method; - err.url = this.url; - this.callback(err); -}; // This only warns, because the request is still likely to work - - -Request.prototype.agent = function () { - console.warn('This is not supported in browser version of superagent'); - return this; -}; - -Request.prototype.buffer = Request.prototype.ca; -Request.prototype.ca = Request.prototype.agent; // This throws, because it can't send/receive data as expected - -Request.prototype.write = () => { - throw new Error('Streaming is not supported in browser version of superagent'); -}; - -Request.prototype.pipe = Request.prototype.write; -/** - * Check if `obj` is a host object, - * we don't want to serialize these :) - * - * @param {Object} obj host object - * @return {Boolean} is a host object - * @api private - */ - -Request.prototype._isHost = function (obj) { - // Native objects stringify to [object File], [object Blob], [object FormData], etc. - return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.prototype.toString.call(obj) !== '[object Object]'; -}; -/** - * Initiate request, invoking callback `fn(res)` - * with an instanceof `Response`. - * - * @param {Function} fn - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.end = function (fn) { - if (this._endCalled) { - console.warn('Warning: .end() was called twice. This is not supported in superagent'); - } - - this._endCalled = true; // store callback - - this._callback = fn || noop; // querystring - - this._finalizeQueryString(); - - this._end(); -}; - -Request.prototype._setUploadTimeout = function () { - const self = this; // upload timeout it's wokrs only if deadline timeout is off - - if (this._uploadTimeout && !this._uploadTimeoutTimer) { - this._uploadTimeoutTimer = setTimeout(() => { - self._timeoutError('Upload timeout of ', self._uploadTimeout, 'ETIMEDOUT'); - }, this._uploadTimeout); - } -}; // eslint-disable-next-line complexity - - -Request.prototype._end = function () { - if (this._aborted) return this.callback(new Error('The request has been aborted even before .end() was called')); - const self = this; - this.xhr = request.getXHR(); - const xhr = this.xhr; - let data = this._formData || this._data; - - this._setTimeouts(); // state change - - - xhr.onreadystatechange = () => { - const readyState = xhr.readyState; - - if (readyState >= 2 && self._responseTimeoutTimer) { - clearTimeout(self._responseTimeoutTimer); - } - - if (readyState !== 4) { - return; - } // 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" - - - let status; - - try { - status = xhr.status; - } catch (err) { - status = 0; - } - - if (!status) { - if (self.timedout || self._aborted) return; - return self.crossDomainError(); - } - - self.emit('end'); - }; // progress - - - const handleProgress = (direction, e) => { - if (e.total > 0) { - e.percent = e.loaded / e.total * 100; - - if (e.percent === 100) { - clearTimeout(self._uploadTimeoutTimer); - } - } - - e.direction = direction; - self.emit('progress', e); - }; - - if (this.hasListeners('progress')) { - try { - xhr.addEventListener('progress', handleProgress.bind(null, 'download')); - - if (xhr.upload) { - xhr.upload.addEventListener('progress', handleProgress.bind(null, 'upload')); - } - } catch (err) {// 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 - } - } - - if (xhr.upload) { - this._setUploadTimeout(); - } // initiate request - - - try { - if (this.username && this.password) { - xhr.open(this.method, this.url, true, this.username, this.password); - } else { - xhr.open(this.method, this.url, true); - } - } catch (err) { - // see #1149 - return this.callback(err); - } // CORS - - - if (this._withCredentials) xhr.withCredentials = true; // body - - if (!this._formData && this.method !== 'GET' && this.method !== 'HEAD' && typeof data !== 'string' && !this._isHost(data)) { - // serialize stuff - const contentType = this._header['content-type']; - let serialize = this._serializer || request.serialize[contentType ? contentType.split(';')[0] : '']; - - if (!serialize && isJSON(contentType)) { - serialize = request.serialize['application/json']; - } - - if (serialize) data = serialize(data); - } // set header fields - - - for (const field in this.header) { - if (this.header[field] === null) continue; - if (Object.prototype.hasOwnProperty.call(this.header, field)) xhr.setRequestHeader(field, this.header[field]); - } - - if (this._responseType) { - xhr.responseType = this._responseType; - } // send stuff - - - this.emit('request', this); // IE11 xhr.send(undefined) sends 'undefined' string as POST payload (instead of nothing) - // We need null here if data is undefined - - xhr.send(typeof data === 'undefined' ? null : data); -}; - -request.agent = () => new Agent(); - -['GET', 'POST', 'OPTIONS', 'PATCH', 'PUT', 'DELETE'].forEach(method => { - Agent.prototype[method.toLowerCase()] = function (url, fn) { - const req = new request.Request(method, url); - - this._setDefaults(req); - - if (fn) { - req.end(fn); - } - - return req; - }; -}); -Agent.prototype.del = Agent.prototype.delete; -/** - * GET `url` with optional callback `fn(res)`. - * - * @param {String} url - * @param {Mixed|Function} [data] or fn - * @param {Function} [fn] - * @return {Request} - * @api public - */ - -request.get = (url, data, fn) => { - const req = request('GET', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.query(data); - if (fn) req.end(fn); - return req; -}; -/** - * HEAD `url` with optional callback `fn(res)`. - * - * @param {String} url - * @param {Mixed|Function} [data] or fn - * @param {Function} [fn] - * @return {Request} - * @api public - */ - - -request.head = (url, data, fn) => { - const req = request('HEAD', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.query(data); - if (fn) req.end(fn); - return req; -}; -/** - * OPTIONS query to `url` with optional callback `fn(res)`. - * - * @param {String} url - * @param {Mixed|Function} [data] or fn - * @param {Function} [fn] - * @return {Request} - * @api public - */ - - -request.options = (url, data, fn) => { - const req = request('OPTIONS', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.send(data); - if (fn) req.end(fn); - return req; -}; -/** - * DELETE `url` with optional `data` and callback `fn(res)`. - * - * @param {String} url - * @param {Mixed} [data] - * @param {Function} [fn] - * @return {Request} - * @api public - */ - - -function del(url, data, fn) { - const req = request('DELETE', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.send(data); - if (fn) req.end(fn); - return req; -} - -request.del = del; -request.delete = del; -/** - * PATCH `url` with optional `data` and callback `fn(res)`. - * - * @param {String} url - * @param {Mixed} [data] - * @param {Function} [fn] - * @return {Request} - * @api public - */ - -request.patch = (url, data, fn) => { - const req = request('PATCH', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.send(data); - if (fn) req.end(fn); - return req; -}; -/** - * POST `url` with optional `data` and callback `fn(res)`. - * - * @param {String} url - * @param {Mixed} [data] - * @param {Function} [fn] - * @return {Request} - * @api public - */ - - -request.post = (url, data, fn) => { - const req = request('POST', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.send(data); - if (fn) req.end(fn); - return req; -}; -/** - * PUT `url` with optional `data` and callback `fn(res)`. - * - * @param {String} url - * @param {Mixed|Function} [data] or fn - * @param {Function} [fn] - * @return {Request} - * @api public - */ - - -request.put = (url, data, fn) => { - const req = request('PUT', url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) req.send(data); - if (fn) req.end(fn); - return req; -}; \ No newline at end of file diff --git a/lib/is-object.js b/lib/is-object.js deleted file mode 100644 index 77b6a126b..000000000 --- a/lib/is-object.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -/** - * Check if `obj` is an object. - * - * @param {Object} obj - * @return {Boolean} - * @api private - */ -function isObject(obj) { - return obj !== null && typeof obj === 'object'; -} - -module.exports = isObject; \ No newline at end of file diff --git a/lib/node/agent.js b/lib/node/agent.js deleted file mode 100644 index 8bd3398d6..000000000 --- a/lib/node/agent.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; -/** - * Module dependencies. - */ -// eslint-disable-next-line node/no-deprecated-api - -const _require = require('url'), - parse = _require.parse; - -const _require2 = require('cookiejar'), - CookieJar = _require2.CookieJar; - -const _require3 = require('cookiejar'), - CookieAccessInfo = _require3.CookieAccessInfo; - -const methods = require('methods'); - -const request = require('../..'); - -const AgentBase = require('../agent-base'); -/** - * Expose `Agent`. - */ - - -module.exports = Agent; -/** - * Initialize a new `Agent`. - * - * @api public - */ - -function Agent(options) { - if (!(this instanceof Agent)) { - return new Agent(options); - } - - AgentBase.call(this); - this.jar = new CookieJar(); - - if (options) { - if (options.ca) { - this.ca(options.ca); - } - - if (options.key) { - this.key(options.key); - } - - if (options.pfx) { - this.pfx(options.pfx); - } - - if (options.cert) { - this.cert(options.cert); - } - } -} - -Agent.prototype = Object.create(AgentBase.prototype); -/** - * Save the cookies in the given `res` to - * the agent's cookie jar for persistence. - * - * @param {Response} res - * @api private - */ - -Agent.prototype._saveCookies = function (res) { - const cookies = res.headers['set-cookie']; - if (cookies) this.jar.setCookies(cookies); -}; -/** - * Attach cookies when available to the given `req`. - * - * @param {Request} req - * @api private - */ - - -Agent.prototype._attachCookies = function (req) { - const url = parse(req.url); - const access = new CookieAccessInfo(url.hostname, url.pathname, url.protocol === 'https:'); - const cookies = this.jar.getCookies(access).toValueString(); - req.cookies = cookies; -}; - -methods.forEach(name => { - const method = name.toUpperCase(); - - Agent.prototype[name] = function (url, fn) { - const req = new request.Request(method, url); - req.on('response', this._saveCookies.bind(this)); - req.on('redirect', this._saveCookies.bind(this)); - req.on('redirect', this._attachCookies.bind(this, req)); - - this._attachCookies(req); - - this._setDefaults(req); - - if (fn) { - req.end(fn); - } - - return req; - }; -}); -Agent.prototype.del = Agent.prototype.delete; \ No newline at end of file diff --git a/lib/node/http2wrapper.js b/lib/node/http2wrapper.js deleted file mode 100644 index 49df6aaae..000000000 --- a/lib/node/http2wrapper.js +++ /dev/null @@ -1,203 +0,0 @@ -'use strict'; - -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - -const http2 = require('http2'); - -const Stream = require('stream'); - -const util = require('util'); - -const net = require('net'); - -const tls = require('tls'); // eslint-disable-next-line node/no-deprecated-api - - -const _require = require('url'), - parse = _require.parse; - -const _http2$constants = http2.constants, - HTTP2_HEADER_PATH = _http2$constants.HTTP2_HEADER_PATH, - HTTP2_HEADER_STATUS = _http2$constants.HTTP2_HEADER_STATUS, - HTTP2_HEADER_METHOD = _http2$constants.HTTP2_HEADER_METHOD, - HTTP2_HEADER_AUTHORITY = _http2$constants.HTTP2_HEADER_AUTHORITY, - HTTP2_HEADER_HOST = _http2$constants.HTTP2_HEADER_HOST, - HTTP2_HEADER_SET_COOKIE = _http2$constants.HTTP2_HEADER_SET_COOKIE, - NGHTTP2_CANCEL = _http2$constants.NGHTTP2_CANCEL; - -function setProtocol(protocol) { - return { - request(options) { - return new Request(protocol, options); - } - - }; -} - -function Request(protocol, options) { - Stream.call(this); - const defaultPort = protocol === 'https:' ? 443 : 80; - const defaultHost = 'localhost'; - const port = options.port || defaultPort; - const host = options.host || defaultHost; - delete options.port; - delete options.host; - this.method = options.method; - this.path = options.path; - this.protocol = protocol; - this.host = host; - delete options.method; - delete options.path; - - const sessionOptions = _objectSpread({}, options); - - if (options.socketPath) { - sessionOptions.socketPath = options.socketPath; - sessionOptions.createConnection = this.createUnixConnection.bind(this); - } - - this._headers = {}; - const session = http2.connect(`${protocol}//${host}:${port}`, sessionOptions); - this.setHeader('host', `${host}:${port}`); - session.on('error', err => this.emit('error', err)); - this.session = session; -} -/** - * Inherit from `Stream` (which inherits from `EventEmitter`). - */ - - -util.inherits(Request, Stream); - -Request.prototype.createUnixConnection = function (authority, options) { - switch (this.protocol) { - case 'http:': - return net.connect(options.socketPath); - - case 'https:': - options.ALPNProtocols = ['h2']; - options.servername = this.host; - options.allowHalfOpen = true; - return tls.connect(options.socketPath, options); - - default: - throw new Error('Unsupported protocol', this.protocol); - } -}; // eslint-disable-next-line no-unused-vars - - -Request.prototype.setNoDelay = function (bool) {// We can not use setNoDelay with HTTP/2. - // Node 10 limits http2session.socket methods to ones safe to use with HTTP/2. - // See also https://nodejs.org/api/http2.html#http2_http2session_socket -}; - -Request.prototype.getFrame = function () { - if (this.frame) { - return this.frame; - } - - const method = { - [HTTP2_HEADER_PATH]: this.path, - [HTTP2_HEADER_METHOD]: this.method - }; - let headers = this.mapToHttp2Header(this._headers); - headers = Object.assign(headers, method); - const frame = this.session.request(headers); // eslint-disable-next-line no-unused-vars - - frame.once('response', (headers, flags) => { - headers = this.mapToHttpHeader(headers); - frame.headers = headers; - frame.statusCode = headers[HTTP2_HEADER_STATUS]; - frame.status = frame.statusCode; - this.emit('response', frame); - }); - this._headerSent = true; - frame.once('drain', () => this.emit('drain')); - frame.on('error', err => this.emit('error', err)); - frame.on('close', () => this.session.close()); - this.frame = frame; - return frame; -}; - -Request.prototype.mapToHttpHeader = function (headers) { - const keys = Object.keys(headers); - const http2Headers = {}; - - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let value = headers[key]; - key = key.toLowerCase(); - - switch (key) { - case HTTP2_HEADER_SET_COOKIE: - value = Array.isArray(value) ? value : [value]; - break; - - default: - break; - } - - http2Headers[key] = value; - } - - return http2Headers; -}; - -Request.prototype.mapToHttp2Header = function (headers) { - const keys = Object.keys(headers); - const http2Headers = {}; - - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let value = headers[key]; - key = key.toLowerCase(); - - switch (key) { - case HTTP2_HEADER_HOST: - key = HTTP2_HEADER_AUTHORITY; - value = /^http:\/\/|^https:\/\//.test(value) ? parse(value).host : value; - break; - - default: - break; - } - - http2Headers[key] = value; - } - - return http2Headers; -}; - -Request.prototype.setHeader = function (name, value) { - this._headers[name.toLowerCase()] = value; -}; - -Request.prototype.getHeader = function (name) { - return this._headers[name.toLowerCase()]; -}; - -Request.prototype.write = function (data, encoding) { - const frame = this.getFrame(); - return frame.write(data, encoding); -}; - -Request.prototype.pipe = function (stream, options) { - const frame = this.getFrame(); - return frame.pipe(stream, options); -}; - -Request.prototype.end = function (data) { - const frame = this.getFrame(); - frame.end(data); -}; // eslint-disable-next-line no-unused-vars - - -Request.prototype.abort = function (data) { - const frame = this.getFrame(); - frame.close(NGHTTP2_CANCEL); - this.session.destroy(); -}; - -exports.setProtocol = setProtocol; \ No newline at end of file diff --git a/lib/node/index.js b/lib/node/index.js deleted file mode 100644 index 16817685a..000000000 --- a/lib/node/index.js +++ /dev/null @@ -1,1331 +0,0 @@ -'use strict'; -/** - * Module dependencies. - */ -// eslint-disable-next-line node/no-deprecated-api - -const _require = require('url'), - parse = _require.parse, - format = _require.format, - resolve = _require.resolve; - -const Stream = require('stream'); - -const https = require('https'); - -const http = require('http'); - -const fs = require('fs'); - -const zlib = require('zlib'); - -const util = require('util'); - -const qs = require('qs'); - -const mime = require('mime'); - -let methods = require('methods'); - -const FormData = require('form-data'); - -const formidable = require('formidable'); - -const debug = require('debug')('superagent'); - -const CookieJar = require('cookiejar'); - -const utils = require('../utils'); - -const pkg = require('../../package.json'); - -const RequestBase = require('../request-base'); - -const _require2 = require('./unzip'), - unzip = _require2.unzip; - -const Response = require('./response'); - -let http2; - -try { - http2 = require('./http2wrapper'); -} catch (_) {} - -function request(method, url) { - // callback - if (typeof url === 'function') { - return new exports.Request('GET', method).end(url); - } // url first - - - if (arguments.length === 1) { - return new exports.Request('GET', method); - } - - return new exports.Request(method, url); -} - -module.exports = request; -exports = module.exports; -/** - * Expose `Request`. - */ - -exports.Request = Request; -/** - * Expose the agent function - */ - -exports.agent = require('./agent'); -/** - * Noop. - */ - -function noop() {} -/** - * Expose `Response`. - */ - - -exports.Response = Response; -/** - * Define "form" mime type. - */ - -mime.define({ - 'application/x-www-form-urlencoded': ['form', 'urlencoded', 'form-data'] -}, true); -/** - * Protocol map. - */ - -exports.protocols = { - 'http:': http, - 'https:': https, - 'http2:': http2 -}; -/** - * Default serialization map. - * - * superagent.serialize['application/xml'] = function(obj){ - * return 'generated xml here'; - * }; - * - */ - -exports.serialize = { - 'application/x-www-form-urlencoded': qs.stringify, - 'application/json': JSON.stringify -}; -/** - * Default parsers. - * - * superagent.parse['application/xml'] = function(res, fn){ - * fn(null, res); - * }; - * - */ - -exports.parse = require('./parsers'); -/** - * Default buffering map. Can be used to set certain - * response types to buffer/not buffer. - * - * superagent.buffer['application/xml'] = true; - */ - -exports.buffer = {}; -/** - * Initialize internal header tracking properties on a request instance. - * - * @param {Object} req the instance - * @api private - */ - -function _initHeaders(req) { - const ua = `node-superagent/${pkg.version}`; - req._header = { - // coerces header names to lowercase - 'user-agent': ua - }; - req.header = { - // preserves header name case - 'User-Agent': ua - }; -} -/** - * Initialize a new `Request` with the given `method` and `url`. - * - * @param {String} method - * @param {String|Object} url - * @api public - */ - - -function Request(method, url) { - Stream.call(this); - if (typeof url !== 'string') url = format(url); - this._enableHttp2 = Boolean(process.env.HTTP2_TEST); // internal only - - this._agent = false; - this._formData = null; - this.method = method; - this.url = url; - - _initHeaders(this); - - this.writable = true; - this._redirects = 0; - this.redirects(method === 'HEAD' ? 0 : 5); - this.cookies = ''; - this.qs = {}; - this._query = []; - this.qsRaw = this._query; // Unused, for backwards compatibility only - - this._redirectList = []; - this._streamRequest = false; - this.once('end', this.clearTimeout.bind(this)); -} -/** - * Inherit from `Stream` (which inherits from `EventEmitter`). - * Mixin `RequestBase`. - */ - - -util.inherits(Request, Stream); // eslint-disable-next-line new-cap - -RequestBase(Request.prototype); -/** - * Enable or Disable http2. - * - * Enable http2. - * - * ``` js - * request.get('http://localhost/') - * .http2() - * .end(callback); - * - * request.get('http://localhost/') - * .http2(true) - * .end(callback); - * ``` - * - * Disable http2. - * - * ``` js - * request = request.http2(); - * request.get('http://localhost/') - * .http2(false) - * .end(callback); - * ``` - * - * @param {Boolean} enable - * @return {Request} for chaining - * @api public - */ - -Request.prototype.http2 = function (bool) { - if (exports.protocols['http2:'] === undefined) { - throw new Error('superagent: this version of Node.js does not support http2'); - } - - this._enableHttp2 = bool === undefined ? true : bool; - return this; -}; -/** - * Queue the given `file` as an attachment to the specified `field`, - * with optional `options` (or filename). - * - * ``` js - * request.post('http://localhost/upload') - * .attach('field', Buffer.from('Hello world'), 'hello.html') - * .end(callback); - * ``` - * - * A filename may also be used: - * - * ``` js - * request.post('http://localhost/upload') - * .attach('files', 'image.jpg') - * .end(callback); - * ``` - * - * @param {String} field - * @param {String|fs.ReadStream|Buffer} file - * @param {String|Object} options - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.attach = function (field, file, options) { - if (file) { - if (this._data) { - throw new Error("superagent can't mix .send() and .attach()"); - } - - let o = options || {}; - - if (typeof options === 'string') { - o = { - filename: options - }; - } - - if (typeof file === 'string') { - if (!o.filename) o.filename = file; - debug('creating `fs.ReadStream` instance for file: %s', file); - file = fs.createReadStream(file); - } else if (!o.filename && file.path) { - o.filename = file.path; - } - - this._getFormData().append(field, file, o); - } - - return this; -}; - -Request.prototype._getFormData = function () { - if (!this._formData) { - this._formData = new FormData(); - - this._formData.on('error', err => { - debug('FormData error', err); - - if (this.called) { - // The request has already finished and the callback was called. - // Silently ignore the error. - return; - } - - this.callback(err); - this.abort(); - }); - } - - return this._formData; -}; -/** - * Gets/sets the `Agent` to use for this HTTP request. The default (if this - * function is not called) is to opt out of connection pooling (`agent: false`). - * - * @param {http.Agent} agent - * @return {http.Agent} - * @api public - */ - - -Request.prototype.agent = function (agent) { - if (arguments.length === 0) return this._agent; - this._agent = agent; - return this; -}; -/** - * Set _Content-Type_ response header passed through `mime.getType()`. - * - * Examples: - * - * request.post('/') - * .type('xml') - * .send(xmlstring) - * .end(callback); - * - * request.post('/') - * .type('json') - * .send(jsonstring) - * .end(callback); - * - * request.post('/') - * .type('application/json') - * .send(jsonstring) - * .end(callback); - * - * @param {String} type - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.type = function (type) { - return this.set('Content-Type', type.indexOf('/') === -1 ? mime.getType(type) : type); -}; -/** - * Set _Accept_ response header passed through `mime.getType()`. - * - * Examples: - * - * superagent.types.json = 'application/json'; - * - * request.get('/agent') - * .accept('json') - * .end(callback); - * - * request.get('/agent') - * .accept('application/json') - * .end(callback); - * - * @param {String} accept - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.accept = function (type) { - return this.set('Accept', type.indexOf('/') === -1 ? mime.getType(type) : type); -}; -/** - * Add query-string `val`. - * - * Examples: - * - * request.get('/shoes') - * .query('size=10') - * .query({ color: 'blue' }) - * - * @param {Object|String} val - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.query = function (val) { - if (typeof val === 'string') { - this._query.push(val); - } else { - Object.assign(this.qs, val); - } - - return this; -}; -/** - * Write raw `data` / `encoding` to the socket. - * - * @param {Buffer|String} data - * @param {String} encoding - * @return {Boolean} - * @api public - */ - - -Request.prototype.write = function (data, encoding) { - const req = this.request(); - - if (!this._streamRequest) { - this._streamRequest = true; - } - - return req.write(data, encoding); -}; -/** - * Pipe the request body to `stream`. - * - * @param {Stream} stream - * @param {Object} options - * @return {Stream} - * @api public - */ - - -Request.prototype.pipe = function (stream, options) { - this.piped = true; // HACK... - - this.buffer(false); - this.end(); - return this._pipeContinue(stream, options); -}; - -Request.prototype._pipeContinue = function (stream, options) { - this.req.once('response', res => { - // redirect - const redirect = isRedirect(res.statusCode); - - if (redirect && this._redirects++ !== this._maxRedirects) { - return this._redirect(res)._pipeContinue(stream, options); - } - - this.res = res; - - this._emitResponse(); - - if (this._aborted) return; - - if (this._shouldUnzip(res)) { - const unzipObj = zlib.createUnzip(); - unzipObj.on('error', err => { - if (err && err.code === 'Z_BUF_ERROR') { - // unexpected end of file is ignored by browsers and curl - stream.emit('end'); - return; - } - - stream.emit('error', err); - }); - res.pipe(unzipObj).pipe(stream, options); - } else { - res.pipe(stream, options); - } - - res.once('end', () => { - this.emit('end'); - }); - }); - return stream; -}; -/** - * Enable / disable buffering. - * - * @return {Boolean} [val] - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.buffer = function (val) { - this._buffer = val !== false; - return this; -}; -/** - * Redirect to `url - * - * @param {IncomingMessage} res - * @return {Request} for chaining - * @api private - */ - - -Request.prototype._redirect = function (res) { - let 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 - - url = resolve(this.url, url); // ensure the response is being consumed - // this is required for Node v0.10+ - - res.resume(); - let headers = this.req._headers; - const changesOrigin = parse(url).host !== parse(this.url).host; // 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, changesOrigin); // force GET - - this.method = this.method === 'HEAD' ? 'HEAD' : 'GET'; // clear data - - this._data = null; - } // 303 is always GET - - - if (res.statusCode === 303) { - // strip Content-* related fields - // in case of POST etc - headers = utils.cleanHeader(this.req._headers, changesOrigin); // force method - - this.method = 'GET'; // clear data - - this._data = null; - } // 307 preserves method - // 308 preserves method - - - delete headers.host; - delete this.req; - delete this._formData; // remove all add header except User-Agent - - _initHeaders(this); // redirect - - - this._endCalled = false; - this.url = url; - this.qs = {}; - this._query.length = 0; - this.set(headers); - this.emit('redirect', res); - - this._redirectList.push(this.url); - - this.end(this._callback); - return this; -}; -/** - * Set Authorization field value with `user` and `pass`. - * - * Examples: - * - * .auth('tobi', 'learnboost') - * .auth('tobi:learnboost') - * .auth('tobi') - * .auth(accessToken, { type: 'bearer' }) - * - * @param {String} user - * @param {String} [pass] - * @param {Object} [options] options with authorization type 'basic' or 'bearer' ('basic' is default) - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.auth = function (user, pass, options) { - if (arguments.length === 1) pass = ''; - - if (typeof pass === 'object' && pass !== null) { - // pass is optional and can be replaced with options - options = pass; - pass = ''; - } - - if (!options) { - options = { - type: 'basic' - }; - } - - const encoder = string => Buffer.from(string).toString('base64'); - - return this._auth(user, pass, options, encoder); -}; -/** - * Set the certificate authority option for https request. - * - * @param {Buffer | Array} cert - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.ca = function (cert) { - this._ca = cert; - return this; -}; -/** - * Set the client certificate key option for https request. - * - * @param {Buffer | String} cert - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.key = function (cert) { - this._key = cert; - return this; -}; -/** - * Set the key, certificate, and CA certs of the client in PFX or PKCS12 format. - * - * @param {Buffer | String} cert - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.pfx = function (cert) { - if (typeof cert === 'object' && !Buffer.isBuffer(cert)) { - this._pfx = cert.pfx; - this._passphrase = cert.passphrase; - } else { - this._pfx = cert; - } - - return this; -}; -/** - * Set the client certificate option for https request. - * - * @param {Buffer | String} cert - * @return {Request} for chaining - * @api public - */ - - -Request.prototype.cert = function (cert) { - this._cert = cert; - return this; -}; -/** - * Return an http[s] request. - * - * @return {OutgoingMessage} - * @api private - */ -// eslint-disable-next-line complexity - - -Request.prototype.request = function () { - if (this.req) return this.req; - const options = {}; - - try { - const query = qs.stringify(this.qs, { - indices: false, - strictNullHandling: true - }); - - if (query) { - this.qs = {}; - - this._query.push(query); - } - - this._finalizeQueryString(); - } catch (err) { - return this.emit('error', err); - } - - let url = this.url; - const retries = this._retries; // Capture backticks as-is from the final query string built above. - // Note: this'll only find backticks entered in req.query(String) - // calls, because qs.stringify unconditionally encodes backticks. - - let queryStringBackticks; - - if (url.indexOf('`') > -1) { - const queryStartIndex = url.indexOf('?'); - - if (queryStartIndex !== -1) { - const queryString = url.substr(queryStartIndex + 1); - queryStringBackticks = queryString.match(/`|%60/g); - } - } // default to http:// - - - if (url.indexOf('http') !== 0) url = `http://${url}`; - url = parse(url); // See https://github.com/visionmedia/superagent/issues/1367 - - if (queryStringBackticks) { - let i = 0; - url.query = url.query.replace(/%60/g, () => queryStringBackticks[i++]); - url.search = `?${url.query}`; - url.path = url.pathname + url.search; - } // support unix sockets - - - if (/^https?\+unix:/.test(url.protocol) === true) { - // get the protocol - url.protocol = `${url.protocol.split('+')[0]}:`; // get the socket, path - - const unixParts = url.path.match(/^([^/]+)(.+)$/); - options.socketPath = unixParts[1].replace(/%2F/g, '/'); - url.path = unixParts[2]; - } // Override IP address of a hostname - - - if (this._connectOverride) { - const _url = url, - hostname = _url.hostname; - const match = hostname in this._connectOverride ? this._connectOverride[hostname] : this._connectOverride['*']; - - if (match) { - // backup the real host - if (!this._header.host) { - this.set('host', url.host); - } // wrap [ipv6] - - - url.host = /:/.test(match) ? `[${match}]` : match; - - if (url.port) { - url.host += `:${url.port}`; - } - - url.hostname = match; - } - } // options - - - options.method = this.method; - options.port = url.port; - options.path = url.path; - options.host = url.hostname; - options.ca = this._ca; - options.key = this._key; - options.pfx = this._pfx; - options.cert = this._cert; - options.passphrase = this._passphrase; - options.agent = this._agent; // Allows request.get('https://1.2.3.4/').set('Host', 'example.com') - - if (this._header.host) { - options.servername = this._header.host.replace(/:\d+$/, ''); - } - - if (this._trustLocalhost && /^(?:localhost|127\.0\.0\.\d+|(0*:)+:0*1)$/.test(url.hostname)) { - options.rejectUnauthorized = false; - } // initiate request - - - const mod = this._enableHttp2 ? exports.protocols['http2:'].setProtocol(url.protocol) : exports.protocols[url.protocol]; // request - - this.req = mod.request(options); - const req = this.req; // set tcp no delay - - req.setNoDelay(true); - - if (options.method !== 'HEAD') { - req.setHeader('Accept-Encoding', 'gzip, deflate'); - } - - this.protocol = url.protocol; - this.host = url.host; // expose events - - req.once('drain', () => { - this.emit('drain'); - }); - req.on('error', err => { - // flag abortion here for out timeouts - // because node will emit a faux-error "socket hang up" - // when request is aborted before a connection is made - if (this._aborted) return; // if not the same, we are in the **old** (cancelled) request, - // so need to continue (same as for above) - - if (this._retries !== retries) return; // if we've received a response then we don't want to let - // an error in the request blow up the response - - if (this.response) return; - this.callback(err); - }); // auth - - if (url.auth) { - const auth = url.auth.split(':'); - this.auth(auth[0], auth[1]); - } - - if (this.username && this.password) { - this.auth(this.username, this.password); - } - - for (const key in this.header) { - if (Object.prototype.hasOwnProperty.call(this.header, key)) req.setHeader(key, this.header[key]); - } // add cookies - - - if (this.cookies) { - if (Object.prototype.hasOwnProperty.call(this._header, 'cookie')) { - // merge - const tmpJar = new CookieJar.CookieJar(); - tmpJar.setCookies(this._header.cookie.split(';')); - tmpJar.setCookies(this.cookies.split(';')); - req.setHeader('Cookie', tmpJar.getCookies(CookieJar.CookieAccessInfo.All).toValueString()); - } else { - req.setHeader('Cookie', this.cookies); - } - } - - return req; -}; -/** - * Invoke the callback with `err` and `res` - * and handle arity check. - * - * @param {Error} err - * @param {Response} res - * @api private - */ - - -Request.prototype.callback = function (err, res) { - if (this._shouldRetry(err, res)) { - return this._retry(); - } // Avoid the error which is emitted from 'socket hang up' to cause the fn undefined error on JS runtime. - - - const fn = this._callback || noop; - this.clearTimeout(); - if (this.called) return console.warn('superagent: double callback bug'); - this.called = true; - - if (!err) { - try { - if (!this._isResponseOK(res)) { - let msg = 'Unsuccessful HTTP response'; - - if (res) { - msg = http.STATUS_CODES[res.status] || msg; - } - - err = new Error(msg); - err.status = res ? res.status : undefined; - } - } catch (err2) { - err = err2; - } - } // It's important that the callback is called outside try/catch - // to avoid double callback - - - if (!err) { - return fn(null, res); - } - - err.response = res; - if (this._maxRetries) err.retries = this._retries - 1; // 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); - } - - fn(err, res); -}; -/** - * Check if `obj` is a host object, - * - * @param {Object} obj host object - * @return {Boolean} is a host object - * @api private - */ - - -Request.prototype._isHost = function (obj) { - return Buffer.isBuffer(obj) || obj instanceof Stream || obj instanceof FormData; -}; -/** - * Initiate request, invoking callback `fn(err, res)` - * with an instanceof `Response`. - * - * @param {Function} fn - * @return {Request} for chaining - * @api public - */ - - -Request.prototype._emitResponse = function (body, files) { - const response = new Response(this); - this.response = response; - response.redirects = this._redirectList; - - if (undefined !== body) { - response.body = body; - } - - response.files = files; - - if (this._endCalled) { - response.pipe = function () { - throw new Error("end() has already been called, so it's too late to start piping"); - }; - } - - this.emit('response', response); - return response; -}; - -Request.prototype.end = function (fn) { - this.request(); - debug('%s %s', this.method, this.url); - - if (this._endCalled) { - throw new Error('.end() was called twice. This is not supported in superagent'); - } - - this._endCalled = true; // store callback - - this._callback = fn || noop; - - this._end(); -}; - -Request.prototype._end = function () { - if (this._aborted) return this.callback(new Error('The request has been aborted even before .end() was called')); - let data = this._data; - const req = this.req; - const method = this.method; - - this._setTimeouts(); // body - - - if (method !== 'HEAD' && !req._headerSent) { - // serialize stuff - if (typeof data !== 'string') { - let contentType = req.getHeader('Content-Type'); // Parse out just the content type from the header (ignore the charset) - - if (contentType) contentType = contentType.split(';')[0]; - let serialize = this._serializer || exports.serialize[contentType]; - - if (!serialize && isJSON(contentType)) { - serialize = exports.serialize['application/json']; - } - - if (serialize) data = serialize(data); - } // content-length - - - if (data && !req.getHeader('Content-Length')) { - req.setHeader('Content-Length', Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)); - } - } // response - // eslint-disable-next-line complexity - - - req.once('response', res => { - debug('%s %s -> %s', this.method, this.url, res.statusCode); - - if (this._responseTimeoutTimer) { - clearTimeout(this._responseTimeoutTimer); - } - - if (this.piped) { - return; - } - - const max = this._maxRedirects; - const mime = utils.type(res.headers['content-type'] || '') || 'text/plain'; - const type = mime.split('/')[0]; - const multipart = type === 'multipart'; - const redirect = isRedirect(res.statusCode); - const responseType = this._responseType; - this.res = res; // redirect - - if (redirect && this._redirects++ !== max) { - return this._redirect(res); - } - - if (this.method === 'HEAD') { - this.emit('end'); - this.callback(null, this._emitResponse()); - return; - } // zlib support - - - if (this._shouldUnzip(res)) { - unzip(req, res); - } - - let buffer = this._buffer; - - if (buffer === undefined && mime in exports.buffer) { - buffer = Boolean(exports.buffer[mime]); - } - - let parser = this._parser; - - if (undefined === buffer) { - if (parser) { - console.warn("A custom superagent parser has been set, but buffering strategy for the parser hasn't been configured. Call `req.buffer(true or false)` or set `superagent.buffer[mime] = true or false`"); - buffer = true; - } - } - - if (!parser) { - if (responseType) { - parser = exports.parse.image; // It's actually a generic Buffer - - buffer = true; - } else if (multipart) { - const form = new formidable.IncomingForm(); - parser = form.parse.bind(form); - buffer = true; - } else if (isImageOrVideo(mime)) { - parser = exports.parse.image; - buffer = true; // For backwards-compatibility buffering default is ad-hoc MIME-dependent - } else if (exports.parse[mime]) { - parser = exports.parse[mime]; - } else if (type === 'text') { - parser = exports.parse.text; - buffer = buffer !== false; // everyone wants their own white-labeled json - } else if (isJSON(mime)) { - parser = exports.parse['application/json']; - buffer = buffer !== false; - } else if (buffer) { - parser = exports.parse.text; - } else if (undefined === buffer) { - parser = exports.parse.image; // It's actually a generic Buffer - - buffer = true; - } - } // by default only buffer text/*, json and messed up thing from hell - - - if (undefined === buffer && isText(mime) || isJSON(mime)) { - buffer = true; - } - - this._resBuffered = buffer; - let parserHandlesEnd = false; - - if (buffer) { - // Protectiona against zip bombs and other nuisance - let responseBytesLeft = this._maxResponseSize || 200000000; - res.on('data', buf => { - responseBytesLeft -= buf.byteLength || buf.length; - - if (responseBytesLeft < 0) { - // This will propagate through error event - const err = new Error('Maximum response size reached'); - err.code = 'ETOOLARGE'; // Parsers aren't required to observe error event, - // so would incorrectly report success - - parserHandlesEnd = false; // Will emit error event - - res.destroy(err); - } - }); - } - - if (parser) { - try { - // Unbuffered parsers are supposed to emit response early, - // which is weird BTW, because response.body won't be there. - parserHandlesEnd = buffer; - parser(res, (err, obj, files) => { - if (this.timedout) { - // Timeout has already handled all callbacks - return; - } // Intentional (non-timeout) abort is supposed to preserve partial response, - // even if it doesn't parse. - - - if (err && !this._aborted) { - return this.callback(err); - } - - if (parserHandlesEnd) { - this.emit('end'); - this.callback(null, this._emitResponse(obj, files)); - } - }); - } catch (err) { - this.callback(err); - return; - } - } - - this.res = res; // unbuffered - - if (!buffer) { - debug('unbuffered %s %s', this.method, this.url); - this.callback(null, this._emitResponse()); - if (multipart) return; // allow multipart to handle end event - - res.once('end', () => { - debug('end %s %s', this.method, this.url); - this.emit('end'); - }); - return; - } // terminating events - - - res.once('error', err => { - parserHandlesEnd = false; - this.callback(err, null); - }); - if (!parserHandlesEnd) res.once('end', () => { - debug('end %s %s', this.method, this.url); // TODO: unless buffering emit earlier to stream - - this.emit('end'); - this.callback(null, this._emitResponse()); - }); - }); - this.emit('request', this); - - const getProgressMonitor = () => { - const lengthComputable = true; - const total = req.getHeader('Content-Length'); - let loaded = 0; - const progress = new Stream.Transform(); - - progress._transform = (chunk, encoding, cb) => { - loaded += chunk.length; - this.emit('progress', { - direction: 'upload', - lengthComputable, - loaded, - total - }); - cb(null, chunk); - }; - - return progress; - }; - - const bufferToChunks = buffer => { - const chunkSize = 16 * 1024; // default highWaterMark value - - const chunking = new Stream.Readable(); - const totalLength = buffer.length; - const remainder = totalLength % chunkSize; - const cutoff = totalLength - remainder; - - for (let i = 0; i < cutoff; i += chunkSize) { - const chunk = buffer.slice(i, i + chunkSize); - chunking.push(chunk); - } - - if (remainder > 0) { - const remainderBuffer = buffer.slice(-remainder); - chunking.push(remainderBuffer); - } - - chunking.push(null); // no more data - - return chunking; - }; // if a FormData instance got created, then we send that as the request body - - - const formData = this._formData; - - if (formData) { - // set headers - const headers = formData.getHeaders(); - - for (const i in headers) { - if (Object.prototype.hasOwnProperty.call(headers, i)) { - debug('setting FormData header: "%s: %s"', i, headers[i]); - req.setHeader(i, headers[i]); - } - } // attempt to get "Content-Length" header - // eslint-disable-next-line handle-callback-err - - - formData.getLength((err, length) => { - // TODO: Add chunked encoding when no length (if err) - debug('got FormData Content-Length: %s', length); - - if (typeof length === 'number') { - req.setHeader('Content-Length', length); - } - - formData.pipe(getProgressMonitor()).pipe(req); - }); - } else if (Buffer.isBuffer(data)) { - bufferToChunks(data).pipe(getProgressMonitor()).pipe(req); - } else { - req.end(data); - } -}; // Check whether response has a non-0-sized gzip-encoded body - - -Request.prototype._shouldUnzip = res => { - if (res.statusCode === 204 || res.statusCode === 304) { - // These aren't supposed to have any body - return false; - } // header content is a string, and distinction between 0 and no information is crucial - - - if (res.headers['content-length'] === '0') { - // We know that the body is empty (unfortunately, this check does not cover chunked encoding) - return false; - } // console.log(res); - - - return /^\s*(?:deflate|gzip)\s*$/.test(res.headers['content-encoding']); -}; // eslint-disable-next-line valid-jsdoc - -/** - * Overrides DNS for selected hostnames. Takes object mapping hostnames to IP addresses. - * - * When making a request to a URL with a hostname exactly matching a key in the object, - * use the given IP address to connect, instead of using DNS to resolve the hostname. - * - * A special host `*` matches every hostname (keep redirects in mind!) - * - * request.connect({ - * 'test.example.com': '127.0.0.1', - * 'ipv6.example.com': '::1', - * }) - */ - - -Request.prototype.connect = function (connectOverride) { - if (typeof connectOverride === 'string') { - this._connectOverride = { - '*': connectOverride - }; - } else if (typeof connectOverride === 'object') { - this._connectOverride = connectOverride; - } else { - this._connectOverride = undefined; - } - - return this; -}; - -Request.prototype.trustLocalhost = function (toggle) { - this._trustLocalhost = toggle === undefined ? true : toggle; - return this; -}; // generate HTTP verb methods - - -if (methods.indexOf('del') === -1) { - // create a copy so we don't cause conflicts with - // other packages using the methods package and - // npm 3.x - methods = methods.slice(0); - methods.push('del'); -} - -methods.forEach(method => { - const name = method; - method = method === 'del' ? 'delete' : method; - method = method.toUpperCase(); - - request[name] = (url, data, fn) => { - const req = request(method, url); - - if (typeof data === 'function') { - fn = data; - data = null; - } - - if (data) { - if (method === 'GET' || method === 'HEAD') { - req.query(data); - } else { - req.send(data); - } - } - - if (fn) req.end(fn); - return req; - }; -}); -/** - * Check if `mime` is text and should be buffered. - * - * @param {String} mime - * @return {Boolean} - * @api public - */ - -function isText(mime) { - const parts = mime.split('/'); - const type = parts[0]; - const subtype = parts[1]; - return type === 'text' || subtype === 'x-www-form-urlencoded'; -} - -function isImageOrVideo(mime) { - const type = mime.split('/')[0]; - return type === 'image' || type === 'video'; -} -/** - * Check if `mime` is json or has +json structured syntax suffix. - * - * @param {String} mime - * @return {Boolean} - * @api private - */ - - -function isJSON(mime) { - // should match /json or +json - // but not /json-seq - return /[/+]json($|[^-\w])/.test(mime); -} -/** - * Check if we should follow the redirect `code`. - * - * @param {Number} code - * @return {Boolean} - * @api private - */ - - -function isRedirect(code) { - return [301, 302, 303, 305, 307, 308].indexOf(code) !== -1; -} \ No newline at end of file diff --git a/lib/node/parsers/image.js b/lib/node/parsers/image.js deleted file mode 100644 index 649e80411..000000000 --- a/lib/node/parsers/image.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -module.exports = (res, fn) => { - const data = []; // Binary data needs binary storage - - res.on('data', chunk => { - data.push(chunk); - }); - res.on('end', () => { - fn(null, Buffer.concat(data)); - }); -}; \ No newline at end of file diff --git a/lib/node/parsers/index.js b/lib/node/parsers/index.js deleted file mode 100644 index 549f6fcc6..000000000 --- a/lib/node/parsers/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -exports['application/x-www-form-urlencoded'] = require('./urlencoded'); -exports['application/json'] = require('./json'); -exports.text = require('./text'); - -const binary = require('./image'); - -exports['application/octet-stream'] = binary; -exports['application/pdf'] = binary; -exports.image = binary; \ No newline at end of file diff --git a/lib/node/parsers/json.js b/lib/node/parsers/json.js deleted file mode 100644 index 04dee2bd9..000000000 --- a/lib/node/parsers/json.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; - -module.exports = function (res, fn) { - res.text = ''; - res.setEncoding('utf8'); - res.on('data', chunk => { - res.text += chunk; - }); - res.on('end', () => { - let body; - let err; - - try { - body = res.text && JSON.parse(res.text); - } catch (err2) { - err = err2; // issue #675: return the raw response if the response parsing fails - - err.rawResponse = res.text || null; // issue #876: return the http status code if the response parsing fails - - err.statusCode = res.statusCode; - } finally { - fn(err, body); - } - }); -}; \ No newline at end of file diff --git a/lib/node/parsers/text.js b/lib/node/parsers/text.js deleted file mode 100644 index 6911179da..000000000 --- a/lib/node/parsers/text.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; - -module.exports = (res, fn) => { - res.text = ''; - res.setEncoding('utf8'); - res.on('data', chunk => { - res.text += chunk; - }); - res.on('end', fn); -}; \ No newline at end of file diff --git a/lib/node/parsers/urlencoded.js b/lib/node/parsers/urlencoded.js deleted file mode 100644 index ed40b61c0..000000000 --- a/lib/node/parsers/urlencoded.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; - -/** - * Module dependencies. - */ -const qs = require('qs'); - -module.exports = (res, fn) => { - res.text = ''; - res.setEncoding('ascii'); - res.on('data', chunk => { - res.text += chunk; - }); - res.on('end', () => { - try { - fn(null, qs.parse(res.text)); - } catch (err) { - fn(err); - } - }); -}; \ No newline at end of file diff --git a/lib/node/response.js b/lib/node/response.js deleted file mode 100644 index 50ab806ed..000000000 --- a/lib/node/response.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; -/** - * Module dependencies. - */ - -const util = require('util'); - -const Stream = require('stream'); - -const ResponseBase = require('../response-base'); -/** - * Expose `Response`. - */ - - -module.exports = Response; -/** - * Initialize a new `Response` with the given `xhr`. - * - * - set flags (.ok, .error, etc) - * - parse header - * - * @param {Request} req - * @param {Object} options - * @constructor - * @extends {Stream} - * @implements {ReadableStream} - * @api private - */ - -function Response(req) { - Stream.call(this); - this.res = req.res; - const res = this.res; - this.request = req; - this.req = req.req; - this.text = res.text; - this.body = res.body === undefined ? {} : res.body; - this.files = res.files || {}; - this.buffered = req._resBuffered; - this.headers = res.headers; - this.header = this.headers; - - this._setStatusProperties(res.statusCode); - - this._setHeaderProperties(this.header); - - this.setEncoding = res.setEncoding.bind(res); - res.on('data', this.emit.bind(this, 'data')); - res.on('end', this.emit.bind(this, 'end')); - res.on('close', this.emit.bind(this, 'close')); - res.on('error', this.emit.bind(this, 'error')); -} -/** - * Inherit from `Stream`. - */ - - -util.inherits(Response, Stream); // eslint-disable-next-line new-cap - -ResponseBase(Response.prototype); -/** - * Implements methods of a `ReadableStream` - */ - -Response.prototype.destroy = function (err) { - this.res.destroy(err); -}; -/** - * Pause. - */ - - -Response.prototype.pause = function () { - this.res.pause(); -}; -/** - * Resume. - */ - - -Response.prototype.resume = function () { - this.res.resume(); -}; -/** - * Return an `Error` representative of this response. - * - * @return {Error} - * @api public - */ - - -Response.prototype.toError = function () { - const req = this.req; - const method = req.method; - const path = req.path; - const msg = `cannot ${method} ${path} (${this.status})`; - const err = new Error(msg); - err.status = this.status; - err.text = this.text; - err.method = method; - err.path = path; - return err; -}; - -Response.prototype.setStatusProperties = function (status) { - console.warn('In superagent 2.x setStatusProperties is a private method'); - return this._setStatusProperties(status); -}; -/** - * To json. - * - * @return {Object} - * @api public - */ - - -Response.prototype.toJSON = function () { - return { - req: this.request.toJSON(), - header: this.header, - status: this.status, - text: this.text - }; -}; \ No newline at end of file diff --git a/lib/node/unzip.js b/lib/node/unzip.js deleted file mode 100644 index d4988fdff..000000000 --- a/lib/node/unzip.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; -/** - * Module dependencies. - */ - -const _require = require('string_decoder'), - StringDecoder = _require.StringDecoder; - -const Stream = require('stream'); - -const zlib = require('zlib'); -/** - * Buffers response data events and re-emits when they're unzipped. - * - * @param {Request} req - * @param {Response} res - * @api private - */ - - -exports.unzip = (req, res) => { - const unzip = zlib.createUnzip(); - const stream = new Stream(); - let decoder; // make node responseOnEnd() happy - - stream.req = req; - unzip.on('error', err => { - if (err && err.code === 'Z_BUF_ERROR') { - // unexpected end of file is ignored by browsers and curl - stream.emit('end'); - return; - } - - stream.emit('error', err); - }); // pipe to unzip - - res.pipe(unzip); // override `setEncoding` to capture encoding - - res.setEncoding = type => { - decoder = new StringDecoder(type); - }; // decode upon decompressing with captured encoding - - - unzip.on('data', buf => { - if (decoder) { - const str = decoder.write(buf); - if (str.length > 0) stream.emit('data', str); - } else { - stream.emit('data', buf); - } - }); - unzip.on('end', () => { - stream.emit('end'); - }); // override `on` to capture data listeners - - const _on = res.on; - - res.on = function (type, fn) { - if (type === 'data' || type === 'end') { - stream.on(type, fn.bind(res)); - } else if (type === 'error') { - stream.on(type, fn.bind(res)); - - _on.call(res, type, fn); - } else { - _on.call(res, type, fn); - } - - return this; - }; -}; \ No newline at end of file diff --git a/lib/request-base.js b/lib/request-base.js deleted file mode 100644 index d3cf20566..000000000 --- a/lib/request-base.js +++ /dev/null @@ -1,744 +0,0 @@ -"use strict"; - -/** - * Module of mixed-in functions shared between node and client code - */ -const isObject = require('./is-object'); -/** - * Expose `RequestBase`. - */ - - -module.exports = RequestBase; -/** - * Initialize a new `RequestBase`. - * - * @api public - */ - -function RequestBase(obj) { - if (obj) return mixin(obj); -} -/** - * Mixin the prototype properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - - -function mixin(obj) { - for (const key in RequestBase.prototype) { - if (Object.prototype.hasOwnProperty.call(RequestBase.prototype, key)) obj[key] = RequestBase.prototype[key]; - } - - return obj; -} -/** - * Clear previous timeout. - * - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.clearTimeout = function () { - clearTimeout(this._timer); - clearTimeout(this._responseTimeoutTimer); - clearTimeout(this._uploadTimeoutTimer); - delete this._timer; - delete this._responseTimeoutTimer; - delete this._uploadTimeoutTimer; - return this; -}; -/** - * Override default response body parser - * - * This function will be called to convert incoming data into request.body - * - * @param {Function} - * @api public - */ - - -RequestBase.prototype.parse = function (fn) { - this._parser = fn; - return this; -}; -/** - * Set format of binary response body. - * In browser valid formats are 'blob' and 'arraybuffer', - * which return Blob and ArrayBuffer, respectively. - * - * In Node all values result in Buffer. - * - * Examples: - * - * req.get('/') - * .responseType('blob') - * .end(callback); - * - * @param {String} val - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.responseType = function (val) { - this._responseType = val; - return this; -}; -/** - * Override default request body serializer - * - * This function will be called to convert data set via .send or .attach into payload to send - * - * @param {Function} - * @api public - */ - - -RequestBase.prototype.serialize = function (fn) { - this._serializer = fn; - return this; -}; -/** - * Set timeouts. - * - * - response timeout is time between sending request and receiving the first byte of the response. Includes DNS and connection time. - * - deadline is the time from start of the request to receiving response body in full. If the deadline is too short large files may not load at all on slow connections. - * - upload is the time since last bit of data was sent or received. This timeout works only if deadline timeout is off - * - * Value of 0 or false means no timeout. - * - * @param {Number|Object} ms or {response, deadline} - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.timeout = function (options) { - if (!options || typeof options !== 'object') { - this._timeout = options; - this._responseTimeout = 0; - this._uploadTimeout = 0; - return this; - } - - for (const option in options) { - if (Object.prototype.hasOwnProperty.call(options, option)) { - switch (option) { - case 'deadline': - this._timeout = options.deadline; - break; - - case 'response': - this._responseTimeout = options.response; - break; - - case 'upload': - this._uploadTimeout = options.upload; - break; - - default: - console.warn('Unknown timeout option', option); - } - } - } - - return this; -}; -/** - * Set number of retry attempts on error. - * - * Failed requests will be retried 'count' times if timeout or err.code >= 500. - * - * @param {Number} count - * @param {Function} [fn] - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.retry = function (count, fn) { - // Default to 1 if no count passed or true - if (arguments.length === 0 || count === true) count = 1; - if (count <= 0) count = 0; - this._maxRetries = count; - this._retries = 0; - this._retryCallback = fn; - return this; -}; - -const ERROR_CODES = ['ECONNRESET', 'ETIMEDOUT', 'EADDRINFO', 'ESOCKETTIMEDOUT']; -/** - * Determine if a request should be retried. - * (Borrowed from segmentio/superagent-retry) - * - * @param {Error} err an error - * @param {Response} [res] response - * @returns {Boolean} if segment should be retried - */ - -RequestBase.prototype._shouldRetry = function (err, res) { - if (!this._maxRetries || this._retries++ >= this._maxRetries) { - return false; - } - - if (this._retryCallback) { - try { - const override = this._retryCallback(err, res); - - if (override === true) return true; - if (override === false) return false; // undefined falls back to defaults - } catch (err2) { - console.error(err2); - } - } - - if (res && res.status && res.status >= 500 && res.status !== 501) return true; - - if (err) { - if (err.code && ERROR_CODES.indexOf(err.code) !== -1) return true; // Superagent timeout - - if (err.timeout && err.code === 'ECONNABORTED') return true; - if (err.crossDomain) return true; - } - - return false; -}; -/** - * Retry request - * - * @return {Request} for chaining - * @api private - */ - - -RequestBase.prototype._retry = function () { - this.clearTimeout(); // node - - if (this.req) { - this.req = null; - this.req = this.request(); - } - - this._aborted = false; - this.timedout = false; - return this._end(); -}; -/** - * Promise support - * - * @param {Function} resolve - * @param {Function} [reject] - * @return {Request} - */ - - -RequestBase.prototype.then = function (resolve, reject) { - if (!this._fullfilledPromise) { - const self = this; - - if (this._endCalled) { - console.warn('Warning: superagent request was sent twice, because both .end() and .then() were called. Never call .end() if you use promises'); - } - - this._fullfilledPromise = new Promise((resolve, reject) => { - self.on('abort', () => { - const err = new Error('Aborted'); - err.code = 'ABORTED'; - err.status = this.status; - err.method = this.method; - err.url = this.url; - reject(err); - }); - self.end((err, res) => { - if (err) reject(err);else resolve(res); - }); - }); - } // eslint-disable-next-line promise/prefer-await-to-then - - - return this._fullfilledPromise.then(resolve, reject); -}; - -RequestBase.prototype.catch = function (cb) { - // eslint-disable-next-line promise/prefer-await-to-then - return this.then(undefined, cb); -}; -/** - * Allow for extension - */ - - -RequestBase.prototype.use = function (fn) { - fn(this); - return this; -}; - -RequestBase.prototype.ok = function (cb) { - if (typeof cb !== 'function') throw new Error('Callback required'); - this._okCallback = cb; - return this; -}; - -RequestBase.prototype._isResponseOK = function (res) { - if (!res) { - return false; - } - - if (this._okCallback) { - return this._okCallback(res); - } - - return res.status >= 200 && res.status < 300; -}; -/** - * Get request header `field`. - * Case-insensitive. - * - * @param {String} field - * @return {String} - * @api public - */ - - -RequestBase.prototype.get = function (field) { - return this._header[field.toLowerCase()]; -}; -/** - * Get case-insensitive header `field` value. - * This is a deprecated internal API. Use `.get(field)` instead. - * - * (getHeader is no longer used internally by the superagent code base) - * - * @param {String} field - * @return {String} - * @api private - * @deprecated - */ - - -RequestBase.prototype.getHeader = RequestBase.prototype.get; -/** - * Set header `field` to `val`, or multiple fields with one object. - * Case-insensitive. - * - * Examples: - * - * req.get('/') - * .set('Accept', 'application/json') - * .set('X-API-Key', 'foobar') - * .end(callback); - * - * req.get('/') - * .set({ Accept: 'application/json', 'X-API-Key': 'foobar' }) - * .end(callback); - * - * @param {String|Object} field - * @param {String} val - * @return {Request} for chaining - * @api public - */ - -RequestBase.prototype.set = function (field, val) { - if (isObject(field)) { - for (const key in field) { - if (Object.prototype.hasOwnProperty.call(field, key)) this.set(key, field[key]); - } - - return this; - } - - this._header[field.toLowerCase()] = val; - this.header[field] = val; - return this; -}; // eslint-disable-next-line valid-jsdoc - -/** - * Remove header `field`. - * Case-insensitive. - * - * Example: - * - * req.get('/') - * .unset('User-Agent') - * .end(callback); - * - * @param {String} field field name - */ - - -RequestBase.prototype.unset = function (field) { - delete this._header[field.toLowerCase()]; - delete this.header[field]; - return this; -}; -/** - * Write the field `name` and `val`, or multiple fields with one object - * for "multipart/form-data" request bodies. - * - * ``` js - * request.post('/upload') - * .field('foo', 'bar') - * .end(callback); - * - * request.post('/upload') - * .field({ foo: 'bar', baz: 'qux' }) - * .end(callback); - * ``` - * - * @param {String|Object} name name of field - * @param {String|Blob|File|Buffer|fs.ReadStream} val value of field - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.field = function (name, val) { - // name should be either a string or an object. - if (name === null || undefined === name) { - throw new Error('.field(name, val) name can not be empty'); - } - - if (this._data) { - throw new Error(".field() can't be used if .send() is used. Please use only .send() or only .field() & .attach()"); - } - - if (isObject(name)) { - for (const key in name) { - if (Object.prototype.hasOwnProperty.call(name, key)) this.field(key, name[key]); - } - - return this; - } - - if (Array.isArray(val)) { - for (const i in val) { - if (Object.prototype.hasOwnProperty.call(val, i)) this.field(name, val[i]); - } - - return this; - } // val should be defined now - - - if (val === null || undefined === val) { - throw new Error('.field(name, val) val can not be empty'); - } - - if (typeof val === 'boolean') { - val = String(val); - } - - this._getFormData().append(name, val); - - return this; -}; -/** - * Abort the request, and clear potential timeout. - * - * @return {Request} request - * @api public - */ - - -RequestBase.prototype.abort = function () { - if (this._aborted) { - return this; - } - - this._aborted = true; - if (this.xhr) this.xhr.abort(); // browser - - if (this.req) this.req.abort(); // node - - this.clearTimeout(); - this.emit('abort'); - return this; -}; - -RequestBase.prototype._auth = function (user, pass, options, base64Encoder) { - switch (options.type) { - case 'basic': - this.set('Authorization', `Basic ${base64Encoder(`${user}:${pass}`)}`); - break; - - case 'auto': - this.username = user; - this.password = pass; - break; - - case 'bearer': - // usage would be .auth(accessToken, { type: 'bearer' }) - this.set('Authorization', `Bearer ${user}`); - break; - - default: - break; - } - - return this; -}; -/** - * Enable transmission of cookies with x-domain requests. - * - * Note that for this to work the origin must not be - * using "Access-Control-Allow-Origin" with a wildcard, - * and also must set "Access-Control-Allow-Credentials" - * to "true". - * - * @api public - */ - - -RequestBase.prototype.withCredentials = function (on) { - // This is browser-only functionality. Node side is no-op. - if (on === undefined) on = true; - this._withCredentials = on; - return this; -}; -/** - * Set the max redirects to `n`. Does noting in browser XHR implementation. - * - * @param {Number} n - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.redirects = function (n) { - this._maxRedirects = n; - return this; -}; -/** - * Maximum size of buffered response body, in bytes. Counts uncompressed size. - * Default 200MB. - * - * @param {Number} n number of bytes - * @return {Request} for chaining - */ - - -RequestBase.prototype.maxResponseSize = function (n) { - if (typeof n !== 'number') { - throw new TypeError('Invalid argument'); - } - - this._maxResponseSize = n; - return this; -}; -/** - * Convert to a plain javascript object (not JSON string) of scalar properties. - * Note as this method is designed to return a useful non-this value, - * it cannot be chained. - * - * @return {Object} describing method, url, and data of this request - * @api public - */ - - -RequestBase.prototype.toJSON = function () { - return { - method: this.method, - url: this.url, - data: this._data, - headers: this._header - }; -}; -/** - * Send `data` as the request body, defaulting the `.type()` to "json" when - * an object is given. - * - * Examples: - * - * // manual json - * request.post('/user') - * .type('json') - * .send('{"name":"tj"}') - * .end(callback) - * - * // auto json - * request.post('/user') - * .send({ name: 'tj' }) - * .end(callback) - * - * // manual x-www-form-urlencoded - * request.post('/user') - * .type('form') - * .send('name=tj') - * .end(callback) - * - * // auto x-www-form-urlencoded - * request.post('/user') - * .type('form') - * .send({ name: 'tj' }) - * .end(callback) - * - * // defaults to x-www-form-urlencoded - * request.post('/user') - * .send('name=tobi') - * .send('species=ferret') - * .end(callback) - * - * @param {String|Object} data - * @return {Request} for chaining - * @api public - */ -// eslint-disable-next-line complexity - - -RequestBase.prototype.send = function (data) { - const isObj = isObject(data); - let type = this._header['content-type']; - - if (this._formData) { - throw new Error(".send() can't be used if .attach() or .field() is used. Please use only .send() or only .field() & .attach()"); - } - - if (isObj && !this._data) { - if (Array.isArray(data)) { - this._data = []; - } else if (!this._isHost(data)) { - this._data = {}; - } - } else if (data && this._data && this._isHost(this._data)) { - throw new Error("Can't merge these send calls"); - } // merge - - - if (isObj && isObject(this._data)) { - for (const key in data) { - if (Object.prototype.hasOwnProperty.call(data, key)) this._data[key] = data[key]; - } - } else if (typeof data === 'string') { - // default to x-www-form-urlencoded - if (!type) this.type('form'); - type = this._header['content-type']; - - if (type === 'application/x-www-form-urlencoded') { - this._data = this._data ? `${this._data}&${data}` : data; - } else { - this._data = (this._data || '') + data; - } - } else { - this._data = data; - } - - if (!isObj || this._isHost(data)) { - return this; - } // default to json - - - if (!type) this.type('json'); - return this; -}; -/** - * Sort `querystring` by the sort function - * - * - * Examples: - * - * // default order - * request.get('/user') - * .query('name=Nick') - * .query('search=Manny') - * .sortQuery() - * .end(callback) - * - * // customized sort function - * request.get('/user') - * .query('name=Nick') - * .query('search=Manny') - * .sortQuery(function(a, b){ - * return a.length - b.length; - * }) - * .end(callback) - * - * - * @param {Function} sort - * @return {Request} for chaining - * @api public - */ - - -RequestBase.prototype.sortQuery = function (sort) { - // _sort default to true but otherwise can be a function or boolean - this._sort = typeof sort === 'undefined' ? true : sort; - return this; -}; -/** - * Compose querystring to append to req.url - * - * @api private - */ - - -RequestBase.prototype._finalizeQueryString = function () { - const query = this._query.join('&'); - - if (query) { - this.url += (this.url.indexOf('?') >= 0 ? '&' : '?') + query; - } - - this._query.length = 0; // Makes the call idempotent - - if (this._sort) { - const index = this.url.indexOf('?'); - - if (index >= 0) { - const queryArr = this.url.substring(index + 1).split('&'); - - if (typeof this._sort === 'function') { - queryArr.sort(this._sort); - } else { - queryArr.sort(); - } - - this.url = this.url.substring(0, index) + '?' + queryArr.join('&'); - } - } -}; // For backwards compat only - - -RequestBase.prototype._appendQueryString = () => { - console.warn('Unsupported'); -}; -/** - * Invoke callback with timeout error. - * - * @api private - */ - - -RequestBase.prototype._timeoutError = function (reason, timeout, errno) { - if (this._aborted) { - return; - } - - const err = new Error(`${reason + timeout}ms exceeded`); - err.timeout = timeout; - err.code = 'ECONNABORTED'; - err.errno = errno; - this.timedout = true; - this.abort(); - this.callback(err); -}; - -RequestBase.prototype._setTimeouts = function () { - const self = this; // deadline - - if (this._timeout && !this._timer) { - this._timer = setTimeout(() => { - self._timeoutError('Timeout of ', self._timeout, 'ETIME'); - }, this._timeout); - } // response timeout - - - if (this._responseTimeout && !this._responseTimeoutTimer) { - this._responseTimeoutTimer = setTimeout(() => { - self._timeoutError('Response timeout of ', self._responseTimeout, 'ETIMEDOUT'); - }, this._responseTimeout); - } -}; \ No newline at end of file diff --git a/lib/response-base.js b/lib/response-base.js deleted file mode 100644 index 53ebeab57..000000000 --- a/lib/response-base.js +++ /dev/null @@ -1,130 +0,0 @@ -"use strict"; - -/** - * Module dependencies. - */ -const utils = require('./utils'); -/** - * Expose `ResponseBase`. - */ - - -module.exports = ResponseBase; -/** - * Initialize a new `ResponseBase`. - * - * @api public - */ - -function ResponseBase(obj) { - if (obj) return mixin(obj); -} -/** - * Mixin the prototype properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - - -function mixin(obj) { - for (const key in ResponseBase.prototype) { - if (Object.prototype.hasOwnProperty.call(ResponseBase.prototype, key)) obj[key] = ResponseBase.prototype[key]; - } - - return obj; -} -/** - * Get case-insensitive `field` value. - * - * @param {String} field - * @return {String} - * @api public - */ - - -ResponseBase.prototype.get = function (field) { - return this.header[field.toLowerCase()]; -}; -/** - * Set header related properties: - * - * - `.type` the content type without params - * - * A response of "Content-Type: text/plain; charset=utf-8" - * will provide you with a `.type` of "text/plain". - * - * @param {Object} header - * @api private - */ - - -ResponseBase.prototype._setHeaderProperties = function (header) { - // TODO: moar! - // TODO: make this a util - // content-type - const ct = header['content-type'] || ''; - this.type = utils.type(ct); // params - - const params = utils.params(ct); - - for (const key in params) { - if (Object.prototype.hasOwnProperty.call(params, key)) this[key] = params[key]; - } - - this.links = {}; // links - - try { - if (header.link) { - this.links = utils.parseLinks(header.link); - } - } catch (err) {// ignore - } -}; -/** - * Set flags such as `.ok` based on `status`. - * - * For example a 2xx response will give you a `.ok` of __true__ - * whereas 5xx will be __false__ and `.error` will be __true__. The - * `.clientError` and `.serverError` are also available to be more - * specific, and `.statusType` is the class of error ranging from 1..5 - * sometimes useful for mapping respond colors etc. - * - * "sugar" properties are also defined for common cases. Currently providing: - * - * - .noContent - * - .badRequest - * - .unauthorized - * - .notAcceptable - * - .notFound - * - * @param {Number} status - * @api private - */ - - -ResponseBase.prototype._setStatusProperties = function (status) { - const type = status / 100 | 0; // status / class - - this.statusCode = status; - this.status = this.statusCode; - this.statusType = type; // basics - - this.info = type === 1; - this.ok = type === 2; - this.redirect = type === 3; - this.clientError = type === 4; - this.serverError = type === 5; - this.error = type === 4 || type === 5 ? this.toError() : false; // sugar - - this.created = status === 201; - this.accepted = status === 202; - this.noContent = status === 204; - this.badRequest = status === 400; - this.unauthorized = status === 401; - this.notAcceptable = status === 406; - this.forbidden = status === 403; - this.notFound = status === 404; - this.unprocessableEntity = status === 422; -}; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 1e2e58c96..000000000 --- a/lib/utils.js +++ /dev/null @@ -1,64 +0,0 @@ -"use strict"; - -/** - * Return the mime type for the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ -exports.type = str => str.split(/ *; */).shift(); -/** - * Return header field parameters. - * - * @param {String} str - * @return {Object} - * @api private - */ - - -exports.params = str => str.split(/ *; */).reduce((obj, str) => { - const parts = str.split(/ *= */); - const key = parts.shift(); - const val = parts.shift(); - if (key && val) obj[key] = val; - return obj; -}, {}); -/** - * Parse Link header fields. - * - * @param {String} str - * @return {Object} - * @api private - */ - - -exports.parseLinks = str => str.split(/ *, */).reduce((obj, str) => { - const parts = str.split(/ *; */); - const url = parts[0].slice(1, -1); - const rel = parts[1].split(/ *= */)[1].slice(1, -1); - obj[rel] = url; - return obj; -}, {}); -/** - * Strip content related fields from `header`. - * - * @param {Object} header - * @return {Object} header - * @api private - */ - - -exports.cleanHeader = (header, changesOrigin) => { - delete header['content-type']; - delete header['content-length']; - delete header['transfer-encoding']; - delete header.host; // secuirty - - if (changesOrigin) { - delete header.authorization; - delete header.cookie; - } - - return header; -}; \ No newline at end of file diff --git a/package.json b/package.json index f82571785..ee3b3ba16 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "dependencies": { "component-emitter": "^1.2.1", "cookiejar": "^2.1.2", - "core-js": "^3.0.0", "debug": "^4.1.1", "form-data": "^2.3.3", "formidable": "^1.2.1", diff --git a/src/client.js b/src/client.js index 7d3e677b5..90f73f3ad 100644 --- a/src/client.js +++ b/src/client.js @@ -17,22 +17,6 @@ if (typeof window !== 'undefined') { root = self; } -// Array.from() is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import -require('core-js/features/array/from'); - -// Symbol is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import -require('core-js/features/symbol'); - -// Object.getOwnPropertySymbols() is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import -require('core-js/features/object/get-own-property-symbols'); - -// Object.setPrototypeOf() is not supported in IE 10 -// eslint-disable-next-line import/no-unassigned-import -require('core-js/features/object/set-prototype-of'); - const Emitter = require('component-emitter'); const RequestBase = require('./request-base'); const isObject = require('./is-object'); diff --git a/yarn.lock b/yarn.lock index a19344963..09ddbfa0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2528,7 +2528,7 @@ core-js-pure@3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.0.0.tgz#a5679adb4875427c8c0488afc93e6f5b7125859b" integrity sha512-yPiS3fQd842RZDgo/TAKGgS0f3p2nxssF1H65DIZvZv0Od5CygP8puHXn3IQiM/39VAvgCbdaMQpresrbGgt9g== -core-js@3.0.0, core-js@^3.0.0: +core-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0.tgz#a8dbfa978d29bfc263bfb66c556d0ca924c28957" integrity sha512-WBmxlgH2122EzEJ6GH8o9L/FeoUKxxxZ6q6VUxoTlsE4EvbTWKJb447eyVxTEuq0LpXjlq/kCB2qgBvsYRkLvQ==