SymphonyElectron/src/app/config-handler.ts
Kiran Niranjan 81c67a1e08
fix: SDA-2287 (Only create context bridge when CI is enabled and create new config filed for defining origin) (#1036)
* SDA-2287 - Only create context bridge when CI is enabled and create new config filed for defining origin

* SDA-2287 - Revert loading context bridge as it is not necessary
2020-07-27 19:41:21 +05:30

402 lines
15 KiB
TypeScript

import { app, dialog } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
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';
const writeFile = util.promisify(fs.writeFile);
export enum CloudConfigDataTypes {
NOT_SET = 'NOT_SET',
ENABLED = 'ENABLED',
DISABLED = 'DISABLED',
}
export interface IConfig {
url: string;
minimizeOnClose: CloudConfigDataTypes;
launchOnStartup: CloudConfigDataTypes;
alwaysOnTop: CloudConfigDataTypes;
bringToFront: CloudConfigDataTypes;
whitelistUrl: string;
isCustomTitleBar: CloudConfigDataTypes;
memoryRefresh: CloudConfigDataTypes;
memoryThreshold: string;
disableGpu: boolean;
enableRendererLogs: boolean;
devToolsEnabled: boolean;
ctWhitelist: string[];
podWhitelist: string[];
autoLaunchPath: string;
permissions: IPermission;
customFlags: ICustomFlag;
buildNumber?: string;
configVersion?: string;
notificationSettings: INotificationSetting;
mainWinPos?: ICustomRectangle;
locale?: string;
installVariant?: string;
}
export interface IGlobalConfig {
contextOriginUrl: string;
url: string;
contextIsolation: boolean;
}
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;
notifications: boolean;
midiSysex: boolean;
pointerLock: boolean;
fullscreen: boolean;
openExternal: boolean;
}
export interface ICustomFlag {
authServerWhitelist: string;
authNegotiateDelegateWhitelist: string;
disableThrottling: CloudConfigDataTypes;
}
export interface INotificationSetting {
position: string;
display: string;
}
export interface ICustomRectangle extends Partial<Electron.Rectangle> {
isMaximized?: boolean;
isFullScreen?: boolean;
}
class Config {
public userConfig: IConfig | {};
public globalConfig: IConfig | {};
public cloudConfig: ICloudConfig | {};
public filteredCloudConfig: ICloudConfig | {};
private isFirstTime: boolean = true;
private installVariant: string | undefined;
private readonly configFileName: string;
private readonly installVariantFilename: string;
private readonly installVariantPath: string;
private readonly userConfigPath: string;
private readonly appPath: string;
private readonly globalConfigPath: string;
private readonly cloudConfigPath: string;
constructor() {
this.configFileName = 'Symphony.config';
this.installVariantFilename = 'InstallVariant.info';
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))
: path.join(this.appPath, (isMac) ? '..' : '', 'config', this.configFileName);
this.installVariantPath = isDevEnv
? path.join(this.appPath, path.join('config', this.installVariantFilename))
: path.join(this.appPath, (isMac) ? '..' : '', 'config', this.installVariantFilename);
if (isLinux) {
this.globalConfigPath = path.join(this.appPath, (isElectronQA) ? '..' : '', 'config', this.configFileName);
this.installVariantPath = path.join(this.appPath, (isElectronQA) ? '..' : '', 'config', this.installVariantFilename);
}
this.globalConfig = {};
this.userConfig = {};
this.cloudConfig = {};
this.filteredCloudConfig = {};
this.readUserConfig();
this.readGlobalConfig();
this.readInstallVariant();
this.readCloudConfig();
this.checkFirstTimeLaunch();
}
/**
* Returns the specified fields from both user and global config file
* and keep values from user config as priority
*
* @param fields
*/
public getConfigFields(fields: string[]): IConfig {
const configFields = { ...this.getGlobalConfigFields(fields), ...this.getUserConfigFields(fields), ...this.getFilteredCloudConfigFields(fields) } as IConfig;
logger.info(`config-handler: getting combined config values for the fields ${fields}`, configFields);
return configFields;
}
/**
* Returns the specified fields from user config file
*
* @param fields {Array}
*/
public getUserConfigFields(fields: string[]): IConfig {
const userConfigData = pick(this.userConfig, fields) as IConfig;
logger.info(`config-handler: getting user config values for the fields ${fields}`, userConfigData);
return userConfigData;
}
/**
* Returns the specified fields from global config file
*
* @param fields {Array}
*/
public getGlobalConfigFields(fields: string[]): IGlobalConfig {
const globalConfigData = pick(this.globalConfig, fields) as IGlobalConfig;
logger.info(`config-handler: getting global config values for the fields ${fields}`, globalConfigData);
return globalConfigData;
}
/**
* Returns filtered & prioritised fields from cloud config file
*
* @param fields {Array}
*/
public getFilteredCloudConfigFields(fields: string[]): IConfig | {} {
const filteredCloudConfigData = pick(this.filteredCloudConfig, fields) as IConfig;
logger.info(`config-handler: getting filtered cloud config values for the ${fields}`, filteredCloudConfigData);
return filteredCloudConfigData;
}
/**
* 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 };
logger.info(`config-handler: prioritized cloud config data`, cloudConfig);
const cloudConfigData = pick(cloudConfig, fields) as IConfig;
logger.info(`config-handler: getting prioritized cloud config values for the fields ${fields}`, cloudConfigData);
return cloudConfigData;
}
/**
* updates new data to the user config
*
* @param data {IConfig}
*/
public async updateUserConfig(data: Partial<IConfig>): Promise<void> {
logger.info(`config-handler: updating user config values with the data`, JSON.stringify(data));
this.userConfig = { ...this.userConfig, ...data };
try {
await writeFile(this.userConfigPath, JSON.stringify(this.userConfig), { encoding: 'utf8' });
logger.info(`config-handler: updated user config values with the data ${JSON.stringify(data)}`);
} catch (error) {
logger.error(`config-handler: failed to update user config file with ${JSON.stringify(data)}`, error);
dialog.showErrorBox(`Update failed`, `Failed to update user config due to error: ${error}`);
}
}
/**
* updates new data to the cloud config
*
* @param data {IConfig}
*/
public async updateCloudConfig(data: Partial<ICloudConfig>): Promise<void> {
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: writing 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
*/
public isFirstTimeLaunch(): boolean {
return this.isFirstTime;
}
/**
* Method that updates user config file
* by modifying the old config file
*/
public async setUpFirstTimeLaunch(): Promise<void> {
const execPath = path.dirname(this.appPath);
const shouldUpdateUserConfig = execPath.indexOf('AppData\\Local\\Programs') !== -1 || isMac;
if (shouldUpdateUserConfig) {
const {
minimizeOnClose,
launchOnStartup,
alwaysOnTop,
memoryRefresh,
bringToFront,
isCustomTitleBar,
...filteredFields }: IConfig = this.userConfig as IConfig;
// update to the new build number
filteredFields.buildNumber = buildNumber;
filteredFields.installVariant = this.installVariant;
logger.info(`config-handler: setting first time launch for build`, buildNumber);
return await this.updateUserConfig(filteredFields);
}
await this.updateUserConfig({ buildNumber, installVariant: this.installVariant });
}
/**
* 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
*
* @param data
*/
private parseConfigData(data: string): object {
let parsedData;
if (!data) {
logger.error(`config-handler: unable to read config file`);
throw new Error('unable to read user config file');
}
try {
parsedData = JSON.parse(data);
logger.info(`config-handler: parsed JSON file with data`, parsedData);
} catch (e) {
logger.error(`config-handler: parsing JSON file failed due to error ${e}`);
throw new Error(e);
}
return parsedData;
}
/**
* Reads a stores the user config file
*
* If user config doesn't exits?
* this creates a new one with { configVersion: current_app_version, buildNumber: current_app_build_number }
*/
private async readUserConfig() {
if (!fs.existsSync(this.userConfigPath)) {
// Need to wait until app ready event to access user data
await app.whenReady();
await this.readGlobalConfig();
logger.info(`config-handler: user config doesn't exist! will create new one and update config`);
const { url, ...rest } = this.globalConfig as IConfig;
await this.updateUserConfig({ configVersion: app.getVersion().toString(), buildNumber, ...rest } as IConfig);
}
this.userConfig = this.parseConfigData(fs.readFileSync(this.userConfigPath, 'utf8'));
logger.info(`config-handler: User configuration: `, this.userConfig);
}
/**
* Reads a stores the global config file
*/
private readGlobalConfig() {
this.globalConfig = this.parseConfigData(fs.readFileSync(this.globalConfigPath, 'utf8'));
logger.info(`config-handler: Global configuration: `, this.globalConfig);
}
/**
* Reads the install variant from a file
*/
private readInstallVariant() {
this.installVariant = fs.readFileSync(this.installVariantPath, 'utf8');
logger.info(`config-handler: Install variant: `, this.installVariant);
}
/**
* 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
*/
private async checkFirstTimeLaunch() {
logger.info('config-handler: checking first time launch');
const installVariant = this.userConfig && (this.userConfig as IConfig).installVariant || null;
if (!installVariant) {
logger.info(`config-handler: there's no install variant found, this is a first time launch`);
this.isFirstTime = true;
return;
}
if (installVariant && typeof installVariant === 'string' && installVariant !== this.installVariant) {
logger.info(`config-handler: install variant found is of a different instance, this is a first time launch`);
this.isFirstTime = true;
return;
}
logger.info(`config-handler: install variant is the same as the existing one, not a first time launch`);
this.isFirstTime = false;
}
}
const config = new Config();
export {
config,
};