mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
886 lines
24 KiB
TypeScript
886 lines
24 KiB
TypeScript
import {
|
|
app,
|
|
BrowserWindow,
|
|
dialog,
|
|
nativeImage,
|
|
screen,
|
|
shell,
|
|
} from 'electron';
|
|
import electron = require('electron');
|
|
import fetch from 'electron-fetch';
|
|
import * as filesize from 'filesize';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { format, parse } from 'url';
|
|
import { apiName } from '../common/api-interface';
|
|
|
|
import { isDevEnv, isLinux, isMac, isNodeEnv } from '../common/env';
|
|
import { i18n, LocaleType } from '../common/i18n';
|
|
import { logger } from '../common/logger';
|
|
import { getGuid } from '../common/utils';
|
|
import { whitelistHandler } from '../common/whitelist-handler';
|
|
import { autoLaunchInstance } from './auto-launch-controller';
|
|
import {
|
|
CloudConfigDataTypes,
|
|
config,
|
|
IConfig,
|
|
ICustomRectangle,
|
|
} from './config-handler';
|
|
import { downloadHandler, IDownloadItem } from './download-handler';
|
|
import { memoryMonitor } from './memory-monitor';
|
|
import { screenSnippet } from './screen-snippet-handler';
|
|
import { updateAlwaysOnTop } from './window-actions';
|
|
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
|
|
|
interface IStyles {
|
|
name: styleNames;
|
|
content: string;
|
|
}
|
|
|
|
enum styleNames {
|
|
titleBar = 'title-bar',
|
|
snackBar = 'snack-bar',
|
|
messageBanner = 'message-banner',
|
|
}
|
|
|
|
const checkValidWindow = true;
|
|
const { ctWhitelist } = config.getConfigFields(['ctWhitelist']);
|
|
|
|
// Network status check variables
|
|
const networkStatusCheckInterval = 10 * 1000;
|
|
let networkStatusCheckIntervalId;
|
|
let isNetworkMonitorInitialized = false;
|
|
|
|
const styles: IStyles[] = [];
|
|
const DOWNLOAD_MANAGER_NAMESPACE = 'DownloadManager';
|
|
|
|
/**
|
|
* Checks if window is valid and exists
|
|
*
|
|
* @param window {BrowserWindow}
|
|
* @return boolean
|
|
*/
|
|
export const windowExists = (window: BrowserWindow): boolean =>
|
|
!!window && typeof window.isDestroyed === 'function' && !window.isDestroyed();
|
|
|
|
/**
|
|
* Prevents window from navigating
|
|
*
|
|
* @param browserWindow
|
|
* @param isPopOutWindow
|
|
*/
|
|
export const preventWindowNavigation = (
|
|
browserWindow: BrowserWindow,
|
|
isPopOutWindow: boolean = false,
|
|
): void => {
|
|
if (!browserWindow || !windowExists(browserWindow)) {
|
|
return;
|
|
}
|
|
logger.info(
|
|
`window-utils: preventing window from navigating!`,
|
|
isPopOutWindow,
|
|
);
|
|
|
|
const listener = async (e: Electron.Event, winUrl: string) => {
|
|
if (!winUrl.startsWith('http' || 'https')) {
|
|
logger.error(
|
|
`window-utils: ${winUrl} doesn't start with http or https, so, not navigating!`,
|
|
);
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (!isPopOutWindow) {
|
|
const isValid = whitelistHandler.isWhitelisted(winUrl);
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
if (browserWindow && windowExists(browserWindow)) {
|
|
const response = await dialog.showMessageBox(browserWindow, {
|
|
type: 'warning',
|
|
buttons: ['OK'],
|
|
title: i18n.t('Not Allowed')(),
|
|
message: `${i18n.t(
|
|
`Sorry, you are not allowed to access this website`,
|
|
)()} (${winUrl}), ${i18n.t(
|
|
'please contact your administrator for more details',
|
|
)()}`,
|
|
});
|
|
logger.info(
|
|
`window-utils: received ${response} response from dialog`,
|
|
);
|
|
}
|
|
}
|
|
|
|
windowHandler.execCmd(windowHandler.screenShareIndicatorFrameUtil, []);
|
|
}
|
|
|
|
if (
|
|
browserWindow.isDestroyed() ||
|
|
browserWindow.webContents.isDestroyed() ||
|
|
winUrl === browserWindow.webContents.getURL()
|
|
) {
|
|
return;
|
|
}
|
|
};
|
|
|
|
browserWindow.webContents.on('will-navigate', listener);
|
|
|
|
browserWindow.once('close', () => {
|
|
browserWindow.webContents.removeListener('will-navigate', listener);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Creates components windows
|
|
*
|
|
* @param componentName
|
|
* @param opts
|
|
* @param shouldFocus {boolean}
|
|
*/
|
|
export const createComponentWindow = (
|
|
componentName: string,
|
|
opts?: Electron.BrowserWindowConstructorOptions,
|
|
shouldFocus: boolean = true,
|
|
): BrowserWindow => {
|
|
const options: Electron.BrowserWindowConstructorOptions = {
|
|
center: true,
|
|
height: 300,
|
|
maximizable: false,
|
|
minimizable: false,
|
|
resizable: false,
|
|
show: false,
|
|
width: 300,
|
|
...opts,
|
|
webPreferences: {
|
|
sandbox: !isNodeEnv,
|
|
nodeIntegration: isNodeEnv,
|
|
preload: path.join(__dirname, '../renderer/_preload-component.js'),
|
|
devTools: isDevEnv,
|
|
},
|
|
};
|
|
|
|
const browserWindow: ICustomBrowserWindow = new BrowserWindow(
|
|
options,
|
|
) as ICustomBrowserWindow;
|
|
if (shouldFocus) {
|
|
browserWindow.once('ready-to-show', () => {
|
|
if (!browserWindow || !windowExists(browserWindow)) {
|
|
return;
|
|
}
|
|
browserWindow.show();
|
|
});
|
|
}
|
|
browserWindow.webContents.once('did-finish-load', () => {
|
|
if (!browserWindow || !windowExists(browserWindow)) {
|
|
return;
|
|
}
|
|
browserWindow.webContents.send('page-load', {
|
|
locale: i18n.getLocale(),
|
|
resource: i18n.loadedResources,
|
|
});
|
|
});
|
|
browserWindow.setMenu(null as any);
|
|
|
|
const targetUrl = format({
|
|
pathname: require.resolve('../renderer/react-window.html'),
|
|
protocol: 'file',
|
|
query: {
|
|
componentName,
|
|
locale: i18n.getLocale(),
|
|
},
|
|
slashes: true,
|
|
});
|
|
|
|
browserWindow.loadURL(targetUrl);
|
|
preventWindowNavigation(browserWindow);
|
|
return browserWindow;
|
|
};
|
|
|
|
/**
|
|
* Shows the badge count
|
|
*
|
|
* @param count {number}
|
|
*/
|
|
export const showBadgeCount = (count: number): void => {
|
|
if (typeof count !== 'number') {
|
|
logger.warn(
|
|
`window-utils: badgeCount: invalid func arg, must be a number: ${count}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
logger.info(`window-utils: updating badge count to ${count}!`);
|
|
|
|
if (isMac || isLinux) {
|
|
// too big of a number here and setBadgeCount crashes
|
|
app.setBadgeCount(Math.min(1e8, count));
|
|
return;
|
|
}
|
|
|
|
// handle ms windows...
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
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, '');
|
|
};
|
|
|
|
/**
|
|
* Sets the data url
|
|
*
|
|
* @param dataUrl
|
|
* @param count
|
|
*/
|
|
export const setDataUrl = (dataUrl: string, count: number): void => {
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
if (mainWindow && dataUrl && count) {
|
|
const img = nativeImage.createFromDataURL(dataUrl);
|
|
// for accessibility screen readers
|
|
const desc = 'Symphony has ' + count + ' unread messages';
|
|
mainWindow.setOverlayIcon(img, desc);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Ensure events comes from a window that we have created.
|
|
*
|
|
* @param {BrowserWindow} browserWin node emitter event to be tested
|
|
* @return {Boolean} returns true if exists otherwise false
|
|
*/
|
|
export const isValidWindow = (
|
|
browserWin: Electron.BrowserWindow | null,
|
|
): boolean => {
|
|
if (!checkValidWindow) {
|
|
return true;
|
|
}
|
|
let result: boolean = false;
|
|
if (browserWin && !browserWin.isDestroyed()) {
|
|
// @ts-ignore
|
|
const winKey = browserWin.webContents.browserWindowOptions.winKey;
|
|
result = windowHandler.hasWindow(winKey, browserWin);
|
|
}
|
|
|
|
if (!result) {
|
|
logger.warn(
|
|
`window-utils: invalid window try to perform action, ignoring action`,
|
|
);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Updates the locale and rebuilds the entire application menu
|
|
*
|
|
* @param locale {LocaleType}
|
|
*/
|
|
export const updateLocale = async (locale: LocaleType): Promise<void> => {
|
|
logger.info(`window-utils: updating locale to ${locale}!`);
|
|
// sets the new locale
|
|
i18n.setLocale(locale);
|
|
const appMenu = windowHandler.appMenu;
|
|
if (appMenu) {
|
|
logger.info(`window-utils: updating app menu with locale ${locale}!`);
|
|
appMenu.update(locale);
|
|
}
|
|
|
|
if (i18n.isValidLocale(locale)) {
|
|
// Update user config file with latest locale changes
|
|
await config.updateUserConfig({ locale });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Displays a popup menu
|
|
*/
|
|
export const showPopupMenu = (opts: Electron.PopupOptions): void => {
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
if (mainWindow && windowExists(mainWindow) && isValidWindow(mainWindow)) {
|
|
const coordinates = windowHandler.isCustomTitleBar
|
|
? { x: 20, y: 15 }
|
|
: { x: 10, y: -20 };
|
|
const { x, y } = mainWindow.isFullScreen() ? { x: 0, y: 0 } : coordinates;
|
|
const popupOpts = { window: mainWindow, x, y };
|
|
const appMenu = windowHandler.appMenu;
|
|
if (appMenu) {
|
|
appMenu.popupMenu({ ...popupOpts, ...opts });
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Method that is invoked when the application is reloaded/navigated
|
|
* window.addEventListener('beforeunload')
|
|
*
|
|
* @param windowName {string}
|
|
*/
|
|
export const sanitize = (windowName: string): void => {
|
|
// To make sure the reload event is from the main window
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
if (mainWindow && windowName === mainWindow.winName) {
|
|
// reset the badge count whenever an user refreshes the electron client
|
|
showBadgeCount(0);
|
|
|
|
// Terminates the screen snippet process on reload
|
|
if (!isMac || !isLinux) {
|
|
screenSnippet.killChildProcess();
|
|
}
|
|
// Closes all the child windows
|
|
windowHandler.closeAllWindows();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the config stored rectangle if it is contained within the workArea of at
|
|
* least one of the screens else returns the default rectangle value with out x, y
|
|
* as the default is to center the window
|
|
*
|
|
* @param winPos {Electron.Rectangle}
|
|
* @param defaultWidth
|
|
* @param defaultHeight
|
|
* @return {x?: Number, y?: Number, width: Number, height: Number}
|
|
*/
|
|
export const getBounds = (
|
|
winPos: ICustomRectangle | Electron.Rectangle | undefined,
|
|
defaultWidth: number,
|
|
defaultHeight: number,
|
|
): Partial<Electron.Rectangle> => {
|
|
logger.info('window-utils: getBounds, winPos: ' + JSON.stringify(winPos));
|
|
|
|
if (!winPos || !winPos.x || !winPos.y || !winPos.width || !winPos.height) {
|
|
return { width: defaultWidth, height: defaultHeight };
|
|
}
|
|
const displays = screen.getAllDisplays();
|
|
|
|
for (let i = 0, len = displays.length; i < len; i++) {
|
|
const bounds = displays[i].bounds;
|
|
logger.info('window-utils: getBounds, bounds: ' + JSON.stringify(bounds));
|
|
if (
|
|
winPos.x >= bounds.x &&
|
|
winPos.y >= bounds.y &&
|
|
winPos.x + winPos.width <= bounds.x + bounds.width &&
|
|
winPos.y + winPos.height <= bounds.y + bounds.height
|
|
) {
|
|
return winPos;
|
|
}
|
|
}
|
|
|
|
// Fit in the middle of immediate display
|
|
const display = screen.getDisplayMatching(winPos as electron.Rectangle);
|
|
|
|
if (display) {
|
|
// Check that defaultWidth fits
|
|
let windowWidth = defaultWidth;
|
|
if (display.workArea.width < defaultWidth) {
|
|
windowWidth = display.workArea.width;
|
|
}
|
|
|
|
// Check that defaultHeight fits
|
|
let windowHeight = defaultHeight;
|
|
if (display.workArea.height < defaultHeight) {
|
|
windowHeight = display.workArea.height;
|
|
}
|
|
|
|
const windowX =
|
|
display.workArea.x + display.workArea.width / 2 - windowWidth / 2;
|
|
const windowY =
|
|
display.workArea.y + display.workArea.height / 2 - windowHeight / 2;
|
|
|
|
return { x: windowX, y: windowY, width: windowWidth, height: windowHeight };
|
|
}
|
|
|
|
return { width: defaultWidth, height: defaultHeight };
|
|
};
|
|
|
|
/**
|
|
* Open downloaded file
|
|
* @param type
|
|
* @param filePath
|
|
*/
|
|
export const downloadManagerAction = async (type, filePath): Promise<void> => {
|
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
|
const message = i18n.t(
|
|
'The file you are trying to open cannot be found in the specified path.',
|
|
DOWNLOAD_MANAGER_NAMESPACE,
|
|
)();
|
|
const title = i18n.t('File not Found', DOWNLOAD_MANAGER_NAMESPACE)();
|
|
|
|
if (!focusedWindow || !windowExists(focusedWindow)) {
|
|
return;
|
|
}
|
|
|
|
if (type === 'open') {
|
|
const fileExists = fs.existsSync(`${filePath}`);
|
|
let openFileResponse;
|
|
if (fileExists) {
|
|
openFileResponse = await shell.openPath(filePath);
|
|
}
|
|
if (
|
|
openFileResponse !== '' &&
|
|
focusedWindow &&
|
|
!focusedWindow.isDestroyed()
|
|
) {
|
|
dialog.showMessageBox(focusedWindow, {
|
|
message,
|
|
title,
|
|
type: 'error',
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (fs.existsSync(filePath)) {
|
|
shell.showItemInFolder(filePath);
|
|
} else {
|
|
dialog.showMessageBox(focusedWindow, {
|
|
message,
|
|
title,
|
|
type: 'error',
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Displays a UI with downloaded file similar to chrome
|
|
* Invoked by webContents session's will-download event
|
|
*
|
|
* @param _event
|
|
* @param item {Electron.DownloadItem}
|
|
* @param webContents {Electron.WebContents}
|
|
*/
|
|
export const handleDownloadManager = (
|
|
_event,
|
|
item: Electron.DownloadItem,
|
|
webContents: Electron.WebContents,
|
|
) => {
|
|
// Send file path when download is complete
|
|
item.once('done', (_e, state) => {
|
|
if (state === 'completed') {
|
|
const savePathSplit = item.getSavePath()?.split(path.sep);
|
|
const data: IDownloadItem = {
|
|
_id: getGuid(),
|
|
savedPath: item.getSavePath() || '',
|
|
total: filesize(item.getTotalBytes() || 0),
|
|
fileName: savePathSplit[savePathSplit.length - 1] || 'No name',
|
|
};
|
|
logger.info(
|
|
'window-utils: Download completed, informing download manager',
|
|
);
|
|
webContents.send('downloadCompleted', data);
|
|
downloadHandler.onDownloadSuccess(data);
|
|
} else {
|
|
logger.info('window-utils: Download failed, informing download manager');
|
|
downloadHandler.onDownloadFailed();
|
|
}
|
|
});
|
|
|
|
item.on('updated', (_e, state) => {
|
|
if (state === 'interrupted') {
|
|
logger.info('window-utils: Download is interrupted but can be resumed');
|
|
} else if (state === 'progressing') {
|
|
if (item.isPaused()) {
|
|
logger.info('window-utils: Download is paused');
|
|
} else {
|
|
logger.info(`window-utils: Received bytes: ${item.getReceivedBytes()}`);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Inserts css in to the window
|
|
*
|
|
* @param window {BrowserWindow}
|
|
*/
|
|
const readAndInsertCSS = async (window): Promise<IStyles[] | void> => {
|
|
if (window && windowExists(window)) {
|
|
return styles.map(({ content }) => window.webContents.insertCSS(content));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Inserts all the required css on to the specified windows
|
|
*
|
|
* @param mainWindow {BrowserWindow}
|
|
* @param isCustomTitleBar {boolean} - whether custom title bar enabled
|
|
*/
|
|
export const injectStyles = async (
|
|
mainWindow: BrowserWindow,
|
|
isCustomTitleBar: boolean,
|
|
): Promise<IStyles[] | void> => {
|
|
if (isCustomTitleBar) {
|
|
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(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// 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(),
|
|
});
|
|
}
|
|
|
|
// Banner styles
|
|
if (
|
|
styles.findIndex(({ name }) => name === styleNames.messageBanner) === -1
|
|
) {
|
|
styles.push({
|
|
name: styleNames.messageBanner,
|
|
content: fs
|
|
.readFileSync(
|
|
path.join(__dirname, '..', '/renderer/styles/message-banner.css'),
|
|
'utf8',
|
|
)
|
|
.toString(),
|
|
});
|
|
}
|
|
|
|
await readAndInsertCSS(mainWindow);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* Proxy verification for root certificates
|
|
*
|
|
* @param request {any}
|
|
* @param callback {(verificationResult: number) => void}
|
|
*/
|
|
export const handleCertificateProxyVerification = (
|
|
request: any,
|
|
callback: (verificationResult: number) => void,
|
|
): void => {
|
|
const { hostname: hostUrl, errorCode } = request;
|
|
|
|
if (errorCode === 0) {
|
|
return callback(0);
|
|
}
|
|
|
|
const { tld, domain } = whitelistHandler.parseDomain(hostUrl);
|
|
const host = domain + tld;
|
|
|
|
if (
|
|
ctWhitelist &&
|
|
Array.isArray(ctWhitelist) &&
|
|
ctWhitelist.length > 0 &&
|
|
ctWhitelist.indexOf(host) > -1
|
|
) {
|
|
return callback(0);
|
|
}
|
|
|
|
return callback(-2);
|
|
};
|
|
|
|
/**
|
|
* Validates the network by fetching the pod url
|
|
* every 10sec, on active reloads the given window
|
|
*
|
|
* @param window {ICustomBrowserWindow}
|
|
* @param url Pod URL to load
|
|
*/
|
|
export const isSymphonyReachable = (
|
|
window: ICustomBrowserWindow | null,
|
|
url: string,
|
|
) => {
|
|
if (networkStatusCheckIntervalId) {
|
|
return;
|
|
}
|
|
if (!window || !windowExists(window)) {
|
|
return;
|
|
}
|
|
networkStatusCheckIntervalId = setInterval(() => {
|
|
const { hostname, protocol } = parse(url);
|
|
if (!hostname || !protocol) {
|
|
return;
|
|
}
|
|
const podUrl = `${protocol}//${hostname}`;
|
|
logger.info(`window-utils: checking to see if pod ${podUrl} is reachable!`);
|
|
fetch(podUrl, { method: 'GET' })
|
|
.then((rsp) => {
|
|
if (rsp.status === 200 && windowHandler.isOnline) {
|
|
logger.info(
|
|
`window-utils: pod ${podUrl} is reachable, loading main window!`,
|
|
);
|
|
windowHandler.reloadSymphony();
|
|
if (networkStatusCheckIntervalId) {
|
|
clearInterval(networkStatusCheckIntervalId);
|
|
networkStatusCheckIntervalId = null;
|
|
}
|
|
return;
|
|
}
|
|
logger.warn(
|
|
`window-utils: POD is down! statusCode: ${rsp.status}, is online: ${windowHandler.isOnline}`,
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
logger.error(
|
|
`window-utils: Network status check: No active network connection ${error}`,
|
|
);
|
|
});
|
|
}, networkStatusCheckInterval);
|
|
};
|
|
|
|
/**
|
|
* Refreshes/Restarts the window based on type
|
|
*
|
|
* @param browserWindow {ICustomBrowserWindow}
|
|
*/
|
|
export const reloadWindow = (browserWindow: ICustomBrowserWindow) => {
|
|
if (!browserWindow || !windowExists(browserWindow)) {
|
|
return;
|
|
}
|
|
|
|
const windowName = browserWindow.winName;
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
// reload the main window
|
|
if (windowName === apiName.mainWindowName) {
|
|
logger.info(`window-utils: reloading the main window`);
|
|
browserWindow.reload();
|
|
|
|
windowHandler.closeAllWindows();
|
|
|
|
windowHandler.execCmd(windowHandler.screenShareIndicatorFrameUtil, []);
|
|
|
|
return;
|
|
}
|
|
// Send an event to SFE that restarts the pop-out window
|
|
if (mainWindow && windowExists(mainWindow)) {
|
|
logger.info(`window-handler: reloading the window`, { windowName });
|
|
const bounds = browserWindow.getBounds();
|
|
mainWindow.webContents.send('restart-floater', { windowName, bounds });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Restrict the zoom in in 2.0
|
|
* @returns void
|
|
*/
|
|
export const zoomIn = () => {
|
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
|
if (!focusedWindow || !windowExists(focusedWindow)) {
|
|
return;
|
|
}
|
|
|
|
if (focusedWindow.getTitle() === 'Screen Sharing Indicator - Symphony') {
|
|
return;
|
|
}
|
|
|
|
const { webContents } = focusedWindow;
|
|
if (windowHandler.isMana) {
|
|
const zoomFactor = webContents.getZoomFactor();
|
|
if (zoomFactor < 1.5) {
|
|
if (zoomFactor < 0.7) {
|
|
webContents.setZoomFactor(0.7);
|
|
} else if (zoomFactor >= 0.7 && zoomFactor < 0.8) {
|
|
webContents.setZoomFactor(0.8);
|
|
} else if (zoomFactor >= 0.8 && zoomFactor < 0.9) {
|
|
webContents.setZoomFactor(0.9);
|
|
} else if (zoomFactor >= 0.9 && zoomFactor < 1.0) {
|
|
webContents.setZoomFactor(1.0);
|
|
} else if (zoomFactor >= 1.0 && zoomFactor < 1.1) {
|
|
webContents.setZoomFactor(1.1);
|
|
} else if (zoomFactor >= 1.1 && zoomFactor < 1.25) {
|
|
webContents.setZoomFactor(1.25);
|
|
} else if (zoomFactor >= 1.25 && zoomFactor < 1.5) {
|
|
webContents.setZoomFactor(1.5);
|
|
}
|
|
}
|
|
} else {
|
|
const currentZoomLevel = focusedWindow.webContents.getZoomLevel();
|
|
focusedWindow.webContents.setZoomLevel(currentZoomLevel + 0.5);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Restrict the zoom out in 2.0
|
|
* @returns void
|
|
*/
|
|
export const zoomOut = () => {
|
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
|
if (!focusedWindow || !windowExists(focusedWindow)) {
|
|
return;
|
|
}
|
|
|
|
if (focusedWindow.getTitle() === 'Screen Sharing Indicator - Symphony') {
|
|
return;
|
|
}
|
|
|
|
const { webContents } = focusedWindow;
|
|
if (windowHandler.isMana) {
|
|
const zoomFactor = webContents.getZoomFactor();
|
|
if (zoomFactor > 0.7) {
|
|
if (zoomFactor > 1.5) {
|
|
webContents.setZoomFactor(1.5);
|
|
} else if (zoomFactor > 1.25 && zoomFactor <= 1.5) {
|
|
webContents.setZoomFactor(1.25);
|
|
} else if (zoomFactor > 1.1 && zoomFactor <= 1.25) {
|
|
webContents.setZoomFactor(1.1);
|
|
} else if (zoomFactor > 1.0 && zoomFactor <= 1.1) {
|
|
webContents.setZoomFactor(1.0);
|
|
} else if (zoomFactor > 0.9 && zoomFactor <= 1.0) {
|
|
webContents.setZoomFactor(0.9);
|
|
} else if (zoomFactor > 0.8 && zoomFactor <= 0.9) {
|
|
webContents.setZoomFactor(0.8);
|
|
} else if (zoomFactor > 0.7 && zoomFactor <= 0.8) {
|
|
webContents.setZoomFactor(0.7);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Verifies if window exists and restores/focuses the window
|
|
*
|
|
* @param browserWindow {ICustomBrowserWindow}
|
|
*/
|
|
export const didVerifyAndRestoreWindow = (
|
|
browserWindow: BrowserWindow | null,
|
|
): boolean => {
|
|
if (!browserWindow || !windowExists(browserWindow)) {
|
|
return false;
|
|
}
|
|
if (browserWindow.isMinimized()) {
|
|
browserWindow.restore();
|
|
}
|
|
browserWindow.focus();
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Finds and returns a specific window by name
|
|
*
|
|
* @param windowName {String}
|
|
*/
|
|
export const getWindowByName = (
|
|
windowName: string,
|
|
): BrowserWindow | undefined => {
|
|
const allWindows = BrowserWindow.getAllWindows();
|
|
return allWindows.find((window) => {
|
|
return (window as ICustomBrowserWindow).winName === windowName;
|
|
});
|
|
};
|
|
|
|
export const updateFeaturesForCloudConfig = async (): Promise<void> => {
|
|
const {
|
|
alwaysOnTop: isAlwaysOnTop,
|
|
launchOnStartup,
|
|
memoryRefresh,
|
|
memoryThreshold,
|
|
} = config.getConfigFields([
|
|
'launchOnStartup',
|
|
'alwaysOnTop',
|
|
'memoryRefresh',
|
|
'memoryThreshold',
|
|
]) as IConfig;
|
|
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
|
|
// Update Always on top feature
|
|
await updateAlwaysOnTop(
|
|
isAlwaysOnTop === CloudConfigDataTypes.ENABLED,
|
|
false,
|
|
false,
|
|
);
|
|
|
|
// Update launch on start up
|
|
launchOnStartup === CloudConfigDataTypes.ENABLED
|
|
? autoLaunchInstance.enableAutoLaunch()
|
|
: autoLaunchInstance.disableAutoLaunch();
|
|
|
|
if (mainWindow && windowExists(mainWindow)) {
|
|
if (memoryRefresh) {
|
|
logger.info(
|
|
`window-utils: updating the memory threshold`,
|
|
memoryThreshold,
|
|
);
|
|
memoryMonitor.setMemoryThreshold(parseInt(memoryThreshold, 10));
|
|
mainWindow.webContents.send('initialize-memory-refresh');
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Monitors network requests and displays red banner on failure
|
|
* @param url: Pod URL to be loaded after network is active again
|
|
*/
|
|
export const monitorNetworkInterception = (url: string) => {
|
|
if (isNetworkMonitorInitialized) {
|
|
return;
|
|
}
|
|
const { hostname, protocol } = parse(url);
|
|
|
|
if (!hostname || !protocol) {
|
|
return;
|
|
}
|
|
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
const podUrl = `${protocol}//${hostname}/`;
|
|
logger.info('window-utils: monitoring network interception for url', podUrl);
|
|
|
|
// Filter applied w.r.t pod url
|
|
const filter = { urls: [podUrl + '*'] };
|
|
|
|
if (mainWindow && windowExists(mainWindow)) {
|
|
isNetworkMonitorInitialized = true;
|
|
mainWindow.webContents.session.webRequest.onErrorOccurred(
|
|
filter,
|
|
async (details) => {
|
|
if (!mainWindow || !windowExists(mainWindow)) {
|
|
return;
|
|
}
|
|
if (
|
|
!windowHandler.isMana &&
|
|
windowHandler.isWebPageLoading &&
|
|
(details.error === 'net::ERR_INTERNET_DISCONNECTED' ||
|
|
details.error === 'net::ERR_NETWORK_CHANGED' ||
|
|
details.error === 'net::ERR_NAME_NOT_RESOLVED')
|
|
) {
|
|
logger.error(`window-utils: URL failed to load`, details);
|
|
mainWindow.webContents.send('show-banner', {
|
|
show: true,
|
|
bannerType: 'error',
|
|
url: podUrl,
|
|
});
|
|
}
|
|
},
|
|
);
|
|
}
|
|
};
|