SDA-1824 (client banner api & cloud config update identifier) (#1345)

* SDA-1824 - client banner api & cloud config updater

* SDA-1824 - Expose the new API
This commit is contained in:
Kiran Niranjan 2022-03-01 22:05:34 +05:30 committed by GitHub
parent f65421dbe1
commit 95b03ff5a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 370 additions and 39 deletions

View File

@ -132,4 +132,149 @@ describe('config', () => {
return expect(configInstance.readUserConfig()).rejects.toBeTruthy();
});
});
describe('compareCloudConfig', () => {
it('should return no updated fields for same objects', () => {
const sdaCloudConfig: object = { configVersion: '4.0.0' };
const sfeCloudConfig: object = { configVersion: '4.0.0' };
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(0);
});
it('should return no updated fields for empty object', () => {
const sdaCloudConfig: object = { configVersion: '4.0.0' };
const sfeCloudConfig: object = {};
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(0);
});
it('should return correct number of updated fields', () => {
const sdaCloudConfig: object = {
memoryThreshold: false,
isCustomTitleBar: true,
};
const sfeCloudConfig: object = {
memoryThreshold: true,
isCustomTitleBar: true,
};
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(1);
expect(updatedFields[0]).toBe('memoryThreshold');
});
it('should compare nested object and return correct fields', () => {
const sdaCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '*.symphony.com',
authServerWhitelist: '',
},
};
const sfeCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '*.symphony.com',
authServerWhitelist: '',
},
};
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(0);
});
it('should return correct number of updated fields for nested object comparison', () => {
const sdaCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '',
authServerWhitelist: '',
},
};
const sfeCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '*.symphony.com',
authServerWhitelist: '',
},
};
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(1);
expect(updatedFields[0]).toBe('customFlags');
});
it('should compare array and return correct fields', () => {
const sdaCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '',
authServerWhitelist: '',
},
ctWhitelist: [],
podWhitelist: [],
};
const sfeCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '',
authServerWhitelist: '',
},
ctWhitelist: [],
podWhitelist: [],
};
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(0);
});
it('should return correct number of updated fields for array comparison', () => {
const sdaCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '',
authServerWhitelist: '',
},
ctWhitelist: ['amulli'],
podWhitelist: [],
};
const sfeCloudConfig: object = {
memoryThreshold: false,
customFlags: {
authNegotiateDelegateWhitelist: '',
authServerWhitelist: '',
},
ctWhitelist: [],
podWhitelist: ['butti'],
};
const updatedFields = configInstance.compareCloudConfig(
sdaCloudConfig,
sfeCloudConfig,
);
expect(updatedFields.length).toBe(2);
expect(updatedFields[0]).toBe('ctWhitelist');
expect(updatedFields[1]).toBe('podWhitelist');
});
});
});

View File

@ -6,7 +6,7 @@ import * as util from 'util';
import { buildNumber } from '../../package.json';
import { isDevEnv, isElectronQA, isLinux, isMac } from '../common/env';
import { logger } from '../common/logger';
import { filterOutSelectedValues, pick } from '../common/utils';
import { arrayEquals, filterOutSelectedValues, pick } from '../common/utils';
const writeFile = util.promisify(fs.writeFile);
@ -15,6 +15,17 @@ export enum CloudConfigDataTypes {
ENABLED = 'ENABLED',
DISABLED = 'DISABLED',
}
export const ConfigFieldsToRestart = new Set([
'permissions',
'disableThrottling',
'isCustomTitleBar',
'ctWhitelist',
'podWhitelist',
'autoLaunchPath',
'customFlags',
]);
export interface IConfig {
url: string;
minimizeOnClose: CloudConfigDataTypes;
@ -66,11 +77,13 @@ export interface IPodLevelEntitlements {
disableThrottling: CloudConfigDataTypes;
launchOnStartup: CloudConfigDataTypes;
memoryThreshold: string;
ctWhitelist: string;
podWhitelist: string;
authNegotiateDelegateWhitelist: string;
ctWhitelist: string[];
podWhitelist: string[];
whitelistUrl: string;
authServerWhitelist: string;
customFlags: {
authNegotiateDelegateWhitelist: string;
authServerWhitelist: string;
};
autoLaunchPath: string;
userDataPath: string;
}
@ -425,6 +438,82 @@ class Config {
});
}
/**
* Compares the SFE cloud config & SDA Cloud config and returns the unmatched key property
* @param sdaCloudConfig Partial<ICloudConfig>
* @param sfeCloudConfig Partial<ICloudConfig>
*/
public compareCloudConfig(
sdaCloudConfig: IConfig,
sfeCloudConfig: IConfig,
): string[] {
const updatedField: string[] = [];
if (sdaCloudConfig && sfeCloudConfig) {
for (const sdaKey in sdaCloudConfig) {
if (sdaCloudConfig.hasOwnProperty(sdaKey)) {
for (const sfeKey in sfeCloudConfig) {
if (sdaKey !== sfeKey) {
continue;
}
if (
Array.isArray(sdaCloudConfig[sdaKey]) &&
Array.isArray(sfeCloudConfig[sdaKey])
) {
if (
!arrayEquals(sdaCloudConfig[sdaKey], sfeCloudConfig[sfeKey])
) {
updatedField.push(sdaKey);
}
continue;
}
if (
typeof sdaCloudConfig[sdaKey] === 'object' &&
typeof sfeCloudConfig[sfeKey] === 'object'
) {
for (const sdaObjectKey in sdaCloudConfig[sdaKey]) {
if (sdaCloudConfig[sdaKey].hasOwnProperty(sdaObjectKey)) {
for (const sfeObjectKey in sfeCloudConfig[sfeKey]) {
if (
sdaObjectKey === sfeObjectKey &&
sdaCloudConfig[sdaKey][sdaObjectKey] !==
sfeCloudConfig[sfeKey][sfeObjectKey]
) {
updatedField.push(sdaKey);
}
}
}
}
continue;
}
if (sdaKey === sfeKey) {
if (sdaCloudConfig[sdaKey] !== sfeCloudConfig[sfeKey]) {
updatedField.push(sdaKey);
}
}
}
}
}
}
logger.info(`config-handler: cloud config updated fields`, [
...new Set(updatedField),
]);
return [...new Set(updatedField)];
}
/**
* Merges the different cloud config into config
* @param cloudConfig
*/
public getMergedConfig(cloudConfig: ICloudConfig): object {
return {
...cloudConfig.acpFeatureLevelEntitlements,
...cloudConfig.podLevelEntitlements,
...cloudConfig.pmpEntitlements,
};
}
/**
* filters out the cloud config
*/

View File

@ -255,39 +255,7 @@ ipcMain.on(
analytics.registerPreloadWindow(event.sender);
break;
case apiCmds.setCloudConfig:
const {
podLevelEntitlements,
acpFeatureLevelEntitlements,
pmpEntitlements,
...rest
} = arg.cloudConfig as ICloudConfig;
if (
podLevelEntitlements &&
podLevelEntitlements.autoLaunchPath &&
podLevelEntitlements.autoLaunchPath.match(/\\\\/g)
) {
podLevelEntitlements.autoLaunchPath = podLevelEntitlements.autoLaunchPath.replace(
/\\+/g,
'\\',
);
}
if (
podLevelEntitlements &&
podLevelEntitlements.userDataPath &&
podLevelEntitlements.userDataPath.match(/\\\\/g)
) {
podLevelEntitlements.userDataPath = podLevelEntitlements.userDataPath.replace(
/\\+/g,
'\\',
);
}
logger.info('main-api-handler: ignored other values from SFE', rest);
await config.updateCloudConfig({
podLevelEntitlements,
acpFeatureLevelEntitlements,
pmpEntitlements,
});
await updateFeaturesForCloudConfig();
await updateFeaturesForCloudConfig(arg.cloudConfig as ICloudConfig);
if (windowHandler.appMenu) {
windowHandler.appMenu.buildMenu();
}

View File

@ -25,6 +25,8 @@ import { autoLaunchInstance } from './auto-launch-controller';
import {
CloudConfigDataTypes,
config,
ConfigFieldsToRestart,
ICloudConfig,
IConfig,
ICustomRectangle,
} from './config-handler';
@ -948,7 +950,64 @@ export const getWindowByName = (
});
};
export const updateFeaturesForCloudConfig = async (): Promise<void> => {
export const updateFeaturesForCloudConfig = async (
cloudConfig: ICloudConfig,
): Promise<void> => {
const {
podLevelEntitlements,
acpFeatureLevelEntitlements,
pmpEntitlements,
...rest
} = cloudConfig as ICloudConfig;
if (
podLevelEntitlements &&
podLevelEntitlements.autoLaunchPath &&
podLevelEntitlements.autoLaunchPath.match(/\\\\/g)
) {
podLevelEntitlements.autoLaunchPath = podLevelEntitlements.autoLaunchPath.replace(
/\\+/g,
'\\',
);
}
if (
podLevelEntitlements &&
podLevelEntitlements.userDataPath &&
podLevelEntitlements.userDataPath.match(/\\\\/g)
) {
podLevelEntitlements.userDataPath = podLevelEntitlements.userDataPath.replace(
/\\+/g,
'\\',
);
}
logger.info(
'window-utils: filtered SDA cloudConfig',
config.getMergedConfig(config.cloudConfig as ICloudConfig) as IConfig,
);
logger.info(
'window-utils: filtered SFE cloud config',
config.getMergedConfig({
podLevelEntitlements,
acpFeatureLevelEntitlements,
pmpEntitlements,
}) as IConfig,
);
const updatedCloudConfigFields = config.compareCloudConfig(
config.getMergedConfig(config.cloudConfig as ICloudConfig) as IConfig,
config.getMergedConfig({
podLevelEntitlements,
acpFeatureLevelEntitlements,
pmpEntitlements,
}) as IConfig,
);
logger.info('window-utils: ignored other values from SFE', rest);
await config.updateCloudConfig({
podLevelEntitlements,
acpFeatureLevelEntitlements,
pmpEntitlements,
});
const {
alwaysOnTop: isAlwaysOnTop,
launchOnStartup,
@ -985,6 +1044,28 @@ export const updateFeaturesForCloudConfig = async (): Promise<void> => {
mainWebContents.send('initialize-memory-refresh');
}
}
logger.info(
`window-utils: Updated cloud config fields`,
updatedCloudConfigFields,
);
if (updatedCloudConfigFields && updatedCloudConfigFields.length) {
if (mainWebContents && !mainWebContents.isDestroyed()) {
const shouldRestart = updatedCloudConfigFields.some((field) =>
ConfigFieldsToRestart.has(field),
);
logger.info(
`window-utils: should restart for updated cloud config field?`,
shouldRestart,
);
if (shouldRestart) {
mainWebContents.send('display-client-banner', {
reason: 'cloudConfig',
action: 'restart',
});
}
}
}
};
/**

View File

@ -263,3 +263,5 @@ export type NotificationActionCallback = (
event: NotificationActions,
data: INotificationData,
) => void;
export type ConfigUpdateType = 'restart' | 'reload';

View File

@ -261,3 +261,17 @@ export const formatString = (str: string, data?: object): string => {
export const calculatePercentage = (value: number, percentage: number) => {
return value * percentage * 0.01;
};
/**
* Compares two arrays and returns true if they are equal
* @param a string[]
* @param b string[]
*/
export const arrayEquals = (a: string[], b: string[]) => {
return (
Array.isArray(a) &&
Array.isArray(b) &&
a.length === b.length &&
a.every((val, index) => val === b[index])
);
};

View File

@ -91,6 +91,7 @@ if (ssfWindow.ssf) {
getNativeWindowHandle: ssfWindow.ssf.getNativeWindowHandle,
getCitrixMediaRedirectionStatus:
ssfWindow.ssf.getCitrixMediaRedirectionStatus,
registerClientBanner: ssfWindow.ssf.registerClientBanner,
});
}

View File

@ -10,6 +10,7 @@ import { IDownloadItem } from '../app/download-handler';
import {
apiCmds,
apiName,
ConfigUpdateType,
IBadgeCount,
IBoundsChange,
ICPUUsage,
@ -65,6 +66,9 @@ export interface ILocalObject {
collectLogsCallback?: Array<() => void>;
analyticsEventHandler?: (arg: any) => void;
restartFloater?: (arg: IRestartFloaterData) => void;
showClientBannerCallback?: Array<
(reason: string, action: ConfigUpdateType) => void
>;
}
const local: ILocalObject = {
@ -751,6 +755,21 @@ export class SSFApi {
cmd: apiCmds.getCitrixMediaRedirectionStatus,
});
}
/**
* Allows JS to register a function to display a client banner
* @param callback
*/
public registerClientBanner(
callback: (reason: string, action: ConfigUpdateType) => void,
): void {
if (!local.showClientBannerCallback) {
local.showClientBannerCallback = new Array<() => void>();
}
if (typeof callback === 'function') {
local.showClientBannerCallback.push(callback);
}
}
}
/**
@ -981,6 +1000,18 @@ local.ipcRenderer.on('notification-actions', (_event, args) => {
}
});
/**
* An event triggered by the main process on updating the cloud config
* @param {string[]}
*/
local.ipcRenderer.on('display-client-banner', (_event, args) => {
if (local.showClientBannerCallback) {
for (const callback of local.showClientBannerCallback) {
callback(args.reason, args.action);
}
}
});
// Invoked whenever the app is reloaded/navigated
const sanitize = (): void => {
if (window.name === apiName.mainWindowName) {