You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1643 line
40KB

  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  4. var Stream = _interopDefault(require('stream'));
  5. var http = _interopDefault(require('http'));
  6. var Url = _interopDefault(require('url'));
  7. var https = _interopDefault(require('https'));
  8. var zlib = _interopDefault(require('zlib'));
  9. // Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
  10. // fix for "Readable" isn't a named export issue
  11. const Readable = Stream.Readable;
  12. const BUFFER = Symbol('buffer');
  13. const TYPE = Symbol('type');
  14. class Blob {
  15. constructor() {
  16. this[TYPE] = '';
  17. const blobParts = arguments[0];
  18. const options = arguments[1];
  19. const buffers = [];
  20. let size = 0;
  21. if (blobParts) {
  22. const a = blobParts;
  23. const length = Number(a.length);
  24. for (let i = 0; i < length; i++) {
  25. const element = a[i];
  26. let buffer;
  27. if (element instanceof Buffer) {
  28. buffer = element;
  29. } else if (ArrayBuffer.isView(element)) {
  30. buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
  31. } else if (element instanceof ArrayBuffer) {
  32. buffer = Buffer.from(element);
  33. } else if (element instanceof Blob) {
  34. buffer = element[BUFFER];
  35. } else {
  36. buffer = Buffer.from(typeof element === 'string' ? element : String(element));
  37. }
  38. size += buffer.length;
  39. buffers.push(buffer);
  40. }
  41. }
  42. this[BUFFER] = Buffer.concat(buffers);
  43. let type = options && options.type !== undefined && String(options.type).toLowerCase();
  44. if (type && !/[^\u0020-\u007E]/.test(type)) {
  45. this[TYPE] = type;
  46. }
  47. }
  48. get size() {
  49. return this[BUFFER].length;
  50. }
  51. get type() {
  52. return this[TYPE];
  53. }
  54. text() {
  55. return Promise.resolve(this[BUFFER].toString());
  56. }
  57. arrayBuffer() {
  58. const buf = this[BUFFER];
  59. const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
  60. return Promise.resolve(ab);
  61. }
  62. stream() {
  63. const readable = new Readable();
  64. readable._read = function () {};
  65. readable.push(this[BUFFER]);
  66. readable.push(null);
  67. return readable;
  68. }
  69. toString() {
  70. return '[object Blob]';
  71. }
  72. slice() {
  73. const size = this.size;
  74. const start = arguments[0];
  75. const end = arguments[1];
  76. let relativeStart, relativeEnd;
  77. if (start === undefined) {
  78. relativeStart = 0;
  79. } else if (start < 0) {
  80. relativeStart = Math.max(size + start, 0);
  81. } else {
  82. relativeStart = Math.min(start, size);
  83. }
  84. if (end === undefined) {
  85. relativeEnd = size;
  86. } else if (end < 0) {
  87. relativeEnd = Math.max(size + end, 0);
  88. } else {
  89. relativeEnd = Math.min(end, size);
  90. }
  91. const span = Math.max(relativeEnd - relativeStart, 0);
  92. const buffer = this[BUFFER];
  93. const slicedBuffer = buffer.slice(relativeStart, relativeStart + span);
  94. const blob = new Blob([], { type: arguments[2] });
  95. blob[BUFFER] = slicedBuffer;
  96. return blob;
  97. }
  98. }
  99. Object.defineProperties(Blob.prototype, {
  100. size: { enumerable: true },
  101. type: { enumerable: true },
  102. slice: { enumerable: true }
  103. });
  104. Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
  105. value: 'Blob',
  106. writable: false,
  107. enumerable: false,
  108. configurable: true
  109. });
  110. /**
  111. * fetch-error.js
  112. *
  113. * FetchError interface for operational errors
  114. */
  115. /**
  116. * Create FetchError instance
  117. *
  118. * @param String message Error message for human
  119. * @param String type Error type for machine
  120. * @param String systemError For Node.js system error
  121. * @return FetchError
  122. */
  123. function FetchError(message, type, systemError) {
  124. Error.call(this, message);
  125. this.message = message;
  126. this.type = type;
  127. // when err.type is `system`, err.code contains system error code
  128. if (systemError) {
  129. this.code = this.errno = systemError.code;
  130. }
  131. // hide custom error implementation details from end-users
  132. Error.captureStackTrace(this, this.constructor);
  133. }
  134. FetchError.prototype = Object.create(Error.prototype);
  135. FetchError.prototype.constructor = FetchError;
  136. FetchError.prototype.name = 'FetchError';
  137. let convert;
  138. try {
  139. convert = require('encoding').convert;
  140. } catch (e) {}
  141. const INTERNALS = Symbol('Body internals');
  142. // fix an issue where "PassThrough" isn't a named export for node <10
  143. const PassThrough = Stream.PassThrough;
  144. /**
  145. * Body mixin
  146. *
  147. * Ref: https://fetch.spec.whatwg.org/#body
  148. *
  149. * @param Stream body Readable stream
  150. * @param Object opts Response options
  151. * @return Void
  152. */
  153. function Body(body) {
  154. var _this = this;
  155. var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
  156. _ref$size = _ref.size;
  157. let size = _ref$size === undefined ? 0 : _ref$size;
  158. var _ref$timeout = _ref.timeout;
  159. let timeout = _ref$timeout === undefined ? 0 : _ref$timeout;
  160. if (body == null) {
  161. // body is undefined or null
  162. body = null;
  163. } else if (isURLSearchParams(body)) {
  164. // body is a URLSearchParams
  165. body = Buffer.from(body.toString());
  166. } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
  167. // body is ArrayBuffer
  168. body = Buffer.from(body);
  169. } else if (ArrayBuffer.isView(body)) {
  170. // body is ArrayBufferView
  171. body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
  172. } else if (body instanceof Stream) ; else {
  173. // none of the above
  174. // coerce to string then buffer
  175. body = Buffer.from(String(body));
  176. }
  177. this[INTERNALS] = {
  178. body,
  179. disturbed: false,
  180. error: null
  181. };
  182. this.size = size;
  183. this.timeout = timeout;
  184. if (body instanceof Stream) {
  185. body.on('error', function (err) {
  186. const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err);
  187. _this[INTERNALS].error = error;
  188. });
  189. }
  190. }
  191. Body.prototype = {
  192. get body() {
  193. return this[INTERNALS].body;
  194. },
  195. get bodyUsed() {
  196. return this[INTERNALS].disturbed;
  197. },
  198. /**
  199. * Decode response as ArrayBuffer
  200. *
  201. * @return Promise
  202. */
  203. arrayBuffer() {
  204. return consumeBody.call(this).then(function (buf) {
  205. return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
  206. });
  207. },
  208. /**
  209. * Return raw response as Blob
  210. *
  211. * @return Promise
  212. */
  213. blob() {
  214. let ct = this.headers && this.headers.get('content-type') || '';
  215. return consumeBody.call(this).then(function (buf) {
  216. return Object.assign(
  217. // Prevent copying
  218. new Blob([], {
  219. type: ct.toLowerCase()
  220. }), {
  221. [BUFFER]: buf
  222. });
  223. });
  224. },
  225. /**
  226. * Decode response as json
  227. *
  228. * @return Promise
  229. */
  230. json() {
  231. var _this2 = this;
  232. return consumeBody.call(this).then(function (buffer) {
  233. try {
  234. return JSON.parse(buffer.toString());
  235. } catch (err) {
  236. return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json'));
  237. }
  238. });
  239. },
  240. /**
  241. * Decode response as text
  242. *
  243. * @return Promise
  244. */
  245. text() {
  246. return consumeBody.call(this).then(function (buffer) {
  247. return buffer.toString();
  248. });
  249. },
  250. /**
  251. * Decode response as buffer (non-spec api)
  252. *
  253. * @return Promise
  254. */
  255. buffer() {
  256. return consumeBody.call(this);
  257. },
  258. /**
  259. * Decode response as text, while automatically detecting the encoding and
  260. * trying to decode to UTF-8 (non-spec api)
  261. *
  262. * @return Promise
  263. */
  264. textConverted() {
  265. var _this3 = this;
  266. return consumeBody.call(this).then(function (buffer) {
  267. return convertBody(buffer, _this3.headers);
  268. });
  269. }
  270. };
  271. // In browsers, all properties are enumerable.
  272. Object.defineProperties(Body.prototype, {
  273. body: { enumerable: true },
  274. bodyUsed: { enumerable: true },
  275. arrayBuffer: { enumerable: true },
  276. blob: { enumerable: true },
  277. json: { enumerable: true },
  278. text: { enumerable: true }
  279. });
  280. Body.mixIn = function (proto) {
  281. for (const name of Object.getOwnPropertyNames(Body.prototype)) {
  282. // istanbul ignore else: future proof
  283. if (!(name in proto)) {
  284. const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
  285. Object.defineProperty(proto, name, desc);
  286. }
  287. }
  288. };
  289. /**
  290. * Consume and convert an entire Body to a Buffer.
  291. *
  292. * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
  293. *
  294. * @return Promise
  295. */
  296. function consumeBody() {
  297. var _this4 = this;
  298. if (this[INTERNALS].disturbed) {
  299. return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
  300. }
  301. this[INTERNALS].disturbed = true;
  302. if (this[INTERNALS].error) {
  303. return Body.Promise.reject(this[INTERNALS].error);
  304. }
  305. let body = this.body;
  306. // body is null
  307. if (body === null) {
  308. return Body.Promise.resolve(Buffer.alloc(0));
  309. }
  310. // body is blob
  311. if (isBlob(body)) {
  312. body = body.stream();
  313. }
  314. // body is buffer
  315. if (Buffer.isBuffer(body)) {
  316. return Body.Promise.resolve(body);
  317. }
  318. // istanbul ignore if: should never happen
  319. if (!(body instanceof Stream)) {
  320. return Body.Promise.resolve(Buffer.alloc(0));
  321. }
  322. // body is stream
  323. // get ready to actually consume the body
  324. let accum = [];
  325. let accumBytes = 0;
  326. let abort = false;
  327. return new Body.Promise(function (resolve, reject) {
  328. let resTimeout;
  329. // allow timeout on slow response body
  330. if (_this4.timeout) {
  331. resTimeout = setTimeout(function () {
  332. abort = true;
  333. reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout'));
  334. }, _this4.timeout);
  335. }
  336. // handle stream errors
  337. body.on('error', function (err) {
  338. if (err.name === 'AbortError') {
  339. // if the request was aborted, reject with this Error
  340. abort = true;
  341. reject(err);
  342. } else {
  343. // other errors, such as incorrect content-encoding
  344. reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err));
  345. }
  346. });
  347. body.on('data', function (chunk) {
  348. if (abort || chunk === null) {
  349. return;
  350. }
  351. if (_this4.size && accumBytes + chunk.length > _this4.size) {
  352. abort = true;
  353. reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size'));
  354. return;
  355. }
  356. accumBytes += chunk.length;
  357. accum.push(chunk);
  358. });
  359. body.on('end', function () {
  360. if (abort) {
  361. return;
  362. }
  363. clearTimeout(resTimeout);
  364. try {
  365. resolve(Buffer.concat(accum, accumBytes));
  366. } catch (err) {
  367. // handle streams that have accumulated too much data (issue #414)
  368. reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err));
  369. }
  370. });
  371. });
  372. }
  373. /**
  374. * Detect buffer encoding and convert to target encoding
  375. * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
  376. *
  377. * @param Buffer buffer Incoming buffer
  378. * @param String encoding Target encoding
  379. * @return String
  380. */
  381. function convertBody(buffer, headers) {
  382. if (typeof convert !== 'function') {
  383. throw new Error('The package `encoding` must be installed to use the textConverted() function');
  384. }
  385. const ct = headers.get('content-type');
  386. let charset = 'utf-8';
  387. let res, str;
  388. // header
  389. if (ct) {
  390. res = /charset=([^;]*)/i.exec(ct);
  391. }
  392. // no charset in content type, peek at response body for at most 1024 bytes
  393. str = buffer.slice(0, 1024).toString();
  394. // html5
  395. if (!res && str) {
  396. res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
  397. }
  398. // html4
  399. if (!res && str) {
  400. res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
  401. if (res) {
  402. res = /charset=(.*)/i.exec(res.pop());
  403. }
  404. }
  405. // xml
  406. if (!res && str) {
  407. res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
  408. }
  409. // found charset
  410. if (res) {
  411. charset = res.pop();
  412. // prevent decode issues when sites use incorrect encoding
  413. // ref: https://hsivonen.fi/encoding-menu/
  414. if (charset === 'gb2312' || charset === 'gbk') {
  415. charset = 'gb18030';
  416. }
  417. }
  418. // turn raw buffers into a single utf-8 buffer
  419. return convert(buffer, 'UTF-8', charset).toString();
  420. }
  421. /**
  422. * Detect a URLSearchParams object
  423. * ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143
  424. *
  425. * @param Object obj Object to detect by type or brand
  426. * @return String
  427. */
  428. function isURLSearchParams(obj) {
  429. // Duck-typing as a necessary condition.
  430. if (typeof obj !== 'object' || typeof obj.append !== 'function' || typeof obj.delete !== 'function' || typeof obj.get !== 'function' || typeof obj.getAll !== 'function' || typeof obj.has !== 'function' || typeof obj.set !== 'function') {
  431. return false;
  432. }
  433. // Brand-checking and more duck-typing as optional condition.
  434. return obj.constructor.name === 'URLSearchParams' || Object.prototype.toString.call(obj) === '[object URLSearchParams]' || typeof obj.sort === 'function';
  435. }
  436. /**
  437. * Check if `obj` is a W3C `Blob` object (which `File` inherits from)
  438. * @param {*} obj
  439. * @return {boolean}
  440. */
  441. function isBlob(obj) {
  442. return typeof obj === 'object' && typeof obj.arrayBuffer === 'function' && typeof obj.type === 'string' && typeof obj.stream === 'function' && typeof obj.constructor === 'function' && typeof obj.constructor.name === 'string' && /^(Blob|File)$/.test(obj.constructor.name) && /^(Blob|File)$/.test(obj[Symbol.toStringTag]);
  443. }
  444. /**
  445. * Clone body given Res/Req instance
  446. *
  447. * @param Mixed instance Response or Request instance
  448. * @return Mixed
  449. */
  450. function clone(instance) {
  451. let p1, p2;
  452. let body = instance.body;
  453. // don't allow cloning a used body
  454. if (instance.bodyUsed) {
  455. throw new Error('cannot clone body after it is used');
  456. }
  457. // check that body is a stream and not form-data object
  458. // note: we can't clone the form-data object without having it as a dependency
  459. if (body instanceof Stream && typeof body.getBoundary !== 'function') {
  460. // tee instance body
  461. p1 = new PassThrough();
  462. p2 = new PassThrough();
  463. body.pipe(p1);
  464. body.pipe(p2);
  465. // set instance body to teed body and return the other teed body
  466. instance[INTERNALS].body = p1;
  467. body = p2;
  468. }
  469. return body;
  470. }
  471. /**
  472. * Performs the operation "extract a `Content-Type` value from |object|" as
  473. * specified in the specification:
  474. * https://fetch.spec.whatwg.org/#concept-bodyinit-extract
  475. *
  476. * This function assumes that instance.body is present.
  477. *
  478. * @param Mixed instance Any options.body input
  479. */
  480. function extractContentType(body) {
  481. if (body === null) {
  482. // body is null
  483. return null;
  484. } else if (typeof body === 'string') {
  485. // body is string
  486. return 'text/plain;charset=UTF-8';
  487. } else if (isURLSearchParams(body)) {
  488. // body is a URLSearchParams
  489. return 'application/x-www-form-urlencoded;charset=UTF-8';
  490. } else if (isBlob(body)) {
  491. // body is blob
  492. return body.type || null;
  493. } else if (Buffer.isBuffer(body)) {
  494. // body is buffer
  495. return null;
  496. } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
  497. // body is ArrayBuffer
  498. return null;
  499. } else if (ArrayBuffer.isView(body)) {
  500. // body is ArrayBufferView
  501. return null;
  502. } else if (typeof body.getBoundary === 'function') {
  503. // detect form data input from form-data module
  504. return `multipart/form-data;boundary=${body.getBoundary()}`;
  505. } else if (body instanceof Stream) {
  506. // body is stream
  507. // can't really do much about this
  508. return null;
  509. } else {
  510. // Body constructor defaults other things to string
  511. return 'text/plain;charset=UTF-8';
  512. }
  513. }
  514. /**
  515. * The Fetch Standard treats this as if "total bytes" is a property on the body.
  516. * For us, we have to explicitly get it with a function.
  517. *
  518. * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
  519. *
  520. * @param Body instance Instance of Body
  521. * @return Number? Number of bytes, or null if not possible
  522. */
  523. function getTotalBytes(instance) {
  524. const body = instance.body;
  525. if (body === null) {
  526. // body is null
  527. return 0;
  528. } else if (isBlob(body)) {
  529. return body.size;
  530. } else if (Buffer.isBuffer(body)) {
  531. // body is buffer
  532. return body.length;
  533. } else if (body && typeof body.getLengthSync === 'function') {
  534. // detect form data input from form-data module
  535. if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x
  536. body.hasKnownLength && body.hasKnownLength()) {
  537. // 2.x
  538. return body.getLengthSync();
  539. }
  540. return null;
  541. } else {
  542. // body is stream
  543. return null;
  544. }
  545. }
  546. /**
  547. * Write a Body to a Node.js WritableStream (e.g. http.Request) object.
  548. *
  549. * @param Body instance Instance of Body
  550. * @return Void
  551. */
  552. function writeToStream(dest, instance) {
  553. const body = instance.body;
  554. if (body === null) {
  555. // body is null
  556. dest.end();
  557. } else if (isBlob(body)) {
  558. body.stream().pipe(dest);
  559. } else if (Buffer.isBuffer(body)) {
  560. // body is buffer
  561. dest.write(body);
  562. dest.end();
  563. } else {
  564. // body is stream
  565. body.pipe(dest);
  566. }
  567. }
  568. // expose Promise
  569. Body.Promise = global.Promise;
  570. /**
  571. * headers.js
  572. *
  573. * Headers class offers convenient helpers
  574. */
  575. const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
  576. const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
  577. function validateName(name) {
  578. name = `${name}`;
  579. if (invalidTokenRegex.test(name) || name === '') {
  580. throw new TypeError(`${name} is not a legal HTTP header name`);
  581. }
  582. }
  583. function validateValue(value) {
  584. value = `${value}`;
  585. if (invalidHeaderCharRegex.test(value)) {
  586. throw new TypeError(`${value} is not a legal HTTP header value`);
  587. }
  588. }
  589. /**
  590. * Find the key in the map object given a header name.
  591. *
  592. * Returns undefined if not found.
  593. *
  594. * @param String name Header name
  595. * @return String|Undefined
  596. */
  597. function find(map, name) {
  598. name = name.toLowerCase();
  599. for (const key in map) {
  600. if (key.toLowerCase() === name) {
  601. return key;
  602. }
  603. }
  604. return undefined;
  605. }
  606. const MAP = Symbol('map');
  607. class Headers {
  608. /**
  609. * Headers class
  610. *
  611. * @param Object headers Response headers
  612. * @return Void
  613. */
  614. constructor() {
  615. let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined;
  616. this[MAP] = Object.create(null);
  617. if (init instanceof Headers) {
  618. const rawHeaders = init.raw();
  619. const headerNames = Object.keys(rawHeaders);
  620. for (const headerName of headerNames) {
  621. for (const value of rawHeaders[headerName]) {
  622. this.append(headerName, value);
  623. }
  624. }
  625. return;
  626. }
  627. // We don't worry about converting prop to ByteString here as append()
  628. // will handle it.
  629. if (init == null) ; else if (typeof init === 'object') {
  630. const method = init[Symbol.iterator];
  631. if (method != null) {
  632. if (typeof method !== 'function') {
  633. throw new TypeError('Header pairs must be iterable');
  634. }
  635. // sequence<sequence<ByteString>>
  636. // Note: per spec we have to first exhaust the lists then process them
  637. const pairs = [];
  638. for (const pair of init) {
  639. if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') {
  640. throw new TypeError('Each header pair must be iterable');
  641. }
  642. pairs.push(Array.from(pair));
  643. }
  644. for (const pair of pairs) {
  645. if (pair.length !== 2) {
  646. throw new TypeError('Each header pair must be a name/value tuple');
  647. }
  648. this.append(pair[0], pair[1]);
  649. }
  650. } else {
  651. // record<ByteString, ByteString>
  652. for (const key of Object.keys(init)) {
  653. const value = init[key];
  654. this.append(key, value);
  655. }
  656. }
  657. } else {
  658. throw new TypeError('Provided initializer must be an object');
  659. }
  660. }
  661. /**
  662. * Return combined header value given name
  663. *
  664. * @param String name Header name
  665. * @return Mixed
  666. */
  667. get(name) {
  668. name = `${name}`;
  669. validateName(name);
  670. const key = find(this[MAP], name);
  671. if (key === undefined) {
  672. return null;
  673. }
  674. return this[MAP][key].join(', ');
  675. }
  676. /**
  677. * Iterate over all headers
  678. *
  679. * @param Function callback Executed for each item with parameters (value, name, thisArg)
  680. * @param Boolean thisArg `this` context for callback function
  681. * @return Void
  682. */
  683. forEach(callback) {
  684. let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
  685. let pairs = getHeaders(this);
  686. let i = 0;
  687. while (i < pairs.length) {
  688. var _pairs$i = pairs[i];
  689. const name = _pairs$i[0],
  690. value = _pairs$i[1];
  691. callback.call(thisArg, value, name, this);
  692. pairs = getHeaders(this);
  693. i++;
  694. }
  695. }
  696. /**
  697. * Overwrite header values given name
  698. *
  699. * @param String name Header name
  700. * @param String value Header value
  701. * @return Void
  702. */
  703. set(name, value) {
  704. name = `${name}`;
  705. value = `${value}`;
  706. validateName(name);
  707. validateValue(value);
  708. const key = find(this[MAP], name);
  709. this[MAP][key !== undefined ? key : name] = [value];
  710. }
  711. /**
  712. * Append a value onto existing header
  713. *
  714. * @param String name Header name
  715. * @param String value Header value
  716. * @return Void
  717. */
  718. append(name, value) {
  719. name = `${name}`;
  720. value = `${value}`;
  721. validateName(name);
  722. validateValue(value);
  723. const key = find(this[MAP], name);
  724. if (key !== undefined) {
  725. this[MAP][key].push(value);
  726. } else {
  727. this[MAP][name] = [value];
  728. }
  729. }
  730. /**
  731. * Check for header name existence
  732. *
  733. * @param String name Header name
  734. * @return Boolean
  735. */
  736. has(name) {
  737. name = `${name}`;
  738. validateName(name);
  739. return find(this[MAP], name) !== undefined;
  740. }
  741. /**
  742. * Delete all header values given name
  743. *
  744. * @param String name Header name
  745. * @return Void
  746. */
  747. delete(name) {
  748. name = `${name}`;
  749. validateName(name);
  750. const key = find(this[MAP], name);
  751. if (key !== undefined) {
  752. delete this[MAP][key];
  753. }
  754. }
  755. /**
  756. * Return raw headers (non-spec api)
  757. *
  758. * @return Object
  759. */
  760. raw() {
  761. return this[MAP];
  762. }
  763. /**
  764. * Get an iterator on keys.
  765. *
  766. * @return Iterator
  767. */
  768. keys() {
  769. return createHeadersIterator(this, 'key');
  770. }
  771. /**
  772. * Get an iterator on values.
  773. *
  774. * @return Iterator
  775. */
  776. values() {
  777. return createHeadersIterator(this, 'value');
  778. }
  779. /**
  780. * Get an iterator on entries.
  781. *
  782. * This is the default iterator of the Headers object.
  783. *
  784. * @return Iterator
  785. */
  786. [Symbol.iterator]() {
  787. return createHeadersIterator(this, 'key+value');
  788. }
  789. }
  790. Headers.prototype.entries = Headers.prototype[Symbol.iterator];
  791. Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
  792. value: 'Headers',
  793. writable: false,
  794. enumerable: false,
  795. configurable: true
  796. });
  797. Object.defineProperties(Headers.prototype, {
  798. get: { enumerable: true },
  799. forEach: { enumerable: true },
  800. set: { enumerable: true },
  801. append: { enumerable: true },
  802. has: { enumerable: true },
  803. delete: { enumerable: true },
  804. keys: { enumerable: true },
  805. values: { enumerable: true },
  806. entries: { enumerable: true }
  807. });
  808. function getHeaders(headers) {
  809. let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value';
  810. const keys = Object.keys(headers[MAP]).sort();
  811. return keys.map(kind === 'key' ? function (k) {
  812. return k.toLowerCase();
  813. } : kind === 'value' ? function (k) {
  814. return headers[MAP][k].join(', ');
  815. } : function (k) {
  816. return [k.toLowerCase(), headers[MAP][k].join(', ')];
  817. });
  818. }
  819. const INTERNAL = Symbol('internal');
  820. function createHeadersIterator(target, kind) {
  821. const iterator = Object.create(HeadersIteratorPrototype);
  822. iterator[INTERNAL] = {
  823. target,
  824. kind,
  825. index: 0
  826. };
  827. return iterator;
  828. }
  829. const HeadersIteratorPrototype = Object.setPrototypeOf({
  830. next() {
  831. // istanbul ignore if
  832. if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
  833. throw new TypeError('Value of `this` is not a HeadersIterator');
  834. }
  835. var _INTERNAL = this[INTERNAL];
  836. const target = _INTERNAL.target,
  837. kind = _INTERNAL.kind,
  838. index = _INTERNAL.index;
  839. const values = getHeaders(target, kind);
  840. const len = values.length;
  841. if (index >= len) {
  842. return {
  843. value: undefined,
  844. done: true
  845. };
  846. }
  847. this[INTERNAL].index = index + 1;
  848. return {
  849. value: values[index],
  850. done: false
  851. };
  852. }
  853. }, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())));
  854. Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
  855. value: 'HeadersIterator',
  856. writable: false,
  857. enumerable: false,
  858. configurable: true
  859. });
  860. /**
  861. * Export the Headers object in a form that Node.js can consume.
  862. *
  863. * @param Headers headers
  864. * @return Object
  865. */
  866. function exportNodeCompatibleHeaders(headers) {
  867. const obj = Object.assign({ __proto__: null }, headers[MAP]);
  868. // http.request() only supports string as Host header. This hack makes
  869. // specifying custom Host header possible.
  870. const hostHeaderKey = find(headers[MAP], 'Host');
  871. if (hostHeaderKey !== undefined) {
  872. obj[hostHeaderKey] = obj[hostHeaderKey][0];
  873. }
  874. return obj;
  875. }
  876. /**
  877. * Create a Headers object from an object of headers, ignoring those that do
  878. * not conform to HTTP grammar productions.
  879. *
  880. * @param Object obj Object of headers
  881. * @return Headers
  882. */
  883. function createHeadersLenient(obj) {
  884. const headers = new Headers();
  885. for (const name of Object.keys(obj)) {
  886. if (invalidTokenRegex.test(name)) {
  887. continue;
  888. }
  889. if (Array.isArray(obj[name])) {
  890. for (const val of obj[name]) {
  891. if (invalidHeaderCharRegex.test(val)) {
  892. continue;
  893. }
  894. if (headers[MAP][name] === undefined) {
  895. headers[MAP][name] = [val];
  896. } else {
  897. headers[MAP][name].push(val);
  898. }
  899. }
  900. } else if (!invalidHeaderCharRegex.test(obj[name])) {
  901. headers[MAP][name] = [obj[name]];
  902. }
  903. }
  904. return headers;
  905. }
  906. const INTERNALS$1 = Symbol('Response internals');
  907. // fix an issue where "STATUS_CODES" aren't a named export for node <10
  908. const STATUS_CODES = http.STATUS_CODES;
  909. /**
  910. * Response class
  911. *
  912. * @param Stream body Readable stream
  913. * @param Object opts Response options
  914. * @return Void
  915. */
  916. class Response {
  917. constructor() {
  918. let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
  919. let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  920. Body.call(this, body, opts);
  921. const status = opts.status || 200;
  922. const headers = new Headers(opts.headers);
  923. if (body != null && !headers.has('Content-Type')) {
  924. const contentType = extractContentType(body);
  925. if (contentType) {
  926. headers.append('Content-Type', contentType);
  927. }
  928. }
  929. this[INTERNALS$1] = {
  930. url: opts.url,
  931. status,
  932. statusText: opts.statusText || STATUS_CODES[status],
  933. headers,
  934. counter: opts.counter
  935. };
  936. }
  937. get url() {
  938. return this[INTERNALS$1].url || '';
  939. }
  940. get status() {
  941. return this[INTERNALS$1].status;
  942. }
  943. /**
  944. * Convenience property representing if the request ended normally
  945. */
  946. get ok() {
  947. return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
  948. }
  949. get redirected() {
  950. return this[INTERNALS$1].counter > 0;
  951. }
  952. get statusText() {
  953. return this[INTERNALS$1].statusText;
  954. }
  955. get headers() {
  956. return this[INTERNALS$1].headers;
  957. }
  958. /**
  959. * Clone this response
  960. *
  961. * @return Response
  962. */
  963. clone() {
  964. return new Response(clone(this), {
  965. url: this.url,
  966. status: this.status,
  967. statusText: this.statusText,
  968. headers: this.headers,
  969. ok: this.ok,
  970. redirected: this.redirected
  971. });
  972. }
  973. }
  974. Body.mixIn(Response.prototype);
  975. Object.defineProperties(Response.prototype, {
  976. url: { enumerable: true },
  977. status: { enumerable: true },
  978. ok: { enumerable: true },
  979. redirected: { enumerable: true },
  980. statusText: { enumerable: true },
  981. headers: { enumerable: true },
  982. clone: { enumerable: true }
  983. });
  984. Object.defineProperty(Response.prototype, Symbol.toStringTag, {
  985. value: 'Response',
  986. writable: false,
  987. enumerable: false,
  988. configurable: true
  989. });
  990. const INTERNALS$2 = Symbol('Request internals');
  991. // fix an issue where "format", "parse" aren't a named export for node <10
  992. const parse_url = Url.parse;
  993. const format_url = Url.format;
  994. const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;
  995. /**
  996. * Check if a value is an instance of Request.
  997. *
  998. * @param Mixed input
  999. * @return Boolean
  1000. */
  1001. function isRequest(input) {
  1002. return typeof input === 'object' && typeof input[INTERNALS$2] === 'object';
  1003. }
  1004. function isAbortSignal(signal) {
  1005. const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal);
  1006. return !!(proto && proto.constructor.name === 'AbortSignal');
  1007. }
  1008. /**
  1009. * Request class
  1010. *
  1011. * @param Mixed input Url or Request instance
  1012. * @param Object init Custom options
  1013. * @return Void
  1014. */
  1015. class Request {
  1016. constructor(input) {
  1017. let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  1018. let parsedURL;
  1019. // normalize input
  1020. if (!isRequest(input)) {
  1021. if (input && input.href) {
  1022. // in order to support Node.js' Url objects; though WHATWG's URL objects
  1023. // will fall into this branch also (since their `toString()` will return
  1024. // `href` property anyway)
  1025. parsedURL = parse_url(input.href);
  1026. } else {
  1027. // coerce input to a string before attempting to parse
  1028. parsedURL = parse_url(`${input}`);
  1029. }
  1030. input = {};
  1031. } else {
  1032. parsedURL = parse_url(input.url);
  1033. }
  1034. let method = init.method || input.method || 'GET';
  1035. method = method.toUpperCase();
  1036. if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) {
  1037. throw new TypeError('Request with GET/HEAD method cannot have body');
  1038. }
  1039. let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null;
  1040. Body.call(this, inputBody, {
  1041. timeout: init.timeout || input.timeout || 0,
  1042. size: init.size || input.size || 0
  1043. });
  1044. const headers = new Headers(init.headers || input.headers || {});
  1045. if (inputBody != null && !headers.has('Content-Type')) {
  1046. const contentType = extractContentType(inputBody);
  1047. if (contentType) {
  1048. headers.append('Content-Type', contentType);
  1049. }
  1050. }
  1051. let signal = isRequest(input) ? input.signal : null;
  1052. if ('signal' in init) signal = init.signal;
  1053. if (signal != null && !isAbortSignal(signal)) {
  1054. throw new TypeError('Expected signal to be an instanceof AbortSignal');
  1055. }
  1056. this[INTERNALS$2] = {
  1057. method,
  1058. redirect: init.redirect || input.redirect || 'follow',
  1059. headers,
  1060. parsedURL,
  1061. signal
  1062. };
  1063. // node-fetch-only options
  1064. this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20;
  1065. this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true;
  1066. this.counter = init.counter || input.counter || 0;
  1067. this.agent = init.agent || input.agent;
  1068. }
  1069. get method() {
  1070. return this[INTERNALS$2].method;
  1071. }
  1072. get url() {
  1073. return format_url(this[INTERNALS$2].parsedURL);
  1074. }
  1075. get headers() {
  1076. return this[INTERNALS$2].headers;
  1077. }
  1078. get redirect() {
  1079. return this[INTERNALS$2].redirect;
  1080. }
  1081. get signal() {
  1082. return this[INTERNALS$2].signal;
  1083. }
  1084. /**
  1085. * Clone this request
  1086. *
  1087. * @return Request
  1088. */
  1089. clone() {
  1090. return new Request(this);
  1091. }
  1092. }
  1093. Body.mixIn(Request.prototype);
  1094. Object.defineProperty(Request.prototype, Symbol.toStringTag, {
  1095. value: 'Request',
  1096. writable: false,
  1097. enumerable: false,
  1098. configurable: true
  1099. });
  1100. Object.defineProperties(Request.prototype, {
  1101. method: { enumerable: true },
  1102. url: { enumerable: true },
  1103. headers: { enumerable: true },
  1104. redirect: { enumerable: true },
  1105. clone: { enumerable: true },
  1106. signal: { enumerable: true }
  1107. });
  1108. /**
  1109. * Convert a Request to Node.js http request options.
  1110. *
  1111. * @param Request A Request instance
  1112. * @return Object The options object to be passed to http.request
  1113. */
  1114. function getNodeRequestOptions(request) {
  1115. const parsedURL = request[INTERNALS$2].parsedURL;
  1116. const headers = new Headers(request[INTERNALS$2].headers);
  1117. // fetch step 1.3
  1118. if (!headers.has('Accept')) {
  1119. headers.set('Accept', '*/*');
  1120. }
  1121. // Basic fetch
  1122. if (!parsedURL.protocol || !parsedURL.hostname) {
  1123. throw new TypeError('Only absolute URLs are supported');
  1124. }
  1125. if (!/^https?:$/.test(parsedURL.protocol)) {
  1126. throw new TypeError('Only HTTP(S) protocols are supported');
  1127. }
  1128. if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) {
  1129. throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8');
  1130. }
  1131. // HTTP-network-or-cache fetch steps 2.4-2.7
  1132. let contentLengthValue = null;
  1133. if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
  1134. contentLengthValue = '0';
  1135. }
  1136. if (request.body != null) {
  1137. const totalBytes = getTotalBytes(request);
  1138. if (typeof totalBytes === 'number') {
  1139. contentLengthValue = String(totalBytes);
  1140. }
  1141. }
  1142. if (contentLengthValue) {
  1143. headers.set('Content-Length', contentLengthValue);
  1144. }
  1145. // HTTP-network-or-cache fetch step 2.11
  1146. if (!headers.has('User-Agent')) {
  1147. headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
  1148. }
  1149. // HTTP-network-or-cache fetch step 2.15
  1150. if (request.compress && !headers.has('Accept-Encoding')) {
  1151. headers.set('Accept-Encoding', 'gzip,deflate');
  1152. }
  1153. let agent = request.agent;
  1154. if (typeof agent === 'function') {
  1155. agent = agent(parsedURL);
  1156. }
  1157. if (!headers.has('Connection') && !agent) {
  1158. headers.set('Connection', 'close');
  1159. }
  1160. // HTTP-network fetch step 4.2
  1161. // chunked encoding is handled by Node.js
  1162. return Object.assign({}, parsedURL, {
  1163. method: request.method,
  1164. headers: exportNodeCompatibleHeaders(headers),
  1165. agent
  1166. });
  1167. }
  1168. /**
  1169. * abort-error.js
  1170. *
  1171. * AbortError interface for cancelled requests
  1172. */
  1173. /**
  1174. * Create AbortError instance
  1175. *
  1176. * @param String message Error message for human
  1177. * @return AbortError
  1178. */
  1179. function AbortError(message) {
  1180. Error.call(this, message);
  1181. this.type = 'aborted';
  1182. this.message = message;
  1183. // hide custom error implementation details from end-users
  1184. Error.captureStackTrace(this, this.constructor);
  1185. }
  1186. AbortError.prototype = Object.create(Error.prototype);
  1187. AbortError.prototype.constructor = AbortError;
  1188. AbortError.prototype.name = 'AbortError';
  1189. // fix an issue where "PassThrough", "resolve" aren't a named export for node <10
  1190. const PassThrough$1 = Stream.PassThrough;
  1191. const resolve_url = Url.resolve;
  1192. /**
  1193. * Fetch function
  1194. *
  1195. * @param Mixed url Absolute url or Request instance
  1196. * @param Object opts Fetch options
  1197. * @return Promise
  1198. */
  1199. function fetch(url, opts) {
  1200. // allow custom promise
  1201. if (!fetch.Promise) {
  1202. throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
  1203. }
  1204. Body.Promise = fetch.Promise;
  1205. // wrap http.request into fetch
  1206. return new fetch.Promise(function (resolve, reject) {
  1207. // build request object
  1208. const request = new Request(url, opts);
  1209. const options = getNodeRequestOptions(request);
  1210. const send = (options.protocol === 'https:' ? https : http).request;
  1211. const signal = request.signal;
  1212. let response = null;
  1213. const abort = function abort() {
  1214. let error = new AbortError('The user aborted a request.');
  1215. reject(error);
  1216. if (request.body && request.body instanceof Stream.Readable) {
  1217. request.body.destroy(error);
  1218. }
  1219. if (!response || !response.body) return;
  1220. response.body.emit('error', error);
  1221. };
  1222. if (signal && signal.aborted) {
  1223. abort();
  1224. return;
  1225. }
  1226. const abortAndFinalize = function abortAndFinalize() {
  1227. abort();
  1228. finalize();
  1229. };
  1230. // send request
  1231. const req = send(options);
  1232. let reqTimeout;
  1233. if (signal) {
  1234. signal.addEventListener('abort', abortAndFinalize);
  1235. }
  1236. function finalize() {
  1237. req.abort();
  1238. if (signal) signal.removeEventListener('abort', abortAndFinalize);
  1239. clearTimeout(reqTimeout);
  1240. }
  1241. if (request.timeout) {
  1242. req.once('socket', function (socket) {
  1243. reqTimeout = setTimeout(function () {
  1244. reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
  1245. finalize();
  1246. }, request.timeout);
  1247. });
  1248. }
  1249. req.on('error', function (err) {
  1250. reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
  1251. finalize();
  1252. });
  1253. req.on('response', function (res) {
  1254. clearTimeout(reqTimeout);
  1255. const headers = createHeadersLenient(res.headers);
  1256. // HTTP fetch step 5
  1257. if (fetch.isRedirect(res.statusCode)) {
  1258. // HTTP fetch step 5.2
  1259. const location = headers.get('Location');
  1260. // HTTP fetch step 5.3
  1261. const locationURL = location === null ? null : resolve_url(request.url, location);
  1262. // HTTP fetch step 5.5
  1263. switch (request.redirect) {
  1264. case 'error':
  1265. reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect'));
  1266. finalize();
  1267. return;
  1268. case 'manual':
  1269. // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
  1270. if (locationURL !== null) {
  1271. // handle corrupted header
  1272. try {
  1273. headers.set('Location', locationURL);
  1274. } catch (err) {
  1275. // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request
  1276. reject(err);
  1277. }
  1278. }
  1279. break;
  1280. case 'follow':
  1281. // HTTP-redirect fetch step 2
  1282. if (locationURL === null) {
  1283. break;
  1284. }
  1285. // HTTP-redirect fetch step 5
  1286. if (request.counter >= request.follow) {
  1287. reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
  1288. finalize();
  1289. return;
  1290. }
  1291. // HTTP-redirect fetch step 6 (counter increment)
  1292. // Create a new Request object.
  1293. const requestOpts = {
  1294. headers: new Headers(request.headers),
  1295. follow: request.follow,
  1296. counter: request.counter + 1,
  1297. agent: request.agent,
  1298. compress: request.compress,
  1299. method: request.method,
  1300. body: request.body,
  1301. signal: request.signal,
  1302. timeout: request.timeout
  1303. };
  1304. // HTTP-redirect fetch step 9
  1305. if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
  1306. reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
  1307. finalize();
  1308. return;
  1309. }
  1310. // HTTP-redirect fetch step 11
  1311. if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
  1312. requestOpts.method = 'GET';
  1313. requestOpts.body = undefined;
  1314. requestOpts.headers.delete('content-length');
  1315. }
  1316. // HTTP-redirect fetch step 15
  1317. resolve(fetch(new Request(locationURL, requestOpts)));
  1318. finalize();
  1319. return;
  1320. }
  1321. }
  1322. // prepare response
  1323. res.once('end', function () {
  1324. if (signal) signal.removeEventListener('abort', abortAndFinalize);
  1325. });
  1326. let body = res.pipe(new PassThrough$1());
  1327. const response_options = {
  1328. url: request.url,
  1329. status: res.statusCode,
  1330. statusText: res.statusMessage,
  1331. headers: headers,
  1332. size: request.size,
  1333. timeout: request.timeout,
  1334. counter: request.counter
  1335. };
  1336. // HTTP-network fetch step 12.1.1.3
  1337. const codings = headers.get('Content-Encoding');
  1338. // HTTP-network fetch step 12.1.1.4: handle content codings
  1339. // in following scenarios we ignore compression support
  1340. // 1. compression support is disabled
  1341. // 2. HEAD request
  1342. // 3. no Content-Encoding header
  1343. // 4. no content response (204)
  1344. // 5. content not modified response (304)
  1345. if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
  1346. response = new Response(body, response_options);
  1347. resolve(response);
  1348. return;
  1349. }
  1350. // For Node v6+
  1351. // Be less strict when decoding compressed responses, since sometimes
  1352. // servers send slightly invalid responses that are still accepted
  1353. // by common browsers.
  1354. // Always using Z_SYNC_FLUSH is what cURL does.
  1355. const zlibOptions = {
  1356. flush: zlib.Z_SYNC_FLUSH,
  1357. finishFlush: zlib.Z_SYNC_FLUSH
  1358. };
  1359. // for gzip
  1360. if (codings == 'gzip' || codings == 'x-gzip') {
  1361. body = body.pipe(zlib.createGunzip(zlibOptions));
  1362. response = new Response(body, response_options);
  1363. resolve(response);
  1364. return;
  1365. }
  1366. // for deflate
  1367. if (codings == 'deflate' || codings == 'x-deflate') {
  1368. // handle the infamous raw deflate response from old servers
  1369. // a hack for old IIS and Apache servers
  1370. const raw = res.pipe(new PassThrough$1());
  1371. raw.once('data', function (chunk) {
  1372. // see http://stackoverflow.com/questions/37519828
  1373. if ((chunk[0] & 0x0F) === 0x08) {
  1374. body = body.pipe(zlib.createInflate());
  1375. } else {
  1376. body = body.pipe(zlib.createInflateRaw());
  1377. }
  1378. response = new Response(body, response_options);
  1379. resolve(response);
  1380. });
  1381. return;
  1382. }
  1383. // for br
  1384. if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
  1385. body = body.pipe(zlib.createBrotliDecompress());
  1386. response = new Response(body, response_options);
  1387. resolve(response);
  1388. return;
  1389. }
  1390. // otherwise, use response as-is
  1391. response = new Response(body, response_options);
  1392. resolve(response);
  1393. });
  1394. writeToStream(req, request);
  1395. });
  1396. }
  1397. /**
  1398. * Redirect code matching
  1399. *
  1400. * @param Number code Status code
  1401. * @return Boolean
  1402. */
  1403. fetch.isRedirect = function (code) {
  1404. return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
  1405. };
  1406. // expose Promise
  1407. fetch.Promise = global.Promise;
  1408. module.exports = exports = fetch;
  1409. Object.defineProperty(exports, "__esModule", { value: true });
  1410. exports.default = exports;
  1411. exports.Headers = Headers;
  1412. exports.Request = Request;
  1413. exports.Response = Response;
  1414. exports.FetchError = FetchError;