import { ExecException , execFile } from 'child_process' ; import { app , BrowserView , BrowserWindow , BrowserWindowConstructorOptions , crashReporter , DesktopCapturerSource , dialog , Event , globalShortcut , ipcMain , RenderProcessGoneDetails , screen , shell , WebContents , } from 'electron' ; import * as fs from 'fs' ; import * as path from 'path' ; import { format , parse } from 'url' ; import { apiName , Themes , WindowTypes } from '../common/api-interface' ; import { isDevEnv , isLinux , isMac , isNodeEnv , isWindowsOS , } from '../common/env' ; import { i18n , LocaleType } from '../common/i18n' ; import { logger } from '../common/logger' ; import { calculatePercentage , getCommandLineArgs , getGuid , } from '../common/utils' ; import { notification } from '../renderer/notification' ; import { cleanAppCacheOnCrash } from './app-cache-handler' ; import { AppMenu } from './app-menu' ; import { handleChildWindow } from './child-window-handler' ; import { CloudConfigDataTypes , config , IConfig , IGlobalConfig , } from './config-handler' ; import crashHandler from './crash-handler' ; import { mainEvents } from './main-event-handler' ; import { SpellChecker } from './spell-check-handler' ; import { checkIfBuildExpired } from './ttl-handler' ; import { versionHandler } from './version-handler' ; import { handlePermissionRequests , monitorWindowActions , onConsoleMessages , } from './window-actions' ; import { createComponentWindow , didVerifyAndRestoreWindow , getBounds , getWindowByName , handleCertificateProxyVerification , handleDownloadManager , injectStyles , isSymphonyReachable , loadBrowserViews , monitorNetworkInterception , preventWindowNavigation , reloadWindow , resetZoomLevel , viewExists , windowExists , zoomIn , zoomOut , } from './window-utils' ; const windowSize = getCommandLineArgs ( process . argv , '--window-size' , false ) ; enum ClientSwitchType { CLIENT_1_5 = 'CLIENT_1_5' , CLIENT_2_0 = 'CLIENT_2_0' , CLIENT_2_0_DAILY = 'CLIENT_2_0_DAILY' } const MAIN_WEB_CONTENTS_EVENTS = [ 'enter-full-screen' , 'leave-full-screen' ] ; export interface ICustomBrowserWindowConstructorOpts extends Electron . BrowserWindowConstructorOptions { winKey : string ; } export interface ICustomBrowserWindow extends Electron . BrowserWindow { winName : string ; notificationData ? : object ; origin ? : string ; } export interface ICustomBrowserView extends Electron . BrowserView { winName : string ; notificationData ? : object ; origin ? : string ; } export const DEFAULT_WIDTH = 900 ; export const DEFAULT_HEIGHT = 900 ; export const DEFAULT_WELCOME_SCREEN_WIDTH = 542 ; export const DEFAULT_WELCOME_SCREEN_HEIGHT = 333 ; export const TITLE_BAR_HEIGHT = 32 ; const LISTEN_TIMEOUT = 25 * 1000 ; export class WindowHandler { param configURL { string } private static getValidUrl ( configURL ) { const parsedUrl = parse ( configURL ) ; if ( ! parsedUrl . protocol || parsedUrl . protocol !== 'https' ) { parsedUrl . protocol = 'https:' ; parsedUrl . slashes = true ; } return format ( parsedUrl ) ; } public mainView : ICustomBrowserView | null ; public titleBarView : ICustomBrowserView | null ; public mainWebContents : WebContents | undefined ; public appMenu : AppMenu | null ; public isAutoReload : boolean ; public isOnline : boolean ; public url : string | undefined ; public startUrl ! : string ; public isMana : boolean = false ; public willQuitApp : boolean = false ; public spellchecker : SpellChecker | undefined ; public isCustomTitleBar : boolean ; public isWebPageLoading : boolean = true ; public isLoggedIn : boolean = false ; public screenShareIndicatorFrameUtil : string ; private readonly defaultPodUrl : string = 'https://[POD].symphony.com' ; private readonly contextIsolation : boolean ; private readonly backgroundThrottling : boolean ; private readonly windowOpts : ICustomBrowserWindowConstructorOpts ; private readonly globalConfig : IGlobalConfig ; private readonly userConfig : IConfig ; private readonly config : IConfig ; private readonly windows : object ; private loadFailError : string | undefined ; private mainWindow : ICustomBrowserWindow | null = null ; private aboutAppWindow : Electron . BrowserWindow | null = null ; private welcomeScreenWindow : Electron . BrowserWindow | null = null ; private screenPickerWindow : Electron . BrowserWindow | null = null ; private screenSharingIndicatorWindow : Electron . BrowserWindow | null = null ; private screenSharingFrameWindow : Electron . BrowserWindow | null = null ; private basicAuthWindow : Electron . BrowserWindow | null = null ; private notificationSettingsWindow : Electron . BrowserWindow | null = null ; private snippingToolWindow : Electron . BrowserWindow | null = null ; private finishedLoading : boolean ; constructor ( opts ? ) { this . config = config . getConfigFields ( [ 'isCustomTitleBar' , 'mainWinPos' , 'minimizeOnClose' , 'notificationSettings' , 'alwaysOnTop' , 'locale' , 'customFlags' , 'clientSwitch' , 'enableRendererLogs' , ] ) ; logger . info ( `window-handler: main windows initialized with following config data` , this . config ) ; this . globalConfig = config . getGlobalConfigFields ( [ 'url' , 'contextIsolation' , 'contextOriginUrl' , 'overrideUserAgent' , ] ) ; this . userConfig = config . getUserConfigFields ( [ 'url' ] ) ; const { customFlags } = this . config ; const { disableThrottling } = config . getCloudConfigFields ( [ 'disableThrottling' , ] ) as any ; this . windows = { } ; this . contextIsolation = true ; if ( this . globalConfig . contextIsolation !== undefined ) { this . contextIsolation = this . globalConfig . contextIsolation ; } this . backgroundThrottling = customFlags . disableThrottling !== CloudConfigDataTypes . ENABLED || disableThrottling !== CloudConfigDataTypes . ENABLED ; this . isCustomTitleBar = isWindowsOS && this . config . isCustomTitleBar === CloudConfigDataTypes . ENABLED ; this . windowOpts = { ... this . getWindowOpts ( { alwaysOnTop : this . config . alwaysOnTop === CloudConfigDataTypes . ENABLED || false , frame : ! this . isCustomTitleBar , minHeight : 300 , minWidth : 300 , title : 'Symphony' , } , { preload : path . join ( __dirname , '../renderer/_preload-main.js' ) , } ) , ... opts , } ; this . isAutoReload = false ; this . isOnline = true ; this . finishedLoading = false ; this . screenShareIndicatorFrameUtil = '' ; if ( isWindowsOS ) { this . screenShareIndicatorFrameUtil = isDevEnv ? path . join ( __dirname , '../../../node_modules/screen-share-indicator-frame/ScreenShareIndicatorFrame.exe' ) : path . join ( path . dirname ( app . getPath ( 'exe' ) ) , 'ScreenShareIndicatorFrame.exe' ) ; } else if ( isMac ) { this . screenShareIndicatorFrameUtil = isDevEnv ? path . join ( __dirname , '../../../node_modules/screen-share-indicator-frame/SymphonyScreenShareIndicator' ) : path . join ( path . dirname ( app . getPath ( 'exe' ) ) , '../node_modules/screen-share-indicator-frame/SymphonyScreenShareIndicator' ) ; } this . appMenu = null ; this . mainView = null ; this . titleBarView = null ; const locale = ( this . config . locale || app . getLocale ( ) ) as LocaleType ; i18n . setLocale ( locale ) ; this . listenForLoad ( ) ; } /**
     * Starting point of the app
     */ public async createApplication ( ) { this . spellchecker = new SpellChecker ( ) ; logger . info ( `window-handler: initialized spellchecker module with locale ${ this . spellchecker . locale }` ) ; logger . info ( 'window-handler: createApplication mainWinPos: ' + JSON . stringify ( this . config . mainWinPos ) ) ; const { isFullScreen , isMaximized } = this . config . mainWinPos ? this . config . mainWinPos : { isFullScreen : false , isMaximized : false } ; this . url = WindowHandler . getValidUrl ( this . userConfig . url ? this . userConfig . url : this . globalConfig . url ) ; logger . info ( `window-handler: setting url ${ this . url } from config file!` ) ; const urlFromCmd = getCommandLineArgs ( process . argv , '--url=' , false ) ; if ( config . isFirstTimeLaunch ( ) && this . globalConfig . url . indexOf ( 'https://my.symphony.com' ) >= 0 && urlFromCmd === null ) { this . url = this . defaultPodUrl ; this . showWelcomeScreen ( ) ; return ; } this . mainWindow = new BrowserWindow ( { ... this . windowOpts , ... getBounds ( this . config . mainWinPos , DEFAULT_WIDTH , DEFAULT_HEIGHT ) , } ) as ICustomBrowserWindow ; logger . info ( 'window-handler: windowSize: ' + JSON . stringify ( windowSize ) ) ; if ( windowSize ) { const args = windowSize . split ( '=' ) ; const sizes = args [ 1 ] . split ( ',' ) ; logger . info ( 'window-handler: windowSize: args: ' + JSON . stringify ( args ) ) ; logger . info ( 'window-handler: windowSize: sizes: ' + JSON . stringify ( sizes ) ) ; if ( this . mainWindow && windowExists ( this . mainWindow ) ) { this . mainWindow . setSize ( Number ( sizes [ 0 ] ) , Number ( sizes [ 1 ] ) ) ; } } if ( isWindowsOS && this . mainWindow && windowExists ( this . mainWindow ) ) { this . mainWindow . setMenuBarVisibility ( false ) ; } logger . info ( 'window-handler: this.mainWindow.getBounds: ' + JSON . stringify ( this . mainWindow . getBounds ( ) ) ) ; this . mainWindow . winName = apiName . mainWindowName ; if ( urlFromCmd ) { const commandLineUrl = urlFromCmd . substr ( 6 ) ; logger . info ( `window-handler: trying to set url ${ commandLineUrl } from command line.` ) ; const { podWhitelist } = config . getConfigFields ( [ 'podWhitelist' ] ) ; logger . info ( `window-handler: checking pod whitelist.` ) ; if ( podWhitelist . length > 0 ) { logger . info ( `window-handler: pod whitelist is not empty ${ podWhitelist }` ) ; if ( podWhitelist . includes ( commandLineUrl ) ) { logger . info ( `window-handler: url from command line is whitelisted in the config file.` ) ; logger . info ( `window-handler: setting ${ commandLineUrl } from the command line as the main window url.` ) ; this . url = commandLineUrl ; } else { logger . info ( `window-handler: url ${ commandLineUrl } from command line is NOT WHITELISTED in the config file.` ) ; } } else { logger . info ( `window-handler: setting ${ commandLineUrl } from the command line as the main window url since pod whitelist is empty.` ) ; this . url = commandLineUrl ; } } const isMaximizedFlag = getCommandLineArgs ( process . argv , '--maximized' , true ) ; if ( isMaximized || isMaximizedFlag ) { this . mainWindow . maximize ( ) ; logger . info ( `window-handler: window is maximized!` , isMaximized , isMaximizedFlag ) ; } if ( isFullScreen ) { logger . info ( `window-handler: window is in full screen!` ) ; this . mainWindow . setFullScreen ( true ) ; } this . startUrl = this . url ; cleanAppCacheOnCrash ( this . mainWindow ) ; logger . info ( `Loading main window with url ${ this . url }` ) ; const userAgent = this . getUserAgent ( this . mainWindow . webContents ) ; if ( this . config . isCustomTitleBar === CloudConfigDataTypes . ENABLED && isWindowsOS && this . mainWindow && windowExists ( this . mainWindow ) ) { this . mainWebContents = await loadBrowserViews ( this . mainWindow ) ; this . mainWebContents . loadURL ( this . url , { userAgent } ) ; } else { this . mainWindow . loadURL ( this . url , { userAgent } ) ; this . mainWebContents = this . mainWindow . webContents ; } this . checkExpiry ( this . mainWindow ) ; this . updateVersionInfo ( ) ; this . mainWindow . origin = this . globalConfig . contextOriginUrl || this . url ; this . mainWebContents . once ( 'did-start-loading' , ( ) => { logger . info ( `window-handler: main window web contents started loading for url ${ this . mainView ?. webContents . getURL ( ) }!` ) ; this . finishedLoading = false ; this . listenForLoad ( ) ; monitorNetworkInterception ( this . url || this . userConfig . url || this . globalConfig . url ) ; } ) ; const logEvents = [ 'did-fail-provisional-load' , 'did-frame-finish-load' , 'did-start-loading' , 'did-stop-loading' , 'will-redirect' , 'did-navigate' , 'did-navigate-in-page' , 'preload-error' , ] ; logEvents . forEach ( ( windowEvent ) => { this . mainWindow ?. webContents . on ( windowEvent , ( ) => { logger . info ( `window-handler: Main Window Event Occurred: ${ windowEvent }` ) ; } ) ; } ) ; this . mainWindow . once ( 'ready-to-show' , ( event ) => { logger . info ( `window-handler: Main Window ready to show: ${ event }` ) ; } ) ; this . mainWebContents . on ( 'did-fail-load' , ( _ , errorCode , errorDescription ) => { logger . error ( `window-handler: Fail loading - Error code: ${ errorCode }. Error desscription: ${ errorDescription }` ) ; } ) ; this . mainWebContents . on ( 'did-finish-load' , async ( ) => { this . isMana = false ; logger . info ( `window-handler: main window web contents finished loading!` ) ; if ( ! this . mainWebContents || this . mainWebContents . isDestroyed ( ) ) { logger . info ( `window-handler: main window web contents destroyed already! exiting` ) ; return ; } this . finishedLoading = true ; this . url = this . mainWebContents ?. getURL ( ) ; if ( this . url ?. indexOf ( 'about:blank' ) === 0 ) { logger . info ( `Looks like about:blank got loaded which may lead to blank screen` ) ; logger . info ( `Reloading the app to check if it resolves the issue` ) ; const url = this . userConfig . url || this . globalConfig . url ; const userAgent = this . getUserAgent ( this . mainWebContents ) ; await this . mainWebContents ?. loadURL ( url , { userAgent } ) ; return ; } logger . info ( 'window-handler: did-finish-load, url: ' + this . url ) ; if ( this . mainWebContents && ! this . mainWebContents . isDestroyed ( ) ) { await injectStyles ( this . mainWebContents , this . isCustomTitleBar ) ; this . mainWebContents . send ( 'page-load' , { isWindowsOS , locale : i18n . getLocale ( ) , resources : i18n . loadedResources , isMainWindow : true , } ) ; this . appMenu = new AppMenu ( ) ; const { permissions } = config . getConfigFields ( [ 'permissions' ] ) ; this . mainWebContents . send ( 'is-screen-share-enabled' , permissions . media ) ; mainEvents . subscribeMultipleEvents ( MAIN_WEB_CONTENTS_EVENTS , this . mainWebContents ) ; } } ) ; this . mainWebContents . on ( 'did-fail-load' , ( _event , errorCode , errorDesc , validatedURL ) => { logger . error ( `window-handler: Failed to load ${ validatedURL }, with an error: ${ errorCode }::${ errorDesc }` ) ; this . loadFailError = errorDesc ; } ) ; this . mainWebContents . on ( 'did-stop-loading' , async ( ) => { if ( this . mainWindow && windowExists ( this . mainWindow ) ) { this . mainWebContents ?. send ( 'page-load-failed' , { locale : i18n . getLocale ( ) , resources : i18n . loadedResources , } ) ; const href = await this . mainWebContents ?. executeJavaScript ( 'document.location.href' ) ; try { if ( href === 'data:text/html,chromewebdata' || href === 'chrome-error://chromewebdata/' ) { if ( this . mainWindow && windowExists ( this . mainWindow ) ) { this . mainWebContents ?. insertCSS ( fs . readFileSync ( path . join ( __dirname , '..' , '/renderer/styles/network-error.css' ) , 'utf8' ) . toString ( ) ) ; this . mainWebContents ?. send ( 'network-error' , { error : this . loadFailError , } ) ; isSymphonyReachable ( this . mainWindow , this . url || this . userConfig . url || this . globalConfig . url ) ; } } } catch ( error ) { logger . error ( `window-handler: Could not read document.location` , error ) ; } } this . registerGlobalShortcuts ( ) ; } ) ; this . mainWebContents . on ( 'render-process-gone' , async ( _event , details ) => { if ( details . reason === 'killed' ) { logger . info ( `window-handler: main window crashed (killed)!` ) ; return ; } logger . info ( `window-handler: main window crashed!` ) ; const { response } = await 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' ] , } ) ; if ( ! this . mainWindow || ! windowExists ( this . mainWindow ) ) { return ; } response === 0 ? this . mainWindow . reload ( ) : this . mainWindow . close ( ) ; } ) ; this . mainWindow . on ( 'session-end' , ( ) => { logger . info ( `window-handler: session-end received` ) ; app . quit ( ) ; } ) ; this . mainWindow . on ( 'close' , ( event ) => { if ( ! this . mainWindow || ! windowExists ( this . mainWindow ) ) { return ; } if ( this . willQuitApp ) { logger . info ( `window-handler: app is quitting, destroying all windows!` ) ; if ( this . mainWindow && ! this . mainWebContents ?. isDestroyed ( ) && this . mainWebContents ?. isDevToolsOpened ( ) ) { this . mainWebContents ?. closeDevTools ( ) ; } return this . destroyAllWindows ( ) ; } const { minimizeOnClose } = config . getConfigFields ( [ 'minimizeOnClose' ] ) ; if ( minimizeOnClose === CloudConfigDataTypes . ENABLED ) { event . preventDefault ( ) ; this . mainWindow . minimize ( ) ; return ; } if ( isMac ) { event . preventDefault ( ) ; this . mainWindow . hide ( ) ; return ; } app . quit ( ) ; } ) ; this . mainWindow . once ( 'closed' , ( ) => { logger . info ( `window-handler: main window closed, destroying all windows!` ) ; if ( isWindowsOS || isMac ) { this . execCmd ( this . screenShareIndicatorFrameUtil , [ ] ) ; } this . closeAllWindows ( ) ; this . destroyAllWindows ( ) ; } ) ; crashReporter . start ( { submitURL : '' , uploadToServer : false , ignoreSystemCrashHandler : false , } ) ; crashHandler . handleRendererCrash ( this . mainWindow ) ; ipcMain . on ( 'reload-symphony' , ( ) => { this . reloadSymphony ( ) ; } ) ; if ( ! isDevEnv ) { this . mainWebContents . session . setCertificateVerifyProc ( handleCertificateProxyVerification ) ; } app . on ( 'browser-window-focus' , ( ) => { this . registerGlobalShortcuts ( ) ; } ) ; app . on ( 'browser-window-blur' , ( ) => { this . unregisterGlobalShortcuts ( ) ; } ) ; preventWindowNavigation ( this . mainWindow , false ) ; handlePermissionRequests ( this . mainWebContents ) ; monitorWindowActions ( this . mainWindow ) ; this . mainWebContents . session . on ( 'will-download' , handleDownloadManager ) ; this . addWindow ( this . windowOpts . winKey , this . mainWindow ) ; handleChildWindow ( this . mainWebContents ) ; if ( this . config . enableRendererLogs ) { this . mainWebContents . on ( 'console-message' , onConsoleMessages ) ; } return this . mainWindow ; } /**
     * Handles the use case of showing
     * welcome screen for first time installs
     */ public showWelcomeScreen ( ) { if ( ! this . url ) { return ; } const opts = this . getWindowOpts ( { width : DEFAULT_WELCOME_SCREEN_WIDTH , height : DEFAULT_WELCOME_SCREEN_HEIGHT , frame : ! this . isCustomTitleBar , alwaysOnTop : isMac , resizable : false , minimizable : false , fullscreenable : false , } , { devTools : isDevEnv , } ) ; this . welcomeScreenWindow = createComponentWindow ( 'welcome' , opts ) ; ( this . welcomeScreenWindow as ICustomBrowserWindow ) . winName = apiName . welcomeScreenName ; if ( this . config . isCustomTitleBar === CloudConfigDataTypes . ENABLED && isWindowsOS ) { const titleBarView = new BrowserView ( { webPreferences : { sandbox : ! isNodeEnv , nodeIntegration : isNodeEnv , preload : path . join ( __dirname , '../renderer/_preload-component.js' ) , devTools : isDevEnv , } , } ) as ICustomBrowserView ; const titleBarWindowUrl = format ( { pathname : require . resolve ( '../renderer/react-window.html' ) , protocol : 'file' , query : { componentName : 'title-bar' , locale : i18n . getLocale ( ) , title : i18n . t ( 'WelcomeText' , 'Welcome' ) ( ) , } , slashes : true , } ) ; titleBarView . webContents . once ( 'did-finish-load' , async ( ) => { if ( ! titleBarView || titleBarView . webContents . isDestroyed ( ) ) { return ; } titleBarView ?. webContents . send ( 'page-load' , { isWindowsOS , locale : i18n . getLocale ( ) , resource : i18n . loadedResources , isMainWindow : true , } ) ; titleBarView . webContents . send ( 'disable-action-button' ) ; } ) ; titleBarView . webContents . loadURL ( titleBarWindowUrl ) ; titleBarView . setBounds ( { x : 0 , y : 0 , height : TITLE_BAR_HEIGHT , width : DEFAULT_WELCOME_SCREEN_WIDTH , } ) ; this . welcomeScreenWindow . setBrowserView ( titleBarView ) ; } this . welcomeScreenWindow . webContents . on ( 'did-finish-load' , ( ) => { if ( ! this . welcomeScreenWindow || this . welcomeScreenWindow . isDestroyed ( ) ) { return ; } logger . info ( `finished loading welcome screen.` ) ; const ssoValue = ! ! ( this . userConfig . url && this . userConfig . url . indexOf ( '/login/sso/initsso' ) > - 1 ) ; this . welcomeScreenWindow . webContents . send ( 'page-load-welcome' , { locale : i18n . getLocale ( ) , resource : i18n . loadedResources , } ) ; const userConfigUrl = this . userConfig . url && this . userConfig . url . indexOf ( '/login/sso/initsso' ) > - 1 ? this . userConfig . url . slice ( 0 , this . userConfig . url . indexOf ( '/login/sso/initsso' ) ) : this . userConfig . url ; this . welcomeScreenWindow . webContents . send ( 'welcome' , { url : userConfigUrl || this . startUrl , message : '' , urlValid : ! ! userConfigUrl , sso : ssoValue , } ) ; this . appMenu = new AppMenu ( ) ; this . addWindow ( opts . winKey , this . welcomeScreenWindow ) ; this . mainWindow = this . welcomeScreenWindow as ICustomBrowserWindow ; } ) ; this . welcomeScreenWindow . once ( 'closed' , ( ) => { this . removeWindow ( opts . winKey ) ; this . welcomeScreenWindow = null ; } ) ; } /**
     * Gets the main window
     */ public getMainWindow ( ) { return this . mainWindow ; } /**
     * Gets the welcome screen window
     */ public getWelcomeScreenWindow ( ) { return this . welcomeScreenWindow ; } /**
     * Gets the main browser webContents
     */ public getMainWebContents ( ) { return this . mainWebContents ; } /**
     * Gets the main browser view
     */ public getMainView ( ) { return this . mainView ; } return { Electron . BrowserWindow } public getAllWindows ( ) { return this . windows ; } return public getMainWindowOpts ( ) { return this . windowOpts ; } param mainView public setMainView ( mainView ) { this . mainView = mainView ; } param titleBarView public setTitleBarView ( titleBarView ) { this . titleBarView = titleBarView ; } param windowType { WindowTypes } param winKey { string } public closeWindow ( windowType , winKey ? ) { logger . info ( `window-handler: closing window type ${ windowType } with key ${ winKey }!` ) ; switch ( windowType ) { case 'screen-picker' : if ( this . screenPickerWindow && windowExists ( this . screenPickerWindow ) ) { this . screenPickerWindow . close ( ) ; } break ; case 'screen-sharing-indicator' : if ( winKey ) { const browserWindow = this . windows [ winKey ] ; if ( browserWindow && windowExists ( browserWindow ) ) { browserWindow . destroy ( ) ; } } if ( isWindowsOS || isMac ) { const timeoutValue = 300 ; setTimeout ( ( ) => this . execCmd ( this . screenShareIndicatorFrameUtil , [ ] ) , timeoutValue ) ; } else { if ( this . screenSharingFrameWindow && windowExists ( this . screenSharingFrameWindow ) ) { this . screenSharingFrameWindow . close ( ) ; } } break ; case 'notification-settings' : if ( this . notificationSettingsWindow && windowExists ( this . notificationSettingsWindow ) ) { this . notificationSettingsWindow . close ( ) ; } break ; default : break ; } } /**
     * Finds all the child window and closes it
     */ public closeAllWindows ( ) { const browserWindows = BrowserWindow . getAllWindows ( ) ; if ( browserWindows && browserWindows . length ) { browserWindows . forEach ( ( win ) => { const browserWindow = win as ICustomBrowserWindow ; if ( browserWindow && windowExists ( browserWindow ) ) { if ( browserWindow . winName !== apiName . mainWindowName ) { if ( browserWindow . closable ) { browserWindow . close ( ) ; } else { browserWindow . destroy ( ) ; } } } } ) ; } notification . cleanUp ( ) ; } param shouldAutoReload { boolean } public setIsAutoReload ( shouldAutoReload ) { this . isAutoReload = shouldAutoReload ; } param window { Electron . BrowserWindow } public hasWindow ( window ) { for ( const key in this . windows ) { if ( this . windows [ key ] === window ) { return true ; } } return this . aboutAppWindow === window ; } param webContents { WeContents } public hasView ( webContents ) { return ( webContents === this . mainView ?. webContents || webContents === this . titleBarView ?. webContents ) ; } /**
     * Move window to the same screen as main window
     */ public moveWindow ( windowToMove , fixedYPosition ? ) { if ( this . mainWindow && windowExists ( this . mainWindow ) ) { const display = screen . getDisplayMatching ( this . mainWindow . getBounds ( ) ) ; logger . info ( 'window-handler: moveWindow, display: ' + JSON . stringify ( display . workArea ) ) ; logger . info ( 'window-handler: moveWindow, windowToMove: ' + JSON . stringify ( windowToMove . getBounds ( ) ) ) ; if ( display . workArea . width < windowToMove . getBounds ( ) . width ) { windowToMove . setSize ( display . workArea . width , windowToMove . getBounds ( ) . height ) ; } if ( display . workArea . height < windowToMove . getBounds ( ) . height ) { windowToMove . setSize ( windowToMove . getBounds ( ) . width , display . workArea . height ) ; } let positionX = Math . trunc ( display . workArea . x + display . workArea . width / 2 - windowToMove . getBounds ( ) . width / 2 ) ; if ( positionX < display . workArea . x ) { positionX = display . workArea . x ; } let positionY ; if ( fixedYPosition ) { positionY = Math . trunc ( display . workArea . y + fixedYPosition ) ; } else { positionY = Math . trunc ( display . workArea . y + display . workArea . height / 2 - windowToMove . getBounds ( ) . height / 2 ) ; } if ( positionY < display . workArea . y ) { positionY = display . workArea . y ; } logger . info ( 'window-handler: moveWindow, positionX: ' + positionX ) ; logger . info ( 'window-handler: moveWindow, positionY: ' + positionY ) ; windowToMove . setPosition ( positionX , positionY ) ; windowToMove . setPosition ( positionX , positionY ) ; } } /**
     * Creates a about app window
     */ public createAboutAppWindow ( windowName ) { if ( didVerifyAndRestoreWindow ( this . aboutAppWindow ) ) { return ; } const selectedParentWindow = getWindowByName ( windowName ) ; const opts = this . getWindowOpts ( { width : 440 , height : 315 , modal : true , alwaysOnTop : isMac , resizable : false , fullscreenable : false , } , { devTools : isDevEnv , } ) ; if ( this . mainWindow && windowExists ( this . mainWindow ) && this . mainWindow . isAlwaysOnTop ( ) ) { opts . alwaysOnTop = true ; } if ( isWindowsOS && selectedParentWindow ) { opts . parent = selectedParentWindow ; } this . aboutAppWindow = createComponentWindow ( 'about-app' , opts ) ; this . moveWindow ( this . aboutAppWindow ) ; this . aboutAppWindow . setVisibleOnAllWorkspaces ( true ) ; this . aboutAppWindow . webContents . once ( 'did-finish-load' , async ( ) => { let client = '' ; if ( this . url && this . url . startsWith ( 'https://corporate.symphony.com' ) ) { client = this . url . includes ( 'bff' ) ? this . url . includes ( 'daily' ) ? 'Symphony 2.0 - Daily' : 'Symphony 2.0' : 'Symphony Classic' ; } const ABOUT_SYMPHONY_NAMESPACE = 'AboutSymphony' ; const versionLocalised = i18n . t ( 'Version' , ABOUT_SYMPHONY_NAMESPACE ) ( ) ; const { hostname } = parse ( this . url || this . globalConfig . url ) ; const userConfig = config . userConfig ; const globalConfig = config . globalConfig ; const cloudConfig = config . cloudConfig ; const filteredConfig = config . filteredCloudConfig ; const finalConfig = { ... globalConfig , ... userConfig , ... filteredConfig , } ; const aboutInfo = { userConfig , globalConfig , cloudConfig , finalConfig , hostname , versionLocalised , ... versionHandler . versionInfo , client , } ; if ( this . aboutAppWindow && windowExists ( this . aboutAppWindow ) ) { this . aboutAppWindow . webContents . send ( 'about-app-data' , aboutInfo ) ; } } ) ; } /**
     * Creates the snipping tool window
     */ public createSnippingToolWindow ( snipImage , snipDimensions ) { if ( didVerifyAndRestoreWindow ( this . snippingToolWindow ) ) { logger . error ( 'window-handler: Could not open snipping tool window' ) ; return ; } logger . info ( 'window-handler, createSnippingToolWindow: Receiving snippet props: ' + JSON . stringify ( { snipImage , snipDimensions , } ) ) ; const allDisplays = screen . getAllDisplays ( ) ; logger . info ( 'window-handler, createSnippingToolWindow: User has these displays: ' + JSON . stringify ( allDisplays ) ) ; if ( ! this . mainWindow ) { logger . error ( 'window-handler: Could not get main window' ) ; return ; } const OS_PADDING = 25 ; const MIN_TOOL_HEIGHT = 312 ; const MIN_TOOL_WIDTH = 320 ; const BUTTON_BAR_TOP_HEIGHT = 48 ; const BUTTON_BAR_BOTTOM_HEIGHT = 72 ; const BUTTON_BARS_HEIGHT = BUTTON_BAR_TOP_HEIGHT + BUTTON_BAR_BOTTOM_HEIGHT ; const display = screen . getDisplayMatching ( this . mainWindow . getBounds ( ) ) ; const workAreaSize = display . workAreaSize ; const maxToolHeight = Math . floor ( calculatePercentage ( workAreaSize . height , 90 ) ) ; const maxToolWidth = Math . floor ( calculatePercentage ( workAreaSize . width , 90 ) ) ; const availableAnnotateAreaHeight = maxToolHeight - BUTTON_BARS_HEIGHT ; const availableAnnotateAreaWidth = maxToolWidth ; const scaleFactor = display . scaleFactor ; const scaledImageDimensions = { height : Math . floor ( snipDimensions . height / scaleFactor ) , width : Math . floor ( snipDimensions . width / scaleFactor ) , } ; logger . info ( 'window-handler, createSnippingToolWindow: Image will open with scaled dimensions: ' + JSON . stringify ( scaledImageDimensions ) ) ; const annotateAreaHeight = scaledImageDimensions . height > availableAnnotateAreaHeight ? availableAnnotateAreaHeight : scaledImageDimensions . height ; const annotateAreaWidth = scaledImageDimensions . width > availableAnnotateAreaWidth ? availableAnnotateAreaWidth : scaledImageDimensions . width ; let toolHeight ; let toolWidth ; if ( scaledImageDimensions . height + BUTTON_BARS_HEIGHT >= maxToolHeight ) { toolHeight = maxToolHeight + OS_PADDING ; } else if ( scaledImageDimensions . height + BUTTON_BARS_HEIGHT <= MIN_TOOL_HEIGHT ) { toolHeight = MIN_TOOL_HEIGHT + OS_PADDING ; } else { toolHeight = scaledImageDimensions . height + BUTTON_BARS_HEIGHT + OS_PADDING ; } if ( scaledImageDimensions . width >= maxToolWidth ) { toolWidth = maxToolWidth ; } else if ( scaledImageDimensions . width <= MIN_TOOL_WIDTH ) { toolWidth = MIN_TOOL_WIDTH ; } else { toolWidth = scaledImageDimensions . width ; } const opts = this . getWindowOpts ( { width : toolWidth , height : toolHeight , modal : false , alwaysOnTop : false , resizable : false , fullscreenable : false , } , { devTools : true , } ) ; if ( this . mainWindow && windowExists ( this . mainWindow ) && this . mainWindow . isAlwaysOnTop ( ) ) { opts . alwaysOnTop = true ; } if ( isWindowsOS && this . mainWindow ) { opts . parent = this . mainWindow ; } this . snippingToolWindow = createComponentWindow ( 'snipping-tool' , opts ) ; this . moveWindow ( this . snippingToolWindow ) ; this . snippingToolWindow . setVisibleOnAllWorkspaces ( true ) ; this . snippingToolWindow . webContents . once ( 'did-finish-load' , async ( ) => { const snippingToolInfo = { snipImage , annotateAreaHeight , annotateAreaWidth , snippetImageHeight : scaledImageDimensions . height , snippetImageWidth : scaledImageDimensions . width , } ; if ( this . snippingToolWindow && windowExists ( this . snippingToolWindow ) ) { this . snippingToolWindow . webContents . setZoomFactor ( 1 ) ; const windowBounds = this . snippingToolWindow . getBounds ( ) ; logger . info ( 'window-handler: Opening snipping tool window on display: ' + JSON . stringify ( display ) ) ; logger . info ( 'window-handler: Opening snipping tool window with size: ' + JSON . stringify ( { toolHeight , toolWidth , } ) ) ; logger . info ( 'window-handler: Opening snipping tool content with metadata: ' + JSON . stringify ( snippingToolInfo ) ) ; logger . info ( 'window-handler: Actual window size: ' + JSON . stringify ( windowBounds ) ) ; if ( windowBounds . height !== toolHeight || windowBounds . width !== toolWidth ) { logger . info ( 'window-handler: Could not create window with correct size, resizing with setBounds and centering with center' ) ; this . snippingToolWindow . setBounds ( { height : toolHeight , width : toolWidth , } ) ; this . snippingToolWindow . center ( ) ; logger . info ( 'window-handler: window bounds after resizing: ' + JSON . stringify ( this . snippingToolWindow . getBounds ( ) ) ) ; } this . snippingToolWindow . webContents . send ( 'snipping-tool-data' , snippingToolInfo ) ; } } ) ; this . snippingToolWindow . once ( 'closed' , ( ) => { logger . info ( 'window-handler, createSnippingToolWindow: Closing snipping window, attempting to delete temp snip image' ) ; this . deleteFile ( snipImage ) ; this . removeWindow ( opts . winKey ) ; this . screenPickerWindow = null ; } ) ; } /**
     * Closes the snipping tool window
     */ public closeSnippingToolWindow ( ) { if ( this . snippingToolWindow && windowExists ( this . snippingToolWindow ) ) { this . snippingToolWindow . close ( ) ; this . snippingToolWindow = null ; } } /**
     * Draw red frame on shared screen application
     *
     */ public drawScreenShareIndicatorFrame ( source ) { const displays = screen . getAllDisplays ( ) ; logger . info ( 'window-utils: displays.length: ' + displays . length ) ; for ( let i = 0 , len = displays . length ; i < len ; i ++ ) { logger . info ( 'window-utils: display[' + i + ']: ' + JSON . stringify ( displays [ i ] ) ) ; } if ( source != null ) { logger . info ( 'window-handler: drawScreenShareIndicatorFrame' ) ; if ( isWindowsOS || isMac ) { const type = source . id . split ( ':' ) [ 0 ] ; if ( type === 'window' ) { const hwnd = source . id . split ( ':' ) [ 1 ] ; this . execCmd ( this . screenShareIndicatorFrameUtil , [ hwnd ] ) ; } else if ( isMac && type === 'screen' ) { const dispId = source . id . split ( ':' ) [ 1 ] ; this . execCmd ( this . screenShareIndicatorFrameUtil , [ dispId ] ) ; } else if ( isWindowsOS && type === 'screen' ) { logger . info ( 'window-handler: source.display_id: ' + source . display_id ) ; if ( source . display_id !== '' ) { this . execCmd ( this . screenShareIndicatorFrameUtil , [ source . display_id , ] ) ; } else { const dispId = source . id . split ( ':' ) [ 1 ] ; const clampedDispId = Math . min ( dispId , displays . length - 1 ) ; const keyId = 'id' ; logger . info ( 'window-utils: dispId: ' + dispId ) ; logger . info ( 'window-utils: clampedDispId: ' + clampedDispId ) ; logger . info ( 'window-utils: displays [' + clampedDispId + '] [id]: ' + displays [ clampedDispId ] [ keyId ] ) ; this . execCmd ( this . screenShareIndicatorFrameUtil , [ displays [ clampedDispId ] [ keyId ] . toString ( ) , ] ) ; } } } } } param window param sources param id public createScreenPickerWindow ( window , sources , id ) { if ( this . screenPickerWindow && windowExists ( this . screenPickerWindow ) ) { this . screenPickerWindow . close ( ) ; } const opts = this . getWindowOpts ( { alwaysOnTop : true , autoHideMenuBar : true , frame : false , modal : true , height : isMac ? 519 : 523 , width : 580 , show : false , fullscreenable : false , } , { devTools : isDevEnv , } ) ; const focusedWindow = BrowserWindow . getFocusedWindow ( ) ; if ( focusedWindow && windowExists ( focusedWindow ) && isWindowsOS ) { opts . parent = focusedWindow ; } this . screenPickerWindow = createComponentWindow ( 'screen-picker' , opts ) ; this . moveWindow ( this . screenPickerWindow ) ; this . screenPickerWindow . webContents . once ( 'did-finish-load' , ( ) => { if ( ! this . screenPickerWindow || ! windowExists ( this . screenPickerWindow ) ) { return ; } this . screenPickerWindow . webContents . setZoomFactor ( 1 ) ; this . screenPickerWindow . webContents . setVisualZoomLevelLimits ( 1 , 1 ) ; this . screenPickerWindow . webContents . send ( 'screen-picker-data' , { sources , id , } ) ; this . addWindow ( opts . winKey , this . screenPickerWindow ) ; } ) ; const screenSourceSelect = ( _event , source ) => { if ( source != null ) { logger . info ( `window-handler: screen-source-select` , source , id ) ; this . execCmd ( this . screenShareIndicatorFrameUtil , [ ] ) ; const timeoutValue = 300 ; setTimeout ( ( ) => this . drawScreenShareIndicatorFrame ( source ) , timeoutValue ) ; } } ; ipcMain . on ( 'screen-source-select' , screenSourceSelect ) ; ipcMain . once ( 'screen-source-selected' , ( _event , source ) => { logger . info ( `window-handler: screen-source-selected` , source , id ) ; if ( source == null ) { this . execCmd ( this . screenShareIndicatorFrameUtil , [ ] ) ; } window . send ( 'start-share' + id , source ) ; if ( this . screenPickerWindow && windowExists ( this . screenPickerWindow ) ) { this . screenPickerWindow . close ( ) ; } } ) ; this . screenPickerWindow . once ( 'closed' , ( ) => { ipcMain . removeListener ( 'screen-source-select' , screenSourceSelect ) ; this . removeWindow ( opts . winKey ) ; this . screenPickerWindow = null ; } ) ; } param window param hostname param isMultipleTries param clearSettings param callback public createBasicAuthWindow ( window , hostname , isMultipleTries , clearSettings , callback ) { const opts = this . getWindowOpts ( { width : 360 , height : isMac ? 270 : 295 , show : false , modal : true , autoHideMenuBar : true , resizable : false , } , { devTools : isDevEnv , } ) ; opts . parent = window ; this . basicAuthWindow = createComponentWindow ( 'basic-auth' , opts ) ; this . moveWindow ( this . basicAuthWindow ) ; this . basicAuthWindow . setVisibleOnAllWorkspaces ( true ) ; this . basicAuthWindow . webContents . once ( 'did-finish-load' , ( ) => { if ( ! this . basicAuthWindow || ! windowExists ( this . basicAuthWindow ) ) { return ; } this . basicAuthWindow . webContents . send ( 'basic-auth-data' , { hostname , isValidCredentials : isMultipleTries , } ) ; } ) ; const closeBasicAuth = ( _event , shouldClearSettings = true ) => { if ( shouldClearSettings ) { clearSettings ( ) ; } if ( this . basicAuthWindow && windowExists ( this . basicAuthWindow ) ) { this . basicAuthWindow . close ( ) ; this . basicAuthWindow = null ; } } ; const login = ( _event , arg ) => { const { username , password } = arg ; callback ( username , password ) ; closeBasicAuth ( null , false ) ; } ; this . basicAuthWindow . once ( 'close' , ( ) => { ipcMain . removeListener ( 'basic-auth-closed' , closeBasicAuth ) ; ipcMain . removeListener ( 'basic-auth-login' , login ) ; } ) ; ipcMain . once ( 'basic-auth-closed' , closeBasicAuth ) ; ipcMain . once ( 'basic-auth-login' , login ) ; } param windowName public createNotificationSettingsWindow ( windowName , theme ) { const opts = this . getWindowOpts ( { width : 540 , height : 455 , show : false , modal : true , minimizable : false , maximizable : false , fullscreenable : false , autoHideMenuBar : true , } , { devTools : isDevEnv , } ) ; if ( didVerifyAndRestoreWindow ( this . notificationSettingsWindow ) ) { return ; } const selectedParentWindow = getWindowByName ( windowName ) ; if ( selectedParentWindow ) { opts . parent = selectedParentWindow ; } this . notificationSettingsWindow = createComponentWindow ( 'notification-settings' , opts ) ; this . moveWindow ( this . notificationSettingsWindow ) ; this . notificationSettingsWindow . setVisibleOnAllWorkspaces ( true ) ; this . notificationSettingsWindow . webContents . on ( 'did-finish-load' , ( ) => { if ( this . notificationSettingsWindow && windowExists ( this . notificationSettingsWindow ) ) { let screens = [ ] ; if ( app . isReady ( ) ) { screens = screen . getAllDisplays ( ) ; } const { position , display } = config . getConfigFields ( [ 'notificationSettings' , ] ) . notificationSettings ; this . notificationSettingsWindow . webContents . send ( 'notification-settings-data' , { screens , position , display , theme } ) ; } } ) ; this . addWindow ( opts . winKey , this . notificationSettingsWindow ) ; ipcMain . once ( 'notification-settings-update' , async ( _event , args ) => { const { display , position } = args ; try { await config . updateUserConfig ( { notificationSettings : { display , position } , } ) ; } catch ( e ) { logger . error ( `NotificationSettings: Could not update user config file error` , e ) ; } if ( this . notificationSettingsWindow && windowExists ( this . notificationSettingsWindow ) ) { this . notificationSettingsWindow . close ( ) ; } notification . updateNotificationSettings ( ) ; } ) ; this . notificationSettingsWindow . once ( 'closed' , ( ) => { this . removeWindow ( opts . winKey ) ; this . notificationSettingsWindow = null ; } ) ; } param screenSharingWebContents { WeContents } param displayId { string } param id { number } param streamId { string } public createScreenSharingIndicatorWindow ( screenSharingWebContents , displayId , id , streamId ) { const indicatorScreen = ( displayId && screen . getAllDisplays ( ) . filter ( ( d ) => displayId . includes ( d . id . toString ( ) ) ) [ 0 ] ) || screen . getPrimaryDisplay ( ) ; const topPositionOfIndicatorScreen = 16 ; const screenRect = indicatorScreen . workArea ; let opts = { ... this . getWindowOpts ( { width : 592 , height : 48 , show : false , modal : true , frame : false , focusable : true , transparent : true , autoHideMenuBar : true , resizable : false , alwaysOnTop : true , fullscreenable : false , titleBarStyle : 'customButtonsOnHover' , minimizable : false , maximizable : false , title : 'Screen Sharing Indicator - Symphony' , closable : false , } , { devTools : isDevEnv , } ) , ... { winKey : streamId } , } ; if ( opts . width && opts . height ) { opts = { ... opts , ... { x : screenRect . x + Math . round ( ( screenRect . width - opts . width ) / 2 ) , y : screenRect . y + topPositionOfIndicatorScreen , } , } ; } logger . info ( 'window-handler: createScreenSharingIndicatorWindow, displayId: ' + displayId ) ; if ( displayId !== '' ) { if ( isLinux ) { const displays = screen . getAllDisplays ( ) ; displays . forEach ( ( element ) => { logger . info ( 'window-handler: element.id.toString(): ' + element . id . toString ( ) ) ; if ( displayId === element . id . toString ( ) ) { logger . info ( `window-handler: element:` , element ) ; this . createScreenSharingFrameWindow ( 'screen-sharing-frame' , element . workArea . width , element . workArea . height , element . workArea . x , element . workArea . y ) ; } } ) ; } } this . screenSharingIndicatorWindow = createComponentWindow ( 'screen-sharing-indicator' , opts ) ; this . moveWindow ( this . screenSharingIndicatorWindow , topPositionOfIndicatorScreen ) ; this . screenSharingIndicatorWindow . setVisibleOnAllWorkspaces ( true ) ; this . screenSharingIndicatorWindow . setSkipTaskbar ( true ) ; this . screenSharingIndicatorWindow . setAlwaysOnTop ( true , 'screen-saver' ) ; this . screenSharingIndicatorWindow . webContents . once ( 'did-finish-load' , ( ) => { if ( ! this . screenSharingIndicatorWindow || ! windowExists ( this . screenSharingIndicatorWindow ) ) { return ; } this . screenSharingIndicatorWindow . webContents . setZoomFactor ( 1 ) ; this . screenSharingIndicatorWindow . webContents . setVisualZoomLevelLimits ( 1 , 1 ) ; this . screenSharingIndicatorWindow . webContents . send ( 'screen-sharing-indicator-data' , { id , streamId } ) ; } ) ; const stopScreenSharing = ( _event , indicatorId ) => { if ( id === indicatorId ) { screenSharingWebContents . send ( 'screen-sharing-stopped' , id ) ; } } ; this . addWindow ( opts . winKey , this . screenSharingIndicatorWindow ) ; this . screenSharingIndicatorWindow . once ( 'close' , ( ) => { this . removeWindow ( streamId ) ; ipcMain . removeListener ( 'stop-screen-sharing' , stopScreenSharing ) ; } ) ; ipcMain . once ( 'stop-screen-sharing' , stopScreenSharing ) ; } /**
     * Creates a screen-sharing frame around the shared area
     */ public createScreenSharingFrameWindow ( windowName , frameWidth , frameHeight , framePositionX , framePositionY ) { if ( didVerifyAndRestoreWindow ( this . screenSharingFrameWindow ) ) { return ; } const selectedParentWindow = getWindowByName ( windowName ) ; const opts = this . getWindowOpts ( { width : frameWidth , height : frameHeight , frame : false , transparent : true , alwaysOnTop : true , } , { devTools : isDevEnv , } ) ; if ( this . mainWindow && windowExists ( this . mainWindow ) && this . mainWindow . isAlwaysOnTop ( ) ) { opts . alwaysOnTop = true ; } if ( isWindowsOS && selectedParentWindow ) { opts . parent = selectedParentWindow ; } this . screenSharingFrameWindow = createComponentWindow ( 'screen-sharing-frame' , opts ) ; const area = this . screenSharingFrameWindow . getBounds ( ) ; area . x = framePositionX ; area . y = framePositionY ; this . screenSharingFrameWindow . setBounds ( area ) ; this . screenSharingFrameWindow . setIgnoreMouseEvents ( true ) ; this . screenSharingFrameWindow . setVisibleOnAllWorkspaces ( true ) ; } /**
     * Update version info on the about app window and more info window
     */ public async updateVersionInfo ( ) { await versionHandler . getClientVersion ( true , this . url ) ; this . setAboutPanel ( ) ; } param urlToOpen public openUrlInDefaultBrowser ( urlToOpen ) { if ( urlToOpen ) { shell . openExternal ( urlToOpen ) ; logger . info ( `window-handler: opened url ${ urlToOpen } in the default browser!` ) ; } } param key { string } param browserWindow { Electron . BrowserWindow } public addWindow ( key , browserWindow ) { this . windows [ key ] = browserWindow ; } param key { string } public removeWindow ( key ) { delete this . windows [ key ] ; } param util { string } param utilArgs { ReadonlyArray < string > } public execCmd ( util , utilArgs ) { logger . info ( `window handler: execCmd: util: ${ util } utilArgs: ${ utilArgs }` ) ; return new Promise < void > ( ( resolve , reject ) => { return execFile ( util , utilArgs , ( error ) => { if ( error ) { logger . info ( `window handler: execCmd: error: ${ error }` ) ; } if ( error && error . killed ) { return reject ( error ) ; } resolve ( ) ; } ) ; } ) ; } param filePath public deleteFile ( filePath ) { fs . unlink ( filePath , ( removeErr ) => { logger . info ( `window-handler: cleaning up temp snippet file: ${ filePath }!` ) ; if ( removeErr ) { logger . info ( `window-handler: error removing temp snippet file, is probably already removed: ${ filePath }, err: ${ removeErr }` ) ; } } ) ; } /**
     * Reloads symphony in case of network failures
     */ public async reloadSymphony ( ) { if ( this . mainWebContents && ! this . mainWebContents . isDestroyed ( ) ) { if ( this . isLoggedIn ) { logger . info ( `window-utils: user has logged in, getting back to Symphony app` ) ; const userAgent = this . getUserAgent ( this . mainWebContents ) ; await this . mainWebContents . loadURL ( this . url || this . userConfig . url || this . globalConfig . url , { userAgent } ) ; return ; } logger . info ( `window-utils: user hasn't logged in yet, loading login page again` ) ; const userAgent = this . getUserAgent ( this . mainWebContents ) ; await this . mainWebContents . loadURL ( this . userConfig . url || this . globalConfig . url , { userAgent , } ) ; } } /**
     * Listens for app load timeouts and reloads if required
     */ private listenForLoad ( ) { setTimeout ( async ( ) => { if ( ! this . finishedLoading ) { logger . info ( `window-handler: Pod load failed on launch` ) ; if ( this . mainWebContents && ! this . mainWebContents . isDestroyed ( ) ) { const webContentsUrl = this . mainWebContents ?. getURL ( ) ; logger . info ( `window-handler: Current main window url is ${ webContentsUrl }.` ) ; const reloadUrl = webContentsUrl || this . userConfig . url || this . globalConfig . url ; logger . info ( `window-handler: Trying to reload ${ reloadUrl }.` ) ; const userAgent = this . getUserAgent ( this . mainWebContents ) ; await this . mainWebContents . loadURL ( reloadUrl , { userAgent } ) ; return ; } logger . error ( `window-handler: Cannot reload as main window does not exist` ) ; } } , LISTEN_TIMEOUT ) ; } /**
     * Sets the about panel details for macOS
     */ private setAboutPanel ( ) { if ( ! isMac ) { return ; } const appName = app . getName ( ) ; const copyright = `Copyright \xA9 ${ new Date ( ) . getFullYear ( ) } ${ appName }` ; app . setAboutPanelOptions ( { applicationName : appName , applicationVersion : versionHandler . versionInfo . clientVersion , version : versionHandler . versionInfo . buildNumber , copyright , } ) ; } /**
     * Registers keyboard shortcuts or devtools
     */ private registerGlobalShortcuts ( ) { logger . info ( 'window-handler: register global shortcuts!' ) ; globalShortcut . register ( isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I' , ( ) => this . onRegisterDevtools ( ) ) ; globalShortcut . register ( 'CmdOrCtrl+R' , this . onReload ) ; if ( this . url && this . url . startsWith ( 'https://corporate.symphony.com' ) ) { globalShortcut . register ( isMac ? 'Cmd+Alt+1' : 'Ctrl+Shift+1' , ( ) => this . switchClient ( ClientSwitchType . CLIENT_1_5 ) ) ; globalShortcut . register ( isMac ? 'Cmd+Alt+2' : 'Ctrl+Shift+2' , ( ) => this . switchClient ( ClientSwitchType . CLIENT_2_0 ) ) ; globalShortcut . register ( isMac ? 'Cmd+Alt+3' : 'Ctrl+Shift+3' , ( ) => this . switchClient ( ClientSwitchType . CLIENT_2_0_DAILY ) ) ; } globalShortcut . register ( 'CmdOrCtrl+=' , zoomIn ) ; globalShortcut . register ( 'CmdOrCtrl+-' , zoomOut ) ; if ( isMac ) { globalShortcut . register ( 'CmdOrCtrl+Plus' , zoomIn ) ; } else if ( isWindowsOS || isLinux ) { globalShortcut . register ( 'Ctrl+=' , zoomIn ) ; globalShortcut . register ( 'Ctrl+numadd' , zoomIn ) ; globalShortcut . register ( 'Ctrl+numsub' , zoomOut ) ; globalShortcut . register ( 'Ctrl+num0' , resetZoomLevel ) ; } } /**
     * Registers keyboard shortcuts or devtools
     */ private unregisterGlobalShortcuts ( ) { logger . info ( 'window-handler: unregister global shortcuts!' ) ; globalShortcut . unregister ( isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I' ) ; globalShortcut . unregister ( 'CmdOrCtrl+R' ) ; globalShortcut . unregister ( 'CmdOrCtrl+=' ) ; globalShortcut . unregister ( 'CmdOrCtrl+-' ) ; if ( isMac ) { globalShortcut . unregister ( 'CmdOrCtrl+Plus' ) ; } else if ( isWindowsOS || isLinux ) { globalShortcut . unregister ( 'Ctrl+numadd' ) ; globalShortcut . unregister ( 'Ctrl+numsub' ) ; globalShortcut . unregister ( 'Ctrl+num0' ) ; } if ( this . url && this . url . startsWith ( 'https://corporate.symphony.com' ) ) { globalShortcut . unregister ( isMac ? 'Cmd+Alt+1' : 'Ctrl+Shift+1' ) ; globalShortcut . unregister ( isMac ? 'Cmd+Alt+2' : 'Ctrl+Shift+2' ) ; globalShortcut . unregister ( isMac ? 'Cmd+Alt+3' : 'Ctrl+Shift+3' ) ; } } /**
     * Verifies and toggle devtool based on global config settings
     * else displays a dialog
     */ private onRegisterDevtools ( ) { const focusedWindow = BrowserWindow . getFocusedWindow ( ) ; if ( ! focusedWindow || ! windowExists ( focusedWindow ) ) { return ; } const { devToolsEnabled } = config . getConfigFields ( [ 'devToolsEnabled' ] ) ; if ( devToolsEnabled ) { if ( this . mainWindow && windowExists ( this . mainWindow ) && focusedWindow === this . mainWindow ) { if ( this . mainView && viewExists ( this . mainView ) ) { this . mainWebContents ?. toggleDevTools ( ) ; return ; } } focusedWindow . webContents . toggleDevTools ( ) ; return ; } focusedWindow . webContents . closeDevTools ( ) ; logger . info ( `window-handler: dev tools disabled by admin, not opening it for the user!` ) ; } /**
     * Reloads the window based on the window type
     */ private onReload ( ) { const focusedWindow = BrowserWindow . getFocusedWindow ( ) ; if ( ! focusedWindow || ! windowExists ( focusedWindow ) ) { return ; } reloadWindow ( focusedWindow as ICustomBrowserWindow ) ; } param clientSwitch private async switchClient ( clientSwitch ) { logger . info ( `window handler: switch to client ${ clientSwitch }` ) ; if ( ! this . mainWebContents || this . mainWebContents . isDestroyed ( ) ) { logger . info ( `window-handler: switch client - main window web contents destroyed already! exiting` ) ; return ; } try { if ( ! this . url ) { this . url = this . globalConfig . url ; } const parsedUrl = parse ( this . url ) ; const csrfToken = await this . mainWebContents ?. executeJavaScript ( `localStorage.getItem('x-km-csrf-token')` ) ; switch ( clientSwitch ) { case ClientSwitchType . CLIENT_1_5 : this . url = this . startUrl + `?x-km-csrf-token=${ csrfToken }` ; break ; case ClientSwitchType . CLIENT_2_0 : this . url = `https://${ parsedUrl . hostname }/client-bff/index.html?x-km-csrf-token=${ csrfToken }` ; break ; case ClientSwitchType . CLIENT_2_0_DAILY : this . url = `https://${ parsedUrl . hostname }/bff-daily/daily/index.html?x-km-csrf-token=${ csrfToken }` ; break ; default : this . url = this . globalConfig . url + `?x-km-csrf-token=${ csrfToken }` ; } await this . execCmd ( this . screenShareIndicatorFrameUtil , [ ] ) ; const userAgent = this . getUserAgent ( this . mainWebContents ) ; await this . mainWebContents . loadURL ( this . url , { userAgent } ) ; } catch ( e ) { logger . error ( `window-handler: failed to switch client because of error ${ e }` ) ; } } /**
     * Cleans up reference
     */ private destroyAllWindows ( ) { for ( const key in this . windows ) { if ( Object . prototype . hasOwnProperty . call ( this . windows , key ) ) { const winKey = this . windows [ key ] ; this . removeWindow ( winKey ) ; } } this . mainWindow = null ; } param browserWindow private async checkExpiry ( browserWindow ) { logger . info ( `window handler: calling ttl handler to check for build expiry!` ) ; const buildExpired = checkIfBuildExpired ( ) ; if ( ! buildExpired ) { logger . info ( `window handler: build not expired, proceeding further!` ) ; return ; } logger . info ( `window handler: build expired, will inform the user and quit the app!` ) ; const options = { type : 'error' , title : i18n . t ( 'Build expired' ) ( ) , message : i18n . t ( 'Sorry, this is a test build and it has expired. Please contact your administrator to get a production build.' ) ( ) , buttons : [ i18n . t ( 'Quit' ) ( ) ] , cancelId : 0 , } ; const { response } = await dialog . showMessageBox ( browserWindow , options ) ; if ( response === 0 ) { app . exit ( ) ; } } param windowOpts { Electron . BrowserWindowConstructorOptions } param webPreferences { Electron . WebPreferences } private getWindowOpts ( windowOpts , webPreferences ) { const defaultPreferencesOpts = { ... { sandbox : ! isNodeEnv , nodeIntegration : isNodeEnv , contextIsolation : isNodeEnv ? false : this . contextIsolation , backgroundThrottling : this . backgroundThrottling , enableRemoteModule : true , } , ... webPreferences , } ; const defaultWindowOpts = { alwaysOnTop : false , webPreferences : defaultPreferencesOpts , winKey : getGuid ( ) , } ; return { ... defaultWindowOpts , ... windowOpts } ; } param webContents returns private getUserAgent ( webContents ) { const doOverrideUserAgents = ! ! this . globalConfig . overrideUserAgent ; let userAgent = webContents . getUserAgent ( ) ; if ( doOverrideUserAgents ) { const electronUserAgentRegex = /Electron/ ; userAgent = userAgent . replace ( electronUserAgentRegex , 'ElectronSymphony' ) ; } return userAgent ; } } const windowHandler = new WindowHandler ( ) ; export { windowHandler } ;