mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-21 16:38:41 -06:00
Typescript 🎉
This commit is contained in:
parent
d4dcb933d1
commit
1fc1e29d1a
15
package.json
15
package.json
@ -1,14 +1,17 @@
|
||||
{
|
||||
"name": "Symphony",
|
||||
"productName": "Symphony",
|
||||
"productName": "Symphony-dev",
|
||||
"version": "4.5.0",
|
||||
"clientVersion": "1.55.0",
|
||||
"buildNumber": "0",
|
||||
"description": "Symphony desktop app (Foundation ODP)",
|
||||
"author": "Symphony",
|
||||
"main": "js/main.js",
|
||||
"main": "src/browser/main.js",
|
||||
"types": "src/browser/main.d.ts",
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js js/",
|
||||
"tsc": "git clean -xdf ./lib && npm run lint && tsc",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
"start": "npm run tsc && electron .",
|
||||
"prebuild": "npm run rebuild && npm run browserify-preload",
|
||||
"browserify-preload": "browserify -o js/preload/_preloadMain.js -x electron --insert-global-vars=__filename,__dirname js/preload/preloadMain.js --exclude electron-spellchecker",
|
||||
"rebuild": "electron-rebuild -f",
|
||||
@ -76,6 +79,9 @@
|
||||
"url": "https://support.symphony.com"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.0",
|
||||
"@types/lodash.omit": "^4.5.4",
|
||||
"@types/node": "10.11.4",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-eslint": "7.2.3",
|
||||
"babel-plugin-transform-async-to-generator": "6.24.1",
|
||||
@ -103,6 +109,9 @@
|
||||
"ncp": "2.0.0",
|
||||
"robotjs": "0.5.1",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"spectron": "5.0.0",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.1.1",
|
||||
"wdio-selenium-standalone-service": "0.0.12"
|
||||
},
|
||||
"dependencies": {
|
||||
|
25
src/browser/app-cache-handler.ts
Normal file
25
src/browser/app-cache-handler.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { app, session } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Cache check file path
|
||||
const cacheCheckFilePath: string = path.join(app.getPath('userData'), 'CacheCheck');
|
||||
|
||||
/**
|
||||
* Deletes app cache file if exists or clears
|
||||
* the cache for the session
|
||||
*/
|
||||
export async function cleanUpAppCache(): Promise<void> {
|
||||
if (fs.existsSync(cacheCheckFilePath)) {
|
||||
await fs.unlinkSync(cacheCheckFilePath);
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => session.defaultSession ? session.defaultSession.clearCache(resolve) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file cache file on app exit
|
||||
*/
|
||||
export function createAppCacheFile(): void {
|
||||
fs.writeFileSync(cacheCheckFilePath, '');
|
||||
}
|
87
src/browser/auto-launch-controller.ts
Normal file
87
src/browser/auto-launch-controller.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import AutoLaunch = require('auto-launch');
|
||||
|
||||
import { isMac } from '../common/mics';
|
||||
import { config, IConfig } from './config-handler';
|
||||
|
||||
const { autoLaunchPath }: IConfig = config.getGlobalConfigFields([ 'autoLaunchPath' ]);
|
||||
|
||||
const props = isMac ? {
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
name: 'Symphony',
|
||||
path: process.execPath,
|
||||
} : {
|
||||
name: 'Symphony',
|
||||
path: autoLaunchPath
|
||||
? autoLaunchPath.replace(/\//g, '\\')
|
||||
: null || process.execPath,
|
||||
};
|
||||
|
||||
export interface IAutoLaunchOptions {
|
||||
name: string;
|
||||
path?: string;
|
||||
isHidden?: boolean;
|
||||
mac?: {
|
||||
useLaunchAgent?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
class AutoLaunchController extends AutoLaunch {
|
||||
|
||||
constructor(opts: IAutoLaunchOptions) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable auto launch
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async enableAutoLaunch(): Promise<void> {
|
||||
// log.send(logLevels.INFO, `Enabling auto launch!`);
|
||||
return await this.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable auto launch
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async disableAutoLaunch(): Promise<void> {
|
||||
// log.send(logLevels.INFO, `Disabling auto launch!`);
|
||||
return await this.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if auto launch is enabled
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
public async isAutoLaunchEnabled(): Promise<boolean> {
|
||||
return await this.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user config and enables auto launch
|
||||
*/
|
||||
public async handleAutoLaunch(): Promise<void> {
|
||||
const { launchOnStartup }: IConfig = config.getConfigFields([ 'launchOnStartup' ]);
|
||||
|
||||
if (typeof launchOnStartup === 'boolean' && launchOnStartup) {
|
||||
if (await !this.isAutoLaunchEnabled()) {
|
||||
await this.enableAutoLaunch();
|
||||
}
|
||||
} else {
|
||||
if (await this.isAutoLaunchEnabled()) {
|
||||
await this.disableAutoLaunch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const autoLaunchInstance = new AutoLaunchController(props);
|
||||
|
||||
export {
|
||||
autoLaunchInstance,
|
||||
};
|
64
src/browser/chrome-flags.ts
Normal file
64
src/browser/chrome-flags.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { app } from 'electron';
|
||||
|
||||
import getCmdLineArg from '../common/get-command-line-args';
|
||||
import { isDevEnv } from '../common/mics';
|
||||
import { config, IConfig } from './config-handler';
|
||||
|
||||
export default function setChromeFlags() {
|
||||
const { customFlags } = config.getGlobalConfigFields([ 'customFlags' ]) as IConfig;
|
||||
|
||||
const configFlags: object = {
|
||||
'auth-negotiate-delegate-whitelist': customFlags.authServerWhitelist,
|
||||
'auth-server-whitelist': customFlags.authNegotiateDelegateWhitelist,
|
||||
'disable-background-timer-throttling': 'true',
|
||||
'disable-d3d11': customFlags.disableGpu || null,
|
||||
'disable-gpu': customFlags.disableGpu || null,
|
||||
'disable-gpu-compositing': customFlags.disableGpu || null,
|
||||
};
|
||||
|
||||
for (const key in configFlags) {
|
||||
if (!Object.prototype.hasOwnProperty.call(configFlags, key)) {
|
||||
continue;
|
||||
}
|
||||
if (key && configFlags[key]) {
|
||||
app.commandLine.appendSwitch(key, configFlags[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDevEnv) {
|
||||
const chromeFlagsFromCmd = getCmdLineArg(process.argv, '--chrome-flags=', false);
|
||||
if (!chromeFlagsFromCmd) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chromeFlagsArgs = chromeFlagsFromCmd.substr(15);
|
||||
if (!chromeFlagsArgs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const flags = chromeFlagsArgs.split(',');
|
||||
if (!flags || !Array.isArray(flags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in flags) {
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(flags, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flags[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const flagArray = flags[key].split(':');
|
||||
|
||||
if (flagArray && Array.isArray(flagArray) && flagArray.length > 0) {
|
||||
const chromeFlagKey = flagArray[0];
|
||||
const chromeFlagValue = flagArray[1];
|
||||
// log.send(logLevels.INFO, `Setting chrome flag ${chromeFlagKey} to ${chromeFlagValue}`);
|
||||
app.commandLine.appendSwitch(chromeFlagKey, chromeFlagValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
206
src/browser/config-handler.ts
Normal file
206
src/browser/config-handler.ts
Normal file
@ -0,0 +1,206 @@
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import compareSemVersions from '../common/compare-sem-versions';
|
||||
import { isDevEnv, isMac } from '../common/mics';
|
||||
import pick from '../common/pick';
|
||||
|
||||
const ignoreSettings = [
|
||||
'minimizeOnClose',
|
||||
'launchOnStartup',
|
||||
'alwaysOnTop',
|
||||
'url',
|
||||
'memoryRefresh',
|
||||
'bringToFront',
|
||||
'isCustomTitleBar',
|
||||
];
|
||||
|
||||
export interface IConfig {
|
||||
url: string;
|
||||
minimizeOnClose: boolean;
|
||||
launchOnStartup: boolean;
|
||||
alwaysOnTop: boolean;
|
||||
bringToFront: boolean;
|
||||
whitelistUrl: string;
|
||||
isCustomTitleBar: boolean;
|
||||
memoryRefresh: boolean;
|
||||
devToolsEnabled: boolean;
|
||||
ctWhitelist: string[];
|
||||
configVersion: string;
|
||||
autoLaunchPath: string;
|
||||
notificationSettings: INotificationSetting;
|
||||
permissions: IPermission;
|
||||
customFlags: ICustomFlag;
|
||||
crashReporter: ICrashReporter;
|
||||
}
|
||||
|
||||
export interface IPermission {
|
||||
media: boolean;
|
||||
geolocation: boolean;
|
||||
notifications: boolean;
|
||||
midiSysex: boolean;
|
||||
pointerLock: boolean;
|
||||
fullscreen: boolean;
|
||||
openExternal: boolean;
|
||||
}
|
||||
|
||||
export interface ICustomFlag {
|
||||
authServerWhitelist: string;
|
||||
authNegotiateDelegateWhitelist: string;
|
||||
disableGpu: boolean;
|
||||
}
|
||||
|
||||
export interface ICrashReporter {
|
||||
submitURL: string;
|
||||
companyName: string;
|
||||
uploadToServer: boolean;
|
||||
}
|
||||
|
||||
export interface INotificationSetting {
|
||||
position: string;
|
||||
display: string;
|
||||
}
|
||||
|
||||
class Config {
|
||||
private userConfig: IConfig | {};
|
||||
private globalConfig: IConfig | {};
|
||||
private isFirstTime: boolean = true;
|
||||
private readonly configFileName: string;
|
||||
private readonly userConfigPath: string;
|
||||
private readonly appPath: string;
|
||||
private readonly globalConfigPath: string;
|
||||
|
||||
constructor() {
|
||||
this.configFileName = 'Symphony.config';
|
||||
this.userConfigPath = path.join(app.getPath('userData'), this.configFileName);
|
||||
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.globalConfig = {};
|
||||
this.userConfig = {};
|
||||
this.readUserConfig();
|
||||
this.readGlobalConfig();
|
||||
|
||||
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 {
|
||||
return { ...this.getGlobalConfigFields(fields), ...this.getUserConfigFields(fields) } as IConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified fields from user config file
|
||||
*
|
||||
* @param fields {Array}
|
||||
*/
|
||||
public getUserConfigFields(fields: string[]): IConfig {
|
||||
return pick(this.userConfig, fields) as IConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified fields from global config file
|
||||
*
|
||||
* @param fields {Array}
|
||||
*/
|
||||
public getGlobalConfigFields(fields: string[]): IConfig {
|
||||
return pick(this.globalConfig, fields) as IConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* updates new data to the user config
|
||||
*
|
||||
* @param data {IConfig}
|
||||
*/
|
||||
public updateUserConfig(data: IConfig): void {
|
||||
this.userConfig = { ...data, ...this.userConfig };
|
||||
fs.writeFileSync(this.userConfigPath, JSON.stringify(this.userConfig), { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
public isFirstTimeLaunch(): boolean {
|
||||
return this.isFirstTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that updates user config file
|
||||
* by modifying the old config file
|
||||
*/
|
||||
public async setUpFirstTimeLaunch(): Promise<void> {
|
||||
const filteredFields: IConfig = omit(this.userConfig, ignoreSettings) as IConfig;
|
||||
await this.updateUserConfig(filteredFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the config data string
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
private parseConfigData(data: string): object {
|
||||
let parsedData;
|
||||
if (!data) {
|
||||
throw new Error('unable to read user config file');
|
||||
}
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (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 }
|
||||
*/
|
||||
private readUserConfig() {
|
||||
if (!fs.existsSync(this.userConfigPath)) {
|
||||
this.updateUserConfig({ configVersion: app.getVersion().toString() } as IConfig);
|
||||
}
|
||||
this.userConfig = this.parseConfigData(fs.readFileSync(this.userConfigPath, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a stores the global config file
|
||||
*/
|
||||
private readGlobalConfig() {
|
||||
this.globalConfig = this.parseConfigData(fs.readFileSync(this.globalConfigPath, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the application is launched for the first time
|
||||
*/
|
||||
private checkFirstTimeLaunch() {
|
||||
const appVersionString = app.getVersion().toString();
|
||||
const execPath = path.dirname(this.appPath);
|
||||
const shouldUpdateUserConfig = execPath.indexOf('AppData\\Local\\Programs') !== -1 || isMac;
|
||||
const userConfigVersion = this.userConfig && (this.userConfig as IConfig).configVersion || null;
|
||||
|
||||
if (!userConfigVersion) {
|
||||
this.isFirstTime = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(userConfigVersion
|
||||
&& typeof userConfigVersion === 'string'
|
||||
&& (compareSemVersions(appVersionString, userConfigVersion) !== 1)) && shouldUpdateUserConfig) {
|
||||
this.isFirstTime = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config = new Config();
|
||||
|
||||
export {
|
||||
config,
|
||||
};
|
26
src/browser/main-api-handler.ts
Normal file
26
src/browser/main-api-handler.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
export enum IApiCmds {
|
||||
'isOnline',
|
||||
'registerLogger',
|
||||
'setBadgeCount',
|
||||
'badgeDataUrl',
|
||||
'activate',
|
||||
'registerBoundsChange',
|
||||
'registerProtocolHandler',
|
||||
'registerActivityDetection',
|
||||
'showNotificationSettings',
|
||||
'sanitize',
|
||||
'bringToFront',
|
||||
'openScreenPickerWindow',
|
||||
'popupMenu',
|
||||
'optimizeMemoryConsumption',
|
||||
'optimizeMemoryRegister',
|
||||
'setIsInMeeting',
|
||||
'setLocale',
|
||||
'keyPress',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
'symphonyapi',
|
||||
}
|
54
src/browser/main.ts
Normal file
54
src/browser/main.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { app } from 'electron';
|
||||
|
||||
import getCmdLineArg from '../common/get-command-line-args';
|
||||
import { isDevEnv } from '../common/mics';
|
||||
import { cleanUpAppCache, createAppCacheFile } from './app-cache-handler';
|
||||
import { autoLaunchInstance } from './auto-launch-controller';
|
||||
import setChromeFlags from './chrome-flags';
|
||||
import { config } from './config-handler';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
const allowMultiInstance: string | boolean = getCmdLineArg(process.argv, '--multiInstance', true) || isDevEnv;
|
||||
const singleInstanceLock: boolean = allowMultiInstance ? false : app.requestSingleInstanceLock();
|
||||
|
||||
if (!singleInstanceLock) {
|
||||
app.quit();
|
||||
} else {
|
||||
main();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await appReady();
|
||||
createAppCacheFile();
|
||||
windowHandler.showLoadingScreen();
|
||||
windowHandler.createApplication();
|
||||
|
||||
if (config.isFirstTimeLaunch()) {
|
||||
await config.setUpFirstTimeLaunch();
|
||||
|
||||
/**
|
||||
* Enables or disables auto launch base on user settings
|
||||
*/
|
||||
await autoLaunchInstance.handleAutoLaunch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets chrome flags from global config
|
||||
*/
|
||||
setChromeFlags();
|
||||
}
|
||||
|
||||
async function appReady(): Promise<any> {
|
||||
await new Promise((res) => app.once('ready', res));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is triggered when all the windows are closed
|
||||
* In which case we quit the app
|
||||
*/
|
||||
app.on('window-all-closed', () => app.quit());
|
||||
|
||||
/**
|
||||
* Creates a new empty cache file when the app is quit
|
||||
*/
|
||||
app.on('quit', () => cleanUpAppCache());
|
115
src/browser/protocol-handler.ts
Normal file
115
src/browser/protocol-handler.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import * as url from 'url';
|
||||
|
||||
// import log from '../logs';
|
||||
// import { LogLevels } from '../logs/interface';
|
||||
import getCmdLineArg from '../common/get-command-line-args';
|
||||
import { isMac } from '../common/mics';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
let protocolWindow: Electron.WebContents;
|
||||
let protocolUrl: string | undefined;
|
||||
|
||||
/**
|
||||
* processes a protocol uri
|
||||
* @param {String} uri - the uri opened in the format 'symphony://...'
|
||||
*/
|
||||
export function processProtocolUri(uri: string): void {
|
||||
// log.send(LogLevels.INFO, `protocol action, uri ${uri}`);
|
||||
if (!protocolWindow) {
|
||||
// log.send(LogLevels.INFO, `protocol window not yet initialized, caching the uri ${uri}`);
|
||||
setProtocolUrl(uri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uri && uri.startsWith('symphony://')) {
|
||||
protocolWindow.send('protocol-action', uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* processes protocol action for windows clients
|
||||
* @param argv {Array} an array of command line arguments
|
||||
* @param isAppAlreadyOpen {Boolean} whether the app is already open
|
||||
*/
|
||||
export function processProtocolArgv(argv: string[], isAppAlreadyOpen: boolean): void {
|
||||
// In case of windows, we need to handle protocol handler
|
||||
// manually because electron doesn't emit
|
||||
// 'open-url' event on windows
|
||||
if (!(process.platform === 'win32')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const protocolUri = getCmdLineArg(argv, 'symphony://', false);
|
||||
// log.send(LogLevels.INFO, `Trying to process a protocol action for uri ${protocolUri}`);
|
||||
|
||||
if (protocolUri) {
|
||||
const parsedURL = url.parse(protocolUri);
|
||||
if (!parsedURL.protocol || !parsedURL.slashes) {
|
||||
return;
|
||||
}
|
||||
// log.send(LogLevels.INFO, `Parsing protocol url successful for ${parsedURL}`);
|
||||
handleProtocolAction(protocolUri, isAppAlreadyOpen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a protocol action based on the current state of the app
|
||||
* @param uri
|
||||
* @param isAppAlreadyOpen {Boolean} whether the app is already open
|
||||
*/
|
||||
export function handleProtocolAction(uri: string, isAppAlreadyOpen: boolean): void {
|
||||
if (!isAppAlreadyOpen) {
|
||||
// log.send(LogLevels.INFO, `App started by protocol url ${uri}. We are caching this to be processed later!`);
|
||||
// app is opened by the protocol url, cache the protocol url to be used later
|
||||
setProtocolUrl(uri);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is needed for mac OS as it brings pop-outs to foreground
|
||||
// (if it has been previously focused) instead of main window
|
||||
if (isMac) {
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
// windowMgr.activate(mainWindow.winName);
|
||||
}
|
||||
}
|
||||
// app is already open, so, just trigger the protocol action method
|
||||
// log.send(LogLevels.INFO, `App opened by protocol url ${uri}`);
|
||||
processProtocolUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the protocol window
|
||||
* @param {Object} win - the renderer window
|
||||
*/
|
||||
export function setProtocolWindow(win: Electron.WebContents): void {
|
||||
protocolWindow = win;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks to see if the app was opened by a uri
|
||||
*/
|
||||
export function checkProtocolAction(): void {
|
||||
// log.send(LogLevels.INFO, `checking if we have a cached protocol url`);
|
||||
if (protocolUrl) {
|
||||
// log.send(LogLevels.INFO, `found a cached protocol url, processing it!`);
|
||||
processProtocolUri(protocolUrl);
|
||||
protocolUrl = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* caches the protocol uri
|
||||
* @param {String} uri - the uri opened in the format 'symphony://...'
|
||||
*/
|
||||
export function setProtocolUrl(uri: string): void {
|
||||
protocolUrl = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the protocol url set against an instance
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getProtocolUrl(): string | undefined {
|
||||
return protocolUrl;
|
||||
}
|
111
src/browser/window-handler.ts
Normal file
111
src/browser/window-handler.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { BrowserWindow, crashReporter } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import getCmdLineArg from '../common/get-command-line-args';
|
||||
import { config, IConfig } from './config-handler';
|
||||
|
||||
export class WindowHandler {
|
||||
private static getMainWindowOpts() {
|
||||
return {
|
||||
alwaysOnTop: false,
|
||||
frame: true,
|
||||
minHeight: 300,
|
||||
minWidth: 400,
|
||||
show: false,
|
||||
title: 'Symphony',
|
||||
webPreferences: {
|
||||
nativeWindowOpen: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static getLoadingWindowOpts() {
|
||||
return {
|
||||
alwaysOnTop: false,
|
||||
center: true,
|
||||
frame: false,
|
||||
height: 200,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
resizable: false,
|
||||
show: false,
|
||||
title: 'Symphony',
|
||||
width: 400,
|
||||
};
|
||||
}
|
||||
|
||||
private readonly windowOpts: Electron.BrowserWindowConstructorOptions;
|
||||
private readonly globalConfig: IConfig;
|
||||
private mainWindow: Electron.BrowserWindow | null;
|
||||
private loadingWindow: Electron.BrowserWindow | null;
|
||||
|
||||
constructor(opts?: Electron.BrowserViewConstructorOptions) {
|
||||
this.windowOpts = { ... WindowHandler.getMainWindowOpts(), ...opts };
|
||||
this.mainWindow = null;
|
||||
this.loadingWindow = null;
|
||||
this.globalConfig = config.getGlobalConfigFields([ 'url', 'crashReporter' ]);
|
||||
|
||||
try {
|
||||
crashReporter.start({
|
||||
companyName: this.globalConfig!.crashReporter!.companyName,
|
||||
extra: {
|
||||
podUrl: this.globalConfig.url,
|
||||
process: 'main',
|
||||
},
|
||||
submitURL: this.globalConfig!.crashReporter!.submitURL,
|
||||
uploadToServer: this.globalConfig!.crashReporter!.uploadToServer,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error('failed to init crash report');
|
||||
}
|
||||
}
|
||||
|
||||
public createApplication() {
|
||||
this.mainWindow = new BrowserWindow(this.windowOpts);
|
||||
|
||||
const urlFromCmd = getCmdLineArg(process.argv, '--url=', false);
|
||||
|
||||
this.mainWindow.loadURL(urlFromCmd && urlFromCmd.substr(6) || this.validateURL(this.globalConfig.url));
|
||||
this.mainWindow.webContents.on('did-finish-load', () => {
|
||||
if (this.loadingWindow) {
|
||||
this.loadingWindow.destroy();
|
||||
}
|
||||
if (this.mainWindow) this.mainWindow.show();
|
||||
});
|
||||
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
public getMainWindow(): Electron.BrowserWindow | null {
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
public validateURL(configURL: string): string {
|
||||
const parsedUrl = url.parse(configURL);
|
||||
|
||||
if (!parsedUrl.protocol || parsedUrl.protocol !== 'https') {
|
||||
parsedUrl.protocol = 'https:';
|
||||
parsedUrl.slashes = true;
|
||||
}
|
||||
return url.format(parsedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a loading window until the main
|
||||
* application is loaded
|
||||
*/
|
||||
public showLoadingScreen() {
|
||||
this.loadingWindow = new BrowserWindow(WindowHandler.getLoadingWindowOpts());
|
||||
this.loadingWindow.once('ready-to-show', () => this.loadingWindow ? this.loadingWindow.show() : null);
|
||||
this.loadingWindow.loadURL(`file://${path.join(__dirname, '../renderer/loading-screen.html')}`);
|
||||
this.loadingWindow.setMenu(null as any);
|
||||
this.loadingWindow.once('closed', () => this.loadingWindow = null);
|
||||
}
|
||||
}
|
||||
|
||||
const windowHandler = new WindowHandler();
|
||||
|
||||
export { windowHandler };
|
81
src/common/compare-sem-versions.ts
Normal file
81
src/common/compare-sem-versions.ts
Normal file
@ -0,0 +1,81 @@
|
||||
// regex match the semver (semantic version) this checks for the pattern X.Y.Z
|
||||
// ex-valid v1.2.0, 1.2.0, 2.3.4-r51
|
||||
const semver = /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)?)?$/i;
|
||||
const patch = /-([0-9A-Za-z-.]+)/;
|
||||
|
||||
/**
|
||||
* This function splits the versions
|
||||
* into major, minor and patch
|
||||
* @param v
|
||||
* @returns {String[]}
|
||||
*/
|
||||
function split(v: string): string[] {
|
||||
const temp = v.replace(/^v/, '').split('.');
|
||||
const arr = temp.splice(0, 2);
|
||||
arr.push(temp.join('.'));
|
||||
return arr;
|
||||
}
|
||||
|
||||
function tryParse(v: string): string | number {
|
||||
return Number.isNaN(Number(v)) ? v : Number(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* This validates the version
|
||||
* with the semver regex and returns
|
||||
* -1 if not valid else 1
|
||||
* @param version
|
||||
* @returns {number}
|
||||
*/
|
||||
function validate(version: string): number {
|
||||
if (!semver.test(version)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function compares the v1 version
|
||||
* with the v2 version for all major, minor, patch
|
||||
* if v1 > v2 returns 1
|
||||
* if v1 < v2 returns -1
|
||||
* if v1 = v2 returns 0
|
||||
* @param v1
|
||||
* @param v2
|
||||
* @returns {number}
|
||||
*/
|
||||
export default function check(v1: string, v2: string): number {
|
||||
if (validate(v1) === -1 || validate(v2) === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const s1 = split(v1);
|
||||
const s2 = split(v2);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const n1 = parseInt(s1[i] || '0', 10);
|
||||
const n2 = parseInt(s2[i] || '0', 10);
|
||||
|
||||
if (n1 > n2) return 1;
|
||||
if (n2 > n1) return -1;
|
||||
}
|
||||
|
||||
if ([ s1[2], s2[2] ].every(patch.test.bind(patch))) {
|
||||
// @ts-ignore
|
||||
const p1 = patch.exec(s1[2])[1].split('.').map(tryParse);
|
||||
// @ts-ignore
|
||||
const p2 = patch.exec(s2[2])[1].split('.').map(tryParse);
|
||||
|
||||
for (let k = 0; k < Math.max(p1.length, p2.length); k++) {
|
||||
if (p1[k] === undefined || typeof p2[k] === 'string' && typeof p1[k] === 'number') return -1;
|
||||
if (p2[k] === undefined || typeof p1[k] === 'string' && typeof p2[k] === 'number') return 1;
|
||||
|
||||
if (p1[k] > p2[k]) return 1;
|
||||
if (p2[k] > p1[k]) return -1;
|
||||
}
|
||||
} else if ([ s1[2], s2[2] ].some(patch.test.bind(patch))) {
|
||||
return patch.test(s1[2]) ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
29
src/common/get-command-line-args.ts
Normal file
29
src/common/get-command-line-args.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// import log from '../logs';
|
||||
// import { LogLevels } from '../logs/interface';
|
||||
|
||||
/**
|
||||
* Search given argv for argName using exact match or starts with. Comparison is case insensitive
|
||||
* @param {Array} argv Array of strings
|
||||
* @param {String} argName Arg name to search for.
|
||||
* @param {Boolean} exactMatch If true then look for exact match otherwise
|
||||
* try finding arg that starts with argName.
|
||||
* @return {String} If found, returns the arg, otherwise null.
|
||||
*/
|
||||
export default function getCmdLineArg(argv: string[], argName: string, exactMatch: boolean): string | null {
|
||||
if (!Array.isArray(argv)) {
|
||||
// log.send(LogLevels.WARN, `getCmdLineArg: TypeError invalid func arg, must be an array: ${argv}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const argNameToFind = argName.toLocaleLowerCase();
|
||||
|
||||
for (let i = 0, len = argv.length; i < len; i++) {
|
||||
const arg = argv[i].toLocaleLowerCase();
|
||||
if ((exactMatch && arg === argNameToFind) ||
|
||||
(!exactMatch && arg.startsWith(argNameToFind))) {
|
||||
return argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
0
src/common/logger.ts
Normal file
0
src/common/logger.ts
Normal file
7
src/common/mics.ts
Normal file
7
src/common/mics.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const isDevEnv = process.env.ELECTRON_DEV ?
|
||||
process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false;
|
||||
|
||||
export const isMac = (process.platform === 'darwin');
|
||||
export const isWindowsOS = (process.platform === 'win32');
|
||||
|
||||
export const isNodeEnv = !!process.env.NODE_ENV;
|
9
src/common/pick.ts
Normal file
9
src/common/pick.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default function pick(object: object, fields: string[]) {
|
||||
const obj = {};
|
||||
for (const field of fields) {
|
||||
if (object[field]) {
|
||||
obj[field] = object[field];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
55
src/renderer/loading-screen.html
Normal file
55
src/renderer/loading-screen.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>About Symphony</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.name {
|
||||
flex: 1;
|
||||
font-size: 1.3em;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
flex: 1;
|
||||
font-size: 1em;
|
||||
color: #2f2f2f;
|
||||
}
|
||||
|
||||
.copyright-text {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
font-size: 0.6em;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 20px;
|
||||
background: url(symphony-background.jpg) no-repeat center center fixed;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<img class="logo" src="symphony-logo.png">
|
||||
<span id="app-name" class="name">Symphony</span>
|
||||
<span id="version" class="version-text"></span>
|
||||
<span id="copyright" class="copyright-text"></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
src/renderer/symphony-background.jpg
Normal file
BIN
src/renderer/symphony-background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
BIN
src/renderer/symphony-logo.png
Normal file
BIN
src/renderer/symphony-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
32
tsconfig.json
Normal file
32
tsconfig.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"pretty": true,
|
||||
"target": "ES2016",
|
||||
"lib": [
|
||||
"es2016",
|
||||
"dom"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedParameters": true,
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
"tests"
|
||||
]
|
||||
}
|
66
tslint.json
Normal file
66
tslint.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"rules": {
|
||||
"curly": false,
|
||||
"eofline": false,
|
||||
"align": [
|
||||
true,
|
||||
"parameters"
|
||||
],
|
||||
"class-name": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"max-line-length": [
|
||||
true,
|
||||
650
|
||||
],
|
||||
"no-trailing-whitespace": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-var-keyword": true,
|
||||
"no-empty": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-requires": true,
|
||||
"no-console": [
|
||||
false,
|
||||
"log",
|
||||
"error"
|
||||
],
|
||||
"one-line": [
|
||||
true,
|
||||
"check-else",
|
||||
"check-whitespace",
|
||||
"check-open-brace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user