UNPKG

@webex/http-core

Version:

Core HTTP library for the Cisco Webex

308 lines (273 loc) • 7.32 kB
/* eslint-disable no-underscore-dangle */ /*! * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file. */ /* eslint-env browser */ // Note: several code paths are ignored in this file. As far as I can tell, any // error conditions that would provoke those paths are otherwise prevented and // reported. import {defaults, isArray, pick} from 'lodash'; import qs from 'qs'; import xhr from '../lib/xhr'; import detect from '../lib/detect'; /** * @name request * @param {Object} options * @returns {Promise} */ export default function _request(options) { return new Promise((resolve) => { const params = pick( options, 'method', 'uri', 'withCredentials', 'headers', 'timeout', 'responseType' ); // Set `response` to `true` to approximate an `HttpResponse` object params.response = true; setXhr(params); bindProgressEvents(params, options); setAuth(params, options); setCookies(params, options); setDefaults(params, options); setResponseType(params, options); setContentType(params, options); setPayload(params, options); setQs(params, options); options.logger.debug( `start http ${options.method ? options.method : 'request'} to ${options.uri}` ); const x = xhr(params, (error, response) => { /* istanbul ignore next */ if (error) { options.logger.warn( `XHR error for ${options.method || 'request'} to ${options.uri} :`, error ); } /* istanbul ignore else */ if (response) { if (response.statusCode >= 400) { options.logger.warn( `http ${options.method ? options.method : 'request'} to ${options.uri} result: ${ response.statusCode }` ); } else { options.logger.debug( `http ${options.method ? options.method : 'request'} to ${options.uri} result: ${ response.statusCode }` ); } response.options = options; processResponseJson(response, params); resolve(response); } else { resolve({ statusCode: 0, options, headers: options.headers, method: options.method, url: options.uri, body: error, }); } }); x.onprogress = options.download.emit.bind(options.download, 'progress'); }).catch((error) => { options.logger.warn(error); /* eslint arrow-body-style: [0] */ /* istanbul ignore next */ return { statusCode: 0, options, headers: options.headers, method: options.method, url: options.uri, body: error, }; }); /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function bindProgressEvents(params, o) { if (params.method && ['PATCH', 'POST', 'PUT'].includes(params.method.toUpperCase())) { if (!params.xhr) { params.xhr = new XMLHttpRequest(); } params.xhr.upload.onprogress = o.upload.emit.bind(o.upload, 'progress'); } } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setXhr(params) { params.xhr = new XMLHttpRequest(); } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setAuth(params, o) { if (o.auth) { if (o.auth.bearer) { params.headers.authorization = `Bearer ${o.auth.bearer}`; } else { const user = o.auth.user || o.auth.username; const pass = o.auth.pass || o.auth.password; const token = btoa(`${user}:${pass}`); params.headers.Authorization = `Basic ${token}`; } } } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setCookies(params, o) { if (o.jar) { params.withCredentials = true; } } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setDefaults(params, o) { const defs = { cors: true, // raynos/xhr defaults withCredentials to true if cors is true, so we need // to make it explicitly false by default withCredentials: false, timeout: 0, }; defaults(params, pick(o, Object.keys(defs)), defs); } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setResponseType(params, o) { if (o.responseType === 'buffer') { params.responseType = 'arraybuffer'; } } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ async function setContentType(params, o) { if (o.body instanceof Blob || o.body instanceof ArrayBuffer) { o.json = params.json = false; params.headers['content-type'] = params.headers['content-type'] || (await detect(o.body)); } } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setQs(params, o) { if (o.qs) { params.uri += `?${qs.stringify(o.qs)}`; } } /** * Converts arraybuffers to blobs before uploading them * @param {mixed} file * @private * @returns {mixed} */ function ensureBlob(file) { if (file instanceof ArrayBuffer) { const ret = file.type ? new Blob([file], {type: file.type}) : new Blob([file]); ret.filename = file.filename || file.name || 'untitled'; return ret; } return file; } /** * Appends an item to a form * @param {FormData} form * @param {string} key * @param {mixed} value * @returns {undefined} */ function append(form, key, value) { if (isArray(value)) { for (const v of value) { append(form, key, v); } return; } value = ensureBlob(value); if (value.name) { value.filename = value.name; form.append(key, value, value.name); } else { form.append(key, value); } } /** * @param {Object} params * @param {Object} o * @private * @returns {undefined} */ function setPayload(params, o) { if ((!('json' in o) || o.json === true) && o.body) { params.json = o.body; } else if (o.form) { params.headers['Content-Type'] = 'application/x-www-form-urlencoded'; params.body = qs.stringify(o.form); Reflect.deleteProperty(params, 'json'); } else if (o.formData) { params.body = Object.keys(o.formData).reduce((fd, key) => { const value = o.formData[key]; append(fd, key, value); return fd; }, new FormData()); } else { params.body = o.body; Reflect.deleteProperty(params, 'json'); } } /** * @param {Object} response * @param {Object} params * @private * @returns {undefined} */ function processResponseJson(response, params) { // If params.json is not defined, xhr won't deserialize the response // so we should give it a shot just in case. if (!params.json && typeof response.body !== 'object') { try { response.body = JSON.parse(response.body); } catch (e) { /* eslint no-empty: [0] */ } } } }