Skip to content

Commit

Permalink
fix(api): Add support for http interceptors (#1039)
Browse files Browse the repository at this point in the history
* fix(api): Add support for http interceptors

* Refactor API singleton object to class to use api instances for each preview instance.
* Add support for using passed in request/response http interceptors.
* Add request and response interceptors to documentation
* Convert metadata API to a class and encapsulate within the API
* Add DownloadReachability to API
  • Loading branch information
mickr authored Jul 29, 2019
1 parent bad7359 commit fbe052b
Show file tree
Hide file tree
Showing 41 changed files with 587 additions and 315 deletions.
36 changes: 19 additions & 17 deletions README.md

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions src/lib/DownloadReachability.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import api from './api';
import { openUrlInsideIframe, isLocalStorageAvailable } from './util';

const DEFAULT_DOWNLOAD_HOST_PREFIX = 'https://dl.';
Expand Down Expand Up @@ -133,14 +132,24 @@ class DownloadReachability {
: null;
}

/** @property {Api} Previews instance of the api for XHR calls */
api;

/**
* @param {Api} client - Previews instance of the api.
*/
constructor(client) {
this.api = client;
}

/**
* Checks if the provided host is reachable. If not set the session storage to reflect this.
*
* @param {string} downloadUrl - Content download URL, may either be a template or an actual URL
* @return {void}
*/
static setDownloadReachability(downloadUrl) {
return api
setDownloadReachability(downloadUrl) {
return this.api
.head(downloadUrl)
.then(() => {
return Promise.resolve(false);
Expand All @@ -157,7 +166,7 @@ class DownloadReachability {
* @param {string} downloadUrl - Content download URL
* @return {void}
*/
static downloadWithReachabilityCheck(downloadUrl) {
downloadWithReachabilityCheck(downloadUrl) {
const defaultDownloadUrl = DownloadReachability.replaceDownloadHostWithDefault(downloadUrl);
if (DownloadReachability.isDownloadHostBlocked() || !DownloadReachability.isCustomDownloadHost(downloadUrl)) {
// If we know the host is blocked, or we are already using the default,
Expand All @@ -166,7 +175,7 @@ class DownloadReachability {
} else {
// Try the custom host, then check reachability
openUrlInsideIframe(downloadUrl);
DownloadReachability.setDownloadReachability(downloadUrl).then(isBlocked => {
this.setDownloadReachability(downloadUrl).then(isBlocked => {
if (isBlocked) {
// If download is unreachable, try again with default
openUrlInsideIframe(defaultDownloadUrl);
Expand Down
49 changes: 40 additions & 9 deletions src/lib/Preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +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 Api from './api';
import Browser from './Browser';
import Logger from './Logger';
import loaderList from './loaders';
Expand All @@ -14,7 +14,6 @@ import PreviewErrorViewer from './viewers/error/PreviewErrorViewer';
import PreviewUI from './PreviewUI';
import getTokens from './tokens';
import Timer from './Timer';
import DownloadReachability from './DownloadReachability';
import {
getProp,
decodeKeydown,
Expand Down Expand Up @@ -84,6 +83,9 @@ const SUPPORT_URL = 'https://support.box.com';
const PREVIEW_LOCATION = findScriptLocation(PREVIEW_SCRIPT_NAME, document.currentScript);

class Preview extends EventEmitter {
/** @property {Api} - Previews Api instance used for XHR calls */
api = new Api();

/** @property {boolean} - Whether preview is open */
open = false;

Expand Down Expand Up @@ -212,6 +214,9 @@ class Preview extends EventEmitter {
this.emit(PREVIEW_METRIC, event);
}

// Eject http interceptors
this.api.ejectInterceptors();

this.viewer.destroy();
}

Expand Down Expand Up @@ -538,14 +543,14 @@ class Preview extends EventEmitter {
params,
);

DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
this.api.reachability.downloadWithReachabilityCheck(downloadUrl);

// Otherwise, get the content download URL of the original file and download
} else {
const getDownloadUrl = appendQueryParams(getDownloadURL(this.file.id, apiHost), queryParams);
api.get(getDownloadUrl, { headers: this.getRequestHeaders() }).then(data => {
this.api.get(getDownloadUrl, { headers: this.getRequestHeaders() }).then(data => {
const downloadUrl = appendQueryParams(data.download_url, queryParams);
DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
this.api.reachability.downloadWithReachabilityCheck(downloadUrl);
});
}

Expand Down Expand Up @@ -773,6 +778,18 @@ class Preview extends EventEmitter {
this.retryCount = 0;
}

// Eject any previous interceptors
this.api.ejectInterceptors();

// Check to see if there are http interceptors and load them
if (this.options.responseInterceptor) {
this.api.addResponseInterceptor(this.options.responseInterceptor);
}

if (this.options.requestInterceptor) {
this.api.addRequestInterceptor(this.options.requestInterceptor);
}

// @TODO: This may not be the best way to detect if we are offline. Make sure this works well if we decided to
// combine Box Elements + Preview. This could potentially break if we have Box Elements fetch the file object
// and pass the well-formed file object directly to the preview library to render.
Expand Down Expand Up @@ -942,6 +959,12 @@ class Preview extends EventEmitter {
// Prefix any user created loaders before our default ones
this.loaders = (options.loaders || []).concat(loaderList);

// Add the request interceptor to the preview instance
this.options.requestInterceptor = options.requestInterceptor;

// Add the response interceptor to the preview instance
this.options.responseInterceptor = options.responseInterceptor;

// Disable or enable viewers based on viewer options
Object.keys(this.options.viewers).forEach(viewerName => {
const isDisabled = this.options.viewers[viewerName].disabled;
Expand All @@ -964,7 +987,12 @@ class Preview extends EventEmitter {
*/
createViewerOptions(moreOptions) {
return cloneDeep(
Object.assign({}, this.options, moreOptions, { location: this.location, cache: this.cache, ui: this.ui }),
Object.assign({}, this.options, moreOptions, {
api: this.api,
location: this.location,
cache: this.cache,
ui: this.ui,
}),
);
}

Expand Down Expand Up @@ -1008,7 +1036,8 @@ class Preview extends EventEmitter {
Timer.start(tag);

const fileInfoUrl = appendQueryParams(getURL(this.file.id, fileVersionId, apiHost), params);
api.get(fileInfoUrl, { headers: this.getRequestHeaders() })
this.api
.get(fileInfoUrl, { headers: this.getRequestHeaders() })
.then(this.handleFileInfoResponse)
.catch(this.handleFetchError);
}
Expand Down Expand Up @@ -1360,7 +1389,8 @@ class Preview extends EventEmitter {
};
const headers = getHeaders({}, token, sharedLink, sharedLinkPassword);

api.post(`${apiHost}/2.0/events`, data, { headers })
this.api
.post(`${apiHost}/2.0/events`, data, { headers })
.then(() => {
// Reset retry count after successfully logging
this.logRetryCount = 0;
Expand Down Expand Up @@ -1646,7 +1676,8 @@ class Preview extends EventEmitter {
const fileInfoUrl = appendQueryParams(getURL(fileId, fileVersionId, apiHost), params);

// Prefetch and cache file information and content
api.get(fileInfoUrl, { headers: this.getRequestHeaders(token) })
this.api
.get(fileInfoUrl, { headers: this.getRequestHeaders(token) })
.then(file => {
// Cache file info
cacheFile(this.cache, file);
Expand Down
12 changes: 9 additions & 3 deletions src/lib/RepStatus.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import EventEmitter from 'events';
import api from './api';
import { appendAuthParams } from './util';
import { STATUS_SUCCESS, STATUS_VIEWABLE, STATUS_PENDING, STATUS_NONE } from './constants';
import PreviewError from './PreviewError';
Expand All @@ -9,6 +8,9 @@ import { ERROR_CODE, LOAD_METRIC } from './events';
const STATUS_UPDATE_INTERVAL_MS = 2000;

class RepStatus extends EventEmitter {
/** @property {Api} - Api used for XHR calls */
api;

/**
* Gets the status out of represenation
*
Expand Down Expand Up @@ -38,6 +40,8 @@ class RepStatus extends EventEmitter {
/**
* [constructor]
*
* @param {Object} options - Options object for representation status
* @param {Api} [options.api] - Optional Api layer for http calls
* @param {Object} options.representation - Representation object
* @param {string} options.token - Access token
* @param {string} options.sharedLink - Shared link
Expand All @@ -46,8 +50,10 @@ class RepStatus extends EventEmitter {
* @param {Object} [options.logger] - Optional logger instance
* @return {RepStatus} RepStatus instance
*/
constructor({ representation, token, sharedLink, sharedLinkPassword, logger, fileId }) {
constructor({ api, representation, token, sharedLink, sharedLinkPassword, logger, fileId }) {
super();

this.api = api;
this.representation = representation;
this.logger = logger;
this.fileId = fileId;
Expand Down Expand Up @@ -85,7 +91,7 @@ class RepStatus extends EventEmitter {
const tag = Timer.createTag(this.fileId, LOAD_METRIC.convertTime);
Timer.start(tag);

return api.get(this.infoUrl).then(info => {
return this.api.get(this.infoUrl).then(info => {
clearTimeout(this.statusTimeout);

if (info.metadata) {
Expand Down
34 changes: 22 additions & 12 deletions src/lib/__tests__/DownloadReachability-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-unused-expressions */
import * as util from '../util';
import api from '../api';
import Api from '../api';
import DownloadReachability from '../DownloadReachability';

const sandbox = sinon.sandbox.create();
Expand Down Expand Up @@ -132,23 +132,33 @@ describe('lib/DownloadReachability', () => {
describe('setDownloadReachability()', () => {
it('should catch an errored response', () => {
const setDownloadHostFallbackStub = sandbox.stub(DownloadReachability, 'setDownloadHostFallback');
sandbox.stub(api, 'head').rejects(new Error());

return DownloadReachability.setDownloadReachability('https://dl3.boxcloud.com').catch(() => {
sandbox.stub(Api.prototype, 'head').rejects(new Error());
const api = new Api();
return api.reachability.setDownloadReachability('https://dl3.boxcloud.com').catch(() => {
expect(setDownloadHostFallbackStub).to.be.called;
});
});
});

describe('downloadWithReachabilityCheck()', () => {
let downloadReachability;

beforeEach(() => {
downloadReachability = new DownloadReachability(new Api());
});

afterEach(() => {
downloadReachability = undefined;
});

it('should download with default host if download host is blocked', () => {
sandbox.stub(DownloadReachability, 'isDownloadHostBlocked').returns(true);
sandbox.stub(util, 'openUrlInsideIframe');

const downloadUrl = 'https://custom.boxcloud.com/blah';
const expected = 'https://dl.boxcloud.com/blah';

DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
downloadReachability.downloadWithReachabilityCheck(downloadUrl);

expect(util.openUrlInsideIframe).to.be.calledWith(expected);
});
Expand All @@ -160,7 +170,7 @@ describe('lib/DownloadReachability', () => {

const downloadUrl = 'https://dl.boxcloud.com/blah';

DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
downloadReachability.downloadWithReachabilityCheck(downloadUrl);

expect(util.openUrlInsideIframe).to.be.calledWith(downloadUrl);
});
Expand All @@ -172,28 +182,28 @@ describe('lib/DownloadReachability', () => {

const downloadUrl = 'https://custom.boxcloud.com/blah';

DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
downloadReachability.downloadWithReachabilityCheck(downloadUrl);

expect(util.openUrlInsideIframe).to.be.calledWith(downloadUrl);
});

it('should check download reachability for custom host', () => {
sandbox.stub(DownloadReachability, 'isDownloadHostBlocked').returns(false);
sandbox.stub(DownloadReachability, 'isCustomDownloadHost').returns(true);
sandbox.stub(DownloadReachability, 'setDownloadReachability').returns(Promise.resolve(false));
sandbox.stub(DownloadReachability.prototype, 'setDownloadReachability').returns(Promise.resolve(false));
sandbox.stub(util, 'openUrlInsideIframe');

const downloadUrl = 'https://custom.boxcloud.com/blah';

DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
downloadReachability.downloadWithReachabilityCheck(downloadUrl);

expect(DownloadReachability.setDownloadReachability).to.be.calledWith(downloadUrl);
expect(downloadReachability.setDownloadReachability).to.be.calledWith(downloadUrl);
});

it('should retry download with default host if custom host is blocked', done => {
sandbox.stub(DownloadReachability, 'isDownloadHostBlocked').returns(false);
sandbox.stub(DownloadReachability, 'isCustomDownloadHost').returns(true);
sandbox.stub(DownloadReachability, 'setDownloadReachability').returns(
sandbox.stub(DownloadReachability.prototype, 'setDownloadReachability').returns(
new Promise(resolve => {
resolve(true);
done();
Expand All @@ -204,7 +214,7 @@ describe('lib/DownloadReachability', () => {
const downloadUrl = 'https://custom.boxcloud.com/blah';
const defaultDownloadUrl = 'https://dl.boxcloud.com/blah';

DownloadReachability.downloadWithReachabilityCheck(downloadUrl);
downloadReachability.downloadWithReachabilityCheck(downloadUrl);

expect(util.openUrlInsideIframe.getCall(0).args[0]).to.equal(downloadUrl);
expect(util.openUrlInsideIframe.getCall(0).args[1]).to.equal(defaultDownloadUrl);
Expand Down
Loading

0 comments on commit fbe052b

Please sign in to comment.