UNPKG

18.7 kBJavaScriptView Raw
1import _regeneratorRuntime from "@babel/runtime-corejs2/regenerator";
2import _createForOfIteratorHelper from "@babel/runtime-corejs2/helpers/createForOfIteratorHelper";
3import _Object$entries from "@babel/runtime-corejs2/core-js/object/entries";
4import _Object$keys from "@babel/runtime-corejs2/core-js/object/keys";
5import _JSON$stringify from "@babel/runtime-corejs2/core-js/json/stringify";
6import _Array$isArray from "@babel/runtime-corejs2/core-js/array/is-array";
7import _typeof from "@babel/runtime-corejs2/helpers/typeof";
8import _slicedToArray from "@babel/runtime-corejs2/helpers/slicedToArray";
9import _Array$from from "@babel/runtime-corejs2/core-js/array/from";
10import _asyncToGenerator from "@babel/runtime-corejs2/helpers/asyncToGenerator";
11import 'cross-fetch/polyfill';
12/* global fetch */
13
14import qs from 'qs';
15import jsYaml from 'js-yaml';
16import pick from 'lodash/pick';
17import isFunction from 'lodash/isFunction';
18import { Buffer } from 'buffer';
19import FormData from './internal/form-data-monkey-patch';
20import { encodeDisallowedCharacters } from './execute/oas3/style-serializer'; // For testing
21
22export var self = {
23 serializeRes: serializeRes,
24 mergeInQueryOrForm: mergeInQueryOrForm
25}; // Handles fetch-like syntax and the case where there is only one object passed-in
26// (which will have the URL as a property). Also serilizes the response.
27
28export default function http(_x) {
29 return _http.apply(this, arguments);
30} // exported for testing
31
32function _http() {
33 _http = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(url) {
34 var request,
35 contentType,
36 res,
37 error,
38 _error,
39 _args = arguments;
40
41 return _regeneratorRuntime.wrap(function _callee$(_context) {
42 while (1) {
43 switch (_context.prev = _context.next) {
44 case 0:
45 request = _args.length > 1 && _args[1] !== undefined ? _args[1] : {};
46
47 if (_typeof(url) === 'object') {
48 request = url;
49 url = request.url;
50 }
51
52 request.headers = request.headers || {}; // Serializes query, for convenience
53 // Should be the last thing we do, as its hard to mutate the URL with
54 // the search string, but much easier to manipulate the req.query object
55
56 self.mergeInQueryOrForm(request); // Newlines in header values cause weird error messages from `window.fetch`,
57 // so let's massage them out.
58 // Context: https://stackoverflow.com/a/50709178
59
60 if (request.headers) {
61 _Object$keys(request.headers).forEach(function (headerName) {
62 var value = request.headers[headerName];
63
64 if (typeof value === 'string') {
65 request.headers[headerName] = value.replace(/\n+/g, ' ');
66 }
67 });
68 } // Wait for the request interceptor, if it was provided
69 // WARNING: don't put anything between this and the request firing unless
70 // you have a good reason!
71
72
73 if (!request.requestInterceptor) {
74 _context.next = 12;
75 break;
76 }
77
78 _context.next = 8;
79 return request.requestInterceptor(request);
80
81 case 8:
82 _context.t0 = _context.sent;
83
84 if (_context.t0) {
85 _context.next = 11;
86 break;
87 }
88
89 _context.t0 = request;
90
91 case 11:
92 request = _context.t0;
93
94 case 12:
95 // for content-type=multipart\/form-data remove content-type from request before fetch
96 // so that correct one with `boundary` is set
97 contentType = request.headers['content-type'] || request.headers['Content-Type'];
98
99 if (/multipart\/form-data/i.test(contentType)) {
100 delete request.headers['content-type'];
101 delete request.headers['Content-Type'];
102 } // eslint-disable-next-line no-undef
103
104
105 _context.prev = 14;
106 _context.next = 17;
107 return (request.userFetch || fetch)(request.url, request);
108
109 case 17:
110 res = _context.sent;
111 _context.next = 20;
112 return self.serializeRes(res, url, request);
113
114 case 20:
115 res = _context.sent;
116
117 if (!request.responseInterceptor) {
118 _context.next = 28;
119 break;
120 }
121
122 _context.next = 24;
123 return request.responseInterceptor(res);
124
125 case 24:
126 _context.t1 = _context.sent;
127
128 if (_context.t1) {
129 _context.next = 27;
130 break;
131 }
132
133 _context.t1 = res;
134
135 case 27:
136 res = _context.t1;
137
138 case 28:
139 _context.next = 39;
140 break;
141
142 case 30:
143 _context.prev = 30;
144 _context.t2 = _context["catch"](14);
145
146 if (res) {
147 _context.next = 34;
148 break;
149 }
150
151 throw _context.t2;
152
153 case 34:
154 error = new Error(res.statusText);
155 error.status = res.status;
156 error.statusCode = res.status;
157 error.responseError = _context.t2;
158 throw error;
159
160 case 39:
161 if (res.ok) {
162 _context.next = 45;
163 break;
164 }
165
166 _error = new Error(res.statusText);
167 _error.status = res.status;
168 _error.statusCode = res.status;
169 _error.response = res;
170 throw _error;
171
172 case 45:
173 return _context.abrupt("return", res);
174
175 case 46:
176 case "end":
177 return _context.stop();
178 }
179 }
180 }, _callee, null, [[14, 30]]);
181 }));
182 return _http.apply(this, arguments);
183}
184
185export var shouldDownloadAsText = function shouldDownloadAsText() {
186 var contentType = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
187 return /(json|xml|yaml|text)\b/.test(contentType);
188};
189
190function parseBody(body, contentType) {
191 if (contentType && (contentType.indexOf('application/json') === 0 || contentType.indexOf('+json') > 0)) {
192 return JSON.parse(body);
193 }
194
195 return jsYaml.safeLoad(body);
196} // Serialize the response, returns a promise with headers and the body part of the hash
197
198
199export function serializeRes(oriRes, url) {
200 var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
201 _ref$loadSpec = _ref.loadSpec,
202 loadSpec = _ref$loadSpec === void 0 ? false : _ref$loadSpec;
203
204 var res = {
205 ok: oriRes.ok,
206 url: oriRes.url || url,
207 status: oriRes.status,
208 statusText: oriRes.statusText,
209 headers: serializeHeaders(oriRes.headers)
210 };
211 var contentType = res.headers['content-type'];
212 var useText = loadSpec || shouldDownloadAsText(contentType);
213 var getBody = useText ? oriRes.text : oriRes.blob || oriRes.buffer;
214 return getBody.call(oriRes).then(function (body) {
215 res.text = body;
216 res.data = body;
217
218 if (useText) {
219 try {
220 var obj = parseBody(body, contentType);
221 res.body = obj;
222 res.obj = obj;
223 } catch (e) {
224 res.parseError = e;
225 }
226 }
227
228 return res;
229 });
230}
231
232function serializeHeaderValue(value) {
233 var isMulti = value.includes(', ');
234 return isMulti ? value.split(', ') : value;
235} // Serialize headers into a hash, where mutliple-headers result in an array.
236//
237// eg: Cookie: one
238// Cookie: two
239// = { Cookie: [ "one", "two" ]
240
241
242export function serializeHeaders() {
243 var headers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
244 if (!isFunction(headers.entries)) return {};
245 return _Array$from(headers.entries()).reduce(function (acc, _ref2) {
246 var _ref3 = _slicedToArray(_ref2, 2),
247 header = _ref3[0],
248 value = _ref3[1];
249
250 acc[header] = serializeHeaderValue(value);
251 return acc;
252 }, {});
253}
254export function isFile(obj, navigatorObj) {
255 if (!navigatorObj && typeof navigator !== 'undefined') {
256 // eslint-disable-next-line no-undef
257 navigatorObj = navigator;
258 }
259
260 if (navigatorObj && navigatorObj.product === 'ReactNative') {
261 if (obj && _typeof(obj) === 'object' && typeof obj.uri === 'string') {
262 return true;
263 }
264
265 return false;
266 }
267
268 if (typeof File !== 'undefined' && obj instanceof File) {
269 // eslint-disable-line no-undef
270 return true;
271 }
272
273 if (typeof Blob !== 'undefined' && obj instanceof Blob) {
274 // eslint-disable-line no-undef
275 return true;
276 }
277
278 if (typeof Buffer !== 'undefined' && obj instanceof Buffer) {
279 return true;
280 }
281
282 return obj !== null && _typeof(obj) === 'object' && typeof obj.pipe === 'function';
283}
284
285function isArrayOfFile(obj, navigatorObj) {
286 return _Array$isArray(obj) && obj.some(function (v) {
287 return isFile(v, navigatorObj);
288 });
289}
290
291var STYLE_SEPARATORS = {
292 form: ',',
293 spaceDelimited: '%20',
294 pipeDelimited: '|'
295};
296var SEPARATORS = {
297 csv: ',',
298 ssv: '%20',
299 tsv: '%09',
300 pipes: '|'
301}; // Formats a key-value and returns an array of key-value pairs.
302//
303// Return value example 1: [['color', 'blue']]
304// Return value example 2: [['color', 'blue,black,brown']]
305// Return value example 3: [['color', ['blue', 'black', 'brown']]]
306// Return value example 4: [['color', 'R,100,G,200,B,150']]
307// Return value example 5: [['R', '100'], ['G', '200'], ['B', '150']]
308// Return value example 6: [['color[R]', '100'], ['color[G]', '200'], ['color[B]', '150']]
309
310function formatKeyValue(key, input) {
311 var skipEncoding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
312 var collectionFormat = input.collectionFormat,
313 allowEmptyValue = input.allowEmptyValue,
314 serializationOption = input.serializationOption,
315 encoding = input.encoding; // `input` can be string
316
317 var value = _typeof(input) === 'object' && !_Array$isArray(input) ? input.value : input;
318 var encodeFn = skipEncoding ? function (k) {
319 return k.toString();
320 } : function (k) {
321 return encodeURIComponent(k);
322 };
323 var encodedKey = encodeFn(key);
324
325 if (typeof value === 'undefined' && allowEmptyValue) {
326 return [[encodedKey, '']];
327 } // file
328
329
330 if (isFile(value) || isArrayOfFile(value)) {
331 return [[encodedKey, value]];
332 } // for OAS 3 Parameter Object for serialization
333
334
335 if (serializationOption) {
336 return formatKeyValueBySerializationOption(key, value, skipEncoding, serializationOption);
337 } // for OAS 3 Encoding Object
338
339
340 if (encoding) {
341 if ([_typeof(encoding.style), _typeof(encoding.explode), _typeof(encoding.allowReserved)].some(function (type) {
342 return type !== 'undefined';
343 })) {
344 return formatKeyValueBySerializationOption(key, value, skipEncoding, pick(encoding, ['style', 'explode', 'allowReserved']));
345 }
346
347 if (encoding.contentType) {
348 if (encoding.contentType === 'application/json') {
349 // If value is a string, assume value is already a JSON string
350 var json = typeof value === 'string' ? value : _JSON$stringify(value);
351 return [[encodedKey, encodeFn(json)]];
352 }
353
354 return [[encodedKey, encodeFn(value.toString())]];
355 } // Primitive
356
357
358 if (_typeof(value) !== 'object') {
359 return [[encodedKey, encodeFn(value)]];
360 } // Array of primitives
361
362
363 if (_Array$isArray(value) && value.every(function (v) {
364 return _typeof(v) !== 'object';
365 })) {
366 return [[encodedKey, value.map(encodeFn).join(',')]];
367 } // Array or object
368
369
370 return [[encodedKey, encodeFn(_JSON$stringify(value))]];
371 } // for OAS 2 Parameter Object
372 // Primitive
373
374
375 if (_typeof(value) !== 'object') {
376 return [[encodedKey, encodeFn(value)]];
377 } // Array
378
379
380 if (_Array$isArray(value)) {
381 if (collectionFormat === 'multi') {
382 // In case of multipart/formdata, it is used as array.
383 // Otherwise, the caller will convert it to a query by qs.stringify.
384 return [[encodedKey, value.map(encodeFn)]];
385 }
386
387 return [[encodedKey, value.map(encodeFn).join(SEPARATORS[collectionFormat || 'csv'])]];
388 } // Object
389
390
391 return [[encodedKey, '']];
392}
393
394function formatKeyValueBySerializationOption(key, value, skipEncoding, serializationOption) {
395 var style = serializationOption.style || 'form';
396 var explode = typeof serializationOption.explode === 'undefined' ? style === 'form' : serializationOption.explode; // eslint-disable-next-line no-nested-ternary
397
398 var escape = skipEncoding ? false : serializationOption && serializationOption.allowReserved ? 'unsafe' : 'reserved';
399
400 var encodeFn = function encodeFn(v) {
401 return encodeDisallowedCharacters(v, {
402 escape: escape
403 });
404 };
405
406 var encodeKeyFn = skipEncoding ? function (k) {
407 return k;
408 } : function (k) {
409 return encodeDisallowedCharacters(k, {
410 escape: escape
411 });
412 }; // Primitive
413
414 if (_typeof(value) !== 'object') {
415 return [[encodeKeyFn(key), encodeFn(value)]];
416 } // Array
417
418
419 if (_Array$isArray(value)) {
420 if (explode) {
421 // In case of multipart/formdata, it is used as array.
422 // Otherwise, the caller will convert it to a query by qs.stringify.
423 return [[encodeKeyFn(key), value.map(encodeFn)]];
424 }
425
426 return [[encodeKeyFn(key), value.map(encodeFn).join(STYLE_SEPARATORS[style])]];
427 } // Object
428
429
430 if (style === 'deepObject') {
431 return _Object$keys(value).map(function (valueKey) {
432 return [encodeKeyFn("".concat(key, "[").concat(valueKey, "]")), encodeFn(value[valueKey])];
433 });
434 }
435
436 if (explode) {
437 return _Object$keys(value).map(function (valueKey) {
438 return [encodeKeyFn(valueKey), encodeFn(value[valueKey])];
439 });
440 }
441
442 return [[encodeKeyFn(key), _Object$keys(value).map(function (valueKey) {
443 return ["".concat(encodeKeyFn(valueKey), ",").concat(encodeFn(value[valueKey]))];
444 }).join(',')]];
445}
446
447function buildFormData(reqForm) {
448 /**
449 * Build a new FormData instance, support array as field value
450 * OAS2.0 - when collectionFormat is multi
451 * OAS3.0 - when explode of Encoding Object is true
452 * @param {Object} reqForm - ori req.form
453 * @return {FormData} - new FormData instance
454 */
455 return _Object$entries(reqForm).reduce(function (formData, _ref4) {
456 var _ref5 = _slicedToArray(_ref4, 2),
457 name = _ref5[0],
458 input = _ref5[1];
459
460 // eslint-disable-next-line no-restricted-syntax
461 var _iterator = _createForOfIteratorHelper(formatKeyValue(name, input, true)),
462 _step;
463
464 try {
465 for (_iterator.s(); !(_step = _iterator.n()).done;) {
466 var _step$value = _slicedToArray(_step.value, 2),
467 key = _step$value[0],
468 value = _step$value[1];
469
470 if (_Array$isArray(value)) {
471 // eslint-disable-next-line no-restricted-syntax
472 var _iterator2 = _createForOfIteratorHelper(value),
473 _step2;
474
475 try {
476 for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
477 var v = _step2.value;
478 formData.append(key, v);
479 }
480 } catch (err) {
481 _iterator2.e(err);
482 } finally {
483 _iterator2.f();
484 }
485 } else {
486 formData.append(key, value);
487 }
488 }
489 } catch (err) {
490 _iterator.e(err);
491 } finally {
492 _iterator.f();
493 }
494
495 return formData;
496 }, new FormData());
497} // Encodes an object using appropriate serializer.
498
499
500export function encodeFormOrQuery(data) {
501 /**
502 * Encode parameter names and values
503 * @param {Object} result - parameter names and values
504 * @param {string} parameterName - Parameter name
505 * @return {object} encoded parameter names and values
506 */
507 var encodedQuery = _Object$keys(data).reduce(function (result, parameterName) {
508 // eslint-disable-next-line no-restricted-syntax
509 var _iterator3 = _createForOfIteratorHelper(formatKeyValue(parameterName, data[parameterName])),
510 _step3;
511
512 try {
513 for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
514 var _step3$value = _slicedToArray(_step3.value, 2),
515 key = _step3$value[0],
516 value = _step3$value[1];
517
518 result[key] = value;
519 }
520 } catch (err) {
521 _iterator3.e(err);
522 } finally {
523 _iterator3.f();
524 }
525
526 return result;
527 }, {});
528
529 return qs.stringify(encodedQuery, {
530 encode: false,
531 indices: false
532 }) || '';
533} // If the request has a `query` object, merge it into the request.url, and delete the object
534// If file and/or multipart, also create FormData instance
535
536export function mergeInQueryOrForm() {
537 var req = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
538 var _req$url = req.url,
539 url = _req$url === void 0 ? '' : _req$url,
540 query = req.query,
541 form = req.form;
542
543 var joinSearch = function joinSearch() {
544 for (var _len = arguments.length, strs = new Array(_len), _key = 0; _key < _len; _key++) {
545 strs[_key] = arguments[_key];
546 }
547
548 var search = strs.filter(function (a) {
549 return a;
550 }).join('&'); // Only truthy value
551
552 return search ? "?".concat(search) : ''; // Only add '?' if there is a str
553 };
554
555 if (form) {
556 var hasFile = _Object$keys(form).some(function (key) {
557 var value = form[key].value;
558 return isFile(value) || isArrayOfFile(value);
559 });
560
561 var contentType = req.headers['content-type'] || req.headers['Content-Type'];
562
563 if (hasFile || /multipart\/form-data/i.test(contentType)) {
564 req.body = buildFormData(req.form);
565 } else {
566 req.body = encodeFormOrQuery(form);
567 }
568
569 delete req.form;
570 }
571
572 if (query) {
573 var _url$split = url.split('?'),
574 _url$split2 = _slicedToArray(_url$split, 2),
575 baseUrl = _url$split2[0],
576 oriSearch = _url$split2[1];
577
578 var newStr = '';
579
580 if (oriSearch) {
581 var oriQuery = qs.parse(oriSearch);
582
583 var keysToRemove = _Object$keys(query);
584
585 keysToRemove.forEach(function (key) {
586 return delete oriQuery[key];
587 });
588 newStr = qs.stringify(oriQuery, {
589 encode: true
590 });
591 }
592
593 var finalStr = joinSearch(newStr, encodeFormOrQuery(query));
594 req.url = baseUrl + finalStr;
595 delete req.query;
596 }
597
598 return req;
599} // Wrap a http function ( there are otherways to do this, consider this deprecated )
600
601export function makeHttp(httpFn, preFetch, postFetch) {
602 postFetch = postFetch || function (a) {
603 return a;
604 };
605
606 preFetch = preFetch || function (a) {
607 return a;
608 };
609
610 return function (req) {
611 if (typeof req === 'string') {
612 req = {
613 url: req
614 };
615 }
616
617 self.mergeInQueryOrForm(req);
618 req = preFetch(req);
619 return postFetch(httpFn(req));
620 };
621}
\No newline at end of file