mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-21 16:38:41 -06:00
Typescript 🎉 (logger, get-guid, string-format and throttle)
This commit is contained in:
parent
1fc1e29d1a
commit
7fcc6aadb3
@ -4,6 +4,7 @@ import * as path from 'path';
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import compareSemVersions from '../common/compare-sem-versions';
|
||||
import { logger } from '../common/logger';
|
||||
import { isDevEnv, isMac } from '../common/mics';
|
||||
import pick from '../common/pick';
|
||||
|
||||
@ -181,6 +182,7 @@ class Config {
|
||||
* Verifies if the application is launched for the first time
|
||||
*/
|
||||
private checkFirstTimeLaunch() {
|
||||
logger.info('checking first launch');
|
||||
const appVersionString = app.getVersion().toString();
|
||||
const execPath = path.dirname(this.appPath);
|
||||
const shouldUpdateUserConfig = execPath.indexOf('AppData\\Local\\Programs') !== -1 || isMac;
|
||||
@ -195,7 +197,9 @@ class Config {
|
||||
&& typeof userConfigVersion === 'string'
|
||||
&& (compareSemVersions(appVersionString, userConfigVersion) !== 1)) && shouldUpdateUserConfig) {
|
||||
this.isFirstTime = true;
|
||||
return;
|
||||
}
|
||||
this.isFirstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,178 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { logger } from '../common/logger';
|
||||
|
||||
export enum IApiCmds {
|
||||
'isOnline',
|
||||
'registerLogger',
|
||||
'setBadgeCount',
|
||||
'badgeDataUrl',
|
||||
'activate',
|
||||
'registerBoundsChange',
|
||||
'registerProtocolHandler',
|
||||
'registerActivityDetection',
|
||||
'showNotificationSettings',
|
||||
'sanitize',
|
||||
'bringToFront',
|
||||
'openScreenPickerWindow',
|
||||
'popupMenu',
|
||||
'optimizeMemoryConsumption',
|
||||
'optimizeMemoryRegister',
|
||||
'setIsInMeeting',
|
||||
'setLocale',
|
||||
'keyPress',
|
||||
export enum ApiCmds {
|
||||
isOnline,
|
||||
registerLogger,
|
||||
setBadgeCount,
|
||||
badgeDataUrl,
|
||||
activate,
|
||||
registerBoundsChange,
|
||||
registerProtocolHandler,
|
||||
registerActivityDetection,
|
||||
showNotificationSettings,
|
||||
sanitize,
|
||||
bringToFront,
|
||||
openScreenPickerWindow,
|
||||
popupMenu,
|
||||
optimizeMemoryConsumption,
|
||||
optimizeMemoryRegister,
|
||||
setIsInMeeting,
|
||||
setLocale,
|
||||
keyPress,
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
'symphonyapi',
|
||||
}
|
||||
symphonyApi = 'symphony-api',
|
||||
}
|
||||
|
||||
export interface IApiArgs {
|
||||
cmd: ApiCmds;
|
||||
isOnline: boolean;
|
||||
count: number;
|
||||
dataUrl: string;
|
||||
windowName: string;
|
||||
period: number;
|
||||
reason: string;
|
||||
sources: Electron.DesktopCapturerSource[];
|
||||
id: number;
|
||||
memory: Electron.ProcessMemoryInfo;
|
||||
cpuUsage: Electron.CPUUsage;
|
||||
isInMeeting: boolean;
|
||||
locale: string;
|
||||
keyCode: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure events comes from a window that we have created.
|
||||
* @param {EventEmitter} event node emitter event to be tested
|
||||
* @return {Boolean} returns true if exists otherwise false
|
||||
*/
|
||||
function isValidWindow(event: Electron.Event) {
|
||||
/*if (!checkValidWindow) {
|
||||
return true;
|
||||
}*/
|
||||
const result = false;
|
||||
if (event && event.sender) {
|
||||
// validate that event sender is from window we created
|
||||
// const browserWin = BrowserWindow.fromWebContents(event.sender);
|
||||
|
||||
// result = windowMgr.hasWindow(browserWin, event.sender.id);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// log.send(logLevels.WARN, 'invalid window try to perform action, ignoring action');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle API related ipc messages from renderers. Only messages from windows
|
||||
* we have created are allowed.
|
||||
*/
|
||||
ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
|
||||
if (!isValidWindow(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (arg.cmd) {
|
||||
/*case ApiCmds.isOnline:
|
||||
if (typeof arg.isOnline === 'boolean') {
|
||||
windowMgr.setIsOnline(arg.isOnline);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.setBadgeCount:
|
||||
if (typeof arg.count === 'number') {
|
||||
badgeCount.show(arg.count);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.registerProtocolHandler:
|
||||
protocolHandler.setProtocolWindow(event.sender);
|
||||
protocolHandler.checkProtocolAction();
|
||||
break;
|
||||
case ApiCmds.badgeDataUrl:
|
||||
if (typeof arg.dataUrl === 'string' && typeof arg.count === 'number') {
|
||||
badgeCount.setDataUrl(arg.dataUrl, arg.count);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.activate:
|
||||
if (typeof arg.windowName === 'string') {
|
||||
windowMgr.activate(arg.windowName);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.registerBoundsChange:
|
||||
windowMgr.setBoundsChangeWindow(event.sender);
|
||||
break;*/
|
||||
case ApiCmds.registerLogger:
|
||||
// renderer window that has a registered logger from JS.
|
||||
logger.setLoggerWindow(event.sender);
|
||||
break;
|
||||
/*case ApiCmds.registerActivityDetection:
|
||||
if (typeof arg.period === 'number') {
|
||||
// renderer window that has a registered activity detection from JS.
|
||||
activityDetection.setActivityWindow(arg.period, event.sender);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.showNotificationSettings:
|
||||
if (typeof arg.windowName === 'string') {
|
||||
configureNotification.openConfigurationWindow(arg.windowName);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.sanitize:
|
||||
if (typeof arg.windowName === 'string') {
|
||||
sanitize(arg.windowName);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.bringToFront:
|
||||
// validates the user bring to front config and activates the wrapper
|
||||
if (typeof arg.reason === 'string' && arg.reason === 'notification') {
|
||||
bringToFront(arg.windowName, arg.reason);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.openScreenPickerWindow:
|
||||
if (Array.isArray(arg.sources) && typeof arg.id === 'number') {
|
||||
openScreenPickerWindow(event.sender, arg.sources, arg.id);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.popupMenu: {
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
if (browserWin && !browserWin.isDestroyed()) {
|
||||
windowMgr.getMenu().popup(browserWin, {x: 20, y: 15, async: true});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ApiCmds.optimizeMemoryConsumption:
|
||||
if (typeof arg.memory === 'object'
|
||||
&& typeof arg.cpuUsage === 'object'
|
||||
&& typeof arg.memory.workingSetSize === 'number') {
|
||||
setPreloadMemoryInfo(arg.memory, arg.cpuUsage);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.optimizeMemoryRegister:
|
||||
setPreloadWindow(event.sender);
|
||||
break;
|
||||
case ApiCmds.setIsInMeeting:
|
||||
if (typeof arg.isInMeeting === 'boolean') {
|
||||
setIsInMeeting(arg.isInMeeting);
|
||||
}
|
||||
break;
|
||||
case ApiCmds.setLocale:
|
||||
if (typeof arg.locale === 'string') {
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
windowMgr.setLocale(browserWin, { language: arg.locale });
|
||||
}
|
||||
break;
|
||||
case ApiCmds.keyPress:
|
||||
if (typeof arg.keyCode === 'number') {
|
||||
windowMgr.handleKeyPress(arg.keyCode);
|
||||
}
|
||||
break;*/
|
||||
default:
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { app } from 'electron';
|
||||
|
||||
import getCmdLineArg from '../common/get-command-line-args';
|
||||
import { logger } from '../common/logger';
|
||||
import { isDevEnv } from '../common/mics';
|
||||
import { cleanUpAppCache, createAppCacheFile } from './app-cache-handler';
|
||||
import { autoLaunchInstance } from './auto-launch-controller';
|
||||
@ -24,6 +25,7 @@ async function main() {
|
||||
windowHandler.createApplication();
|
||||
|
||||
if (config.isFirstTimeLaunch()) {
|
||||
logger.info('first time launch');
|
||||
await config.setUpFirstTimeLaunch();
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@ export class WindowHandler {
|
||||
webPreferences: {
|
||||
nativeWindowOpen: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, '../renderer/preload'),
|
||||
sandbox: false,
|
||||
},
|
||||
};
|
||||
@ -49,15 +50,8 @@ export class WindowHandler {
|
||||
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,
|
||||
});
|
||||
const extra = { podUrl: this.globalConfig.url, process: 'main' };
|
||||
crashReporter.start({ ...this.globalConfig.crashReporter, ...extra });
|
||||
} catch (e) {
|
||||
throw new Error('failed to init crash report');
|
||||
}
|
||||
@ -67,15 +61,14 @@ export class WindowHandler {
|
||||
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();
|
||||
this.loadingWindow = null;
|
||||
}
|
||||
if (this.mainWindow) this.mainWindow.show();
|
||||
});
|
||||
|
||||
return this.mainWindow;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
// 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
|
||||
@ -11,8 +8,7 @@
|
||||
*/
|
||||
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;
|
||||
throw new Error(`get-command-line-args: TypeError invalid func arg, must be an array: ${argv}`);
|
||||
}
|
||||
|
||||
const argNameToFind = argName.toLocaleLowerCase();
|
||||
|
14
src/common/get-guid.ts
Normal file
14
src/common/get-guid.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generates a guid,
|
||||
* http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
|
||||
*
|
||||
* @return {String} guid value in string
|
||||
*/
|
||||
export default function getGuid(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
|
||||
(c) => {
|
||||
const r = Math.random() * 16 | 0; // tslint:disable-line:no-bitwise
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8); // tslint:disable-line:no-bitwise
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
import { app } from 'electron';
|
||||
import electronLog, { LogLevel, transports } from 'electron-log';
|
||||
import * as path from 'path';
|
||||
|
||||
import getCmdLineArg from './get-command-line-args';
|
||||
import { isElectronQA } from './mics';
|
||||
import stringFormat from './string-format';
|
||||
|
||||
interface ILogMsg {
|
||||
level: LogLevel;
|
||||
details: any;
|
||||
showInConsole: boolean;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
interface IClientLogMsg {
|
||||
msgs?: ILogMsg[];
|
||||
logLevel?: LogLevel;
|
||||
showInConsole?: boolean;
|
||||
}
|
||||
|
||||
const MAX_LOG_QUEUE_LENGTH = 100;
|
||||
|
||||
export class Logger {
|
||||
private readonly showInConsole: boolean = false;
|
||||
private readonly desiredLogLevel?: LogLevel;
|
||||
private readonly logQueue: ILogMsg[];
|
||||
private loggerWindow: Electron.WebContents | null;
|
||||
|
||||
constructor() {
|
||||
this.loggerWindow = null;
|
||||
this.logQueue = [];
|
||||
if (!isElectronQA) {
|
||||
transports.file.file = path.join(app.getPath('logs'), 'app.log');
|
||||
transports.file.level = 'debug';
|
||||
transports.file.format = '{h}:{i}:{s}:{ms} {text}';
|
||||
transports.file.maxSize = 10 * 1024 * 1024;
|
||||
transports.file.appName = 'Symphony';
|
||||
}
|
||||
|
||||
const logLevel = getCmdLineArg(process.argv, '--logLevel=', false);
|
||||
if (logLevel) {
|
||||
const level = logLevel.split('=')[1];
|
||||
if (level) {
|
||||
this.desiredLogLevel = level as LogLevel;
|
||||
}
|
||||
}
|
||||
|
||||
if (getCmdLineArg(process.argv, '--enableConsoleLogging', false)) {
|
||||
this.showInConsole = true;
|
||||
}
|
||||
}
|
||||
|
||||
public error(message: string, data?: object): void {
|
||||
this.log('error', message, data);
|
||||
}
|
||||
|
||||
public warn(message: string, data?: object): void {
|
||||
this.log('warn', message, data);
|
||||
}
|
||||
|
||||
public info(message: string, data?: object): void {
|
||||
this.log('info', message, data);
|
||||
}
|
||||
|
||||
public verbose(message: string, data?: object): void {
|
||||
this.log('verbose', message, data);
|
||||
}
|
||||
|
||||
public debug(message: string, data?: object): void {
|
||||
this.log('debug', message, data);
|
||||
}
|
||||
|
||||
public silly(message: string, data?: object): void {
|
||||
this.log('silly', message, data);
|
||||
}
|
||||
|
||||
public setLoggerWindow(window: Electron.WebContents): void {
|
||||
this.loggerWindow = window;
|
||||
|
||||
if (this.loggerWindow) {
|
||||
const logMsgs: IClientLogMsg = {};
|
||||
if (this.logQueue.length) logMsgs.msgs = this.logQueue;
|
||||
if (this.desiredLogLevel) logMsgs.logLevel = this.desiredLogLevel;
|
||||
if (Object.keys(logMsgs).length) this.loggerWindow.send('log', logMsgs);
|
||||
}
|
||||
}
|
||||
|
||||
private log(logLevel: LogLevel, message: string, data?: object): void {
|
||||
message = stringFormat(message, data);
|
||||
if (!isElectronQA) {
|
||||
switch (logLevel) {
|
||||
case 'error': electronLog.error(message); break;
|
||||
case 'warn': electronLog.warn(message); break;
|
||||
case 'info': electronLog.info(message); break;
|
||||
case 'verbose': electronLog.verbose(message); break;
|
||||
case 'debug': electronLog.debug(message); break;
|
||||
case 'silly': electronLog.silly(message); break;
|
||||
default: electronLog.info(message);
|
||||
}
|
||||
}
|
||||
this.sendToCloud(this.formatLogMsg(logLevel, message));
|
||||
}
|
||||
|
||||
private formatLogMsg(level: LogLevel, details: any): ILogMsg {
|
||||
return {
|
||||
details,
|
||||
level,
|
||||
showInConsole: this.showInConsole,
|
||||
startTime: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send the logs to the client if loggerWindow
|
||||
* else adds the logs to a Queue
|
||||
*
|
||||
* @param logMsg {ILogMsg}
|
||||
*/
|
||||
private sendToCloud(logMsg: ILogMsg) {
|
||||
// don't send logs if it is not desired by the user
|
||||
if (this.desiredLogLevel && this.desiredLogLevel !== logMsg.level) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.loggerWindow) {
|
||||
this.loggerWindow.send('log', { msgs: [ logMsg ] });
|
||||
} else {
|
||||
this.logQueue.push(logMsg);
|
||||
// don't store more than 100 msgs. keep most recent log msgs.
|
||||
if (this.logQueue.length > MAX_LOG_QUEUE_LENGTH) {
|
||||
this.logQueue.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
export {
|
||||
logger,
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
export const isDevEnv = process.env.ELECTRON_DEV ?
|
||||
process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false;
|
||||
export const isElectronQA = process.env.ELECTRON_QA;
|
||||
|
||||
export const isMac = (process.platform === 'darwin');
|
||||
export const isWindowsOS = (process.platform === 'win32');
|
||||
|
30
src/common/string-format.ts
Normal file
30
src/common/string-format.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Injects content into string
|
||||
* @param str {String}
|
||||
* @param data {Object} - content to replace
|
||||
*
|
||||
* @example
|
||||
* StringFormat(this will log {time}`, { time: '1234' })
|
||||
*
|
||||
* result:
|
||||
* this will log 1234
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
export default function StringFormat(str: string, data?: object): string {
|
||||
|
||||
if (!str || !data) return str;
|
||||
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
return str.replace(/({([^}]+)})/g, (i) => {
|
||||
const replacedKey = i.replace(/{/, '').replace(/}/, '');
|
||||
if (!data[replacedKey]) {
|
||||
return i;
|
||||
}
|
||||
return data[replacedKey];
|
||||
});
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
22
src/common/throttle.ts
Normal file
22
src/common/throttle.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* limits your function to be called at most every milliseconds
|
||||
*
|
||||
* @param func
|
||||
* @param wait
|
||||
* @example const throttled = throttle(anyFunc, 500);
|
||||
*/
|
||||
export default function throttle(func: () => void, wait: number): () => void {
|
||||
if (wait <= 0) {
|
||||
throw Error('throttle: invalid throttleTime arg, must be a number: ' + wait);
|
||||
}
|
||||
|
||||
let isCalled: boolean = false;
|
||||
|
||||
return (...args) => {
|
||||
if (!isCalled) {
|
||||
func(...args);
|
||||
isCalled = true;
|
||||
setTimeout(() => isCalled = false, wait);
|
||||
}
|
||||
};
|
||||
}
|
0
src/renderer/preload.ts
Normal file
0
src/renderer/preload.ts
Normal file
Loading…
Reference in New Issue
Block a user