apisauce.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {value: true});
  3. function _interopDefault(ex) {
  4. return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex;
  5. }
  6. var axios = _interopDefault(require('../../axios'));
  7. var R = _interopDefault(require('ramda'));
  8. var RS = _interopDefault(require('ramdasauce'));
  9. var _extends = Object.assign || function (target) {
  10. for (var i = 1; i < arguments.length; i++) {
  11. var source = arguments[i];
  12. for (var key in source) {
  13. if (Object.prototype.hasOwnProperty.call(source, key)) {
  14. target[key] = source[key];
  15. }
  16. }
  17. }
  18. return target;
  19. };
  20. // check for an invalid config
  21. var isInvalidConfig = R.anyPass([R.isNil, R.isEmpty, R.complement(R.has('baseURL')), R.complement(R.propIs(String, 'baseURL')), R.propSatisfies(R.isEmpty, 'baseURL')]);
  22. // the default headers given to axios
  23. var DEFAULT_HEADERS = {
  24. 'Accept': 'application/json',
  25. 'Content-Type': 'application/json'
  26. };
  27. // the default configuration for axios, default headers will also be merged in
  28. var DEFAULT_CONFIG = {
  29. timeout: 0
  30. };
  31. var NONE = null;
  32. var CLIENT_ERROR = 'CLIENT_ERROR';
  33. var SERVER_ERROR = 'SERVER_ERROR';
  34. var TIMEOUT_ERROR = 'TIMEOUT_ERROR';
  35. var CONNECTION_ERROR = 'CONNECTION_ERROR';
  36. var NETWORK_ERROR = 'NETWORK_ERROR';
  37. var UNKNOWN_ERROR = 'UNKNOWN_ERROR';
  38. var TIMEOUT_ERROR_CODES = ['ECONNABORTED'];
  39. var NODEJS_CONNECTION_ERROR_CODES = ['ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET'];
  40. var in200s = RS.isWithin(200, 299);
  41. var in400s = RS.isWithin(400, 499);
  42. var in500s = RS.isWithin(500, 599);
  43. /**
  44. Creates a instance of our API using the configuration.
  45. */
  46. var create = function create(config) {
  47. // quick sanity check
  48. if (isInvalidConfig(config)) throw new Error('config must have a baseURL');
  49. // combine the user's defaults with ours
  50. var headers = R.merge(DEFAULT_HEADERS, config.headers || {});
  51. var combinedConfig = R.merge(DEFAULT_CONFIG, R.dissoc('headers', config));
  52. // create the axios instance
  53. var instance = axios.create(combinedConfig);
  54. var monitors = [];
  55. var addMonitor = function addMonitor(monitor) {
  56. monitors.push(monitor);
  57. };
  58. var requestTransforms = [];
  59. var responseTransforms = [];
  60. var addRequestTransform = function addRequestTransform(transform) {
  61. return requestTransforms.push(transform);
  62. };
  63. var addResponseTransform = function addResponseTransform(transform) {
  64. return responseTransforms.push(transform);
  65. };
  66. // convenience for setting new request headers
  67. var setHeader = function setHeader(name, value) {
  68. headers[name] = value;
  69. return instance;
  70. };
  71. // sets headers in bulk
  72. var setHeaders = function setHeaders(headers) {
  73. var keys = R.keys(headers);
  74. R.forEach(function (header) {
  75. return setHeader(header, headers[header]);
  76. }, keys);
  77. return instance;
  78. };
  79. /**
  80. Make the request for GET, HEAD, DELETE
  81. */
  82. var doRequestWithoutBody = function doRequestWithoutBody(method, url) {
  83. var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
  84. var axiosConfig = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
  85. return doRequest(R.merge({url: url, params: params, method: method}, axiosConfig));
  86. };
  87. /**
  88. Make the request for POST, PUT, PATCH
  89. */
  90. var doRequestWithBody = function doRequestWithBody(method, url) {
  91. var data = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
  92. var axiosConfig = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
  93. return doRequest(R.merge({url: url, method: method, data: data}, axiosConfig));
  94. };
  95. /**
  96. Make the request with this config!
  97. */
  98. var doRequest = function doRequest(axiosRequestConfig) {
  99. var startedAt = RS.toNumber(new Date());
  100. axiosRequestConfig.headers = _extends({}, headers, axiosRequestConfig.headers);
  101. // add the request transforms
  102. if (requestTransforms.length > 0) {
  103. (function () {
  104. // create an object to feed through the request transforms
  105. var request = R.pick(['url', 'method', 'data', 'headers', 'params'], axiosRequestConfig);
  106. // go go go!
  107. R.forEach(function (transform) {
  108. return transform(request);
  109. }, requestTransforms);
  110. // overwrite our axios request with whatever our object looks like now
  111. axiosRequestConfig = R.merge(axiosRequestConfig, request);
  112. })();
  113. }
  114. // first convert the axios response, then execute our callback
  115. var chain = R.pipe(R.partial(convertResponse, [startedAt]), runMonitors);
  116. // Make the request and execute the identical pipeline for both promise paths.
  117. return instance.request(axiosRequestConfig).then(chain).catch(chain);
  118. };
  119. /**
  120. Fires after we convert from axios' response into our response. Exceptions
  121. raised for each monitor will be ignored.
  122. */
  123. var runMonitors = function runMonitors(ourResponse) {
  124. monitors.forEach(function (fn) {
  125. try {
  126. fn(ourResponse);
  127. } catch (error) {
  128. // all monitor complaints will be ignored
  129. }
  130. });
  131. return ourResponse;
  132. };
  133. /**
  134. Converts an axios response/error into our response.
  135. */
  136. var convertResponse = function convertResponse(startedAt, axiosResponse) {
  137. var end = RS.toNumber(new Date());
  138. var duration = end - startedAt;
  139. // new in Axios 0.13 -- some data could be buried 1 level now
  140. var isError = axiosResponse instanceof Error;
  141. var response = isError ? axiosResponse.response : axiosResponse;
  142. var status = response && response.status || null;
  143. var problem = isError ? getProblemFromError(axiosResponse) : getProblemFromStatus(status);
  144. var ok = in200s(status);
  145. var config = axiosResponse.config || null;
  146. var headers = response && response.headers || null;
  147. var data = response && response.data || null;
  148. // give an opportunity for anything to the response transforms to change stuff along the way
  149. if (responseTransforms.length > 0) {
  150. R.forEach(function (transform) {
  151. transform({
  152. duration: duration,
  153. problem: problem,
  154. ok: ok,
  155. status: status,
  156. headers: headers,
  157. config: config,
  158. data: data
  159. });
  160. }, responseTransforms);
  161. }
  162. return {duration: duration, problem: problem, ok: ok, status: status, headers: headers, config: config, data: data};
  163. };
  164. /**
  165. What's the problem for this response?
  166. TODO: We're losing some error granularity, but i'm cool with that
  167. until someone cares.
  168. */
  169. var getProblemFromError = function getProblemFromError(error) {
  170. // first check if the error message is Network Error (set by axios at 0.12) on platforms other than NodeJS.
  171. if (error.message === 'Network Error') return NETWORK_ERROR;
  172. // then check the specific error code
  173. return R.cond([
  174. // if we don't have an error code, we have a response status
  175. [R.isNil, function () {
  176. return getProblemFromStatus(error.response.status);
  177. }], [R.contains(R.__, TIMEOUT_ERROR_CODES), R.always(TIMEOUT_ERROR)], [R.contains(R.__, NODEJS_CONNECTION_ERROR_CODES), R.always(CONNECTION_ERROR)], [R.T, R.always(UNKNOWN_ERROR)]])(error.code);
  178. };
  179. /**
  180. * Given a HTTP status code, return back the appropriate problem enum.
  181. */
  182. var getProblemFromStatus = function getProblemFromStatus(status) {
  183. return R.cond([[R.isNil, R.always(UNKNOWN_ERROR)], [in200s, R.always(NONE)], [in400s, R.always(CLIENT_ERROR)], [in500s, R.always(SERVER_ERROR)], [R.T, R.always(UNKNOWN_ERROR)]])(status);
  184. };
  185. // create the base object
  186. var sauce = {
  187. axiosInstance: instance,
  188. monitors: monitors,
  189. addMonitor: addMonitor,
  190. requestTransforms: requestTransforms,
  191. responseTransforms: responseTransforms,
  192. addRequestTransform: addRequestTransform,
  193. addResponseTransform: addResponseTransform,
  194. setHeader: setHeader,
  195. setHeaders: setHeaders,
  196. headers: headers,
  197. get: R.partial(doRequestWithoutBody, ['get']),
  198. delete: R.partial(doRequestWithoutBody, ['delete']),
  199. head: R.partial(doRequestWithoutBody, ['head']),
  200. post: R.partial(doRequestWithBody, ['post']),
  201. put: R.partial(doRequestWithBody, ['put']),
  202. patch: R.partial(doRequestWithBody, ['patch'])
  203. };
  204. // send back the sauce
  205. return sauce;
  206. };
  207. var apisauce = {
  208. DEFAULT_HEADERS: DEFAULT_HEADERS,
  209. NONE: NONE,
  210. CLIENT_ERROR: CLIENT_ERROR,
  211. SERVER_ERROR: SERVER_ERROR,
  212. TIMEOUT_ERROR: TIMEOUT_ERROR,
  213. CONNECTION_ERROR: CONNECTION_ERROR,
  214. NETWORK_ERROR: NETWORK_ERROR,
  215. UNKNOWN_ERROR: UNKNOWN_ERROR,
  216. create: create
  217. };
  218. exports.DEFAULT_HEADERS = DEFAULT_HEADERS;
  219. exports.NONE = NONE;
  220. exports.CLIENT_ERROR = CLIENT_ERROR;
  221. exports.SERVER_ERROR = SERVER_ERROR;
  222. exports.TIMEOUT_ERROR = TIMEOUT_ERROR;
  223. exports.CONNECTION_ERROR = CONNECTION_ERROR;
  224. exports.NETWORK_ERROR = NETWORK_ERROR;
  225. exports.UNKNOWN_ERROR = UNKNOWN_ERROR;
  226. exports.create = create;
  227. exports['default'] = apisauce;