box-ui-elements-mlh
Version:
325 lines (268 loc) • 11.2 kB
JavaScript
// @flow
import Browser, { BROWSER_CONSTANTS } from './BrowserUtils';
import promiseOne from './promise';
import ActiveXChannel from './ActiveXChannel';
import Channel from './Channel';
import HTTPChannel from './HTTPChannel';
import SafariChannel from './SafariChannel';
import CONSTANTS from './constants';
const MIN_FIREFOX_VERSION_FOR_MIXED_CONTENT = 55;
const MIN_EDGE_16_VERSION_FOR_MIXED_CONTENT = '16.16299';
const MIN_EDGE_VERSION_FOR_MIXED_CONTENT = '17.17134';
const REQUEST_TIMEOUT_MS = 5000;
let boxToolsLogData: {
activex_channel_status: string,
box_tools_version: ?string,
browser_name: string,
browser_version: string,
error_message: ?string,
http_channel_status: string,
https_channel_status: string,
installation_type: ?string,
safari_channel_status: string,
};
function createHTTPChannel(appName: string): HTTPChannel {
const { BOX_UNSECURE_LOCAL_BASE_URL, CREATED_STATUS, HTTP_CHANNEL_NAME } = CONSTANTS;
boxToolsLogData.http_channel_status = CREATED_STATUS;
return new HTTPChannel(appName, BOX_UNSECURE_LOCAL_BASE_URL, HTTP_CHANNEL_NAME);
}
function createSafariChannel(appName: string): SafariChannel {
const { CREATED_STATUS } = CONSTANTS;
boxToolsLogData.safari_channel_status = CREATED_STATUS;
return new SafariChannel(appName);
}
function createActiveXChannel(appName: string): ActiveXChannel {
const { CREATED_STATUS } = CONSTANTS;
boxToolsLogData.activex_channel_status = CREATED_STATUS;
return new ActiveXChannel(appName, false);
}
/**
* Returns an instance of the ActiveX Channel that runs commands in the ActiveX process synchronously.
* This is required for running in certain embedded IE-based webviews.
*/
function createSynchronousActiveXChannel(appName): ActiveXChannel {
const { CREATED_STATUS } = CONSTANTS;
boxToolsLogData.activex_channel_status = CREATED_STATUS;
return new ActiveXChannel(appName, true);
}
/**
* Returns TRUE for MS Edge versions 17.17692+ OR
* Returns TRUE for MS Edge version 16 greater than 16.16299
* @returns {boolean}
*/
function isSupportedMSEdgeVersion() {
const { EDGE } = BROWSER_CONSTANTS;
return (
Browser.isMinBrowser(EDGE, MIN_EDGE_VERSION_FOR_MIXED_CONTENT) ||
(Browser.isMinBrowser(EDGE, MIN_EDGE_16_VERSION_FOR_MIXED_CONTENT) && Browser.getVersion().startsWith('16.'))
);
}
function isUnsupportedMSEdgeVersion() {
return Browser.isEdge() && !isSupportedMSEdgeVersion();
}
function isMixedContentAllowedOnLocalhost() {
const { CHROME, FIREFOX } = BROWSER_CONSTANTS;
// TODO can we do this with feature detection rather than sniffing?
return (
Browser.isMinBrowser(CHROME, 53) ||
Browser.isMinBrowser(FIREFOX, MIN_FIREFOX_VERSION_FOR_MIXED_CONTENT) ||
isSupportedMSEdgeVersion()
);
}
function isSupportedSafariVersion() {
return Browser.isMinBrowser(BROWSER_CONSTANTS.SAFARI, 10);
}
function isUnsupportedSafariVersion() {
return Browser.isSafari() && !isSupportedSafariVersion();
}
/**
* @TODO: (2018-07-24) Rename this to isFirefoxWithoutMixedContentCapability
* since we do not have an Extension planned for the Firefox versions below 55.
*/
function isFirefoxWithExtensionsCapability() {
return (
Browser.isFirefox() && !Browser.isMinBrowser(BROWSER_CONSTANTS.FIREFOX, MIN_FIREFOX_VERSION_FOR_MIXED_CONTENT)
);
}
/**
* Checks if the IE version is supported
* @returns {boolean}
*/
function isSupportedIEVersion() {
return Browser.isMinBrowser(BROWSER_CONSTANTS.IE, 11);
}
/**
* Checks if the user is on IE 11 and has a specific ActiveXObject plugin loaded on the page
*/
function isSupportedIEAndBoxToolsPluginAvailable() {
// Browser Plugins Support is the check for ActiveX-like plugins
return isSupportedIEVersion() && Browser.isIEAndSpecificBrowserPluginSupported(CONSTANTS.BOX_TOOLS_PLUGIN_NAME);
}
/**
* Analyze the cause of Channel failure and return a rejected Promise with an error message
*/
function comServerErrorGenerator(reject) {
const {
BOX_EDIT_NOT_SUPPORTED_ERROR,
BOX_EDIT_SAFARI_ERROR,
BOX_EDIT_UNINSTALLED_ERROR,
BOX_EDIT_UPGRADE_BROWSER_ERROR,
} = CONSTANTS;
let errorMessageID = BOX_EDIT_NOT_SUPPORTED_ERROR;
if (isMixedContentAllowedOnLocalhost()) {
errorMessageID = BOX_EDIT_UNINSTALLED_ERROR;
} else if (isSupportedIEVersion()) {
errorMessageID = BOX_EDIT_UNINSTALLED_ERROR;
} else if (
Browser.isFirefox() ||
Browser.isChrome() ||
isUnsupportedSafariVersion() ||
isUnsupportedMSEdgeVersion()
// Show UPGRADE message when MS Edge support has been enabled
) {
errorMessageID = BOX_EDIT_UPGRADE_BROWSER_ERROR;
} else if (isSupportedSafariVersion()) {
errorMessageID = BOX_EDIT_SAFARI_ERROR;
}
boxToolsLogData.error_message = errorMessageID;
return reject(new Error(errorMessageID));
}
/**
* Default returns the timeout value of 5000ms, if a timeout is not passed.
* When passed validates it to be a number and parse it to the lower integer value
*
* @param {number} [customTimeoutMS] optional field to override the timeout value passed in miliseconds
* @returns {number}
*/
function validateAndReturnBrowserToComServerTimeout(customTimeoutMS) {
let timeoutMS = REQUEST_TIMEOUT_MS;
// validate timeout is a positive number
if (typeof customTimeoutMS === 'number' && customTimeoutMS >= 0) {
timeoutMS = Math.floor(customTimeoutMS);
}
return timeoutMS;
}
/**
* Returns reduced timeout converted to seconds
* We need to use a shortened timeout for the connection between local com server and application,
* so that we will receive a message that that connection has timed out,
* before the connection between the browser and the local com server itself times out.
*/
function shortenAndReturnComServerToApplicationTimeout(browserToComServerTimeoutMS) {
let timeoutSec = +(browserToComServerTimeoutMS / 1000).toFixed(2);
if (browserToComServerTimeoutMS < 2) {
timeoutSec /= 2;
} else {
timeoutSec -= 1;
}
return timeoutSec;
}
function initBoxToolsLogData() {
const browserName = Browser.getName();
const browserVersion = Browser.getVersion();
const { UNCREATED_STATUS } = CONSTANTS;
boxToolsLogData = {
box_tools_version: null,
browser_name: browserName,
browser_version: browserVersion,
error_message: null,
installation_type: null,
http_channel_status: UNCREATED_STATUS,
https_channel_status: UNCREATED_STATUS,
activex_channel_status: UNCREATED_STATUS,
safari_channel_status: UNCREATED_STATUS,
};
}
class ComServerClient {
activeChannel: Channel;
channels: Array<Channel>;
isInitialized: boolean = false;
browser: string;
constructor(appName: string) {
this.channels = [];
this.isInitialized = true;
initBoxToolsLogData();
if (isMixedContentAllowedOnLocalhost()) {
this.channels.push(createHTTPChannel(appName));
} else if (isSupportedSafariVersion()) {
this.channels.push(createSafariChannel(appName));
} else if (isSupportedIEAndBoxToolsPluginAvailable()) {
this.channels.push(createActiveXChannel(appName));
} else if (isFirefoxWithExtensionsCapability() || isUnsupportedMSEdgeVersion()) {
// @NOTE (2018-07-24) No Action - Trying all channels is not an option in this case
// @TODO (2018-07-24) Remove this empty case from here?
} else {
// @NOTE: (2018-01-16) Trying all channels in case of custom useragent
this.channels = this.channels.concat([
createHTTPChannel(appName),
createSafariChannel(appName),
createSynchronousActiveXChannel(appName),
]);
}
}
getComServerStatus(customTimeoutMS: ?number): Promise<any> {
const { ACTIVE_STATUS } = CONSTANTS;
const browserToComServerTimeoutMS = validateAndReturnBrowserToComServerTimeout(customTimeoutMS);
const comServerToApplicationTimeoutSec = shortenAndReturnComServerToApplicationTimeout(
browserToComServerTimeoutMS,
);
const shouldRejectPromiseDueToUnSupportedMSEdgeOrVersion = isUnsupportedMSEdgeVersion();
return new Promise((resolve, reject) => {
if (shouldRejectPromiseDueToUnSupportedMSEdgeOrVersion) {
return comServerErrorGenerator.call(null, reject);
}
if (!this.channels.length) {
return comServerErrorGenerator.call(null, reject);
}
return promiseOne(
this.channels.map(channel => {
return channel
.getComServerStatus(browserToComServerTimeoutMS, comServerToApplicationTimeoutSec)
.then(res => {
this.activeChannel = channel;
if (res) {
boxToolsLogData.installation_type = res.installation_type;
boxToolsLogData.box_tools_version = res.version;
}
boxToolsLogData[`${channel.channelName}_status`] = ACTIVE_STATUS;
return resolve(res);
});
}),
).catch(comServerErrorGenerator.bind(null, reject));
});
}
// TODO isSynchronous? do we need it?
sendRequest(requestData: string, isSynchronous: ?boolean, customTimeoutMS: ?number): Object {
const browserToComServerTimeoutMS = validateAndReturnBrowserToComServerTimeout(customTimeoutMS);
const comServerToApplicationTimeoutSec = shortenAndReturnComServerToApplicationTimeout(
browserToComServerTimeoutMS,
);
if (this.activeChannel) {
return this.activeChannel.sendRequest(
requestData,
browserToComServerTimeoutMS,
comServerToApplicationTimeoutSec,
);
}
return this.getComServerStatus().then(() => {
return this.activeChannel.sendRequest(
requestData,
browserToComServerTimeoutMS,
comServerToApplicationTimeoutSec,
);
});
}
sendCommand(data: string, customTimeoutMS: number): Promise<any> {
const browserToComServerTimeoutMS = validateAndReturnBrowserToComServerTimeout(customTimeoutMS);
const comServerToApplicationTimeoutSec = shortenAndReturnComServerToApplicationTimeout(
browserToComServerTimeoutMS,
);
if (this.activeChannel) {
return this.activeChannel.sendCommand(data, browserToComServerTimeoutMS, comServerToApplicationTimeoutSec);
}
return this.getComServerStatus().then(() => {
return this.activeChannel.sendCommand(data, browserToComServerTimeoutMS, comServerToApplicationTimeoutSec);
});
}
}
export default ComServerClient;