From fb76b138433ceab13c4383093ac6e3ee0c416d12 Mon Sep 17 00:00:00 2001 From: Jared Stoffan Date: Thu, 21 Feb 2019 17:34:31 -0800 Subject: [PATCH] Upgrade: Migrate from whatwg-fetch to axios for all requests (#939) --- package.json | 7 +- src/index.html | 6 +- src/lib/DownloadReachability.js | 4 +- src/lib/Preview.js | 21 ++- src/lib/RepStatus.js | 5 +- .../__tests__/DownloadReachability-test.js | 10 +- src/lib/__tests__/Preview-test.js | 21 ++- src/lib/__tests__/RepStatus-test.js | 9 +- src/lib/__tests__/api-test.js | 161 +++++++++++++++++ src/lib/__tests__/util-test.js | 161 ----------------- src/lib/api.js | 132 ++++++++++++++ src/lib/util.js | 170 ------------------ src/lib/viewers/box3d/Box3DRenderer.js | 4 +- src/lib/viewers/box3d/Box3DViewer.js | 7 +- .../box3d/__tests__/Box3DViewer-test.js | 8 +- .../video360/__tests__/Video360Viewer-test.js | 2 - src/lib/viewers/doc/DocBaseViewer.js | 16 +- src/lib/viewers/doc/DocPreloader.js | 5 +- .../doc/__tests__/DocBaseViewer-test.js | 18 +- .../doc/__tests__/DocPreloader-test.js | 5 +- src/lib/viewers/image/ImageBaseViewer.js | 5 +- .../image/__tests__/ImageBaseViewer-test.js | 6 +- src/lib/viewers/media/DashViewer.js | 5 +- .../media/__tests__/DashViewer-test.js | 10 +- src/lib/viewers/office/OfficeViewer.js | 4 +- .../office/__tests__/OfficeViewer-test.js | 8 +- src/lib/viewers/text/CSVViewer.js | 7 +- src/lib/viewers/text/PlainTextViewer.js | 8 +- .../viewers/text/__tests__/CSVViewer-test.js | 15 +- .../text/__tests__/PlainTextViewer-test.js | 27 +-- yarn.lock | 89 ++++----- 31 files changed, 459 insertions(+), 497 deletions(-) create mode 100644 src/lib/__tests__/api-test.js create mode 100644 src/lib/api.js diff --git a/package.json b/package.json index f542c3fe1..10855ed6b 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,6 @@ "eslint-plugin-jsx-a11y": "^6.0.2", "eslint-plugin-react": "^7.5.1", "extract-text-webpack-plugin": "3.0.2", - "fetch-mock": "^5.13.1", - "fetch-mock-forwarder": "^1.0.0", "file-loader": "^1.1.5", "fscreen": "^1.0.2", "husky": "^0.14.3", @@ -88,7 +86,7 @@ "react-virtualized": "^9.13.0", "sass-loader": "^6.0.6", "selenium-webdriver": "^3.6.0", - "sinon": "^7.2.2", + "sinon": "^7.2.3", "sinon-chai": "3.3.0", "string-replace-loader": "^1.3.0", "style-loader": "^0.19.0", @@ -100,8 +98,7 @@ "webdriverio": "^4.12.0", "webpack": "^3.10.0", "webpack-bundle-analyzer": "^2.9.1", - "webpack-dev-server": "^2.11.3", - "whatwg-fetch": "^2.0.3" + "webpack-dev-server": "^2.11.3" }, "engines": { "node": ">=8.9.4", diff --git a/src/index.html b/src/index.html index 85eb465ff..b5f156732 100644 --- a/src/index.html +++ b/src/index.html @@ -94,7 +94,11 @@ /* global Box */ var preview = new Box.Preview(); - var previewOptions = options || {}; + var previewOptions = options || { + enableThumbnailsSidebar: true, + showAnnotations: true, + showDownload: true, + }; previewOptions.container = '.preview-container'; preview.show(fileid, token, previewOptions); diff --git a/src/lib/DownloadReachability.js b/src/lib/DownloadReachability.js index 57f307bd8..82fc79876 100644 --- a/src/lib/DownloadReachability.js +++ b/src/lib/DownloadReachability.js @@ -1,3 +1,4 @@ +import api from './api'; import { openUrlInsideIframe, isLocalStorageAvailable } from './util'; const DEFAULT_DOWNLOAD_HOST_PREFIX = 'https://dl.'; @@ -147,7 +148,8 @@ class DownloadReachability { * @return {void} */ static setDownloadReachability(downloadUrl) { - return fetch(downloadUrl, { method: 'HEAD' }) + return api + .head(downloadUrl) .then(() => { return Promise.resolve(false); }) diff --git a/src/lib/Preview.js b/src/lib/Preview.js index aa37acde4..a2619dae3 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -4,6 +4,7 @@ import EventEmitter from 'events'; import cloneDeep from 'lodash/cloneDeep'; import throttle from 'lodash/throttle'; /* eslint-enable import/first */ +import api from './api'; import Browser from './Browser'; import Logger from './Logger'; import loaderList from './loaders'; @@ -15,9 +16,7 @@ import getTokens from './tokens'; import Timer from './Timer'; import DownloadReachability from './DownloadReachability'; import { - get, getProp, - post, decodeKeydown, getHeaders, findScriptLocation, @@ -544,7 +543,7 @@ class Preview extends EventEmitter { // Otherwise, get the content download URL of the original file and download } else { const getDownloadUrl = appendQueryParams(getDownloadURL(this.file.id, apiHost), queryParams); - get(getDownloadUrl, this.getRequestHeaders()).then((data) => { + api.get(getDownloadUrl, { headers: this.getRequestHeaders() }).then((data) => { const downloadUrl = appendQueryParams(data.download_url, queryParams); DownloadReachability.downloadWithReachabilityCheck(downloadUrl); }); @@ -1009,7 +1008,8 @@ class Preview extends EventEmitter { Timer.start(tag); const fileInfoUrl = appendQueryParams(getURL(this.file.id, fileVersionId, apiHost), params); - get(fileInfoUrl, this.getRequestHeaders()) + api + .get(fileInfoUrl, { headers: this.getRequestHeaders() }) .then(this.handleFileInfoResponse) .catch(this.handleFetchError); } @@ -1349,15 +1349,17 @@ class Preview extends EventEmitter { this.logRetryCount = this.logRetryCount || 0; const { apiHost, token, sharedLink, sharedLinkPassword } = options; - const headers = getHeaders({}, token, sharedLink, sharedLinkPassword); - - post(`${apiHost}/2.0/events`, headers, { + const data = { event_type: 'preview', source: { type: 'file', id: fileId } - }) + }; + const headers = getHeaders({}, token, sharedLink, sharedLinkPassword); + + api + .post(`${apiHost}/2.0/events`, data, { headers }) .then(() => { // Reset retry count after successfully logging this.logRetryCount = 0; @@ -1637,7 +1639,8 @@ class Preview extends EventEmitter { const fileInfoUrl = appendQueryParams(getURL(fileId, fileVersionId, apiHost), params); // Prefetch and cache file information and content - get(fileInfoUrl, this.getRequestHeaders(token)) + api + .get(fileInfoUrl, { headers: this.getRequestHeaders(token) }) .then((file) => { // Cache file info cacheFile(this.cache, file); diff --git a/src/lib/RepStatus.js b/src/lib/RepStatus.js index 842e07bfc..213f374ba 100644 --- a/src/lib/RepStatus.js +++ b/src/lib/RepStatus.js @@ -1,5 +1,6 @@ import EventEmitter from 'events'; -import { get, appendAuthParams } from './util'; +import api from './api'; +import { appendAuthParams } from './util'; import { STATUS_SUCCESS, STATUS_VIEWABLE, STATUS_PENDING, STATUS_NONE } from './constants'; import PreviewError from './PreviewError'; import Timer from './Timer'; @@ -84,7 +85,7 @@ class RepStatus extends EventEmitter { const tag = Timer.createTag(this.fileId, LOAD_METRIC.convertTime); Timer.start(tag); - return get(this.infoUrl).then((info) => { + return api.get(this.infoUrl).then((info) => { clearTimeout(this.statusTimeout); if (info.metadata) { diff --git a/src/lib/__tests__/DownloadReachability-test.js b/src/lib/__tests__/DownloadReachability-test.js index 946bb99c7..3c13df311 100644 --- a/src/lib/__tests__/DownloadReachability-test.js +++ b/src/lib/__tests__/DownloadReachability-test.js @@ -1,8 +1,7 @@ /* eslint-disable no-unused-expressions */ -import 'whatwg-fetch'; -import fetchMock from 'fetch-mock'; -import DownloadReachability from '../DownloadReachability'; import * as util from '../util'; +import api from '../api'; +import DownloadReachability from '../DownloadReachability'; const sandbox = sinon.sandbox.create(); @@ -130,12 +129,9 @@ describe('lib/DownloadReachability', () => { }); describe('setDownloadReachability()', () => { - afterEach(() => { - fetchMock.restore(); - }); it('should catch an errored response', () => { const setDownloadHostFallbackStub = sandbox.stub(DownloadReachability, 'setDownloadHostFallback'); - fetchMock.head('https://dl3.boxcloud.com', { throws: new Error() }); + sandbox.stub(api, 'head').rejects(new Error()); return DownloadReachability.setDownloadReachability('https://dl3.boxcloud.com').catch(() => { expect(setDownloadHostFallbackStub).to.be.called; diff --git a/src/lib/__tests__/Preview-test.js b/src/lib/__tests__/Preview-test.js index 43f4b039e..538ec7d5c 100644 --- a/src/lib/__tests__/Preview-test.js +++ b/src/lib/__tests__/Preview-test.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-expressions */ -import fetchMock from 'fetch-mock'; +import api from '../api'; import Preview from '../Preview'; import loaders from '../loaders'; import Logger from '../Logger'; @@ -40,7 +40,6 @@ describe('lib/Preview', () => { afterEach(() => { sandbox.verifyAndRestore(); fixture.cleanup(); - fetchMock.restore(); preview.destroy(); preview = null; stubs = null; @@ -818,7 +817,7 @@ describe('lib/Preview', () => { sandbox.stub(file, 'getDownloadURL'); sandbox.stub(preview, 'getRequestHeaders'); - sandbox.stub(util, 'get'); + sandbox.stub(api, 'get'); }); it('should show error notification and not download file if file cannot be downloaded', () => { @@ -873,7 +872,7 @@ describe('lib/Preview', () => { const promise = Promise.resolve({ download_url: url }); - util.get.returns(promise); + api.get.returns(promise); preview.download(); @@ -893,7 +892,7 @@ describe('lib/Preview', () => { download_url: url }); - util.get.returns(promise); + api.get.returns(promise); preview.download(); expect(preview.emit).to.be.calledWith('preview_metric'); }); @@ -1365,7 +1364,7 @@ describe('lib/Preview', () => { describe('loadFromServer()', () => { beforeEach(() => { stubs.promise = Promise.resolve('file'); - stubs.get = sandbox.stub(util, 'get').returns(stubs.promise); + stubs.get = sandbox.stub(api, 'get').returns(stubs.promise); stubs.handleFileInfoResponse = sandbox.stub(preview, 'handleFileInfoResponse'); stubs.handleFetchError = sandbox.stub(preview, 'handleFetchError'); stubs.getURL = sandbox.stub(file, 'getURL').returns('/get_url'); @@ -1998,14 +1997,14 @@ describe('lib/Preview', () => { }); it('should get the headers for the post request', () => { - sandbox.stub(util, 'post').returns(stubs.promiseResolve); + sandbox.stub(api, 'post').returns(stubs.promiseResolve); preview.logPreviewEvent(0, {}); expect(stubs.getHeaders).to.be.called; }); it('should reset the log retry count on a successful post', () => { - sandbox.stub(util, 'post').returns(stubs.promiseResolve); + sandbox.stub(api, 'post').returns(stubs.promiseResolve); preview.logRetryCount = 3; preview.logPreviewEvent(0, {}); @@ -2016,7 +2015,7 @@ describe('lib/Preview', () => { it('should reset the log retry count if the post fails and retry limit has been reached', () => { const promiseReject = Promise.reject({}); // eslint-disable-line prefer-promise-reject-errors - sandbox.stub(util, 'post').returns(promiseReject); + sandbox.stub(api, 'post').returns(promiseReject); preview.logRetryCount = 3; preview.logRetryTimeout = true; @@ -2030,7 +2029,7 @@ describe('lib/Preview', () => { it('should set a timeout to try to log the preview event again if post fails and the limit has not been met', () => { const promiseReject = Promise.reject({}); // eslint-disable-line prefer-promise-reject-errors sandbox - .stub(util, 'post') + .stub(api, 'post') .onCall(0) .returns(promiseReject); preview.logRetryCount = 3; @@ -2452,7 +2451,7 @@ describe('lib/Preview', () => { id: 0 }); - stubs.get = sandbox.stub(util, 'get').returns(stubs.getPromiseResolve); + stubs.get = sandbox.stub(api, 'get').returns(stubs.getPromiseResolve); stubs.getURL = sandbox.stub(file, 'getURL'); stubs.getRequestHeaders = sandbox.stub(preview, 'getRequestHeaders'); stubs.set = sandbox.stub(preview.cache, 'set'); diff --git a/src/lib/__tests__/RepStatus-test.js b/src/lib/__tests__/RepStatus-test.js index 6d1bb75c7..5cfe1a361 100644 --- a/src/lib/__tests__/RepStatus-test.js +++ b/src/lib/__tests__/RepStatus-test.js @@ -1,6 +1,7 @@ /* eslint-disable no-unused-expressions */ -import RepStatus from '../RepStatus'; import * as util from '../util'; +import api from '../api'; +import RepStatus from '../RepStatus'; import { LOAD_METRIC } from '../events'; import Timer from '../Timer'; import { STATUS_SUCCESS } from '../constants'; @@ -106,7 +107,7 @@ describe('lib/RepStatus', () => { it('should fetch latest status', () => { sandbox - .mock(util) + .mock(api) .expects('get') .returns( Promise.resolve({ @@ -124,7 +125,7 @@ describe('lib/RepStatus', () => { it('should update provided metadata', () => { sandbox - .mock(util) + .mock(api) .expects('get') .returns( Promise.resolve({ @@ -146,7 +147,7 @@ describe('lib/RepStatus', () => { it('should return a resolved promise if there is no info url', () => { sandbox - .mock(util) + .mock(api) .expects('get') .never(); repStatus.infoUrl = ''; diff --git a/src/lib/__tests__/api-test.js b/src/lib/__tests__/api-test.js new file mode 100644 index 000000000..4872c103c --- /dev/null +++ b/src/lib/__tests__/api-test.js @@ -0,0 +1,161 @@ +import api from '../api'; + +const sandbox = sinon.sandbox.create(); + +describe('API helper', () => { + afterEach(() => { + sandbox.verifyAndRestore(); + }); + + describe('get()', () => { + const url = '/foo/bar'; + + it('should call fetch on the URL', () => { + sandbox.stub(api, 'xhr').resolves({ status: 200 }); + + return api.get(url).then(() => { + expect(api.xhr).to.have.been.calledWith(url, { method: 'get', responseType: 'json' }); + }); + }); + + it('should call fetch on URL but fail when status is 404', () => { + sandbox.stub(api, 'xhr').resolves({ status: 404 }); + + return api.get(url).catch((err) => { + expect(api.xhr).to.have.been.calledWith(url, { method: 'get', responseType: 'json' }); + expect(err.response.status).to.equal(404); + expect(err.response.statusText).to.equal('Not Found'); + }); + }); + + it('should call fetch on URL with headers', () => { + const headers = { darth: 'vader' }; + sandbox.stub(api, 'xhr').resolves({ status: 200 }); + + return api.get(url, { headers }).then(() => { + expect(api.xhr).to.have.been.calledWith(url, { headers, method: 'get', responseType: 'json' }); + }); + }); + + it('should call fetch on URL with headers and type text', () => { + const responseText = 'lukeskywalker'; + const headers = { baz: 'but' }; + sandbox.stub(api, 'xhr').resolves({ + data: responseText, + status: 200 + }); + + return api.get(url, { headers, type: 'text' }).then((response) => { + expect(api.xhr).to.have.been.calledWith(url, { headers, method: 'get', responseType: 'text' }); + expect(response.data).to.equal(responseText); + }); + }); + + it('should call fetch on URL with type blob', () => { + const blob = new Blob(['text'], { type: 'text/plain' }); + sandbox.stub(api, 'xhr').resolves({ + data: blob, + status: 200 + }); + + return api.get(url, { type: 'blob' }).then((response) => { + expect(api.xhr).to.have.been.calledWith(url, { method: 'get', responseType: 'blob' }); + expect(response.data).to.deep.equal(blob); + }); + }); + + it('should call fetch on URL with type text', () => { + const responseText = 'darthsidious'; + sandbox.stub(api, 'xhr').resolves({ + data: responseText, + status: 200 + }); + + return api.get(url, { type: 'text' }).then((response) => { + expect(api.xhr).to.have.been.calledWith(url, { method: 'get', responseType: 'text' }); + expect(response.data).to.equal(responseText); + }); + }); + + it('should call get on URL with type any', () => { + sandbox.stub(api, 'xhr').resolves({ + data: 'greedo', + status: 200 + }); + + return api.get(url, { type: 'document' }).then((response) => { + expect(api.xhr).to.have.been.calledWith(url, { method: 'get', responseType: 'document' }); + expect(typeof response === 'object').to.be.true; // eslint-disable-line + }); + }); + }); + + describe('head()', () => { + it('should call head on URL', () => { + const url = 'someurl'; + + sandbox.stub(api, 'xhr').resolves({ status: 200 }); + + return api.head(url).then(() => { + expect(api.xhr).to.have.been.calledWith(url, { method: 'head' }); + }); + }); + }); + + describe('post()', () => { + it('should call post on URL', () => { + const url = 'someurl'; + const data = { bar: 'bum' }; + const headers = { baz: 'but' }; + + sandbox.stub(api, 'xhr').resolves({ + body: { + foo: 'bar' + }, + status: 200 + }); + + return api.post(url, data, { headers }).then(() => { + expect(api.xhr).to.have.been.calledWith(url, { data, headers, method: 'post' }); + }); + }); + }); + + describe('delete()', () => { + it('should call delete on URL', () => { + const url = 'someurl'; + const data = { bar: 'bum' }; + const headers = { baz: 'but' }; + + sandbox.stub(api, 'xhr').resolves({ + body: { + foo: 'bar' + }, + status: 200 + }); + + return api.delete(url, data, { headers }).then(() => { + expect(api.xhr).to.have.been.calledWith(url, { data, headers, method: 'delete' }); + }); + }); + }); + + describe('put()', () => { + it('should call put on URL', () => { + const url = 'someurl'; + const data = { bar: 'bum' }; + const headers = { baz: 'but' }; + + sandbox.stub(api, 'xhr').resolves({ + body: { + foo: 'bar' + }, + status: 200 + }); + + return api.put(url, data, { headers }).then(() => { + expect(api.xhr).to.have.been.calledWith(url, { data, headers, method: 'put' }); + }); + }); + }); +}); diff --git a/src/lib/__tests__/util-test.js b/src/lib/__tests__/util-test.js index c9d226aa2..aebe21506 100644 --- a/src/lib/__tests__/util-test.js +++ b/src/lib/__tests__/util-test.js @@ -1,6 +1,4 @@ /* eslint-disable no-unused-expressions */ -import 'whatwg-fetch'; -import fetchMock from 'fetch-mock'; import Location from '../Location'; import * as util from '../util'; import DownloadReachability from '../DownloadReachability'; @@ -12,165 +10,6 @@ describe('lib/util', () => { sandbox.verifyAndRestore(); }); - describe('get()', () => { - let url; - beforeEach(() => { - url = 'foo?bar=bum'; - }); - - afterEach(() => { - fetchMock.restore(); - }); - - it('should call fetch on the URL', () => { - fetchMock.get(url, {}); - return util.get(url).then(() => { - expect(fetchMock.called(url)).to.be.true; - }); - }); - - it('should call fetch on URL but fail when status is 404', () => { - fetchMock.get(url, { status: 404 }); - return util.get(url).catch((err) => { - expect(fetchMock.called(url)).to.be.true; - expect(err.response.status).to.equal(404); - expect(err.response.statusText).to.equal('Not Found'); - }); - }); - - it('should call fetch on URL with headers', () => { - const headers = { darth: 'vader' }; - fetchMock.get(url, {}); - - return util.get(url, headers).then(() => { - expect(fetchMock.called(url)).to.be.true; - expect(fetchMock.lastOptions(url).headers).to.deep.equal(headers); - }); - }); - - it('should call fetch on URL with headers and type text', () => { - const responseText = 'lukeskywalker'; - const headers = { baz: 'but' }; - fetchMock.get(url, { - body: responseText, - sendAsJson: false - }); - - return util.get(url, headers, 'text').then((response) => { - expect(fetchMock.called(url)).to.be.true; - expect(response).to.equal(responseText); - expect(fetchMock.lastOptions(url).headers).to.deep.equal(headers); - }); - }); - - it('should call fetch on URL with type blob', () => { - const blob = new Blob(['text'], { type: 'text/plain' }); - fetchMock.get(url, { - body: blob, - sendAsJson: false - }); - - return util.get(url, 'blob').then((response) => { - expect(fetchMock.called(url)).to.be.true; - expect(response).to.deep.equal(blob); - }); - }); - - it('should call fetch on URL with type text', () => { - const responseText = 'darthsidious'; - fetchMock.get(url, { - body: responseText, - sendAsJson: false - }); - - return util.get(url, 'text').then((response) => { - expect(fetchMock.called(url)).to.be.true; - expect(response).to.equal(responseText); - }); - }); - - it('should call get on URL with type any', () => { - fetchMock.get(url, { - body: 'greedo', - sendAsJson: false - }); - - return util.get(url, 'any').then((response) => { - expect(fetchMock.called(url)).to.be.true; - expect(typeof response === 'object').to.be.true; - }); - }); - }); - - describe('post()', () => { - afterEach(() => { - fetchMock.restore(); - }); - - it('should call post on URL', () => { - const url = 'someurl'; - const data = { bar: 'bum' }; - const headers = { baz: 'but' }; - - fetchMock.post(url, { - body: { - foo: 'bar' - } - }); - - return util.post(url, headers, data).then(() => { - expect(JSON.parse(fetchMock.lastOptions(url).body)).to.deep.equal(data); - expect(fetchMock.lastOptions(url).headers).to.deep.equal(headers); - }); - }); - }); - - describe('del()', () => { - afterEach(() => { - fetchMock.restore(); - }); - - it('should call delete on URL', () => { - const url = 'someurl'; - const data = { bar: 'bum' }; - const headers = { baz: 'but' }; - - fetchMock.delete(url, { - body: { - foo: 'bar' - } - }); - - return util.del(url, headers, data).then(() => { - expect(JSON.parse(fetchMock.lastOptions(url).body)).to.deep.equal(data); - expect(fetchMock.lastOptions(url).headers).to.deep.equal(headers); - }); - }); - }); - - describe('put()', () => { - afterEach(() => { - fetchMock.restore(); - }); - - it('should call put on URL', () => { - const url = 'someurl'; - const data = { bar: 'bum' }; - const headers = { baz: 'but' }; - - fetchMock.put(url, { - body: { - foo: 'bar' - } - }); - - return util.put(url, headers, data).then(() => { - expect(JSON.parse(fetchMock.lastOptions(url).body)).to.deep.equal(data); - expect(fetchMock.lastOptions(url).headers).to.deep.equal(headers); - }); - }); - }); - describe('iframe', () => { let iframe; diff --git a/src/lib/api.js b/src/lib/api.js new file mode 100644 index 000000000..5f64059c5 --- /dev/null +++ b/src/lib/api.js @@ -0,0 +1,132 @@ +import axios from 'axios'; + +const api = { + /** + * Filter empty values from the Axios request options object + * + * @private + * @param {Object} options - The request options + * @return {Object} The cleaned request options + */ + filterOptions(options = {}) { + const result = {}; + + Object.keys(options).forEach((key) => { + if (options[key] !== undefined && options[key] !== null && options[key] !== '') { + result[key] = options[key]; + } + }); + + return result; + }, + + /** + * Helper function to convert an Axios error to the format Preview expects + * + * @private + * @param {Object} response - Axios error response + * @throws {Error} - Throws when an error response object exists + * @return {void} + */ + handleError({ response }) { + if (response) { + const error = new Error(response.statusText); + error.response = response; // Need to pass response through so we can see what kind of HTTP error this was + throw error; + } + }, + + /** + * Retrieves JSON from response. + * + * @private + * @param {Response} response - Response to parse + * @return {Promise|Response} Response if 204, otherwise promise that resolves with JSON + */ + parseResponse: (response) => { + if (response.status === 204) { + return response; + } + + return response.data; + }, + + /** + * Wrapper function for XHR post put and delete + * + * @private + * @param {string} url - The URL for XHR + * @param {Object} options - The request options + * @return {Promise} - XHR promise + */ + xhr(url, options = {}) { + return axios(url, api.filterOptions(options)) + .then(api.parseResponse) + .catch(api.handleError); + }, + + /** + * HTTP GETs a URL + * + * @public + * @param {string} url - The URL to fetch + * @param {Object} options - The request options + * @return {Promise} - HTTP response + */ + get(url, { type: responseType = 'json', ...options } = {}) { + return api.xhr(url, { method: 'get', responseType, ...options }); + }, + + /** + * HTTP HEAD a URL + * + * @public + * @param {string} url - The URL to fetch + * @param {Object} options - The request options + * @return {Promise} HTTP response + */ + head(url, options = {}) { + return api.xhr(url, { method: 'head', ...options }); + }, + + /** + * HTTP POSTs a URL with JSON data + * + * @public + * @param {string} url - The URL to fetch + * @param {Object} data - JS Object representation of JSON data to send + * @param {Object} options - The request options + * @return {Promise} HTTP response + */ + post(url, data, options = {}) { + return api.xhr(url, { method: 'post', data, ...options }); + }, + + /** + * HTTP DELETEs a URL with JSON data + * + * @public + * @param {string} url - The URL to fetch + * @param {Object} data - JS Object representation of JSON data to send + * @param {Object} options - The request options + * @return {Promise} HTTP response + */ + delete(url, data, options = {}) { + return api.xhr(url, { method: 'delete', data, ...options }); + }, + + /** + * HTTP PUTs a url with JSON data + * + * @public + * @param {string} url - The url to fetch + * @param {Object} data - JS Object representation of JSON data to send + * @param {Object} options - The request options + * @return {Promise} HTTP response + */ + put(url, data, options = {}) { + return api.xhr(url, { method: 'put', data, ...options }); + } +}; + +export default api; diff --git a/src/lib/util.js b/src/lib/util.js index 7f0001b15..c4859f3d3 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1,5 +1,4 @@ import Uri from 'jsuri'; -import 'whatwg-fetch'; import { decode } from 'box-react-ui/lib/utils/keys'; import DownloadReachability from './DownloadReachability'; import Location from './Location'; @@ -13,86 +12,6 @@ const CLIENT_NAME = __NAME__; export const CLIENT_VERSION = __VERSION__; /* eslint-enable no-undef */ -/** - * Retrieves JSON from response. - * - * @private - * @param {Response} response - Response to parse - * @return {Promise|Response} Response if 204, otherwise promise that resolves with JSON - */ -const parseJSON = (response) => { - if (response.status === 204) { - return response; - } - - return response.json(); -}; - -/** - * Extract response body as text. - * - * @private - * @param {Response} response - Response to parse - * @return {Promise} Promise that resolves with text - */ -const parseText = (response) => response.text(); - -/** - * Extract response body as blob. - * - * @private - * @param {Response} response - Response to parse - * @return {Promise} Promise that resolves with blob - */ -const parseBlob = (response) => response.blob(); - -/** - * Pass through response. - * - * @private - * @param {Response} response - Response to pass through - * @return {Response} Unextracted response - */ -const parseThrough = (response) => response; - -/** - * Helper function to convert HTTP status codes into throwable errors - * - * @private - * @param {Response} response - Fetch's Response object - * @throws {Error} - Throws when the HTTP status is not 2XX - * @return {Response} - Pass-thru the response if there are no errors - */ -function checkStatus(response) { - if (response.status >= 200 && response.status < 300) { - return response; - } - - const error = new Error(response.statusText); - error.response = response; // Need to pass response through so we can see what kind of HTTP error this was - throw error; -} - -/** - * Wrapper function for XHR post put and delete - * - * @private - * @param {string} method - XHR method - * @param {string} url - URL for XHR - * @param {Object} headers - Request headers - * @param {Object} data - Request data - * @return {Promise} XHR promise - */ -function xhr(method, url, headers = {}, data = {}) { - return fetch(url, { - headers, - method, - body: JSON.stringify(data) - }) - .then(checkStatus) - .then(parseJSON); -} - /** * Creates an empty iframe or uses an existing one * for the purposes of downloading or printing @@ -116,95 +35,6 @@ function createDownloadIframe() { return iframe; } -/** - * HTTP GETs a URL - * Usage: - * get(url, headers, type) - * get(url, headers) - * get(url, type) - * get(url) - * - * @public - * @param {string} url - The URL to fetch - * @param {Object} [headers] - Key-value map of headers - * @param {string} [type] - response type json (default), text, blob or any - * @return {Promise} - HTTP response - */ -export function get(url, ...rest) { - let headers; - let type; - - if (typeof rest[0] === 'string') { - type = rest[0]; - } else { - headers = rest[0]; - type = rest[1]; - } - - headers = headers || {}; - type = type || 'json'; - - let parser; - switch (type) { - case 'text': - parser = parseText; - break; - case 'blob': - parser = parseBlob; - break; - case 'any': - parser = parseThrough; - break; - case 'json': - default: - parser = parseJSON; - break; - } - - return fetch(url, { headers }) - .then(checkStatus) - .then(parser); -} - -/** - * HTTP POSTs a URL with JSON data - * - * @public - * @param {string} url - The URL to fetch - * @param {Object} headers - Key-value map of headers - * @param {Object} data - JS Object representation of JSON data to send - * @return {Promise} HTTP response - */ -export function post(...rest) { - return xhr('post', ...rest); -} - -/** - * HTTP PUTs a URL with JSON data - * - * @public - * @param {string} url - The URL to fetch - * @param {Object} headers - Key-value map of headers - * @param {Object} data - JS Object representation of JSON data to send - * @return {Promise} HTTP response - */ -export function del(...rest) { - return xhr('delete', ...rest); -} - -/** - * HTTP PUTs a url with JSON data - * - * @public - * @param {string} url - The url to fetch - * @param {Object} headers - Key-value map of headers - * @param {Object} data - JS Object representation of JSON data to send - * @return {Promise} HTTP response - */ -export function put(...rest) { - return xhr('put', ...rest); -} - /** * Opens url in an iframe * Used for downloads diff --git a/src/lib/viewers/box3d/Box3DRenderer.js b/src/lib/viewers/box3d/Box3DRenderer.js index c94a102d0..3066ab6ce 100644 --- a/src/lib/viewers/box3d/Box3DRenderer.js +++ b/src/lib/viewers/box3d/Box3DRenderer.js @@ -1,7 +1,7 @@ /* global Box3D */ /* eslint no-param-reassign:0 */ -import 'whatwg-fetch'; import EventEmitter from 'events'; +import api from '../../api'; import { EVENT_SHOW_VR_BUTTON, EVENT_SCENE_LOADED, @@ -205,7 +205,7 @@ class Box3DRenderer extends EventEmitter { * @return {Promise} - A promise that resolves on completion of the load. */ getEntitiesFromUrl(url) { - return fetch(url).then((response) => response.json()); + return api.get(url); } /** diff --git a/src/lib/viewers/box3d/Box3DViewer.js b/src/lib/viewers/box3d/Box3DViewer.js index ffbc45864..a5ebc813d 100644 --- a/src/lib/viewers/box3d/Box3DViewer.js +++ b/src/lib/viewers/box3d/Box3DViewer.js @@ -1,9 +1,9 @@ +import api from '../../api'; import BaseViewer from '../BaseViewer'; import Box3DControls from './Box3DControls'; import Box3DRenderer from './Box3DRenderer'; import Browser from '../../Browser'; import Notification from '../../Notification'; -import { get } from '../../util'; import { CSS_CLASS_BOX3D, EVENT_ERROR, @@ -221,7 +221,10 @@ class Box3DViewer extends BaseViewer { const { representation } = this.options; if (content && this.isRepresentationReady(representation)) { const template = representation.content.url_template; - get(this.createContentUrl(template, 'entities.json'), this.appendAuthHeader(), 'any'); + api.get(this.createContentUrl(template, 'entities.json'), { + headers: this.appendAuthHeader(), + type: 'document' + }); } } diff --git a/src/lib/viewers/box3d/__tests__/Box3DViewer-test.js b/src/lib/viewers/box3d/__tests__/Box3DViewer-test.js index 6a91aacb9..9be1c7d2f 100644 --- a/src/lib/viewers/box3d/__tests__/Box3DViewer-test.js +++ b/src/lib/viewers/box3d/__tests__/Box3DViewer-test.js @@ -1,11 +1,11 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import Box3DViewer from '../Box3DViewer'; import Box3DControls from '../Box3DControls'; import Box3DRenderer from '../Box3DRenderer'; import BaseViewer from '../../BaseViewer'; import Browser from '../../../Browser'; import fullscreen from '../../../Fullscreen'; -import * as util from '../../../util'; import { EVENT_ERROR, EVENT_LOAD, @@ -415,9 +415,9 @@ describe('lib/viewers/box3d/Box3DViewer', () => { sandbox.stub(box3d, 'appendAuthHeader').returns(headers); sandbox.stub(box3d, 'isRepresentationReady').returns(true); sandbox - .mock(util) + .mock(api) .expects('get') - .withArgs(contentUrl, headers, 'any'); + .withArgs(contentUrl, { headers, type: 'document' }); box3d.prefetch({ assets: false, content: true }); }); @@ -425,7 +425,7 @@ describe('lib/viewers/box3d/Box3DViewer', () => { it('should not prefetch content if content is true but representation is not ready', () => { sandbox.stub(box3d, 'isRepresentationReady').returns(false); sandbox - .mock(util) + .mock(api) .expects('get') .never(); box3d.prefetch({ assets: false, content: true }); diff --git a/src/lib/viewers/box3d/video360/__tests__/Video360Viewer-test.js b/src/lib/viewers/box3d/video360/__tests__/Video360Viewer-test.js index dbd030328..eca2f3f8a 100644 --- a/src/lib/viewers/box3d/video360/__tests__/Video360Viewer-test.js +++ b/src/lib/viewers/box3d/video360/__tests__/Video360Viewer-test.js @@ -344,7 +344,6 @@ describe('lib/viewers/box3d/video360/Video360Viewer', () => { expect(controls.destroy).to.be.called; }); - it('should bind mousemove listener to display video player UI', () => { viewer.destroyControls(); @@ -357,7 +356,6 @@ describe('lib/viewers/box3d/video360/Video360Viewer', () => { expect(canvas.removeEventListener).to.be.calledWith('touchstart'); }); - }); describe('resize()', () => { diff --git a/src/lib/viewers/doc/DocBaseViewer.js b/src/lib/viewers/doc/DocBaseViewer.js index a536a3df0..fb6d62112 100644 --- a/src/lib/viewers/doc/DocBaseViewer.js +++ b/src/lib/viewers/doc/DocBaseViewer.js @@ -1,4 +1,5 @@ import throttle from 'lodash/throttle'; +import api from '../../api'; import BaseViewer from '../BaseViewer'; import Browser from '../../Browser'; import Controls from '../../Controls'; @@ -22,14 +23,7 @@ import { CLASS_BOX_PREVIEW_THUMBNAILS_CONTAINER } from '../../constants'; import { checkPermission, getRepresentation } from '../../file'; -import { - appendQueryParams, - get, - createAssetUrlCreator, - getMidpoint, - getDistance, - getClosestPageToPinch -} from '../../util'; +import { appendQueryParams, createAssetUrlCreator, getMidpoint, getDistance, getClosestPageToPinch } from '../../util'; import { ICON_PRINT_CHECKMARK, ICON_ZOOM_OUT, @@ -248,13 +242,13 @@ class DocBaseViewer extends BaseViewer { const { url_template: template } = preloadRep.content; // Prefetch as blob since preload needs to load image as a blob - get(this.createContentUrlWithAuthParams(template), 'blob'); + api.get(this.createContentUrlWithAuthParams(template), { type: 'blob' }); } } if (content && !isWatermarked && this.isRepresentationReady(representation)) { const { url_template: template } = representation.content; - get(this.createContentUrlWithAuthParams(template), 'any'); + api.get(this.createContentUrlWithAuthParams(template), { type: 'document' }); } } @@ -868,7 +862,7 @@ class DocBaseViewer extends BaseViewer { * @return {Promise} Promise setting print blob */ fetchPrintBlob(pdfUrl) { - return get(pdfUrl, 'blob').then((blob) => { + return api.get(pdfUrl, { type: 'blob' }).then((blob) => { this.printBlob = blob; }); } diff --git a/src/lib/viewers/doc/DocPreloader.js b/src/lib/viewers/doc/DocPreloader.js index db07da321..a5c617796 100644 --- a/src/lib/viewers/doc/DocPreloader.js +++ b/src/lib/viewers/doc/DocPreloader.js @@ -13,7 +13,8 @@ import { PDFJS_WIDTH_PADDING_PX, PDFJS_HEIGHT_PADDING_PX } from '../../constants'; -import { get, setDimensions } from '../../util'; +import api from '../../api'; +import { setDimensions } from '../../util'; const EXIF_COMMENT_TAG_NAME = 'UserComment'; // Read EXIF data from 'UserComment' tag const EXIF_COMMENT_REGEX = /pdfWidth:([0-9.]+)pts,pdfHeight:([0-9.]+)pts,numPages:([0-9]+)/; @@ -80,7 +81,7 @@ class DocPreloader extends EventEmitter { this.containerEl = containerEl; // Need to load image as a blob to read EXIF - return get(preloadUrlWithAuth, 'blob').then((imgBlob) => { + return api.get(preloadUrlWithAuth, { type: 'blob' }).then((imgBlob) => { if (this.checkDocumentLoaded()) { return; } diff --git a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js index bcbef545b..7e4ae4ecb 100644 --- a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js +++ b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js @@ -1,4 +1,5 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import DocBaseViewer from '../DocBaseViewer'; import DocFindBar from '../DocFindBar'; import Browser from '../../../Browser'; @@ -209,7 +210,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { describe('prefetch()', () => { it('should prefetch assets if assets is true', () => { sandbox.stub(docBase, 'prefetchAssets'); - sandbox.stub(util, 'get'); + sandbox.stub(api, 'get'); docBase.prefetch({ assets: true, preload: false, content: false }); expect(docBase.prefetchAssets).to.be.called; }); @@ -224,7 +225,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { state: 'success' } }; - sandbox.stub(util, 'get'); + sandbox.stub(api, 'get'); sandbox.stub(file, 'getRepresentation').returns(preloadRep); sandbox.stub(docBase, 'createContentUrlWithAuthParams'); @@ -243,7 +244,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { state: 'pending' } }; - sandbox.stub(util, 'get'); + sandbox.stub(api, 'get'); sandbox.stub(file, 'getRepresentation').returns(preloadRep); sandbox.stub(docBase, 'createContentUrlWithAuthParams'); @@ -268,9 +269,9 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { sandbox.stub(docBase, 'createContentUrlWithAuthParams').returns(contentUrl); sandbox.stub(docBase, 'isRepresentationReady').returns(true); sandbox - .mock(util) + .mock(api) .expects('get') - .withArgs(contentUrl, 'any'); + .withArgs(contentUrl, { type: 'document' }); docBase.prefetch({ assets: false, preload: false, content: true }); }); @@ -278,7 +279,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { it('should not prefetch content if content is true but representation is not ready', () => { sandbox.stub(docBase, 'isRepresentationReady').returns(false); sandbox - .mock(util) + .mock(api) .expects('get') .never(); docBase.prefetch({ assets: false, preload: false, content: true }); @@ -289,7 +290,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { is_watermarked: true }; sandbox - .mock(util) + .mock(api) .expects('get') .never(); docBase.prefetch({ assets: false, preload: false, content: true }); @@ -482,6 +483,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { }); it('should load a document', () => { + sandbox.stub(api, 'get'); sandbox.stub(docBase, 'setup'); Object.defineProperty(BaseViewer.prototype, 'load', { value: sandbox.mock() }); sandbox.stub(docBase, 'createContentUrlWithAuthParams'); @@ -1485,7 +1487,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { describe('fetchPrintBlob()', () => { beforeEach(() => { - stubs.get = sandbox.stub(util, 'get').returns(Promise.resolve('blob')); + stubs.get = sandbox.stub(api, 'get').resolves('blob'); }); it('should get and set the blob', () => { diff --git a/src/lib/viewers/doc/__tests__/DocPreloader-test.js b/src/lib/viewers/doc/__tests__/DocPreloader-test.js index 1ca9663cd..1f8e83eeb 100644 --- a/src/lib/viewers/doc/__tests__/DocPreloader-test.js +++ b/src/lib/viewers/doc/__tests__/DocPreloader-test.js @@ -1,4 +1,5 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import DocPreloader from '../DocPreloader'; import * as util from '../../../util'; import { @@ -38,7 +39,7 @@ describe('lib/viewers/doc/DocPreloader', () => { describe('showPreload()', () => { it('should not do anything if document is loaded', () => { sandbox.stub(docPreloader, 'checkDocumentLoaded').returns(true); - sandbox.stub(util, 'get').returns(Promise.resolve({})); + sandbox.stub(api, 'get').returns(Promise.resolve({})); sandbox.stub(docPreloader, 'bindDOMListeners'); return docPreloader.showPreload('someUrl', containerEl).then(() => { @@ -49,7 +50,7 @@ describe('lib/viewers/doc/DocPreloader', () => { it('should set up preload DOM structure and bind image load handler', () => { const imgSrc = 'https://someblobimgsrc/'; - sandbox.stub(util, 'get').returns(Promise.resolve({})); + sandbox.stub(api, 'get').returns(Promise.resolve({})); sandbox.stub(URL, 'createObjectURL').returns(imgSrc); sandbox.stub(docPreloader, 'bindDOMListeners'); diff --git a/src/lib/viewers/image/ImageBaseViewer.js b/src/lib/viewers/image/ImageBaseViewer.js index df1b9543f..98eadc577 100644 --- a/src/lib/viewers/image/ImageBaseViewer.js +++ b/src/lib/viewers/image/ImageBaseViewer.js @@ -1,9 +1,9 @@ +import api from '../../api'; import Controls from '../../Controls'; import BaseViewer from '../BaseViewer'; import Browser from '../../Browser'; import PreviewError from '../../PreviewError'; import { ICON_ZOOM_IN, ICON_ZOOM_OUT } from '../../icons/icons'; -import { get } from '../../util'; import { CLASS_INVISIBLE } from '../../constants'; import { ERROR_CODE, VIEWER_EVENT } from '../../events'; @@ -217,7 +217,8 @@ class ImageBaseViewer extends BaseViewer { // Case when natural dimensions are not assigned // By default, assigned width and height in Chrome/Safari/Firefox will be 300x150. // IE11 workaround. Dimensions only displayed if the image is attached to the document. - get(imageEl.src, {}, 'text') + api + .get(imageEl.src, { type: 'text' }) .then((imageAsText) => { const parser = new DOMParser(); const svgEl = parser.parseFromString(imageAsText, 'image/svg+xml'); diff --git a/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js b/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js index 1d4d65ed3..9bccac946 100644 --- a/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js +++ b/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js @@ -1,10 +1,10 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import ImageBaseViewer from '../ImageBaseViewer'; import BaseViewer from '../../BaseViewer'; import Browser from '../../../Browser'; import fullscreen from '../../../Fullscreen'; import PreviewError from '../../../PreviewError'; -import * as util from '../../../util'; import { ICON_ZOOM_IN, ICON_ZOOM_OUT } from '../../../icons/icons'; import { VIEWER_EVENT } from '../../../events'; @@ -251,7 +251,7 @@ describe('lib/viewers/image/ImageBaseViewer', () => { getAttribute: (name) => imageEl[name] }; - sandbox.stub(util, 'get').returns(Promise.resolve('not real a image')); + sandbox.stub(api, 'get').resolves('not real a image'); const promise = imageBase.setOriginalImageSize(imageEl); promise .then(() => { @@ -266,7 +266,7 @@ describe('lib/viewers/image/ImageBaseViewer', () => { it('should resolve when the get call fails', (done) => { const imageEl = {}; - sandbox.stub(util, 'get').returns(Promise.reject()); + sandbox.stub(api, 'get').returns(Promise.reject()); const promise = imageBase.setOriginalImageSize(imageEl); promise.then(() => Assert.fail()).catch(() => done()); }); diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index 9de1b77f0..c447d05ff 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -1,7 +1,8 @@ +import api from '../../api'; import VideoBaseViewer from './VideoBaseViewer'; import PreviewError from '../../PreviewError'; import fullscreen from '../../Fullscreen'; -import { appendQueryParams, get, getProp } from '../../util'; +import { appendQueryParams, getProp } from '../../util'; import { getRepresentation } from '../../file'; import { MEDIA_STATIC_ASSETS_VERSION } from '../../constants'; import getLanguageName from '../../lang'; @@ -127,7 +128,7 @@ class DashViewer extends VideoBaseViewer { const { representation } = this.options; if (content && this.isRepresentationReady(representation)) { const template = representation.content.url_template; - get(this.createContentUrlWithAuthParams(template, MANIFEST), 'any'); + api.get(this.createContentUrlWithAuthParams(template, MANIFEST), { type: 'document' }); } } diff --git a/src/lib/viewers/media/__tests__/DashViewer-test.js b/src/lib/viewers/media/__tests__/DashViewer-test.js index 4876e0962..ad4ea8076 100644 --- a/src/lib/viewers/media/__tests__/DashViewer-test.js +++ b/src/lib/viewers/media/__tests__/DashViewer-test.js @@ -1,9 +1,9 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import DashViewer from '../DashViewer'; import VideoBaseViewer from '../VideoBaseViewer'; import BaseViewer from '../../BaseViewer'; import PreviewError from '../../../PreviewError'; -import * as util from '../../../util'; import { MEDIA_STATIC_ASSETS_VERSION } from '../../../constants'; import { VIEWER_EVENT } from '../../../events'; @@ -177,7 +177,7 @@ describe('lib/viewers/media/DashViewer', () => { it('should not prefetch rep content if content is false', () => { sandbox - .mock(util) + .mock(api) .expects('get') .never(); dash.prefetch({ assets: false, content: false }); @@ -187,7 +187,7 @@ describe('lib/viewers/media/DashViewer', () => { it('should not prefetch rep content if representation is not ready', () => { stubs.repReady.returns(false); sandbox - .mock(util) + .mock(api) .expects('get') .never(); @@ -199,9 +199,9 @@ describe('lib/viewers/media/DashViewer', () => { const contentUrl = 'someUrl'; stubs.createUrl.returns(contentUrl); sandbox - .mock(util) + .mock(api) .expects('get') - .withArgs(contentUrl, 'any'); + .withArgs(contentUrl, { type: 'document' }); dash.prefetch({ assets: false, content: true }); expect(stubs.prefetchAssets).to.not.be.called; diff --git a/src/lib/viewers/office/OfficeViewer.js b/src/lib/viewers/office/OfficeViewer.js index 9feecf8b6..b31d0881b 100644 --- a/src/lib/viewers/office/OfficeViewer.js +++ b/src/lib/viewers/office/OfficeViewer.js @@ -5,7 +5,7 @@ import Popup from '../../Popup'; import { CLASS_HIDDEN } from '../../constants'; import { getRepresentation } from '../../file'; import { ICON_PRINT_CHECKMARK } from '../../icons/icons'; -import { get } from '../../util'; +import api from '../../api'; import { VIEWER_EVENT } from '../../events'; const LOAD_TIMEOUT_MS = 120000; @@ -319,7 +319,7 @@ class OfficeViewer extends BaseViewer { * @return {Promise} Promise setting print blob */ fetchPrintBlob(pdfUrl) { - return get(pdfUrl, 'blob').then((blob) => { + return api.get(pdfUrl, { type: 'blob' }).then((blob) => { this.printBlob = blob; }); } diff --git a/src/lib/viewers/office/__tests__/OfficeViewer-test.js b/src/lib/viewers/office/__tests__/OfficeViewer-test.js index ec8793ed0..231f3d24e 100644 --- a/src/lib/viewers/office/__tests__/OfficeViewer-test.js +++ b/src/lib/viewers/office/__tests__/OfficeViewer-test.js @@ -1,9 +1,9 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import BaseViewer from '../../BaseViewer'; import Browser from '../../../Browser'; import Location from '../../../Location'; import OfficeViewer from '../OfficeViewer'; -import * as util from '../../../util'; import { CLASS_HIDDEN } from '../../../constants'; import { ICON_PRINT_CHECKMARK } from '../../../icons/icons'; @@ -298,7 +298,9 @@ describe('lib/viewers/office/OfficeViewer', () => { it('should correctly set the action URL', () => { expect(stubs.formEl.getAttribute('action')).to.equal( - `${EXCEL_ONLINE_URL}?ui=${office.options.location.locale}&rs=${office.options.location.locale}&WOPISrc=src&sc=${stubs.sessionContext}` + `${EXCEL_ONLINE_URL}?ui=${office.options.location.locale}&rs=${ + office.options.location.locale + }&WOPISrc=src&sc=${stubs.sessionContext}` ); expect(stubs.formEl.getAttribute('method')).to.equal('POST'); expect(stubs.formEl.getAttribute('target')).to.equal(OFFICE_ONLINE_IFRAME_NAME); @@ -466,7 +468,7 @@ describe('lib/viewers/office/OfficeViewer', () => { describe('fetchPrintBlob()', () => { beforeEach(() => { stubs.promise = Promise.resolve({ blob: 'blob' }); - stubs.get = sandbox.stub(util, 'get').returns(stubs.promise); + stubs.get = sandbox.stub(api, 'get').returns(stubs.promise); stubs.appendAuthHeader = sandbox.stub(office, 'appendAuthHeader'); office.initPrint(); }); diff --git a/src/lib/viewers/text/CSVViewer.js b/src/lib/viewers/text/CSVViewer.js index 9935d7e06..f10aa658c 100644 --- a/src/lib/viewers/text/CSVViewer.js +++ b/src/lib/viewers/text/CSVViewer.js @@ -1,5 +1,6 @@ +import api from '../../api'; import TextBaseViewer from './TextBaseViewer'; -import { createAssetUrlCreator, get } from '../../util'; +import { createAssetUrlCreator } from '../../util'; import { TEXT_STATIC_ASSETS_VERSION } from '../../constants'; import './CSV.scss'; import { ERROR_CODE, VIEWER_EVENT } from '../../events'; @@ -47,7 +48,7 @@ class CSVViewer extends TextBaseViewer { return Promise.all([this.loadAssets(JS), this.getRepStatus().getPromise()]) .then(() => { - get(papaWorkerUrl, 'blob').then((papaWorkerBlob) => { + api.get(papaWorkerUrl, { type: 'blob' }).then((papaWorkerBlob) => { /* global Papa */ const workerSrc = URL.createObjectURL(papaWorkerBlob); Papa.SCRIPT_PATH = workerSrc; @@ -89,7 +90,7 @@ class CSVViewer extends TextBaseViewer { const { representation } = this.options; if (content && this.isRepresentationReady(representation)) { const template = representation.content.url_template; - get(this.createContentUrlWithAuthParams(template), 'any'); + api.get(this.createContentUrlWithAuthParams(template), { type: 'document' }); } } diff --git a/src/lib/viewers/text/PlainTextViewer.js b/src/lib/viewers/text/PlainTextViewer.js index 82d209263..6fe693b6c 100644 --- a/src/lib/viewers/text/PlainTextViewer.js +++ b/src/lib/viewers/text/PlainTextViewer.js @@ -1,11 +1,12 @@ import './Text.scss'; +import api from '../../api'; import TextBaseViewer from './TextBaseViewer'; import Browser from '../../Browser'; import Popup from '../../Popup'; import { CLASS_HIDDEN, TEXT_STATIC_ASSETS_VERSION } from '../../constants'; import { ICON_PRINT_CHECKMARK } from '../../icons/icons'; import { HIGHLIGHTTABLE_EXTENSIONS } from '../../extensions'; -import { get, openContentInsideIframe, createAssetUrlCreator, createStylesheet } from '../../util'; +import { openContentInsideIframe, createAssetUrlCreator, createStylesheet } from '../../util'; import { VIEWER_EVENT } from '../../events'; // Inline web worker JS @@ -70,7 +71,7 @@ class PlainTextViewer extends TextBaseViewer { const { representation } = this.options; if (content && this.isRepresentationReady(representation)) { const template = representation.content.url_template; - get(this.createContentUrlWithAuthParams(template), 'any'); + api.get(this.createContentUrlWithAuthParams(template), { type: 'document' }); } } @@ -194,7 +195,8 @@ class PlainTextViewer extends TextBaseViewer { const contentUrl = this.createContentUrlWithAuthParams(template); this.startLoadTimer(); - return get(contentUrl, headers, 'text') + return api + .get(contentUrl, { headers, type: 'text' }) .catch((error) => { this.handleDownloadError(error, contentUrl); }) diff --git a/src/lib/viewers/text/__tests__/CSVViewer-test.js b/src/lib/viewers/text/__tests__/CSVViewer-test.js index 8edbe5a3c..44241a31d 100644 --- a/src/lib/viewers/text/__tests__/CSVViewer-test.js +++ b/src/lib/viewers/text/__tests__/CSVViewer-test.js @@ -1,6 +1,7 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; // eslint-disable-line no-unused-vars import createReactClass from 'create-react-class'; +import api from '../../../api'; import CSVViewer from '../CSVViewer'; import TextBaseViewer from '../TextBaseViewer'; import BaseViewer from '../../BaseViewer'; @@ -82,9 +83,9 @@ describe('lib/viewers/text/CSVViewer', () => { Object.defineProperty(TextBaseViewer.prototype, 'load', { value: sandbox.mock() }); sandbox - .mock(util) + .mock(api) .expects('get') - .withArgs(workerUrl, 'blob') + .withArgs(workerUrl, { type: 'blob' }) .returns(Promise.resolve(blob)); return csv.load().then(() => { @@ -101,7 +102,7 @@ describe('lib/viewers/text/CSVViewer', () => { csv.options.sharedLink = 'sharedLink'; csv.options.sharedLinkPassword = 'sharedLinkPassword'; - sandbox.stub(util, 'get').returns(Promise.resolve()); + sandbox.stub(api, 'get').returns(Promise.resolve()); const csvUrlWithAuth = `csvUrl/?access_token=token&shared_link=sharedLink&shared_link_password=sharedLinkPassword&box_client_name=${__NAME__}&box_client_version=${__VERSION__}`; @@ -121,7 +122,7 @@ describe('lib/viewers/text/CSVViewer', () => { csv.options.token = 'token'; csv.options.sharedLink = 'sharedLink'; csv.options.sharedLinkPassword = 'sharedLinkPassword'; - sandbox.stub(util, 'get').returns(Promise.resolve()); + sandbox.stub(api, 'get').returns(Promise.resolve()); sandbox.stub(csv, 'startLoadTimer'); return csv.load().then(() => { @@ -142,9 +143,9 @@ describe('lib/viewers/text/CSVViewer', () => { sandbox.stub(csv, 'createContentUrlWithAuthParams').returns(contentUrl); sandbox.stub(csv, 'isRepresentationReady').returns(true); sandbox - .mock(util) + .mock(api) .expects('get') - .withArgs(contentUrl, 'any'); + .withArgs(contentUrl, { type: 'document' }); csv.prefetch({ assets: false, content: true }); }); @@ -152,7 +153,7 @@ describe('lib/viewers/text/CSVViewer', () => { it('should not prefetch content if content is true but representation is not ready', () => { sandbox.stub(csv, 'isRepresentationReady').returns(false); sandbox - .mock(util) + .mock(api) .expects('get') .never(); csv.prefetch({ assets: false, content: true }); diff --git a/src/lib/viewers/text/__tests__/PlainTextViewer-test.js b/src/lib/viewers/text/__tests__/PlainTextViewer-test.js index 85d47d15d..f98e31351 100644 --- a/src/lib/viewers/text/__tests__/PlainTextViewer-test.js +++ b/src/lib/viewers/text/__tests__/PlainTextViewer-test.js @@ -1,4 +1,5 @@ /* eslint-disable no-unused-expressions */ +import api from '../../../api'; import Browser from '../../../Browser'; import PlainTextViewer from '../PlainTextViewer'; import BaseViewer from '../../BaseViewer'; @@ -131,14 +132,20 @@ describe('lib/viewers/text/PlainTextViewer', () => { const contentUrl = 'someContentUrl'; sandbox.stub(text, 'createContentUrlWithAuthParams').returns(contentUrl); sandbox.stub(text, 'isRepresentationReady').returns(true); - sandbox.mock(util).expects('get').withArgs(contentUrl, 'any'); + sandbox + .mock(api) + .expects('get') + .withArgs(contentUrl, { type: 'document' }); text.prefetch({ assets: false, content: true }); }); it('should not prefetch content if content is true but representation is not ready', () => { sandbox.stub(text, 'isRepresentationReady').returns(false); - sandbox.mock(util).expects('get').never(); + sandbox + .mock(api) + .expects('get') + .never(); text.prefetch({ assets: false, content: true }); }); }); @@ -177,13 +184,13 @@ describe('lib/viewers/text/PlainTextViewer', () => { const getPromise = Promise.resolve(''); text.options.file.size = 196608 - 1; // 192KB - 1 - sandbox.stub(util, 'get').returns(getPromise); + sandbox.stub(api, 'get').returns(getPromise); sandbox.stub(text, 'createContentUrlWithAuthParams').returns(urlWithAccessToken); text.postLoad(); return getPromise.then(() => { expect(text.truncated).to.be.false; - expect(util.get).to.be.calledWith(urlWithAccessToken, {}, 'text'); + expect(api.get).to.be.calledWith(urlWithAccessToken, { headers: {}, type: 'text' }); }); }); @@ -193,21 +200,21 @@ describe('lib/viewers/text/PlainTextViewer', () => { const headersWithRange = { Range: 'bytes=0-196608' }; text.options.file.size = 196608 + 1; // 192KB + 1 - sandbox.stub(util, 'get').returns(getPromise); + sandbox.stub(api, 'get').returns(getPromise); sandbox.stub(text, 'createContentUrlWithAuthParams').returns(url); text.postLoad(); return getPromise.then(() => { expect(text.truncated).to.be.true; - expect(util.get).to.be.calledWith(url, headersWithRange, 'text'); + expect(api.get).to.be.calledWith(url, { headers: headersWithRange, type: 'text' }); }); }); it('should append dots to text if truncated', () => { const someText = 'blah'; const getPromise = Promise.resolve(someText); - sandbox.stub(util, 'get').returns(getPromise); + sandbox.stub(api, 'get').returns(getPromise); sandbox.stub(text, 'finishLoading'); text.options.file.size = 196608 + 1; // 192KB + 1; @@ -221,7 +228,7 @@ describe('lib/viewers/text/PlainTextViewer', () => { it('should call initHighlightJs if file has code extension', () => { const someText = 'blah'; const getPromise = Promise.resolve(someText); - sandbox.stub(util, 'get').returns(getPromise); + sandbox.stub(api, 'get').returns(getPromise); sandbox.stub(text, 'initHighlightJs'); text.options.file.size = 196608 + 1; // 192KB + 1 text.options.file.extension = 'js'; // code extension @@ -235,7 +242,7 @@ describe('lib/viewers/text/PlainTextViewer', () => { it('should invoke startLoadTimer()', () => { sandbox.stub(text, 'startLoadTimer'); - sandbox.stub(util, 'get').returns(Promise.resolve('')); + sandbox.stub(api, 'get').returns(Promise.resolve('')); const someText = 'blah'; const getPromise = Promise.resolve(someText); @@ -249,7 +256,7 @@ describe('lib/viewers/text/PlainTextViewer', () => { it('should handle a download error', () => { const getPromise = Promise.reject(); - sandbox.stub(util, 'get').returns(getPromise); + sandbox.stub(api, 'get').returns(getPromise); sandbox.stub(text, 'handleDownloadError'); const promise = text.postLoad(); diff --git a/yarn.lock b/yarn.lock index 1344f23e3..70338179d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -213,7 +213,7 @@ mkdirp "^0.5.1" rimraf "^2.5.2" -"@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.2.0": +"@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.3.0.tgz#50a2754016b6f30a994ceda6d9a0a8c36adda849" integrity sha1-UKJ1QBa28wqZTO2m2aCow2rdqEk= @@ -227,7 +227,7 @@ dependencies: "@sinonjs/samsam" "^2 || ^3" -"@sinonjs/samsam@^2 || ^3", "@sinonjs/samsam@^3.0.2": +"@sinonjs/samsam@^2 || ^3": version "3.0.2" resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.0.2.tgz#304fb33bd5585a0b2df8a4c801fcb47fa84d8e43" integrity sha1-ME+zO9VYWgst+KTIAfy0f6hNjkM= @@ -236,6 +236,20 @@ array-from "^2.1.1" lodash.get "^4.4.2" +"@sinonjs/samsam@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.1.1.tgz#8e2eceb2353f6626e2867352e3def951d3366240" + integrity sha512-ILlwvQUwAiaVBzr3qz8oT1moM7AIUHqUc2UmEjQcH9lLe+E+BZPwUMuc9FFojMswRK4r96x5zDTTrowMLw/vuA== + dependencies: + "@sinonjs/commons" "^1.0.2" + array-from "^2.1.1" + lodash "^4.17.11" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + "@types/blob-util@1.3.3": version "1.3.3" resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" @@ -4229,22 +4243,6 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" -fetch-mock-forwarder@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fetch-mock-forwarder/-/fetch-mock-forwarder-1.0.0.tgz#3003492fac75c0eddc8fa170e4819d0387f4f022" - integrity sha1-MANJL6x1wO3cj6Fw5IGdA4f08CI= - dependencies: - whatwg-fetch "^0.10.1" - -fetch-mock@^5.13.1: - version "5.13.1" - resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7" - integrity sha512-eWUo2KI4sRGnRu8tKELCBfasALM5BfvrCxdI7J02j3eUM9mf+uYzJkURA0PSn/29JVapVrYFm+z+9XijXu1PdA== - dependencies: - glob-to-regexp "^0.3.0" - node-fetch "^1.3.3" - path-to-regexp "^1.7.0" - figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -4687,11 +4685,6 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -6621,7 +6614,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= -lodash@4.17.11, lodash@^4.14.0, lodash@^4.17.0, lodash@^4.17.2: +lodash@4.17.11, lodash@^4.14.0, lodash@^4.17.0, lodash@^4.17.11, lodash@^4.17.2: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -6700,10 +6693,10 @@ lolex@^2.3.2: resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807" integrity sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng== -lolex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-3.0.0.tgz#f04ee1a8aa13f60f1abd7b0e8f4213ec72ec193e" - integrity sha1-8E7hqKoT9g8avXsOj0IT7HLsGT4= +lolex@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-3.1.0.tgz#1a7feb2fefd75b3e3a7f79f0e110d9476e294434" + integrity sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw== longest-streak@^2.0.1: version "2.0.2" @@ -7229,18 +7222,18 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nise@^1.4.7: - version "1.4.8" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.8.tgz#ce91c31e86cf9b2c4cac49d7fcd7f56779bfd6b0" - integrity sha1-zpHDHobPmyxMrEnX/Nf1Z3m/1rA= +nise@^1.4.10: + version "1.4.10" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.10.tgz#ae46a09a26436fae91a38a60919356ae6db143b6" + integrity sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA== dependencies: "@sinonjs/formatio" "^3.1.0" + "@sinonjs/text-encoding" "^0.7.1" just-extend "^4.0.2" lolex "^2.3.2" path-to-regexp "^1.7.0" - text-encoding "^0.6.4" -node-fetch@^1.0.1, node-fetch@^1.3.3: +node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -9575,17 +9568,17 @@ sinon-chai@3.3.0: resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.3.0.tgz#8084ff99451064910fbe2c2cb8ab540c00b740ea" integrity sha1-gIT/mUUQZJEPviwsuKtUDAC3QOo= -sinon@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.2.2.tgz#388ecabd42fa93c592bfc71d35a70894d5a0ca07" - integrity sha1-OI7KvUL6k8WSv8cdNacIlNWgygc= +sinon@^7.2.3: + version "7.2.4" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.2.4.tgz#d834b9a38d8533b4ca3274a9a9ffa8e54c95d10c" + integrity sha512-FGlcfrkiBRfaEIKRw8s/9mk4nP4AMGswvKFixLo+AzsOhskjaBCHAHGLMd8pCJpQGS+9ZI71px6qoQUyvADeyA== dependencies: - "@sinonjs/commons" "^1.2.0" + "@sinonjs/commons" "^1.3.0" "@sinonjs/formatio" "^3.1.0" - "@sinonjs/samsam" "^3.0.2" + "@sinonjs/samsam" "^3.1.1" diff "^3.5.0" - lolex "^3.0.0" - nise "^1.4.7" + lolex "^3.1.0" + nise "^1.4.10" supports-color "^5.5.0" slash@^1.0.0: @@ -10362,11 +10355,6 @@ test-exclude@^4.1.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" -text-encoding@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" - integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk= - text-extensions@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.7.0.tgz#faaaba2625ed746d568a23e4d0aacd9bf08a8b39" @@ -11111,16 +11099,11 @@ wgxpath@~1.0.0: resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690" integrity sha1-7vikudVYzEla06mit1FZfs2a9pA= -whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.3: +whatwg-fetch@>=0.10.0: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" integrity sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ= -whatwg-fetch@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.10.1.tgz#365125d40b36823feac8ab41b71c4d56e84a531f" - integrity sha1-NlEl1As2gj/qyKtBtxxNVuhKUx8= - whet.extend@~0.9.9: version "0.9.9" resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"