box-ui-elements-mlh
Version:
196 lines (170 loc) • 6.47 kB
Flow
/**
* @flow
* @file Helper for Box uploads reachability test
* @author Box
*/
import axios from 'axios';
import LocalStore from '../../utils/LocalStore';
import { DEFAULT_HOSTNAME_UPLOAD, DEFAULT_HOSTNAME_UPLOAD_APP, HEADER_CONTENT_TYPE } from '../../constants';
import type { StringAnyMap, StringMap } from '../../common/types/core';
const CACHED_RESULTS_LOCAL_STORE_KEY = 'bcu-uploads-reachability-cached-results';
type CachedResult = {
expirationTimestampMS: number,
isReachable: boolean,
};
class UploadsReachability {
localStore: LocalStore;
cachedResults: { string: CachedResult };
/**
* [constructor]
*/
constructor() {
this.localStore = new LocalStore();
this.cachedResults = {};
// Populate cachedResults with valid values from the local store, and then save the new
// cachedResults back to local store.
// This cleanup of invalid entries is not strictly necessary to maintain correctness,
// but if we don't do this cleanup results may accumulate in local storage forever.
this.populateCachedResultsWithValidLocalStoreValues();
this.saveCachedResultsToLocalStore();
}
/**
* Returns existing reachability results from local store
*
* @private
* @return {?Object} The results, or null if there was a problem reading the value from local store
*/
getCachedResultsFromLocalStore() {
return this.localStore.getItem(CACHED_RESULTS_LOCAL_STORE_KEY);
}
/**
* Saves the cachedResults variable as a JSON string in local store.
*
* @private
* @return {void}
*/
saveCachedResultsToLocalStore() {
this.localStore.setItem(CACHED_RESULTS_LOCAL_STORE_KEY, this.cachedResults);
}
/**
* Returns true if the given cached result is still valid (i.e. it has not expired yet)
*
* @private
* @param {StringAnyMap} result - A result object for one host
* @return {boolean} Whether or not the result is valid
*/
isCachedHostValid(result: StringAnyMap) {
return result.expirationTimestampMS > Date.now();
}
/**
* Returns the cached result for the given uploadHost
*
* @private
* @param {string} uploadHost - The host URL
* @return {null|StringAnyMap} The result object or null if there isn't one
*/
getCachedResult(uploadHost: string) {
if (uploadHost in this.cachedResults) {
const result = this.cachedResults[uploadHost];
if (this.isCachedHostValid(result)) {
return result;
}
}
return null;
}
/**
* Updates a cached result. Changes both the in-memory cachedResult variable and what's stored in local store
*
* @private
* @param {string} uploadHost - The host URL that was tested
* @param {boolean} isHostReachable - Whether or not the host was reachable
* @return {void}
*/
updateCachedResult(uploadHost: string, isHostReachable: boolean) {
this.cachedResults[uploadHost] = {
isReachable: isHostReachable,
expirationTimestampMS: Date.now() + 1000 * 86400,
};
this.saveCachedResultsToLocalStore();
}
/**
* Adds to the cachedResults object with valid entries from local storage
*
* @private
* @return {void}
*/
populateCachedResultsWithValidLocalStoreValues() {
const localStoreResults = this.getCachedResultsFromLocalStore();
if (!localStoreResults) {
return;
}
Object.keys(localStoreResults).forEach(uploadHost => {
const result = localStoreResults[uploadHost];
if (this.isCachedHostValid(result)) {
this.cachedResults[uploadHost] = result;
}
});
}
/**
* Returns the host URLs that, according to the cached reachability test results, are unreachable
*
* @return {Array} The unreachable host URLs
*/
getUnreachableHostsUrls() {
const unreachableHosts = [];
if (!this.cachedResults) {
return unreachableHosts;
}
Object.keys(this.cachedResults).forEach(uploadHost => {
const value = this.cachedResults[uploadHost];
if (this.isCachedHostValid(value) && !value.isReachable) {
unreachableHosts.push(uploadHost);
}
});
return unreachableHosts;
}
/**
* Determines whether the given host is reachable by either making a test request to the uploadHost
* or returning the result of the last reachability test it did
*
* @param {string} uploadHost - The upload host URL that will be stored in the cached test result and returned in
* getUnreachableHostsUrls() if test fails (this is usually a prefix of the uploadUrl)
* @return {Promise<boolean>} Promise that resolved to true if the host is reachable, false if it is not
*/
async isReachable(uploadHost: string) {
// The default upload host should always reachable
if (uploadHost === `${DEFAULT_HOSTNAME_UPLOAD}/` || uploadHost === `${DEFAULT_HOSTNAME_UPLOAD_APP}/`) {
return true;
}
const cachedResult = this.getCachedResult(uploadHost);
if (cachedResult) {
return cachedResult.isReachable;
}
const isHostReachable = await this.makeReachabilityRequest(uploadHost);
this.updateCachedResult(uploadHost, isHostReachable);
return isHostReachable;
}
/**
* Determines if the given uploadHost is reachable by making a test upload request to it.
* Does not read or modify any cached results.
*
* @param {string} uploadHost - The upload host url to make a test request against
* @return {Promise<boolean>}
*/
async makeReachabilityRequest(uploadHost: string) {
const url = `${uploadHost}html5?reachability_test=run`;
const headers: StringMap = {
[HEADER_CONTENT_TYPE]: 'application/x-www-form-urlencoded; charset=UTF-8',
'X-File-Name': 'reachability_pseudofile.txt',
'X-File-Size': '1234',
};
const data = 'reachability_file=test_file_data';
try {
await axios.post(url, data, { headers });
} catch (error) {
return false;
}
return true;
}
}
export default UploadsReachability;