mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-27 17:31:36 -06:00
Typescript (Optimize loading styles & register global shortcuts) (#629)
* Typescript - Optimize loading styles and register global shortcuts * Typescript - Update version info logic to support BC * Typescript - Implement memory refresh logic * Typescript - Optimize memory refresh, devtools & appBridge code * Typescript - Mock window-handler to fix unit tests * Typescript - Change logic to remove if else and early exit
This commit is contained in:
parent
b9bc65008d
commit
6aca522a1e
195
demo/index.html
195
demo/index.html
@ -68,13 +68,6 @@
|
||||
<button id='open-config-win'>Open configure window</button>
|
||||
<br>
|
||||
<hr>
|
||||
<p>
|
||||
Crash Process:
|
||||
<p>
|
||||
<button id='crash'>Crash Renderer</button>
|
||||
</p>
|
||||
<br>
|
||||
<hr>
|
||||
<textarea id="text-val" rows="4">Writes some thing to the file</textarea>
|
||||
<br/>
|
||||
<input type="button" id="download-file1" value="Download"/>
|
||||
@ -147,6 +140,8 @@
|
||||
</body>
|
||||
<script>
|
||||
|
||||
window.name = 'main';
|
||||
|
||||
const apiCmds = {
|
||||
isOnline: 'is-online',
|
||||
getVersionInfo: 'get-version-info',
|
||||
@ -294,32 +289,98 @@
|
||||
}
|
||||
});
|
||||
|
||||
// crash the renderer process
|
||||
const crash = document.getElementById('crash');
|
||||
crash.addEventListener('click', function () {
|
||||
ssf.crashRendererProcess();
|
||||
});
|
||||
|
||||
var getVersionInfo = document.getElementById('get-version');
|
||||
getVersionInfo.addEventListener('click', function() {
|
||||
ssf.getVersionInfo().then(function(verInfo) {
|
||||
let apiVersionInfo = document.getElementById('api-version');
|
||||
let containerIdentifier = document.getElementById('container-identifier');
|
||||
let version = document.getElementById('container-ver');
|
||||
let buildNumber = document.getElementById('build-number');
|
||||
let searchApiVer = document.getElementById('search-api-ver');
|
||||
getVersionInfo.addEventListener('click', () => {
|
||||
if (window.ssf) {
|
||||
ssf.getVersionInfo().then((verInfo) => {
|
||||
let apiVersionInfo = document.getElementById('api-version');
|
||||
let containerIdentifier = document.getElementById('container-identifier');
|
||||
let version = document.getElementById('container-ver');
|
||||
let buildNumber = document.getElementById('build-number');
|
||||
let searchApiVer = document.getElementById('search-api-ver');
|
||||
|
||||
apiVersionInfo.innerText = verInfo.apiVer;
|
||||
containerIdentifier.innerText = verInfo.containerIdentifier;
|
||||
version.innerText = verInfo.containerVer;
|
||||
buildNumber.innerText = verInfo.buildNumber;
|
||||
searchApiVer.innerText = verInfo.searchApiVer;
|
||||
});
|
||||
apiVersionInfo.innerText = verInfo.apiVer;
|
||||
containerIdentifier.innerText = verInfo.containerIdentifier;
|
||||
version.innerText = verInfo.containerVer;
|
||||
buildNumber.innerText = verInfo.buildNumber;
|
||||
searchApiVer.innerText = verInfo.searchApiVer;
|
||||
});
|
||||
} else {
|
||||
postRequest(apiCmds.getVersionInfo, null, {
|
||||
successCallback: (verInfo) => {
|
||||
let apiVersionInfo = document.getElementById('api-version');
|
||||
let containerIdentifier = document.getElementById('container-identifier');
|
||||
let version = document.getElementById('container-ver');
|
||||
let buildNumber = document.getElementById('build-number');
|
||||
let searchApiVer = document.getElementById('search-api-ver');
|
||||
|
||||
apiVersionInfo.innerText = verInfo.apiVer;
|
||||
containerIdentifier.innerText = verInfo.containerIdentifier;
|
||||
version.innerText = verInfo.containerVer;
|
||||
buildNumber.innerText = verInfo.buildNumber;
|
||||
searchApiVer.innerText = verInfo.searchApiVer;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Core post message api handler
|
||||
*/
|
||||
const pending = [];
|
||||
/**
|
||||
* Delete the request once we get the response
|
||||
* @returns {boolean}
|
||||
* @param id
|
||||
*/
|
||||
const forgetRequest = (id) => {
|
||||
return delete pending[ id ];
|
||||
};
|
||||
|
||||
const broadcastMessage = (method, data) => {
|
||||
window.postMessage({ method: method, data: data }, window.origin);
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param method
|
||||
* @param data
|
||||
* @param handlers
|
||||
*/
|
||||
const postRequest = (method, data = {}, handlers) => {
|
||||
const request = Object.assign({ requestId: ++requestId }, data);
|
||||
pending[ request.requestId ] = {
|
||||
errorCallback: (error) => {
|
||||
return handlers.errorCallback(error)
|
||||
&& forgetRequest(request.requestId);
|
||||
},
|
||||
successCallback: (response) => {
|
||||
return handlers.successCallback(response)
|
||||
&& forgetRequest(request.requestId);
|
||||
},
|
||||
};
|
||||
broadcastMessage(method, request);
|
||||
return request.requestId;
|
||||
};
|
||||
|
||||
const handleResponse = (data) => {
|
||||
if (data && data.requestId) {
|
||||
const id = data.requestId;
|
||||
const content = data;
|
||||
const resolver = pending[ id ];
|
||||
if (!resolver) {
|
||||
return;
|
||||
}
|
||||
if (content.error) {
|
||||
resolver.errorCallback(content.error);
|
||||
return;
|
||||
}
|
||||
if (content.response) {
|
||||
resolver.successCallback(content.response);
|
||||
return;
|
||||
}
|
||||
resolver.successCallback(content);
|
||||
}
|
||||
};
|
||||
|
||||
const postMessage = (method, data) => {
|
||||
window.postMessage({ method, data }, '*');
|
||||
@ -331,35 +392,6 @@
|
||||
case 'activity-callback':
|
||||
activityCallback(data);
|
||||
break;
|
||||
case 'media-source-callback':
|
||||
if (data.error) {
|
||||
if (data.error.message === 'User Cancelled') {
|
||||
console.error('user canceled');
|
||||
} else {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const constraints = {
|
||||
mandatory: {
|
||||
chromeMediaSource: 'desktop',
|
||||
maxWidth: screen.width > 1920 ? 1920 : screen.width,
|
||||
maxHeight: screen.height > 1080 ? 1080 : screen.height,
|
||||
chromeMediaSourceId: data.source.id
|
||||
},
|
||||
optional: []
|
||||
};
|
||||
navigator.mediaDevices.getUserMedia({ video: constraints }).then((stream) => {
|
||||
handleStream(stream, data.source.display_id);
|
||||
});
|
||||
break;
|
||||
case 'screen-sharing-indicator-callback':
|
||||
if (!data || typeof data.type !== 'string' || !requestId) {
|
||||
console.error(`screen-sharing-indicator-callback invalid args ${data}`);
|
||||
} else {
|
||||
console.log(`screen sharing will be stopped`);
|
||||
}
|
||||
break;
|
||||
case 'screen-snippet-callback':
|
||||
if (data) {
|
||||
if (data.type && data.type === 'ERROR') {
|
||||
@ -372,6 +404,12 @@
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'media-source-callback':
|
||||
case 'screen-sharing-indicator-callback':
|
||||
case 'get-version-info-callback':
|
||||
handleResponse(data);
|
||||
console.log(event.data);
|
||||
break;
|
||||
default:
|
||||
console.log(event.data);
|
||||
}
|
||||
@ -457,7 +495,33 @@
|
||||
}, handleError);
|
||||
});
|
||||
} else {
|
||||
postMessage(apiCmds.getMediaSource, { types: ['window', 'screen'], requestId: ++requestId });
|
||||
const successHandler = (data) => {
|
||||
const constraints = {
|
||||
mandatory: {
|
||||
chromeMediaSource: 'desktop',
|
||||
maxWidth: screen.width > 1920 ? 1920 : screen.width,
|
||||
maxHeight: screen.height > 1080 ? 1080 : screen.height,
|
||||
chromeMediaSourceId: data.source.id
|
||||
},
|
||||
optional: []
|
||||
};
|
||||
navigator.mediaDevices.getUserMedia({ video: constraints }).then((stream) => {
|
||||
handleStream(stream, data.source.display_id);
|
||||
});
|
||||
};
|
||||
const errorHandler = (data) => {
|
||||
if (data.error) {
|
||||
if (data.error.message === 'User Cancelled') {
|
||||
console.error('user canceled');
|
||||
} else {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
}
|
||||
};
|
||||
postRequest(apiCmds.getMediaSource, { types: ['window', 'screen'] }, {
|
||||
successCallback: (data) => successHandler(data),
|
||||
errorCallback: (error) => errorHandler(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -475,7 +539,24 @@
|
||||
}
|
||||
});
|
||||
} else {
|
||||
postMessage(apiCmds.openScreenSharingIndicator, { streamId: stream.id, displayId, requestId: ++requestId });
|
||||
const success = (data) => {
|
||||
if (data.error) {
|
||||
if (data.error.message === 'User Cancelled') {
|
||||
console.error('user canceled');
|
||||
} else {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!data || typeof data.type !== 'string' || !requestId) {
|
||||
console.error(`screen-sharing-indicator-callback invalid args ${data}`);
|
||||
} else {
|
||||
console.log(`screen sharing will be stopped`);
|
||||
}
|
||||
};
|
||||
postRequest(apiCmds.openScreenSharingIndicator, { streamId: stream.id, displayId }, {
|
||||
successCallback: (data) => success(data),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -30,7 +30,11 @@ Test Window has been opened
|
||||
var incBadgeEl = document.getElementById('inc-badge');
|
||||
incBadgeEl.addEventListener('click', function() {
|
||||
badgeCount++;
|
||||
ssf.setBadgeCount(badgeCount);
|
||||
if (window.ssf) {
|
||||
ssf.setBadgeCount(badgeCount);
|
||||
} else {
|
||||
postMessage({ method: 'set-badge-count', data: badgeCount }, window.origin);
|
||||
}
|
||||
});
|
||||
|
||||
var openWinButton = document.getElementById('open-win');
|
||||
|
@ -1,5 +1,13 @@
|
||||
jest.mock('electron-log');
|
||||
|
||||
jest.mock('../src/app/window-handler', () => {
|
||||
return {
|
||||
windowHandler: {
|
||||
setIsAutoReload: jest.fn(() => true),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('activity detection', () => {
|
||||
const originalTimeout: number = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
|
||||
|
@ -3,6 +3,7 @@ import { app } from 'electron';
|
||||
import Timer = NodeJS.Timer;
|
||||
|
||||
import { logger } from '../common/logger';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
class ActivityDetection {
|
||||
private idleThreshold: number;
|
||||
@ -49,6 +50,9 @@ class ActivityDetection {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
// set auto reload to false so the
|
||||
// activate func works normally
|
||||
windowHandler.setIsAutoReload(false);
|
||||
this.timer = undefined;
|
||||
logger.info(`activity-detection: activity occurred`);
|
||||
return;
|
||||
|
@ -96,11 +96,11 @@ export class AppMenu {
|
||||
* @param opts {Electron.PopupOptions}
|
||||
*/
|
||||
public popupMenu(opts: Electron.PopupOptions): void {
|
||||
if (this.menu) {
|
||||
this.menu.popup(opts);
|
||||
} else {
|
||||
if (!this.menu) {
|
||||
logger.error(`app-menu: tried popup menu, but failed menu not defined`);
|
||||
return;
|
||||
}
|
||||
this.menu.popup(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -311,18 +311,19 @@ export class AppMenu {
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click(_item, focusedWindow) {
|
||||
const devToolsEnabled = config.getGlobalConfigFields([ 'devToolsEnabled' ]);
|
||||
if (focusedWindow && windowExists(focusedWindow)) {
|
||||
if (devToolsEnabled) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
} else {
|
||||
dialog.showMessageBox(focusedWindow, {
|
||||
type: 'warning',
|
||||
buttons: [ 'Ok' ],
|
||||
title: i18n.t('Dev Tools disabled')(),
|
||||
message: i18n.t('Dev Tools has been disabled. Please contact your system administrator')(),
|
||||
});
|
||||
}
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
return;
|
||||
}
|
||||
if (devToolsEnabled) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
return;
|
||||
}
|
||||
dialog.showMessageBox(focusedWindow, {
|
||||
type: 'warning',
|
||||
buttons: [ 'Ok' ],
|
||||
title: i18n.t('Dev Tools disabled')(),
|
||||
message: i18n.t('Dev Tools has been disabled. Please contact your system administrator')(),
|
||||
});
|
||||
},
|
||||
},{
|
||||
click: () => windowHandler.createMoreInfoWindow(),
|
||||
|
@ -100,10 +100,10 @@ class AutoLaunchController extends AutoLaunch {
|
||||
if (!isAutoLaunchEnabled) {
|
||||
await this.enableAutoLaunch();
|
||||
}
|
||||
} else {
|
||||
if (isAutoLaunchEnabled) {
|
||||
await this.disableAutoLaunch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isAutoLaunchEnabled) {
|
||||
await this.disableAutoLaunch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { LocaleType } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { activityDetection } from './activity-detection';
|
||||
import { config } from './config-handler';
|
||||
import { memoryMonitor } from './memory-monitor';
|
||||
import { protocolHandler } from './protocol-handler';
|
||||
import { screenSnippet } from './screen-snippet-handler';
|
||||
import { activate, handleKeyPress } from './window-actions';
|
||||
@ -132,6 +133,16 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
|
||||
event.returnValue = windowHandler.spellchecker ? windowHandler.spellchecker.isMisspelled(arg.word) : false;
|
||||
}
|
||||
break;
|
||||
case apiCmds.setIsInMeeting:
|
||||
if (typeof arg.isInMeeting === 'boolean') {
|
||||
memoryMonitor.setMeetingStatus(arg.isInMeeting);
|
||||
}
|
||||
break;
|
||||
case apiCmds.memoryInfo:
|
||||
if (typeof arg.memoryInfo === 'object') {
|
||||
memoryMonitor.setMemoryInfo(arg.memoryInfo);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -108,9 +108,9 @@ app.on('activate', () => {
|
||||
const mainWindow: ICustomBrowserWindow | null = windowHandler.getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
startApplication();
|
||||
} else {
|
||||
mainWindow.show();
|
||||
return;
|
||||
}
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
/**
|
||||
|
91
src/app/memory-monitor.ts
Normal file
91
src/app/memory-monitor.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import * as electron from 'electron';
|
||||
|
||||
import { logger } from '../common/logger';
|
||||
import { config } from './config-handler';
|
||||
import { windowHandler } from './window-handler';
|
||||
import { windowExists } from './window-utils';
|
||||
|
||||
class MemoryMonitor {
|
||||
private memoryInfo: Electron.ProcessMemoryInfo | undefined = undefined;
|
||||
private isInMeeting: boolean;
|
||||
private canReload: boolean;
|
||||
|
||||
private readonly maxIdleTime: number;
|
||||
private readonly memoryThreshold: number;
|
||||
private readonly memoryRefreshThreshold: number;
|
||||
|
||||
constructor() {
|
||||
this.isInMeeting = false;
|
||||
this.canReload = true;
|
||||
this.maxIdleTime = 4 * 60 * 60 * 1000; // 4 hours
|
||||
this.memoryThreshold = 800 * 1024; // 800MB
|
||||
this.memoryRefreshThreshold = 60 * 60 * 1000; // 1 hour
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets process memory from ipc events every hour
|
||||
* and refreshes the client if the conditions passes
|
||||
*
|
||||
* @param memoryInfo {Electron.ProcessMemoryInfo}
|
||||
*/
|
||||
public setMemoryInfo(memoryInfo: Electron.ProcessMemoryInfo): void {
|
||||
this.memoryInfo = memoryInfo;
|
||||
this.validateMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the web app's RTC meeting status
|
||||
*
|
||||
* @param isInMeeting {boolean} whether user is in an active RTC meeting
|
||||
*/
|
||||
public setMeetingStatus(isInMeeting: boolean): void {
|
||||
this.isInMeeting = isInMeeting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the predefined conditions and refreshes the client
|
||||
*/
|
||||
private validateMemory(): void {
|
||||
const { memoryRefresh } = config.getConfigFields([ 'memoryRefresh' ]);
|
||||
if (!memoryRefresh) {
|
||||
logger.info(`memory refresh is disabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
(electron.powerMonitor as any).querySystemIdleTime((time) => {
|
||||
const idleTime = time * 1000;
|
||||
if (!(!this.isInMeeting
|
||||
&& windowHandler.isOnline
|
||||
&& this.canReload
|
||||
&& idleTime > this.maxIdleTime
|
||||
&& (this.memoryInfo && this.memoryInfo.private > this.memoryThreshold))
|
||||
) {
|
||||
logger.info(`Not Reloading the app as
|
||||
application was refreshed less than a hour ago? ${this.canReload ? 'no' : 'yes'}
|
||||
memory consumption is ${(this.memoryInfo && this.memoryInfo.private) || 'unknown'}kb is less than? ${this.memoryThreshold}kb
|
||||
system idle tick was ${idleTime}ms is less than? ${this.maxIdleTime}ms
|
||||
user was in a meeting? ${this.isInMeeting}
|
||||
is network online? ${windowHandler.isOnline}`);
|
||||
return;
|
||||
}
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow && windowExists(mainWindow)) {
|
||||
logger.info(`Reloading the app to optimize memory usage as
|
||||
memory consumption is ${this.memoryInfo.private}kb is greater than? ${this.memoryThreshold}kb threshold
|
||||
system idle tick was ${idleTime}ms is greater than ${this.maxIdleTime}ms
|
||||
user was in a meeting? ${this.isInMeeting}
|
||||
is network online? ${windowHandler.isOnline}`);
|
||||
windowHandler.setIsAutoReload(true);
|
||||
mainWindow.reload();
|
||||
this.canReload = false;
|
||||
setTimeout(() => {
|
||||
this.canReload = true;
|
||||
}, this.memoryRefreshThreshold);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const memoryMonitor = new MemoryMonitor();
|
||||
|
||||
export { memoryMonitor };
|
@ -25,14 +25,10 @@ class ScreenSnippet {
|
||||
constructor() {
|
||||
this.tempDir = os.tmpdir();
|
||||
this.isAlwaysOnTop = false;
|
||||
if (isMac) {
|
||||
this.captureUtil = '/usr/sbin/screencapture';
|
||||
} else {
|
||||
this.captureUtil = isDevEnv
|
||||
? path.join(__dirname,
|
||||
'../../node_modules/screen-snippet/bin/Release/ScreenSnippet.exe')
|
||||
: path.join(path.dirname(app.getPath('exe')), 'ScreenSnippet.exe');
|
||||
}
|
||||
this.captureUtil = isMac ? '/usr/sbin/screencapture' : isDevEnv
|
||||
? path.join(__dirname,
|
||||
'../../node_modules/screen-snippet/bin/Release/ScreenSnippet.exe')
|
||||
: path.join(path.dirname(app.getPath('exe')), 'ScreenSnippet.exe');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,14 +115,12 @@ class ScreenSnippet {
|
||||
const output = Buffer.from(data).toString('base64');
|
||||
return { message: 'success', data: output, type: 'image/png;base64' };
|
||||
} catch (error) {
|
||||
if (error && error.code === 'ENOENT') {
|
||||
// no such file exists, user likely aborted
|
||||
// creating snippet. also include any error when
|
||||
// creating child process.
|
||||
return { message: `file does not exist`, type: 'ERROR' };
|
||||
} else {
|
||||
return { message: `${error}`, type: 'ERROR' };
|
||||
}
|
||||
// no such file exists or user likely aborted
|
||||
// creating snippet. also include any error when
|
||||
// creating child process.
|
||||
return error && error.code === 'ENOENT'
|
||||
? { message: `file does not exist`, type: 'ERROR' }
|
||||
: { message: `${error}`, type: 'ERROR' };
|
||||
} finally {
|
||||
// remove tmp file (async)
|
||||
if (this.outputFileName) {
|
||||
|
@ -127,6 +127,9 @@ export const handleKeyPress = (key: number): void => {
|
||||
break;
|
||||
}
|
||||
case KeyCodes.Alt:
|
||||
if (isMac) {
|
||||
return;
|
||||
}
|
||||
const browserWin = BrowserWindow.getFocusedWindow();
|
||||
if (browserWin && !browserWin.isDestroyed()) {
|
||||
showPopupMenu({ window: browserWin });
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as electron from 'electron';
|
||||
import { app, BrowserWindow, crashReporter, ipcMain } from 'electron';
|
||||
import { app, BrowserWindow, crashReporter, globalShortcut, ipcMain } from 'electron';
|
||||
import * as path from 'path';
|
||||
import { format, parse } from 'url';
|
||||
|
||||
@ -98,6 +98,9 @@ export class WindowHandler {
|
||||
height: 360,
|
||||
show: false,
|
||||
modal: true,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
@ -257,8 +260,7 @@ export class WindowHandler {
|
||||
}
|
||||
this.url = this.mainWindow.webContents.getURL();
|
||||
|
||||
// Injects custom title bar css into the webContents
|
||||
// only for Window and if it is enabled
|
||||
// Injects custom title bar and snack bar css into the webContents
|
||||
await injectStyles(this.mainWindow, this.isCustomTitleBar);
|
||||
|
||||
this.mainWindow.webContents.send('page-load', {
|
||||
@ -297,6 +299,23 @@ export class WindowHandler {
|
||||
}
|
||||
});
|
||||
|
||||
this.mainWindow.webContents.on('crashed', (_event: Event, killed: boolean) => {
|
||||
if (killed) {
|
||||
return;
|
||||
}
|
||||
electron.dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: i18n.t('Renderer Process Crashed')(),
|
||||
message: i18n.t('Oops! Looks like we have had a crash. Please reload or close this window.')(),
|
||||
buttons: [ 'Reload', 'Close' ],
|
||||
}, (index: number) => {
|
||||
if (!this.mainWindow || !windowExists(this.mainWindow)) {
|
||||
return;
|
||||
}
|
||||
index === 0 ? this.mainWindow.reload() : this.mainWindow.close();
|
||||
});
|
||||
});
|
||||
|
||||
// Handle main window close
|
||||
this.mainWindow.on('close', (event) => {
|
||||
if (!this.mainWindow || !windowExists(this.mainWindow)) {
|
||||
@ -304,15 +323,20 @@ export class WindowHandler {
|
||||
}
|
||||
|
||||
if (this.willQuitApp) {
|
||||
return this.destroyAllWindow();
|
||||
return this.destroyAllWindows();
|
||||
}
|
||||
|
||||
if (config.getConfigFields([ 'minimizeOnClose' ]).minimizeOnClose) {
|
||||
const { minimizeOnClose } = config.getConfigFields([ 'minimizeOnClose' ]);
|
||||
if (minimizeOnClose) {
|
||||
event.preventDefault();
|
||||
isMac ? this.mainWindow.hide() : this.mainWindow.minimize();
|
||||
} else {
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
app.quit();
|
||||
});
|
||||
|
||||
this.mainWindow.once('closed', () => {
|
||||
this.destroyAllWindows();
|
||||
});
|
||||
|
||||
// Certificate verification proxy
|
||||
@ -320,6 +344,9 @@ export class WindowHandler {
|
||||
this.mainWindow.webContents.session.setCertificateVerifyProc(handleCertificateProxyVerification);
|
||||
}
|
||||
|
||||
// Register global shortcuts
|
||||
this.registerGlobalShortcuts();
|
||||
|
||||
// Validate window navigation
|
||||
preventWindowNavigation(this.mainWindow, false);
|
||||
|
||||
@ -355,7 +382,9 @@ export class WindowHandler {
|
||||
}
|
||||
|
||||
// Ready to show the window
|
||||
this.mainWindow.show();
|
||||
if (!this.isAutoReload) {
|
||||
this.mainWindow.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -760,10 +789,48 @@ export class WindowHandler {
|
||||
delete this.windows[ key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers keyboard shortcuts or devtools
|
||||
*/
|
||||
private registerGlobalShortcuts(): void {
|
||||
globalShortcut.register(isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I', this.onRegisterDevtools);
|
||||
|
||||
app.on('browser-window-focus', () => {
|
||||
globalShortcut.register(isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I', this.onRegisterDevtools);
|
||||
});
|
||||
|
||||
app.on('browser-window-blur', () => {
|
||||
globalShortcut.unregister(isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies and toggle devtool based on global config settings
|
||||
* else displays a dialog
|
||||
*/
|
||||
private onRegisterDevtools(): void {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
const { devToolsEnabled } = config.getGlobalConfigFields([ 'devToolsEnabled' ]);
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
return;
|
||||
}
|
||||
if (devToolsEnabled) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
return;
|
||||
}
|
||||
focusedWindow.webContents.closeDevTools();
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
type: 'warning',
|
||||
buttons: [ 'Ok' ],
|
||||
title: i18n.t('Dev Tools disabled')(),
|
||||
message: i18n.t('Dev Tools has been disabled! Please contact your system administrator to enable it!')(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up reference
|
||||
*/
|
||||
private destroyAllWindow(): void {
|
||||
private destroyAllWindows(): void {
|
||||
for (const key in this.windows) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.windows, key)) {
|
||||
const winKey = this.windows[ key ];
|
||||
|
@ -15,6 +15,16 @@ import { config } from './config-handler';
|
||||
import { screenSnippet } from './screen-snippet-handler';
|
||||
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
||||
|
||||
interface IStyles {
|
||||
name: styleNames;
|
||||
content: string;
|
||||
}
|
||||
|
||||
enum styleNames {
|
||||
titleBar = 'title-bar',
|
||||
snackBar = 'snack-bar',
|
||||
}
|
||||
|
||||
const checkValidWindow = true;
|
||||
const { url: configUrl, ctWhitelist } = config.getGlobalConfigFields([ 'url', 'ctWhitelist' ]);
|
||||
|
||||
@ -22,6 +32,8 @@ const { url: configUrl, ctWhitelist } = config.getGlobalConfigFields([ 'url', 'c
|
||||
const networkStatusCheckInterval = 10 * 1000;
|
||||
let networkStatusCheckIntervalId;
|
||||
|
||||
const styles: IStyles[] = [];
|
||||
|
||||
/**
|
||||
* Checks if window is valid and exists
|
||||
*
|
||||
@ -153,16 +165,19 @@ export const showBadgeCount = (count: number): void => {
|
||||
|
||||
// handle ms windows...
|
||||
const mainWindow = windowHandler.getMainWindow();
|
||||
if (mainWindow) {
|
||||
if (count > 0) {
|
||||
// get badge img from renderer process, will return
|
||||
// img dataUrl in setDataUrl func.
|
||||
mainWindow.webContents.send('create-badge-data-url', { count });
|
||||
} else {
|
||||
// clear badge count icon
|
||||
mainWindow.setOverlayIcon(null, '');
|
||||
}
|
||||
if (!mainWindow || !windowExists(mainWindow)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get badge img from renderer process, will return
|
||||
// img dataUrl in setDataUrl func.
|
||||
if (count > 0) {
|
||||
mainWindow.webContents.send('create-badge-data-url', { count });
|
||||
return;
|
||||
}
|
||||
|
||||
// clear badge count icon
|
||||
mainWindow.setOverlayIcon(null, '');
|
||||
};
|
||||
|
||||
/**
|
||||
@ -288,10 +303,15 @@ export const getBounds = (winPos: Electron.Rectangle, defaultWidth: number, defa
|
||||
* @param type
|
||||
* @param filePath
|
||||
*/
|
||||
export const downloadManagerAction = (type, filePath) => {
|
||||
export const downloadManagerAction = (type, filePath): void => {
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (!focusedWindow || !windowExists(focusedWindow)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'open') {
|
||||
const openResponse = electron.shell.openExternal(`file:///${filePath}`);
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
if (!openResponse && focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message: i18n.t('The file you are trying to open cannot be found in the specified path.')(),
|
||||
@ -299,16 +319,16 @@ export const downloadManagerAction = (type, filePath) => {
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const showResponse = electron.shell.showItemInFolder(filePath);
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
if (!showResponse && focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message: i18n.t('The file you are trying to open cannot be found in the specified path.')(),
|
||||
title: i18n.t('File not Found')(),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const showResponse = electron.shell.showItemInFolder(filePath);
|
||||
if (!showResponse) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message: i18n.t('The file you are trying to open cannot be found in the specified path.')(),
|
||||
title: i18n.t('File not Found')(),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -339,10 +359,11 @@ export const handleDownloadManager = (_event, item: Electron.DownloadItem, webCo
|
||||
* Inserts css in to the window
|
||||
*
|
||||
* @param window {BrowserWindow}
|
||||
* @param paths {string[]}
|
||||
*/
|
||||
const readAndInsertCSS = async (window, paths): Promise<void> => {
|
||||
return paths.map((filePath) => window.webContents.insertCSS(fs.readFileSync(filePath, 'utf8').toString()));
|
||||
const readAndInsertCSS = async (window): Promise<IStyles[] | void> => {
|
||||
if (window && windowExists(window)) {
|
||||
return styles.map(({ content }) => window.webContents.insertCSS(content));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -351,31 +372,36 @@ const readAndInsertCSS = async (window, paths): Promise<void> => {
|
||||
* @param mainWindow {BrowserWindow}
|
||||
* @param isCustomTitleBar {boolean} - whether custom title bar enabled
|
||||
*/
|
||||
export const injectStyles = async (mainWindow: BrowserWindow, isCustomTitleBar: boolean): Promise<void> => {
|
||||
const paths: string[] = [];
|
||||
export const injectStyles = async (mainWindow: BrowserWindow, isCustomTitleBar: boolean): Promise<IStyles[] | void> => {
|
||||
if (isCustomTitleBar) {
|
||||
let titleBarStylesPath;
|
||||
const stylesFileName = path.join('config', 'titleBarStyles.css');
|
||||
if (isDevEnv) {
|
||||
titleBarStylesPath = path.join(app.getAppPath(), stylesFileName);
|
||||
} else {
|
||||
const execPath = path.dirname(app.getPath('exe'));
|
||||
titleBarStylesPath = path.join(execPath, stylesFileName);
|
||||
const index = styles.findIndex(({ name }) => name === styleNames.titleBar);
|
||||
if (index === -1) {
|
||||
let titleBarStylesPath;
|
||||
const stylesFileName = path.join('config', 'titleBarStyles.css');
|
||||
if (isDevEnv) {
|
||||
titleBarStylesPath = path.join(app.getAppPath(), stylesFileName);
|
||||
} else {
|
||||
const execPath = path.dirname(app.getPath('exe'));
|
||||
titleBarStylesPath = path.join(execPath, stylesFileName);
|
||||
}
|
||||
// Window custom title bar styles
|
||||
if (fs.existsSync(titleBarStylesPath)) {
|
||||
styles.push({ name: styleNames.titleBar, content: fs.readFileSync(titleBarStylesPath, 'utf8').toString() });
|
||||
} else {
|
||||
const stylePath = path.join(__dirname, '..', '/renderer/styles/title-bar.css');
|
||||
styles.push({ name: styleNames.titleBar, content: fs.readFileSync(stylePath, 'utf8').toString() });
|
||||
}
|
||||
}
|
||||
// Window custom title bar styles
|
||||
if (fs.existsSync(titleBarStylesPath)) {
|
||||
paths.push(titleBarStylesPath);
|
||||
} else {
|
||||
paths.push(path.join(__dirname, '..', '/renderer/styles/title-bar.css'));
|
||||
}
|
||||
} else {
|
||||
paths.push(path.join(__dirname, '..', '/renderer/styles/title-bar.css'));
|
||||
}
|
||||
// Snack bar styles
|
||||
if (styles.findIndex(({ name }) => name === styleNames.snackBar) === -1) {
|
||||
styles.push({
|
||||
name: styleNames.snackBar,
|
||||
content: fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Snack bar styles
|
||||
paths.push(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'));
|
||||
|
||||
return await readAndInsertCSS(mainWindow, paths);
|
||||
return await readAndInsertCSS(mainWindow);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -430,9 +456,9 @@ export const isSymphonyReachable = (window: ICustomBrowserWindow | null) => {
|
||||
clearInterval(networkStatusCheckIntervalId);
|
||||
networkStatusCheckIntervalId = null;
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Symphony down! statusCode: ${rsp.status} is online: ${windowHandler.isOnline}`);
|
||||
return;
|
||||
}
|
||||
logger.warn(`Symphony down! statusCode: ${rsp.status} is online: ${windowHandler.isOnline}`);
|
||||
}).catch((error) => {
|
||||
logger.error(`Network status check: No active network connection ${error}`);
|
||||
});
|
||||
|
@ -13,13 +13,13 @@ export class AnimationQueue {
|
||||
*
|
||||
* @param object
|
||||
*/
|
||||
public push(object) {
|
||||
public push(object): void {
|
||||
if (this.running) {
|
||||
this.queue.push(object);
|
||||
} else {
|
||||
this.running = true;
|
||||
setTimeout(() => this.animate(object), 0);
|
||||
return;
|
||||
}
|
||||
this.running = true;
|
||||
setTimeout(() => this.animate(object), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,7 +44,7 @@ export class AnimationQueue {
|
||||
/**
|
||||
* Clears the queue
|
||||
*/
|
||||
public clear() {
|
||||
public clear(): void {
|
||||
this.queue = [];
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ export enum apiCmds {
|
||||
closeNotification = 'close-notification',
|
||||
initMainWindow = 'init-main-window',
|
||||
isMisspelled = 'is-misspelled',
|
||||
memoryInfo = 'memory-info',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
@ -37,6 +38,7 @@ export enum apiName {
|
||||
}
|
||||
|
||||
export interface IApiArgs {
|
||||
memoryInfo: Electron.ProcessMemoryInfo;
|
||||
word: string;
|
||||
cmd: apiCmds;
|
||||
isOnline: boolean;
|
||||
|
@ -211,12 +211,13 @@ class Logger {
|
||||
|
||||
if (this.loggerWindow) {
|
||||
this.loggerWindow.send('log', { msgs: [ logMsg ], logLevel: this.desiredLogLevel, showInConsole: this.showInConsole });
|
||||
} 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();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ export default class AppBridge {
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
private handleMessage(event): void {
|
||||
private async handleMessage(event): Promise<void> {
|
||||
if (!AppBridge.isValidEvent(event)) {
|
||||
return;
|
||||
}
|
||||
@ -68,7 +68,11 @@ export default class AppBridge {
|
||||
const { method, data } = event.data;
|
||||
switch (method) {
|
||||
case apiCmds.getVersionInfo:
|
||||
this.broadcastMessage('get-version-info-callback', ssf.getVersionInfo());
|
||||
const versionInfo = await ssf.getVersionInfo();
|
||||
this.broadcastMessage('get-version-info-callback', {
|
||||
requestId: data.requestId,
|
||||
response: versionInfo,
|
||||
});
|
||||
break;
|
||||
case apiCmds.activate:
|
||||
ssf.activate(data as string);
|
||||
@ -120,6 +124,11 @@ export default class AppBridge {
|
||||
case apiCmds.showNotificationSettings:
|
||||
ssf.showNotificationSettings();
|
||||
break;
|
||||
case apiCmds.setIsInMeeting:
|
||||
if (typeof data === 'boolean') {
|
||||
ssf.setIsInMeeting(data as boolean);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,16 +39,18 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
this.window.on('leave-full-screen', () => this.updateState({ isFullScreen: false }));
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
const contentWrapper = document.getElementById('content-wrapper');
|
||||
if (contentWrapper) {
|
||||
if (this.state.isFullScreen) {
|
||||
contentWrapper.style.marginTop = '0px';
|
||||
document.body.style.removeProperty('margin-top');
|
||||
} else {
|
||||
contentWrapper.style.marginTop = this.state.titleBarHeight;
|
||||
}
|
||||
if (!contentWrapper) {
|
||||
document.body.style.marginTop = this.state.titleBarHeight;
|
||||
return;
|
||||
}
|
||||
if (this.state.isFullScreen) {
|
||||
contentWrapper.style.marginTop = '0px';
|
||||
document.body.style.removeProperty('margin-top');
|
||||
return;
|
||||
}
|
||||
contentWrapper.style.marginTop = this.state.titleBarHeight;
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
@ -146,23 +148,22 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button
|
||||
className='title-bar-button'
|
||||
title={i18n.t('unMaximize')()}
|
||||
onClick={this.eventHandlers.onMaximize }
|
||||
onMouseDown={this.handleMouseDown}
|
||||
>
|
||||
<svg x='0px' y='0px' viewBox='0 0 14 10.2'>
|
||||
<path
|
||||
fill='rgba(255, 255, 255, 0.9)'
|
||||
d='M0,0v10.1h10.2V0H0z M9.2,9.2H1.1V1h8.1V9.2z'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
className='title-bar-button'
|
||||
title={i18n.t('unMaximize')()}
|
||||
onClick={this.eventHandlers.onMaximize}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
>
|
||||
<svg x='0px' y='0px' viewBox='0 0 14 10.2'>
|
||||
<path
|
||||
fill='rgba(255, 255, 255, 0.9)'
|
||||
d='M0,0v10.1h10.2V0H0z M9.2,9.2H1.1V1h8.1V9.2z'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@ interface ISSFWindow extends Window {
|
||||
|
||||
const ssfWindow: ISSFWindow = window;
|
||||
const appBridge = new AppBridge();
|
||||
const memoryInfoFetchInterval = 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* creates API exposed from electron.
|
||||
@ -87,5 +88,13 @@ ipcRenderer.on('page-load', (_event, { locale, resources, origin, enableCustomTi
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.initMainWindow,
|
||||
});
|
||||
|
||||
setInterval(async () => {
|
||||
const memoryInfo = await process.getProcessMemoryInfo();
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.memoryInfo,
|
||||
memoryInfo,
|
||||
});
|
||||
}, memoryInfoFetchInterval);
|
||||
}
|
||||
});
|
||||
|
@ -79,6 +79,13 @@ const throttledCloseScreenShareIndicator = throttle((streamId) => {
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const throttledSetIsInMeetingStatus = throttle((isInMeeting) => {
|
||||
local.ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.setIsInMeeting,
|
||||
isInMeeting,
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
let cryptoLib: ICryptoLib | null;
|
||||
try {
|
||||
cryptoLib = remote.require('../app/crypto-handler.js').cryptoLibrary;
|
||||
@ -134,17 +141,17 @@ export class SSFApi {
|
||||
/**
|
||||
* Method that returns various version info
|
||||
*/
|
||||
public getVersionInfo(): IVersionInfo {
|
||||
public getVersionInfo(): Promise<IVersionInfo> {
|
||||
const appName = remote.app.getName();
|
||||
const appVer = remote.app.getVersion();
|
||||
|
||||
return {
|
||||
return Promise.resolve({
|
||||
containerIdentifier: appName,
|
||||
containerVer: appVer,
|
||||
buildNumber,
|
||||
apiVer: '2.0.0',
|
||||
searchApiVer: '3.0.0',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,6 +272,14 @@ export class SSFApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the user is in an active meeting
|
||||
* will be used to handle memory refresh functionality
|
||||
*/
|
||||
public setIsInMeeting(isInMeeting): void {
|
||||
throttledSetIsInMeetingStatus(isInMeeting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a modal window to configure notification preference.
|
||||
*/
|
||||
|
@ -22,7 +22,7 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.snackbar {
|
||||
.SnackBar {
|
||||
font-family: @font-family;
|
||||
visibility: hidden;
|
||||
min-width: 250px;
|
||||
|
Loading…
Reference in New Issue
Block a user