UNPKG

@hrobarikt/box-ui-elements

Version:
547 lines (481 loc) 20.1 kB
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /** * * @file Network utilities * @author Box */ import axios from 'axios'; import getProp from 'lodash/get'; import includes from 'lodash/includes'; import lowerCase from 'lodash/lowerCase'; import TokenService from './TokenService'; import { HEADER_ACCEPT, HEADER_ACCEPT_LANGUAGE, HEADER_CLIENT_NAME, HEADER_CLIENT_VERSION, HEADER_CONTENT_TYPE, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE, HTTP_OPTIONS, HTTP_HEAD, HTTP_STATUS_CODE_RATE_LIMIT } from '../constants'; var DEFAULT_UPLOAD_TIMEOUT_MS = 120000; var MAX_NUM_RETRIES = 3; var RETRYABLE_HTTP_METHODS = [HTTP_GET, HTTP_OPTIONS, HTTP_HEAD].map(lowerCase); var Xhr = /*#__PURE__*/ function () { /** * [constructor] * * @param {Object} options * @param {string} options.id - item id * @param {string} options.clientName - Client Name * @param {string|function} options.token - Auth token * @param {string} [options.language] - Accept-Language header value * @param {string} [options.sharedLink] - Shared link * @param {string} [options.sharedLinkPassword] - Shared link password * @param {string} [options.requestInterceptor] - Request interceptor * @param {string} [options.responseInterceptor] - Response interceptor * @param {number[]} [options.retryableStatusCodes] - Response codes to retry * @param {boolean} [options.shouldRetry] - Should retry failed requests * @return {Xhr} Cache instance */ function Xhr() { var _this = this; var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, id = _ref.id, clientName = _ref.clientName, language = _ref.language, token = _ref.token, version = _ref.version, sharedLink = _ref.sharedLink, sharedLinkPassword = _ref.sharedLinkPassword, responseInterceptor = _ref.responseInterceptor, requestInterceptor = _ref.requestInterceptor, _ref$retryableStatusC = _ref.retryableStatusCodes, retryableStatusCodes = _ref$retryableStatusC === void 0 ? [HTTP_STATUS_CODE_RATE_LIMIT] : _ref$retryableStatusC, _ref$shouldRetry = _ref.shouldRetry, _shouldRetry = _ref$shouldRetry === void 0 ? true : _ref$shouldRetry; _classCallCheck(this, Xhr); _defineProperty(this, "retryCount", 0); _defineProperty(this, "errorInterceptor", function (error) { var shouldRetry = _this.shouldRetryRequest(error); if (shouldRetry) { _this.retryCount += 1; var delay = _this.getExponentialRetryTimeoutInMs(_this.retryCount); return new Promise(function (resolve, reject) { _this.retryTimeout = setTimeout(function () { _this.axios(error.config).then(resolve, reject); }, delay); }); } var errorObject = getProp(error, 'response.data') || error; // In the case of 401, response.data is empty so fall back to error _this.responseInterceptor(errorObject); return Promise.reject(error); }); this.clientName = clientName; this.id = id; this.language = language; this.responseInterceptor = responseInterceptor || this.defaultResponseInterceptor; this.retryableStatusCodes = retryableStatusCodes; this.sharedLink = sharedLink; this.sharedLinkPassword = sharedLinkPassword; this.shouldRetry = _shouldRetry; this.token = token; this.version = version; this.axios = axios.create(); this.axiosSource = axios.CancelToken.source(); this.axios.interceptors.response.use(this.responseInterceptor, this.errorInterceptor); if (typeof requestInterceptor === 'function') { this.axios.interceptors.request.use(requestInterceptor); } } /** * Default response interceptor which just returns the response * * @param {Object} response - the axios response * @return the response */ _createClass(Xhr, [{ key: "defaultResponseInterceptor", value: function defaultResponseInterceptor(response) { return response; } /** * Determines if a request should be retried * * @param {Object} error - Error object from axios * @return {boolean} true if the request should be retried */ }, { key: "shouldRetryRequest", value: function shouldRetryRequest(error) { if (!this.shouldRetry || this.retryCount >= MAX_NUM_RETRIES) { return false; } var response = error.response, request = error.request, config = error.config; // Retry if there is a network error (e.g. ECONNRESET) or rate limited var status = getProp(response, 'status'); var method = getProp(config, 'method'); var isNetworkError = request && !response; var isRateLimitError = status === HTTP_STATUS_CODE_RATE_LIMIT; var isOtherRetryableError = includes(this.retryableStatusCodes, status) && includes(RETRYABLE_HTTP_METHODS, method); return isNetworkError || isRateLimitError || isOtherRetryableError; } /** * Calculate the exponential backoff time with randomized jitter. * * @param {number} numRetries Which retry number this one will be. Must be > 0 * @returns {number} The number of milliseconds after which to retry */ }, { key: "getExponentialRetryTimeoutInMs", value: function getExponentialRetryTimeoutInMs(numRetries) { var randomizationMs = Math.ceil(Math.random() * 1000); var exponentialMs = Math.pow(2, numRetries - 1) * 1000; return exponentialMs + randomizationMs; } /** * Error interceptor that wraps the passed in responseInterceptor * * @param {Object} error - Error object from axios * @return {Promise} rejected promise with error info */ }, { key: "getParsedUrl", /** * Utility to parse a URL. * * @param {string} url - Url to parse * @return {Object} parsed url */ value: function getParsedUrl(url) { var a = document.createElement('a'); a.href = url; return { api: url.replace("".concat(a.origin, "/2.0"), ''), host: a.host, hostname: a.hostname, pathname: a.pathname, origin: a.origin, protocol: a.protocol, hash: a.hash, port: a.port }; } /** * Builds a list of required XHR headers. * * @param {string} [id] - Optional box item id * @param {Object} [args] - Optional existing headers * @return {Object} Headers */ }, { key: "getHeaders", value: function () { var _getHeaders = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee(id) { var args, headers, itemId, token, _args = arguments; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: args = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}; headers = _objectSpread(_defineProperty({ Accept: 'application/json' }, HEADER_CONTENT_TYPE, 'application/json'), args); if (this.language && !headers[HEADER_ACCEPT_LANGUAGE]) { headers[HEADER_ACCEPT_LANGUAGE] = this.language; } if (this.sharedLink) { headers.BoxApi = "shared_link=".concat(this.sharedLink); if (this.sharedLinkPassword) { headers.BoxApi = "".concat(headers.BoxApi, "&shared_link_password=").concat(this.sharedLinkPassword); } } if (this.clientName) { headers[HEADER_CLIENT_NAME] = this.clientName; } if (this.version) { headers[HEADER_CLIENT_VERSION] = this.version; } // If id is passed in, use that, otherwise default to this.id itemId = id || this.id || ''; _context.next = 9; return TokenService.getWriteToken(itemId, this.token); case 9: token = _context.sent; if (token) { // Only add a token when there was one found headers.Authorization = "Bearer ".concat(token); } return _context.abrupt("return", headers); case 12: case "end": return _context.stop(); } } }, _callee, this); })); function getHeaders(_x) { return _getHeaders.apply(this, arguments); } return getHeaders; }() /** * HTTP GETs a URL * * @param {string} id - Box item id * @param {string} url - The URL to fetch * @param {Object} [headers] - Key-value map of headers * @param {Object} [params] - Key-value map of querystring params * @return {Promise} - HTTP response */ }, { key: "get", value: function get(_ref2) { var _this2 = this; var url = _ref2.url, id = _ref2.id, _ref2$params = _ref2.params, params = _ref2$params === void 0 ? {} : _ref2$params, _ref2$headers = _ref2.headers, headers = _ref2$headers === void 0 ? {} : _ref2$headers; return this.getHeaders(id, headers).then(function (hdrs) { return _this2.axios.get(url, { cancelToken: _this2.axiosSource.token, params: params, headers: hdrs, parsedUrl: _this2.getParsedUrl(url) }); }); } /** * HTTP POSTs a URL with JSON data * * @param {string} id - Box item id * @param {string} url - The URL to fetch * @param {Object} data - JS Object representation of JSON data to send * @param {Object} params - Optional query params for the request * @param {Object} [headers] - Key-value map of headers * @param {string} [method] - xhr type * @return {Promise} - HTTP response */ }, { key: "post", value: function post(_ref3) { var _this3 = this; var url = _ref3.url, id = _ref3.id, data = _ref3.data, params = _ref3.params, _ref3$headers = _ref3.headers, headers = _ref3$headers === void 0 ? {} : _ref3$headers, _ref3$method = _ref3.method, method = _ref3$method === void 0 ? HTTP_POST : _ref3$method; return this.getHeaders(id, headers).then(function (hdrs) { return _this3.axios({ url: url, data: data, params: params, method: method, parsedUrl: _this3.getParsedUrl(url), headers: hdrs }); }); } /** * HTTP PUTs a URL with JSON data * * @param {string} id - Box item id * @param {string} url - The URL to fetch * @param {Object} data - JS Object representation of JSON data to send * @param {Object} params - Optional query params for the request * @param {Object} [headers] - Key-value map of headers * @return {Promise} - HTTP response */ }, { key: "put", value: function put(_ref4) { var url = _ref4.url, id = _ref4.id, data = _ref4.data, params = _ref4.params, _ref4$headers = _ref4.headers, headers = _ref4$headers === void 0 ? {} : _ref4$headers; return this.post({ id: id, url: url, data: data, params: params, headers: headers, method: HTTP_PUT }); } /** * HTTP DELETEs a URL with JSON data * * @param {string} id - Box item id * @param {string} url - The URL to fetch * @param {Object} data - JS Object representation of JSON data to send * @param {Object} [headers] - Key-value map of headers * @return {Promise} - HTTP response */ }, { key: "delete", value: function _delete(_ref5) { var url = _ref5.url, id = _ref5.id, _ref5$data = _ref5.data, data = _ref5$data === void 0 ? {} : _ref5$data, _ref5$headers = _ref5.headers, headers = _ref5$headers === void 0 ? {} : _ref5$headers; return this.post({ id: id, url: url, data: data, headers: headers, method: HTTP_DELETE }); } /** * HTTP OPTIONs a URL with JSON data. * * @param {string} id - Box item id * @param {string} url - The URL to post to * @param {Object} data - The non-file post data that should accompany the post * @param {Object} [headers] - Key-value map of headers * @param {Function} successHandler - Load success handler * @param {Function} errorHandler - Error handler * @return {void} */ }, { key: "options", value: function options(_ref6) { var _this4 = this; var id = _ref6.id, url = _ref6.url, data = _ref6.data, _ref6$headers = _ref6.headers, headers = _ref6$headers === void 0 ? {} : _ref6$headers, successHandler = _ref6.successHandler, errorHandler = _ref6.errorHandler; return this.getHeaders(id, headers).then(function (hdrs) { return _this4.axios({ url: url, data: data, method: HTTP_OPTIONS, headers: hdrs }).then(successHandler).catch(errorHandler); }).catch(errorHandler); } /** * HTTP POST or PUT a URL with File data. Uses native XHR for progress event. * * @param {string} id - Box item id * @param {string} url - The URL to post to * @param {Object} [data] - File data and attributes * @param {Object} [headers] - Key-value map of headers * @param {string} [method] - XHR method, supports 'POST' and 'PUT' * @param {Function} successHandler - Load success handler * @param {Function} errorHandler - Error handler * @param {Function} progressHandler - Progress handler * @param {boolean} [withIdleTimeout] - enable idle timeout * @param {number} [idleTimeoutDuration] - idle timeout duration * @param {Function} [idleTimeoutHandler] * @return {void} */ }, { key: "uploadFile", value: function uploadFile(_ref7) { var _this5 = this; var id = _ref7.id, url = _ref7.url, data = _ref7.data, _ref7$headers = _ref7.headers, headers = _ref7$headers === void 0 ? {} : _ref7$headers, _ref7$method = _ref7.method, method = _ref7$method === void 0 ? HTTP_POST : _ref7$method, successHandler = _ref7.successHandler, errorHandler = _ref7.errorHandler, progressHandler = _ref7.progressHandler, _ref7$withIdleTimeout = _ref7.withIdleTimeout, withIdleTimeout = _ref7$withIdleTimeout === void 0 ? false : _ref7$withIdleTimeout, _ref7$idleTimeoutDura = _ref7.idleTimeoutDuration, idleTimeoutDuration = _ref7$idleTimeoutDura === void 0 ? DEFAULT_UPLOAD_TIMEOUT_MS : _ref7$idleTimeoutDura, idleTimeoutHandler = _ref7.idleTimeoutHandler; return this.getHeaders(id, headers).then(function (hdrs) { var idleTimeout; var progressHandlerToUse = progressHandler; if (withIdleTimeout) { // Func that aborts upload and executes timeout callback var idleTimeoutFunc = function idleTimeoutFunc() { _this5.abort(); if (idleTimeoutHandler) { idleTimeoutHandler(); } }; idleTimeout = setTimeout(idleTimeoutFunc, idleTimeoutDuration); // Progress handler that aborts upload if there has been no progress for >= timeoutMs progressHandlerToUse = function progressHandlerToUse(event) { clearTimeout(idleTimeout); idleTimeout = setTimeout(idleTimeoutFunc, idleTimeoutDuration); progressHandler(event); }; } _this5.axios({ url: url, data: data, transformRequest: function transformRequest(reqData, reqHeaders) { // Remove Accept & Content-Type added by getHeaders() delete reqHeaders[HEADER_ACCEPT]; delete reqHeaders[HEADER_CONTENT_TYPE]; if (headers[HEADER_CONTENT_TYPE]) { reqHeaders[HEADER_CONTENT_TYPE] = headers[HEADER_CONTENT_TYPE]; } // Convert to FormData if needed if (reqData && !(reqData instanceof Blob) && reqData.attributes) { var formData = new FormData(); Object.keys(reqData).forEach(function (key) { formData.append(key, reqData[key]); }); return formData; } return reqData; }, method: method, headers: hdrs, onUploadProgress: progressHandlerToUse, cancelToken: _this5.axiosSource.token }).then(function (response) { clearTimeout(idleTimeout); successHandler(response); }).catch(function (error) { clearTimeout(idleTimeout); errorHandler(error); }); }).catch(errorHandler); } /** * Aborts an axios request. * * @return {void} */ }, { key: "abort", value: function abort() { if (this.retryTimeout) { clearTimeout(this.retryTimeout); } if (this.axiosSource) { this.axiosSource.cancel(); this.axiosSource = axios.CancelToken.source(); } } }]); return Xhr; }(); export default Xhr; //# sourceMappingURL=Xhr.js.map