diff --git a/config/Symphony.config b/config/Symphony.config index 7cf68cb1..109a35ca 100644 --- a/config/Symphony.config +++ b/config/Symphony.config @@ -1,12 +1,13 @@ { "url":"https://foundation-dev.symphony.com", - "minimizeOnClose" : true, - "launchOnStartup" : true, - "alwaysOnTop" : false, - "bringToFront": false, + "minimizeOnClose" : "ENABLED", + "launchOnStartup" : "ENABLED", + "alwaysOnTop" : "DISABLED", + "bringToFront": "DISABLED", "whitelistUrl": "*", - "isCustomTitleBar": true, - "memoryRefresh": true, + "isCustomTitleBar": "ENABLED", + "memoryRefresh": "ENABLED", + "memoryThreshold": "800", "devToolsEnabled": true, "contextIsolation": true, "ctWhitelist": [], diff --git a/spec/appMenu.spec.ts b/spec/appMenu.spec.ts index d7ac6d1d..7be8354b 100644 --- a/spec/appMenu.spec.ts +++ b/spec/appMenu.spec.ts @@ -39,14 +39,20 @@ jest.mock('../src/app/auto-launch-controller', () => { jest.mock('../src/app/config-handler', () => { return { + CloudConfigDataTypes: { + NOT_SET: 'NOT_SET', + ENABLED: 'ENABLED', + DISABLED: 'DISABLED', + }, config: { getConfigFields: jest.fn(() => { return { - minimizeOnClose: true, - launchOnStartup: true, - alwaysOnTop: true, - isAlwaysOnTop: true, - bringToFront: true, + minimizeOnClose: 'ENABLED', + launchOnStartup: 'ENABLED', + alwaysOnTop: 'ENABLED', + isAlwaysOnTop: 'ENABLED', + bringToFront: 'ENABLED', + devToolsEnabled: true, }; }), getGlobalConfigFields: jest.fn(() => { @@ -54,6 +60,16 @@ jest.mock('../src/app/config-handler', () => { devToolsEnabled: true, }; }), + getFilteredCloudConfigFields: jest.fn(() => { + return { + devToolsEnabled: true, + }; + }), + getCloudConfigFields: jest.fn(() => { + return { + devToolsEnabled: true, + }; + }), updateUserConfig: jest.fn(), }, }; @@ -202,7 +218,7 @@ describe('app menu', () => { it('should disable `AutoLaunch` when click is triggered', async () => { const spyFn = 'disableAutoLaunch'; const spyConfig = jest.spyOn(config, updateUserFnLabel); - const expectedValue = { launchOnStartup: false }; + const expectedValue = { launchOnStartup: 'NOT_SET' }; const spy = jest.spyOn(autoLaunchInstance, spyFn); const customItem = { checked: false, @@ -215,7 +231,7 @@ describe('app menu', () => { it('should enable `AutoLaunch` when click is triggered', async () => { const spyFn = 'enableAutoLaunch'; const spyConfig = jest.spyOn(config, updateUserFnLabel); - const expectedValue = { launchOnStartup: true }; + const expectedValue = { launchOnStartup: 'ENABLED' }; const spy = jest.spyOn(autoLaunchInstance, spyFn); await autoLaunchMenuItem.click(item); expect(spy).toBeCalled(); @@ -231,7 +247,7 @@ describe('app menu', () => { it('should update `minimizeOnClose` value when click is triggered', async () => { const spyConfig = jest.spyOn(config, updateUserFnLabel); - const expectedValue = { minimizeOnClose: true }; + const expectedValue = { minimizeOnClose: 'ENABLED' }; const menuItem = findMenuItemBuildWindowMenu('Minimize on Close'); await menuItem.click(item); expect(spyConfig).lastCalledWith(expectedValue); @@ -240,7 +256,7 @@ describe('app menu', () => { describe('`bringToFront`', () => { it('should update `bringToFront` value when click is triggered', async () => { const spyConfig = jest.spyOn(config, updateUserFnLabel); - const expectedValue = { bringToFront: true }; + const expectedValue = { bringToFront: 'ENABLED' }; const menuItem = findMenuItemBuildWindowMenu('Bring to Front on Notifications'); await menuItem.click(item); expect(spyConfig).lastCalledWith(expectedValue); diff --git a/spec/chromeFlags.spec.ts b/spec/chromeFlags.spec.ts index da2ec1ca..9474be1a 100644 --- a/spec/chromeFlags.spec.ts +++ b/spec/chromeFlags.spec.ts @@ -6,7 +6,7 @@ import { app } from './__mocks__/electron'; jest.mock('../src/common/utils', () => { return { config: { - getGlobalConfigFields: jest.fn(() => { + getCloudConfigField: jest.fn(() => { return { customFlags: { authServerWhitelist: 'url', @@ -35,7 +35,7 @@ describe('chrome flags', () => { (isMac as any) = true; (isWindowsOS as any) = false; (isLinux as any) = false; - config.getGlobalConfigFields = jest.fn(() => { + config.getConfigFields = jest.fn(() => { return { customFlags: { authServerWhitelist: 'url', @@ -59,7 +59,7 @@ describe('chrome flags', () => { }); it('should call `setChromeFlags` correctly when `disableGpu` is false', () => { - config.getGlobalConfigFields = jest.fn(() => { + config.getConfigFields = jest.fn(() => { return { customFlags: { authServerWhitelist: 'url', diff --git a/spec/config.spec.ts b/spec/config.spec.ts index 0b378ca4..c79d3c57 100644 --- a/spec/config.spec.ts +++ b/spec/config.spec.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { IConfig } from '../src/app/config-handler'; +import { IConfig, IGlobalConfig } from '../src/app/config-handler'; jest.mock('electron-log'); @@ -75,7 +75,7 @@ describe('config', () => { configInstance.readUserConfig(); configInstance.readGlobalConfig(); - const configField: IConfig = configInstance.getConfigFields(fieldMock); + const configField: IGlobalConfig = configInstance.getGlobalConfigFields(fieldMock); expect(configField.url).toBe('something'); }); diff --git a/spec/mainApiHandler.spec.ts b/spec/mainApiHandler.spec.ts index f21bc968..c119a651 100644 --- a/spec/mainApiHandler.spec.ts +++ b/spec/mainApiHandler.spec.ts @@ -71,10 +71,15 @@ jest.mock('../src/common/logger', () => { jest.mock('../src/app/config-handler', () => { return { + CloudConfigDataTypes: { + NOT_SET: 'NOT_SET', + ENABLED: 'ENABLED', + DISABLED: 'DISABLED', + }, config: { getConfigFields: jest.fn(() => { return { - bringToFront: true, + bringToFront: 'ENABLED', }; }), }, diff --git a/src/app/app-menu.ts b/src/app/app-menu.ts index 90272d5d..ed2e3468 100644 --- a/src/app/app-menu.ts +++ b/src/app/app-menu.ts @@ -10,7 +10,7 @@ import { MenuActionTypes, } from './analytics-handler'; import { autoLaunchInstance as autoLaunch } from './auto-launch-controller'; -import { config, IConfig } from './config-handler'; +import { CloudConfigDataTypes, config, IConfig } from './config-handler'; import { titleBarChangeDialog } from './dialog-handler'; import { exportCrashDumps, exportLogs } from './reports-handler'; import { updateAlwaysOnTop } from './window-actions'; @@ -25,11 +25,6 @@ export const menuSections = { help: 'help', // tslint:disable-line }; -enum TitleBarStyles { - CUSTOM, - NATIVE, -} - const windowsAccelerator = Object.assign({ close: 'Ctrl+W', copy: 'Ctrl+C', @@ -52,12 +47,16 @@ let { alwaysOnTop: isAlwaysOnTop, bringToFront, memoryRefresh, + isCustomTitleBar, + devToolsEnabled, } = config.getConfigFields([ 'minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'bringToFront', 'memoryRefresh', + 'isCustomTitleBar', + 'devToolsEnabled', ]) as IConfig; let initialAnalyticsSent = false; @@ -66,29 +65,28 @@ const menuItemsArray = Object.keys(menuSections) .filter((value) => isMac ? true : value !== menuSections.about); -const { devToolsEnabled } = config.getGlobalConfigFields([ 'devToolsEnabled' ]); - export class AppMenu { private menu: Electron.Menu | undefined; private menuList: Electron.MenuItemConstructorOptions[]; private locale: LocaleType; - private titleBarStyle: TitleBarStyles; + private cloudConfig: IConfig | {}; + + private readonly menuItemConfigFields: string[]; constructor() { this.menuList = []; this.locale = i18n.getLocale(); - this.titleBarStyle = config.getConfigFields([ 'isCustomTitleBar' ]).isCustomTitleBar - ? TitleBarStyles.CUSTOM - : TitleBarStyles.NATIVE; + this.menuItemConfigFields = [ 'minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'bringToFront', 'memoryRefresh', 'isCustomTitleBar', 'devToolsEnabled' ]; + this.cloudConfig = config.getFilteredCloudConfigFields(this.menuItemConfigFields); this.buildMenu(); // send initial analytic if (!initialAnalyticsSent) { - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.MINIMIZE_ON_CLOSE, minimizeOnClose); - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.AUTO_LAUNCH_ON_START_UP, launchOnStartup); - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.ALWAYS_ON_TOP, isAlwaysOnTop); - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR, bringToFront); - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.REFRESH_APP_IN_IDLE, memoryRefresh); - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.HAMBURGER_MENU, (isMac || isLinux) ? false : this.titleBarStyle === TitleBarStyles.CUSTOM); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.MINIMIZE_ON_CLOSE, minimizeOnClose === CloudConfigDataTypes.ENABLED); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.AUTO_LAUNCH_ON_START_UP, launchOnStartup === CloudConfigDataTypes.ENABLED); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.ALWAYS_ON_TOP, isAlwaysOnTop === CloudConfigDataTypes.ENABLED); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR, bringToFront === CloudConfigDataTypes.ENABLED); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.REFRESH_APP_IN_IDLE, memoryRefresh === CloudConfigDataTypes.ENABLED); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.HAMBURGER_MENU, (isMac || isLinux) ? false : isCustomTitleBar === CloudConfigDataTypes.ENABLED); } initialAnalyticsSent = true; } @@ -97,6 +95,9 @@ export class AppMenu { * Builds the menu items for all the menu */ public buildMenu(): void { + // updates the global variables + this.updateGlobals(); + this.menuList = menuItemsArray.reduce((map: Electron.MenuItemConstructorOptions, key: string) => { map[ key ] = this.buildMenuKey(key); return map; @@ -133,6 +134,23 @@ export class AppMenu { } } + /** + * Updates the global variables + */ + public updateGlobals(): void { + const configData = config.getConfigFields(this.menuItemConfigFields) as IConfig; + minimizeOnClose = configData.minimizeOnClose; + launchOnStartup = configData.launchOnStartup; + isAlwaysOnTop = configData.alwaysOnTop; + bringToFront = configData.bringToFront; + memoryRefresh = configData.memoryRefresh; + isCustomTitleBar = configData.isCustomTitleBar; + devToolsEnabled = configData.devToolsEnabled; + + // fetch updated cloud config + this.cloudConfig = config.getFilteredCloudConfigFields(this.menuItemConfigFields); + } + /** * Displays popup application at the x,y coordinates * @@ -257,51 +275,63 @@ export class AppMenu { private buildWindowMenu(): Electron.MenuItemConstructorOptions { logger.info(`app-menu: building window menu`); + const { + alwaysOnTop: isAlwaysOnTopCC, + minimizeOnClose: minimizeOnCloseCC, + launchOnStartup: launchOnStartupCC, + bringToFront: bringToFrontCC, + memoryRefresh: memoryRefreshCC, + isCustomTitleBar: isCustomTitleBarCC, + } = this.cloudConfig as IConfig; + const submenu: MenuItemConstructorOptions[] = [ this.assignRoleOrLabel({ role: 'minimize', label: i18n.t('Minimize')() }), this.assignRoleOrLabel({ role: 'close', label: i18n.t('Close')() }), this.buildSeparator(), { - checked: launchOnStartup, + checked: launchOnStartup === CloudConfigDataTypes.ENABLED, click: async (item) => { if (item.checked) { autoLaunch.enableAutoLaunch(); } else { autoLaunch.disableAutoLaunch(); } - launchOnStartup = item.checked; + launchOnStartup = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET; await config.updateUserConfig({ launchOnStartup }); this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.AUTO_LAUNCH_ON_START_UP, item.checked); }, label: i18n.t('Auto Launch On Startup')(), type: 'checkbox', visible: !isLinux, + enabled: !launchOnStartupCC || launchOnStartupCC === CloudConfigDataTypes.NOT_SET, }, { - checked: isAlwaysOnTop, + checked: isAlwaysOnTop === CloudConfigDataTypes.ENABLED, click: async (item) => { - isAlwaysOnTop = item.checked; + isAlwaysOnTop = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET; await updateAlwaysOnTop(item.checked, true); this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.ALWAYS_ON_TOP, item.checked); }, label: i18n.t('Always on Top')(), type: 'checkbox', visible: !isLinux, + enabled: !isAlwaysOnTopCC || isAlwaysOnTopCC === CloudConfigDataTypes.NOT_SET, }, { - checked: minimizeOnClose, + checked: minimizeOnClose === CloudConfigDataTypes.ENABLED, click: async (item) => { - minimizeOnClose = item.checked; + minimizeOnClose = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET; await config.updateUserConfig({ minimizeOnClose }); this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.MINIMIZE_ON_CLOSE, item.checked); }, label: i18n.t('Minimize on Close')(), type: 'checkbox', + enabled: !minimizeOnCloseCC || minimizeOnCloseCC === CloudConfigDataTypes.NOT_SET, }, { - checked: bringToFront, + checked: bringToFront === CloudConfigDataTypes.ENABLED, click: async (item) => { - bringToFront = item.checked; + bringToFront = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET; await config.updateUserConfig({ bringToFront }); this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.FLASH_NOTIFICATION_IN_TASK_BAR, item.checked); }, @@ -309,30 +339,30 @@ export class AppMenu { ? i18n.t('Flash Notification in Taskbar')() : i18n.t('Bring to Front on Notifications')(), type: 'checkbox', + enabled: !bringToFrontCC || bringToFrontCC === CloudConfigDataTypes.NOT_SET, }, this.buildSeparator(), { - label: this.titleBarStyle === TitleBarStyles.NATIVE + label: (isCustomTitleBar === CloudConfigDataTypes.DISABLED || isCustomTitleBar === CloudConfigDataTypes.NOT_SET) ? i18n.t('Enable Hamburger menu')() : i18n.t('Disable Hamburger menu')(), visible: isWindowsOS, click: () => { - const isNativeStyle = this.titleBarStyle === TitleBarStyles.NATIVE; - - this.titleBarStyle = isNativeStyle ? TitleBarStyles.NATIVE : TitleBarStyles.CUSTOM; - titleBarChangeDialog(isNativeStyle); - this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.HAMBURGER_MENU, this.titleBarStyle === TitleBarStyles.CUSTOM); + titleBarChangeDialog(isCustomTitleBar === CloudConfigDataTypes.DISABLED ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.DISABLED); + this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.HAMBURGER_MENU, isCustomTitleBar === CloudConfigDataTypes.ENABLED); }, + enabled: !isCustomTitleBarCC || isCustomTitleBarCC === CloudConfigDataTypes.NOT_SET, }, { - checked: memoryRefresh, + checked: memoryRefresh === CloudConfigDataTypes.ENABLED, click: async (item) => { - memoryRefresh = item.checked; + memoryRefresh = item.checked ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET; await config.updateUserConfig({ memoryRefresh }); this.sendAnalytics(AnalyticsElements.MENU, MenuActionTypes.REFRESH_APP_IN_IDLE, item.checked); }, label: i18n.t('Refresh app when idle')(), type: 'checkbox', + enabled: !memoryRefreshCC || memoryRefreshCC === CloudConfigDataTypes.NOT_SET, }, { click: (_item, focusedWindow) => { @@ -387,6 +417,7 @@ export class AppMenu { if (isLinux) { showCrashesLabel = i18n.t('Show crash dump in File Manager')(); } + const { devToolsEnabled: isDevToolsEnabledCC } = this.cloudConfig as IConfig; return { label: i18n.t('Help')(), @@ -409,7 +440,7 @@ export class AppMenu { }, { label: i18n.t('Toggle Developer Tools')(), accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I', - visible: devToolsEnabled, + visible: isDevToolsEnabledCC, click(_item, focusedWindow) { if (!focusedWindow || !windowExists(focusedWindow)) { return; diff --git a/src/app/auto-launch-controller.ts b/src/app/auto-launch-controller.ts index 5b087fec..abc6870d 100644 --- a/src/app/auto-launch-controller.ts +++ b/src/app/auto-launch-controller.ts @@ -4,7 +4,7 @@ import { isMac } from '../common/env'; import { logger } from '../common/logger'; import { config, IConfig } from './config-handler'; -const { autoLaunchPath }: IConfig = config.getGlobalConfigFields([ 'autoLaunchPath' ]); +const { autoLaunchPath }: IConfig = config.getConfigFields([ 'autoLaunchPath' ]); const props = isMac ? { mac: { diff --git a/src/app/child-window-handler.ts b/src/app/child-window-handler.ts index 93562f38..2221bb06 100644 --- a/src/app/child-window-handler.ts +++ b/src/app/child-window-handler.ts @@ -215,7 +215,7 @@ export const handleChildWindow = (webContents: WebContents): void => { } // Updates media permissions for preload context - const { permissions } = config.getGlobalConfigFields([ 'permissions' ]); + const { permissions } = config.getConfigFields([ 'permissions' ]); browserWin.webContents.send('is-screen-share-enabled', permissions.media); } }); diff --git a/src/app/chrome-flags.ts b/src/app/chrome-flags.ts index 969f52f1..123680ac 100644 --- a/src/app/chrome-flags.ts +++ b/src/app/chrome-flags.ts @@ -15,7 +15,7 @@ const specialArgs = [ '--url', '--multiInstance', '--userDataPath=', 'symphony:/ */ export const setChromeFlags = () => { logger.info(`chrome-flags: Checking if we need to set chrome flags!`); - const { customFlags } = config.getGlobalConfigFields([ 'customFlags' ]) as IConfig; + const { customFlags } = config.getConfigFields([ 'customFlags' ]) as IConfig; const configFlags: object = { 'auth-negotiate-delegate-whitelist': customFlags.authServerWhitelist, @@ -72,7 +72,7 @@ export const setChromeFlags = () => { */ export const setSessionProperties = () => { logger.info(`chrome-flags: Settings session properties`); - const { customFlags } = config.getGlobalConfigFields([ 'customFlags' ]) as IConfig; + const { customFlags } = config.getConfigFields([ 'customFlags' ]) as IConfig; if (session.defaultSession && customFlags && customFlags.authServerWhitelist && customFlags.authServerWhitelist !== '') { session.defaultSession.allowNTLMCredentialsForDomains(customFlags.authServerWhitelist); diff --git a/src/app/config-handler.ts b/src/app/config-handler.ts index b6b52148..887c0117 100644 --- a/src/app/config-handler.ts +++ b/src/app/config-handler.ts @@ -6,33 +6,81 @@ import * as util from 'util'; import { buildNumber } from '../../package.json'; import { isDevEnv, isElectronQA, isLinux, isMac } from '../common/env'; import { logger } from '../common/logger'; -import { pick } from '../common/utils'; +import { filterOutSelectedValues, pick } from '../common/utils'; +import { getDefaultUserConfig } from './config-utils'; const writeFile = util.promisify(fs.writeFile); -export interface IConfig { +export enum CloudConfigDataTypes { + NOT_SET = 'NOT_SET', + ENABLED = 'ENABLED', + DISABLED = 'DISABLED', +} + +export interface IGlobalConfig { url: string; - minimizeOnClose: boolean; - launchOnStartup: boolean; - alwaysOnTop: boolean; - bringToFront: boolean; - whitelistUrl: string; - isCustomTitleBar: boolean; - memoryRefresh: boolean; - devToolsEnabled: boolean; contextIsolation: boolean; +} + +export interface IConfig { + minimizeOnClose: CloudConfigDataTypes; + launchOnStartup: CloudConfigDataTypes; + alwaysOnTop: CloudConfigDataTypes; + bringToFront: CloudConfigDataTypes; + whitelistUrl: string; + isCustomTitleBar: CloudConfigDataTypes; + memoryRefresh: CloudConfigDataTypes; + memoryThreshold: string; + devToolsEnabled: boolean; ctWhitelist: string[]; podWhitelist: string[]; - configVersion: string; - buildNumber: string; autoLaunchPath: string; - notificationSettings: INotificationSetting; permissions: IPermission; customFlags: ICustomFlag; + buildNumber?: string; + configVersion?: string; + notificationSettings: INotificationSetting; mainWinPos?: ICustomRectangle; locale?: string; } +export interface ICloudConfig { + configVersion?: string; + podLevelEntitlements: IPodLevelEntitlements; + acpFeatureLevelEntitlements: IACPFeatureLevelEntitlements; + pmpEntitlements: IPMPEntitlements; +} + +export interface IPodLevelEntitlements { + minimizeOnClose: CloudConfigDataTypes; + isCustomTitleBar: CloudConfigDataTypes; + alwaysOnTop: CloudConfigDataTypes; + memoryRefresh: CloudConfigDataTypes; + bringToFront: CloudConfigDataTypes; + disableThrottling: CloudConfigDataTypes; + launchOnStartup: CloudConfigDataTypes; + memoryThreshold: string; + ctWhitelist: string; + podWhitelist: string; + authNegotiateDelegateWhitelist: string; + whitelistUrl: string; + authServerWhitelist: string; + autoLaunchPath: string; +} + +export interface IACPFeatureLevelEntitlements { + devToolsEnabled: boolean; + permissions: IPermission; +} + +export interface IPMPEntitlements { + minimizeOnClose: CloudConfigDataTypes; + bringToFront: CloudConfigDataTypes; + memoryRefresh: CloudConfigDataTypes; + refreshAppThreshold: CloudConfigDataTypes; + disableThrottling: CloudConfigDataTypes; +} + export interface IPermission { media: boolean; geolocation: boolean; @@ -63,15 +111,19 @@ export interface ICustomRectangle extends Partial { class Config { private userConfig: IConfig | {}; private globalConfig: IConfig | {}; + private cloudConfig: ICloudConfig | {}; + private filteredCloudConfig: ICloudConfig | {}; private isFirstTime: boolean = true; private readonly configFileName: string; private readonly userConfigPath: string; private readonly appPath: string; private readonly globalConfigPath: string; + private readonly cloudConfigPath: string; constructor() { this.configFileName = 'Symphony.config'; this.userConfigPath = path.join(app.getPath('userData'), this.configFileName); + this.cloudConfigPath = path.join(app.getPath('userData'), 'cloudConfig.config'); this.appPath = isDevEnv ? app.getAppPath() : path.dirname(app.getPath('exe')); this.globalConfigPath = isDevEnv ? path.join(this.appPath, path.join('config', this.configFileName)) @@ -83,8 +135,11 @@ class Config { this.globalConfig = {}; this.userConfig = {}; + this.cloudConfig = {}; + this.filteredCloudConfig = {}; this.readUserConfig(); this.readGlobalConfig(); + this.readCloudConfig(); this.checkFirstTimeLaunch(); } @@ -96,8 +151,8 @@ class Config { * @param fields */ public getConfigFields(fields: string[]): IConfig { - logger.info(`config-handler: Trying to get config values for the fields`, fields); - return { ...this.getGlobalConfigFields(fields), ...this.getUserConfigFields(fields) } as IConfig; + logger.info(`config-handler: Trying to get cloud config values for the fields`, fields); + return { ...this.getGlobalConfigFields(fields), ...this.getUserConfigFields(fields), ...this.getFilteredCloudConfigFields(fields) } as IConfig; } /** @@ -115,9 +170,28 @@ class Config { * * @param fields {Array} */ - public getGlobalConfigFields(fields: string[]): IConfig { + public getGlobalConfigFields(fields: string[]): IGlobalConfig { logger.info(`config-handler: Trying to get global config values for the fields`, fields); - return pick(this.globalConfig, fields) as IConfig; + return pick(this.globalConfig, fields) as IGlobalConfig; + } + + /** + * Returns filtered & prioritised fields from cloud config file + * + * @param fields {Array} + */ + public getFilteredCloudConfigFields(fields: string[]): IConfig | {} { + return pick(this.filteredCloudConfig, fields) as IConfig; + } + + /** + * Returns the actual cloud config with priority + * @param fields + */ + public getCloudConfigFields(fields: string[]): IConfig { + const { acpFeatureLevelEntitlements, podLevelEntitlements, pmpEntitlements } = this.cloudConfig as ICloudConfig; + const cloudConfig = { ...acpFeatureLevelEntitlements, ...podLevelEntitlements, ...pmpEntitlements }; + return pick(cloudConfig, fields) as IConfig; } /** @@ -137,6 +211,25 @@ class Config { } } + /** + * updates new data to the cloud config + * + * @param data {IConfig} + */ + public async updateCloudConfig(data: Partial): Promise { + logger.info(`config-handler: Updating the cloud config data from SFE: `, data); + this.cloudConfig = { ...this.cloudConfig, ...data }; + // recalculate cloud config when we have data from SFE + this.filterCloudConfig(); + logger.info(`config-handler: prioritized and filtered cloud config: `, this.filteredCloudConfig); + try { + await writeFile(this.cloudConfigPath, JSON.stringify(this.cloudConfig), { encoding: 'utf8' }); + logger.info(`config-handler: writting cloud config values to file`); + } catch (error) { + logger.error(`config-handler: failed to update cloud config file with ${data}`, error); + } + } + /** * Return true if the app is launched for the first time * otherwise false @@ -157,7 +250,6 @@ class Config { minimizeOnClose, launchOnStartup, alwaysOnTop, - url, memoryRefresh, bringToFront, isCustomTitleBar, @@ -169,6 +261,21 @@ class Config { } } + /** + * filters out the cloud config + */ + private filterCloudConfig(): void { + const { acpFeatureLevelEntitlements, podLevelEntitlements, pmpEntitlements } = this.cloudConfig as ICloudConfig; + + // Filter out some values + const filteredACP = filterOutSelectedValues(acpFeatureLevelEntitlements, [ true, 'NOT_SET' ]); + const filteredPod = filterOutSelectedValues(podLevelEntitlements, [ true, 'NOT_SET' ]); + const filteredPMP = filterOutSelectedValues(pmpEntitlements, [ true, 'NOT_SET' ]); + + // priority is PMP > ACP > SDA + this.filteredCloudConfig = { ...filteredACP, ...filteredPod, ...filteredPMP }; + } + /** * Parses the config data string * @@ -201,7 +308,7 @@ class Config { // Need to wait until app ready event to access user data await app.whenReady(); logger.info(`config-handler: user config doesn't exist! will create new one and update config`); - await this.updateUserConfig({ configVersion: app.getVersion().toString(), buildNumber } as IConfig); + await this.updateUserConfig({ configVersion: app.getVersion().toString(), buildNumber, ...getDefaultUserConfig() } as IConfig); } this.userConfig = this.parseConfigData(fs.readFileSync(this.userConfigPath, 'utf8')); logger.info(`config-handler: User configuration: `, this.userConfig); @@ -215,6 +322,23 @@ class Config { logger.info(`config-handler: Global configuration: `, this.globalConfig); } + /** + * Reads and stores the cloud config file + * + * If cloud config doesn't exits? + * this creates a new one with { } + */ + private async readCloudConfig() { + if (!fs.existsSync(this.cloudConfigPath)) { + await app.whenReady(); + await this.updateCloudConfig({ configVersion: app.getVersion().toString() }); + } + this.cloudConfig = this.parseConfigData(fs.readFileSync(this.cloudConfigPath, 'utf8')); + // recalculate cloud config when we the application starts + this.filterCloudConfig(); + logger.info(`config-handler: Cloud configuration: `, this.userConfig); + } + /** * Verifies if the application is launched for the first time */ diff --git a/src/app/config-utils.ts b/src/app/config-utils.ts new file mode 100644 index 00000000..ab63996f --- /dev/null +++ b/src/app/config-utils.ts @@ -0,0 +1,40 @@ +/** + * Returns the default user config data + */ +import { CloudConfigDataTypes, IConfig } from './config-handler'; + +export const getDefaultUserConfig = (): IConfig => { + return { + minimizeOnClose: CloudConfigDataTypes.ENABLED, + launchOnStartup: CloudConfigDataTypes.ENABLED, + alwaysOnTop: CloudConfigDataTypes.DISABLED, + bringToFront: CloudConfigDataTypes.DISABLED, + whitelistUrl: '*', + isCustomTitleBar: CloudConfigDataTypes.ENABLED, + memoryRefresh: CloudConfigDataTypes.ENABLED, + memoryThreshold: '800', + devToolsEnabled: true, + ctWhitelist: [], + podWhitelist: [], + notificationSettings: { + position: 'upper-right', + display: '', + }, + customFlags: { + authServerWhitelist: '', + authNegotiateDelegateWhitelist: '', + disableGpu: false, + disableThrottling: false, + }, + permissions: { + media: true, + geolocation: true, + notifications: true, + midiSysex: true, + pointerLock: true, + fullscreen: true, + openExternal: true, + }, + autoLaunchPath: '', + }; +}; diff --git a/src/app/dialog-handler.ts b/src/app/dialog-handler.ts index dfc00db2..c6bf16b3 100644 --- a/src/app/dialog-handler.ts +++ b/src/app/dialog-handler.ts @@ -3,7 +3,7 @@ import { app } from 'electron'; import { i18n } from '../common/i18n'; import { logger } from '../common/logger'; -import { config } from './config-handler'; +import { CloudConfigDataTypes, config } from './config-handler'; import { ICustomBrowserWindow, windowHandler } from './window-handler'; import { windowExists } from './window-utils'; @@ -146,7 +146,7 @@ export const showNetworkConnectivityError = (browserWindow: Electron.BrowserWind * * @param isNativeStyle {boolean} */ -export const titleBarChangeDialog = async (isNativeStyle: boolean) => { +export const titleBarChangeDialog = async (isNativeStyle: CloudConfigDataTypes) => { const focusedWindow = electron.BrowserWindow.getFocusedWindow(); if (!focusedWindow || !windowExists(focusedWindow)) { return; @@ -161,7 +161,8 @@ export const titleBarChangeDialog = async (isNativeStyle: boolean) => { }; const { response } = await electron.dialog.showMessageBox(focusedWindow, options); if (response === 0) { - await config.updateUserConfig({ isCustomTitleBar: isNativeStyle }); + logger.error(`test`, isNativeStyle); + await config.updateUserConfig({ isCustomTitleBar: isNativeStyle }); app.relaunch(); app.exit(); } diff --git a/src/app/main-api-handler.ts b/src/app/main-api-handler.ts index 58ec1818..2e841628 100644 --- a/src/app/main-api-handler.ts +++ b/src/app/main-api-handler.ts @@ -5,7 +5,7 @@ import { LocaleType } from '../common/i18n'; import { logger } from '../common/logger'; import { activityDetection } from './activity-detection'; import { analytics } from './analytics-handler'; -import { config } from './config-handler'; +import { CloudConfigDataTypes, config, ICloudConfig } from './config-handler'; import { memoryMonitor } from './memory-monitor'; import { protocolHandler } from './protocol-handler'; import { finalizeLogExports, registerLogRetriever } from './reports-handler'; @@ -19,6 +19,7 @@ import { setDataUrl, showBadgeCount, showPopupMenu, + updateFeaturesForCloudConfig, updateLocale, windowExists, } from './window-utils'; @@ -27,7 +28,7 @@ import { * Handle API related ipc messages from renderers. Only messages from windows * we have created are allowed. */ -ipcMain.on(apiName.symphonyApi, (event: Electron.IpcMainEvent, arg: IApiArgs) => { +ipcMain.on(apiName.symphonyApi, async (event: Electron.IpcMainEvent, arg: IApiArgs) => { if (!isValidWindow(BrowserWindow.fromWebContents(event.sender))) { logger.error(`main-api-handler: invalid window try to perform action, ignoring action`, arg.cmd); return; @@ -99,7 +100,7 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.IpcMainEvent, arg: IApiArgs) => // validates the user bring to front config and activates the wrapper if (typeof arg.reason === 'string' && arg.reason === 'notification') { const { bringToFront } = config.getConfigFields([ 'bringToFront' ]); - if (bringToFront) { + if (bringToFront === CloudConfigDataTypes.ENABLED) { activate(arg.windowName, false); } } @@ -168,6 +169,14 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.IpcMainEvent, arg: IApiArgs) => case apiCmds.registerAnalyticsHandler: analytics.registerPreloadWindow(event.sender); break; + case apiCmds.setCloudConfig: + const { podLevelEntitlements, acpFeatureLevelEntitlements, pmpEntitlements, ...rest } = arg.cloudConfig as ICloudConfig; + logger.info('main-api-handler: ignored other values from SFE', rest); + await config.updateCloudConfig({ podLevelEntitlements, acpFeatureLevelEntitlements, pmpEntitlements }); + await updateFeaturesForCloudConfig(); + if (windowHandler.appMenu) { + windowHandler.appMenu.buildMenu(); + } default: } diff --git a/src/app/memory-monitor.ts b/src/app/memory-monitor.ts index f6c72a82..b579abf0 100644 --- a/src/app/memory-monitor.ts +++ b/src/app/memory-monitor.ts @@ -2,7 +2,7 @@ import * as electron from 'electron'; import { isMac } from '../common/env'; import { logger } from '../common/logger'; -import { config } from './config-handler'; +import { CloudConfigDataTypes, config } from './config-handler'; import { windowHandler } from './window-handler'; import { windowExists } from './window-utils'; @@ -11,9 +11,9 @@ class MemoryMonitor { private isInMeeting: boolean; private canReload: boolean; private lastReloadTime?: number; + private memoryThreshold: number; private readonly maxIdleTime: number; - private readonly memoryThreshold: number; private readonly memoryRefreshThreshold: number; constructor() { @@ -46,13 +46,22 @@ class MemoryMonitor { logger.info(`memory-monitor: setting meeting status to ${isInMeeting}`); } + /** + * Sets the memory threshold + * + * @param memoryThreshold + */ + public setMemoryThreshold(memoryThreshold: number): void { + this.memoryThreshold = memoryThreshold * 1024; + } + /** * Validates the predefined conditions and refreshes the client */ private validateMemory(): void { logger.info(`memory-monitor: validating memory refresh conditions`); const { memoryRefresh } = config.getConfigFields([ 'memoryRefresh' ]); - if (!memoryRefresh) { + if (memoryRefresh !== CloudConfigDataTypes.ENABLED) { logger.info(`memory-monitor: memory reload is disabled in the config, not going to refresh!`); return; } diff --git a/src/app/perf-handler.ts b/src/app/perf-handler.ts index 8c02fbfc..784ff7a6 100644 --- a/src/app/perf-handler.ts +++ b/src/app/perf-handler.ts @@ -3,9 +3,9 @@ import { logger } from '../common/logger'; import { config, IConfig } from './config-handler'; export const handlePerformanceSettings = () => { - const { customFlags } = config.getGlobalConfigFields([ 'customFlags' ]) as IConfig; + const { customFlags } = config.getCloudConfigFields([ 'customFlags' ]) as IConfig; - if (customFlags.disableThrottling) { + if (customFlags && customFlags.disableThrottling) { logger.info(`perf-handler: Disabling power throttling!`); powerSaveBlocker.start('prevent-display-sleep'); return; diff --git a/src/app/screen-snippet-handler.ts b/src/app/screen-snippet-handler.ts index 68aa96e6..af18728e 100644 --- a/src/app/screen-snippet-handler.ts +++ b/src/app/screen-snippet-handler.ts @@ -47,7 +47,7 @@ class ScreenSnippet { if (mainWindow && windowExists(mainWindow) && isWindowsOS) { this.shouldUpdateAlwaysOnTop = mainWindow.isAlwaysOnTop(); if (this.shouldUpdateAlwaysOnTop) { - await updateAlwaysOnTop(false, false); + await updateAlwaysOnTop(false, false, false); } } logger.info(`screen-snippet-handler: Starting screen capture!`); @@ -179,7 +179,7 @@ class ScreenSnippet { */ private async verifyAndUpdateAlwaysOnTop(): Promise { if (this.shouldUpdateAlwaysOnTop) { - await updateAlwaysOnTop(true, false); + await updateAlwaysOnTop(true, false, false); this.shouldUpdateAlwaysOnTop = false; } } diff --git a/src/app/version-handler.ts b/src/app/version-handler.ts index 98366c70..e3466bf4 100644 --- a/src/app/version-handler.ts +++ b/src/app/version-handler.ts @@ -2,7 +2,7 @@ import { net } from 'electron'; import * as nodeURL from 'url'; import { buildNumber, clientVersion, optionalDependencies, searchAPIVersion, sfeVersion, version } from '../../package.json'; import { logger } from '../common/logger'; -import { config, IConfig } from './config-handler'; +import { config, IGlobalConfig } from './config-handler'; interface IVersionInfo { clientVersion: string; @@ -69,7 +69,7 @@ class VersionHandler { this.mainUrl = mainUrl; } - const { url: podUrl }: IConfig = config.getGlobalConfigFields(['url']); + const { url: podUrl }: IGlobalConfig = config.getGlobalConfigFields(['url']); if (!this.mainUrl || !nodeURL.parse(this.mainUrl)) { this.mainUrl = podUrl; diff --git a/src/app/window-actions.ts b/src/app/window-actions.ts index 5e9b7abf..6a0c906f 100644 --- a/src/app/window-actions.ts +++ b/src/app/window-actions.ts @@ -6,7 +6,7 @@ import { i18n } from '../common/i18n'; import { logger } from '../common/logger'; import { throttle } from '../common/utils'; import { notification } from '../renderer/notification'; -import { config } from './config-handler'; +import { CloudConfigDataTypes, config } from './config-handler'; import { ICustomBrowserWindow, windowHandler } from './window-handler'; import { showPopupMenu, windowExists } from './window-utils'; @@ -146,14 +146,18 @@ export const activate = (windowName: string, shouldFocus: boolean = true): void * * @param shouldSetAlwaysOnTop {boolean} - Whether to enable always on top or not * @param shouldActivateMainWindow {boolean} - Whether to active main window + * @param shouldUpdateUserConfig {boolean} - whether to update config file */ export const updateAlwaysOnTop = async ( shouldSetAlwaysOnTop: boolean, shouldActivateMainWindow: boolean = true, + shouldUpdateUserConfig: boolean = true, ): Promise => { logger.info(`window-actions: Should we set always on top? ${shouldSetAlwaysOnTop}!`); const browserWins: ICustomBrowserWindow[] = BrowserWindow.getAllWindows() as ICustomBrowserWindow[]; - await config.updateUserConfig({ alwaysOnTop: shouldSetAlwaysOnTop }); + if (shouldUpdateUserConfig) { + await config.updateUserConfig({ alwaysOnTop: shouldSetAlwaysOnTop ? CloudConfigDataTypes.ENABLED : CloudConfigDataTypes.NOT_SET }); + } if (browserWins.length > 0) { browserWins .filter((browser) => typeof browser.notificationData !== 'object') @@ -310,7 +314,7 @@ export const handlePermissionRequests = (webContents: Electron.webContents): voi } const { session } = webContents; - const { permissions } = config.getGlobalConfigFields([ 'permissions' ]); + const { permissions } = config.getConfigFields([ 'permissions' ]); if (!permissions) { logger.error('permissions configuration is invalid, so, everything will be true by default!'); return; diff --git a/src/app/window-handler.ts b/src/app/window-handler.ts index 32feebe9..8fed101a 100644 --- a/src/app/window-handler.ts +++ b/src/app/window-handler.ts @@ -1,10 +1,10 @@ +import { ChildProcess, ExecException, execFile } from 'child_process'; import * as electron from 'electron'; -import { app, BrowserWindow, BrowserWindowConstructorOptions, crashReporter, globalShortcut, ipcMain } from 'electron'; +import { app, BrowserWindow, BrowserWindowConstructorOptions, crashReporter, DesktopCapturerSource, globalShortcut, ipcMain } from 'electron'; import * as fs from 'fs'; import * as path from 'path'; import { format, parse } from 'url'; -import { ChildProcess, ExecException, execFile } from 'child_process'; import { apiName, WindowTypes } from '../common/api-interface'; import { isDevEnv, isMac, isWindowsOS } from '../common/env'; import { i18n, LocaleType } from '../common/i18n'; @@ -13,10 +13,9 @@ import { getCommandLineArgs, getGuid } from '../common/utils'; import { notification } from '../renderer/notification'; import { AppMenu } from './app-menu'; import { handleChildWindow } from './child-window-handler'; -import { config, IConfig } from './config-handler'; +import { CloudConfigDataTypes, config, IConfig, IGlobalConfig } from './config-handler'; import { SpellChecker } from './spell-check-handler'; import { checkIfBuildExpired } from './ttl-handler'; -import DesktopCapturerSource = Electron.DesktopCapturerSource; import { versionHandler } from './version-handler'; import { handlePermissionRequests, monitorWindowActions } from './window-actions'; import { @@ -47,8 +46,6 @@ export interface ICustomBrowserWindow extends Electron.BrowserWindow { const DEFAULT_WIDTH: number = 900; const DEFAULT_HEIGHT: number = 900; -const {devToolsEnabled} = config.getGlobalConfigFields(['devToolsEnabled']); - export class WindowHandler { /** @@ -79,7 +76,7 @@ export class WindowHandler { private readonly contextIsolation: boolean; private readonly backgroundThrottling: boolean; private readonly windowOpts: ICustomBrowserWindowConstructorOpts; - private readonly globalConfig: IConfig; + private readonly globalConfig: IGlobalConfig; private readonly config: IConfig; // Window reference private readonly windows: object; @@ -96,18 +93,20 @@ export class WindowHandler { constructor(opts?: Electron.BrowserViewConstructorOptions) { // Use these variables only on initial setup - this.config = config.getConfigFields([ 'isCustomTitleBar', 'mainWinPos', 'minimizeOnClose', 'notificationSettings', 'alwaysOnTop', 'locale' ]); - this.globalConfig = config.getGlobalConfigFields(['url', 'contextIsolation', 'customFlags']); - const {url, contextIsolation, customFlags}: IConfig = this.globalConfig; + this.config = config.getConfigFields([ 'isCustomTitleBar', 'mainWinPos', 'minimizeOnClose', 'notificationSettings', 'alwaysOnTop', 'locale', 'customFlags' ]); + logger.info(`window-handler: main windows initialized with following config data`, this.config); + this.globalConfig = config.getGlobalConfigFields([ 'url', 'contextIsolation' ]); + const { url, contextIsolation }: IGlobalConfig = this.globalConfig; + const { customFlags } = this.config; this.windows = {}; this.contextIsolation = contextIsolation || false; this.backgroundThrottling = !customFlags.disableThrottling; this.contextIsolation = contextIsolation || false; - this.isCustomTitleBar = isWindowsOS && this.config.isCustomTitleBar; + this.isCustomTitleBar = isWindowsOS && this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED; this.windowOpts = { ...this.getWindowOpts({ - alwaysOnTop: this.config.alwaysOnTop || false, + alwaysOnTop: this.config.alwaysOnTop === CloudConfigDataTypes.ENABLED || false, frame: !this.isCustomTitleBar, minHeight: 300, minWidth: 300, @@ -185,7 +184,7 @@ export class WindowHandler { // Event needed to hide native menu bar on Windows 10 as we use custom menu bar this.mainWindow.webContents.once('did-start-loading', () => { logger.info(`window-handler: main window web contents started loading!`); - if ((this.config.isCustomTitleBar && isWindowsOS) && this.mainWindow && windowExists(this.mainWindow)) { + if ((this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED && isWindowsOS) && this.mainWindow && windowExists(this.mainWindow)) { this.mainWindow.setMenuBarVisibility(false); } // monitors network connection and @@ -202,7 +201,7 @@ export class WindowHandler { if (urlFromCmd) { const commandLineUrl = urlFromCmd.substr(6); logger.info(`window-handler: trying to set url ${commandLineUrl} from command line.`); - const { podWhitelist } = config.getGlobalConfigFields([ 'podWhitelist' ]); + const { podWhitelist } = config.getConfigFields([ 'podWhitelist' ]); logger.info(`window-handler: checking pod whitelist.`); if (podWhitelist.length > 0) { logger.info(`window-handler: pod whitelist is not empty ${podWhitelist}`); @@ -245,7 +244,7 @@ export class WindowHandler { isMainWindow: true, }); this.appMenu = new AppMenu(); - const { permissions } = config.getGlobalConfigFields(['permissions']); + const { permissions } = config.getConfigFields([ 'permissions' ]); this.mainWindow.webContents.send('is-screen-share-enabled', permissions.media); }); @@ -304,8 +303,8 @@ export class WindowHandler { return this.destroyAllWindows(); } - const {minimizeOnClose} = config.getConfigFields(['minimizeOnClose']); - if (minimizeOnClose) { + const { minimizeOnClose } = config.getConfigFields([ 'minimizeOnClose' ]); + if (minimizeOnClose === CloudConfigDataTypes.ENABLED) { event.preventDefault(); this.mainWindow.minimize(); return; @@ -716,7 +715,7 @@ export class WindowHandler { if (app.isReady()) { screens = electron.screen.getAllDisplays(); } - const {position, display} = config.getConfigFields(['notificationSettings']).notificationSettings; + const { position, display } = config.getConfigFields([ 'notificationSettings' ]).notificationSettings; this.notificationSettingsWindow.webContents.send('notification-settings-data', {screens, position, display}); } }); @@ -964,6 +963,7 @@ export class WindowHandler { if (!focusedWindow || !windowExists(focusedWindow)) { return; } + const { devToolsEnabled } = config.getConfigFields([ 'devToolsEnabled' ]); if (devToolsEnabled) { focusedWindow.webContents.toggleDevTools(); return; diff --git a/src/app/window-utils.ts b/src/app/window-utils.ts index 112bf167..a1d482f7 100644 --- a/src/app/window-utils.ts +++ b/src/app/window-utils.ts @@ -12,8 +12,11 @@ import { i18n, LocaleType } from '../common/i18n'; import { logger } from '../common/logger'; import { getGuid } from '../common/utils'; import { whitelistHandler } from '../common/whitelist-handler'; -import { config, ICustomRectangle } from './config-handler'; +import { autoLaunchInstance } from './auto-launch-controller'; +import { CloudConfigDataTypes, config, IConfig, ICustomRectangle } from './config-handler'; +import { memoryMonitor } from './memory-monitor'; import { screenSnippet } from './screen-snippet-handler'; +import { updateAlwaysOnTop } from './window-actions'; import { ICustomBrowserWindow, windowHandler } from './window-handler'; interface IStyles { @@ -28,7 +31,8 @@ enum styleNames { } const checkValidWindow = true; -const { url: configUrl, ctWhitelist } = config.getGlobalConfigFields([ 'url', 'ctWhitelist' ]); +const { url: configUrl } = config.getGlobalConfigFields([ 'url' ]); +const { ctWhitelist } = config.getConfigFields([ 'ctWhitelist' ]); // Network status check variables const networkStatusCheckInterval = 10 * 1000; @@ -581,6 +585,36 @@ export const getWindowByName = (windowName: string): BrowserWindow | undefined = }); }; +export const updateFeaturesForCloudConfig = async (): Promise => { + const { + alwaysOnTop: isAlwaysOnTop, + launchOnStartup, + memoryRefresh, + memoryThreshold, + } = config.getConfigFields([ + 'launchOnStartup', + 'alwaysOnTop', + 'memoryRefresh', + 'memoryThreshold', + ]) as IConfig; + + const mainWindow = windowHandler.getMainWindow(); + + // Update Always on top feature + await updateAlwaysOnTop(isAlwaysOnTop === CloudConfigDataTypes.ENABLED, false, false); + + // Update launch on start up + launchOnStartup === CloudConfigDataTypes.ENABLED ? autoLaunchInstance.enableAutoLaunch() : autoLaunchInstance.disableAutoLaunch(); + + if (mainWindow && windowExists(mainWindow)) { + if (memoryRefresh) { + logger.info(`window-utils: updating the memory threshold`, memoryThreshold); + memoryMonitor.setMemoryThreshold(parseInt(memoryThreshold, 10)); + mainWindow.webContents.send('initialize-memory-refresh'); + } + } +}; + /** * Monitors network requests and displays red banner on failure */ diff --git a/src/common/api-interface.ts b/src/common/api-interface.ts index 1f7669c8..ebd4d68d 100644 --- a/src/common/api-interface.ts +++ b/src/common/api-interface.ts @@ -35,6 +35,7 @@ export enum apiCmds { swiftSearch = 'swift-search', getConfigUrl = 'get-config-url', registerRestartFloater = 'register-restart-floater', + setCloudConfig = 'set-cloud-config', } export enum apiName { @@ -67,6 +68,7 @@ export interface IApiArgs { type: string; logName: string; logs: ILogs; + cloudConfig: object; } export type WindowTypes = 'screen-picker' | 'screen-sharing-indicator' | 'notification-settings'; diff --git a/src/common/utils.ts b/src/common/utils.ts index 105a373d..c11334c2 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -153,6 +153,25 @@ export const pick = (object: object, fields: string[]) => { return obj; }; +/** + * Filters out truthy values + * + * @param data {Object} { test: true, test1: false, test2: 'NOT_SET' } + * @param values {Array} [ true, "NOT_SET" ] + * @return {Object} { test1: false } + */ +export const filterOutSelectedValues = (data: object, values): object => { + if (!data) { + return {}; + } + return Object.keys(data).reduce((obj, key) => { + if (values.indexOf(data[key]) <= -1) { + obj[key] = data[key]; + } + return obj; + }, {}); +}; + /** * Limits your function to be called at most every milliseconds * diff --git a/src/common/whitelist-handler.ts b/src/common/whitelist-handler.ts index fbd1dbc1..2087b66d 100644 --- a/src/common/whitelist-handler.ts +++ b/src/common/whitelist-handler.ts @@ -18,7 +18,7 @@ export class WhitelistHandler { * @returns {boolean} */ public isWhitelisted(url: string): boolean { - const { whitelistUrl } = config.getGlobalConfigFields([ 'whitelistUrl' ]); + const { whitelistUrl } = config.getConfigFields([ 'whitelistUrl' ]); return this.checkWhitelist(url, whitelistUrl); } diff --git a/src/renderer/app-bridge.ts b/src/renderer/app-bridge.ts index f0badfe9..6b7c3342 100644 --- a/src/renderer/app-bridge.ts +++ b/src/renderer/app-bridge.ts @@ -166,6 +166,9 @@ export class AppBridge { case apiCmds.registerRestartFloater: ssf.registerRestartFloater(this.callbackHandlers.restartFloater); break; + case apiCmds.setCloudConfig: + ssf.setCloudConfig(data as object); + break; case apiCmds.swiftSearch: if (ssInstance) { ssInstance.handleMessageEvents(data); diff --git a/src/renderer/preload-main.ts b/src/renderer/preload-main.ts index b0187322..7c443871 100644 --- a/src/renderer/preload-main.ts +++ b/src/renderer/preload-main.ts @@ -77,7 +77,7 @@ const monitorMemory = (time) => { }; // When the window is completely loaded -ipcRenderer.on('page-load', (_event, { locale, resources, enableCustomTitleBar, isMainWindow }) => { +ipcRenderer.on('page-load', (_event, { locale, resources, enableCustomTitleBar }) => { i18n.setResource(locale, resources); @@ -113,10 +113,6 @@ ipcRenderer.on('page-load', (_event, { locale, resources, enableCustomTitleBar, // initialize red banner banner.initBanner(); banner.showBanner(false, 'error'); - - if (isMainWindow) { - monitorMemory(getRandomTime(minMemoryFetchInterval, maxMemoryFetchInterval)); - } }); // When the window fails to load @@ -140,3 +136,7 @@ ipcRenderer.on('show-banner', (_event, { show, bannerType, url }) => { } banner.showBanner(show, bannerType, url); }); + +ipcRenderer.on('initialize-memory-refresh', () => { + monitorMemory(getRandomTime(minMemoryFetchInterval, maxMemoryFetchInterval)); +}); diff --git a/src/renderer/ssf-api.ts b/src/renderer/ssf-api.ts index 6b82c436..ad38cb1d 100644 --- a/src/renderer/ssf-api.ts +++ b/src/renderer/ssf-api.ts @@ -93,6 +93,13 @@ const throttledSetIsInMeetingStatus = throttle((isInMeeting) => { }); }, 1000); +const throttledSetCloudConfig = throttle((data) => { + ipcRenderer.send(apiName.symphonyApi, { + cmd: apiCmds.setCloudConfig, + cloudConfig: data, + }); +}, 1000); + let cryptoLib: ICryptoLib | null; try { cryptoLib = remote.require('../app/crypto-handler.js').cryptoLibrary; @@ -470,6 +477,15 @@ export class SSFApi { local.restartFloater = callback; } + /** + * Allows JS to set the PMP & ACP cloud config + * + * @param data {ICloudConfig} + */ + public setCloudConfig(data: {}): void { + throttledSetCloudConfig(data); + } + } /**