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:
Kiran Niranjan 2019-04-12 16:11:04 +05:30 committed by Vishwas Shashidhar
parent b9bc65008d
commit 6aca522a1e
21 changed files with 518 additions and 191 deletions

View File

@ -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),
});
}
};

View File

@ -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');

View File

@ -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;

View File

@ -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;

View File

@ -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(),

View File

@ -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();
}
}
}

View File

@ -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:
}

View File

@ -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
View 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 };

View File

@ -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) {

View File

@ -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 });

View File

@ -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 ];

View File

@ -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}`);
});

View File

@ -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 = [];
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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>
);
}
/**

View File

@ -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);
}
});

View File

@ -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.
*/

View File

@ -22,7 +22,7 @@
opacity: 0;
}
}
.snackbar {
.SnackBar {
font-family: @font-family;
visibility: hidden;
min-width: 250px;