Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

1632 linhas
40KB

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