2019-12-06 19:17:34 +01:00
/ *
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
Copyright 2017 , 2019 Michael Telatynski < 7t3chguy @ gmail.com >
2021-01-13 16:21:00 +01:00
Copyright 2018 - 2021 New Vector Ltd
2019-12-06 19:17:34 +01:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2021-07-27 12:47:44 +02:00
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
import "./squirrelhooks" ;
2023-03-16 11:31:06 +01:00
import { app , BrowserWindow , Menu , autoUpdater , protocol , dialog , Input } from "electron" ;
import * as Sentry from "@sentry/electron/main" ;
2021-06-25 15:35:58 +02:00
import AutoLaunch from "auto-launch" ;
import path from "path" ;
2022-12-15 12:00:58 +01:00
import windowStateKeeper from "electron-window-state" ;
import Store from "electron-store" ;
2021-06-25 15:35:58 +02:00
import fs , { promises as afs } from "fs" ;
import { URL } from "url" ;
2021-07-01 10:22:57 +02:00
import minimist from "minimist" ;
2021-06-25 15:35:58 +02:00
2022-07-01 21:17:40 +02:00
import "./ipc" ;
import "./keytar" ;
import "./seshat" ;
import "./settings" ;
2021-06-25 15:35:58 +02:00
import * as tray from "./tray" ;
2022-12-15 12:00:58 +01:00
import { buildMenuTemplate } from "./vectormenu" ;
import webContentsHandler from "./webcontents-handler" ;
import * as updater from "./updater" ;
import { getProfileFromDeeplink , protocolInit } from "./protocol" ;
import { _t , AppLocalization } from "./language-helper" ;
2019-12-06 19:17:34 +01:00
2021-07-01 10:22:57 +02:00
const argv = minimist ( process . argv , {
alias : { help : "h" } ,
} ) ;
2019-12-06 19:17:34 +01:00
if ( argv [ "help" ] ) {
console . log ( "Options:" ) ;
console . log ( " --profile-dir {path}: Path to where to store the profile." ) ;
console . log ( " --profile {name}: Name of alternate profile to use, allows for running multiple accounts." ) ;
console . log ( " --devtools: Install and use react-devtools and react-perf." ) ;
console . log ( " --no-update: Disable automatic updating." ) ;
console . log ( " --hidden: Start the application hidden in the system tray." ) ;
console . log ( " --help: Displays this help message." ) ;
2022-12-15 12:00:58 +01:00
console . log ( "And more such as --proxy, see:" + "https://electronjs.org/docs/api/command-line-switches" ) ;
2019-12-06 19:17:34 +01:00
app . exit ( ) ;
}
2020-07-15 12:41:30 +02:00
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
// as soon as the app path is set, so pick a random path in it that must exist if it's a
// real user data directory.
2022-05-23 16:44:29 +02:00
function isRealUserDataDir ( d : string ) : boolean {
2022-12-15 12:00:58 +01:00
return fs . existsSync ( path . join ( d , "IndexedDB" ) ) ;
2020-07-15 12:41:30 +02:00
}
2020-04-14 14:29:47 +02:00
// check if we are passed a profile in the SSO callback url
2022-05-23 16:44:29 +02:00
let userDataPath : string ;
2020-07-07 19:57:29 +02:00
2020-04-14 14:29:47 +02:00
const userDataPathInProtocol = getProfileFromDeeplink ( argv [ "_" ] ) ;
if ( userDataPathInProtocol ) {
2020-07-07 19:57:29 +02:00
userDataPath = userDataPathInProtocol ;
2022-12-15 12:00:58 +01:00
} else if ( argv [ "profile-dir" ] ) {
userDataPath = argv [ "profile-dir" ] ;
2020-07-02 14:30:11 +02:00
} else {
2022-12-15 12:00:58 +01:00
let newUserDataPath = app . getPath ( "userData" ) ;
if ( argv [ "profile" ] ) {
newUserDataPath += "-" + argv [ "profile" ] ;
2020-07-02 14:30:11 +02:00
}
const newUserDataPathExists = isRealUserDataDir ( newUserDataPath ) ;
2022-12-15 12:00:58 +01:00
let oldUserDataPath = path . join ( app . getPath ( "appData" ) , app . getName ( ) . replace ( "Element" , "Riot" ) ) ;
if ( argv [ "profile" ] ) {
oldUserDataPath += "-" + argv [ "profile" ] ;
2020-07-21 18:57:54 +02:00
}
2020-07-02 14:30:11 +02:00
const oldUserDataPathExists = isRealUserDataDir ( oldUserDataPath ) ;
2022-12-15 12:00:58 +01:00
console . log ( newUserDataPath + " exists: " + ( newUserDataPathExists ? "yes" : "no" ) ) ;
console . log ( oldUserDataPath + " exists: " + ( oldUserDataPathExists ? "yes" : "no" ) ) ;
2020-07-02 14:30:11 +02:00
if ( ! newUserDataPathExists && oldUserDataPathExists ) {
console . log ( "Using legacy user data path: " + oldUserDataPath ) ;
2020-07-07 19:57:29 +02:00
userDataPath = oldUserDataPath ;
} else {
userDataPath = newUserDataPath ;
2020-07-02 14:30:11 +02:00
}
2019-12-06 19:17:34 +01:00
}
2022-12-15 12:00:58 +01:00
app . setPath ( "userData" , userDataPath ) ;
2019-12-06 19:17:34 +01:00
2022-05-23 16:44:29 +02:00
async function tryPaths ( name : string , root : string , rawPaths : string [ ] ) : Promise < string > {
2020-04-16 14:08:16 +02:00
// Make everything relative to root
2022-12-15 12:00:58 +01:00
const paths = rawPaths . map ( ( p ) = > path . join ( root , p ) ) ;
2019-12-10 18:40:17 +01:00
for ( const p of paths ) {
try {
await afs . stat ( p ) ;
2022-12-15 12:00:58 +01:00
return p + "/" ;
} catch ( e ) { }
2019-12-10 18:40:17 +01:00
}
2020-04-16 14:08:16 +02:00
console . log ( ` Couldn't find ${ name } files in any of: ` ) ;
2019-12-10 18:40:17 +01:00
for ( const p of paths ) {
2022-12-15 12:00:58 +01:00
console . log ( "\t" + path . resolve ( p ) ) ;
2019-12-10 18:40:17 +01:00
}
2020-04-16 14:08:16 +02:00
throw new Error ( ` Failed to find ${ name } files ` ) ;
2019-12-06 19:17:34 +01:00
}
2022-12-15 12:00:58 +01:00
const homeserverProps = [ "default_is_url" , "default_hs_url" , "default_server_name" , "default_server_config" ] as const ;
2022-11-30 14:51:54 +01:00
2023-03-16 11:31:06 +01:00
let asarPathPromise : Promise < string > | undefined ;
// Get the webapp resource file path, memoizes result
function getAsarPath ( ) : Promise < string > {
if ( ! asarPathPromise ) {
asarPathPromise = tryPaths ( "webapp" , __dirname , [
// If run from the source checkout, this will be in the directory above
"../webapp.asar" ,
// but if run from a packaged application, electron-main.js will be in
// a different asar file, so it will be two levels above
"../../webapp.asar" ,
// also try without the 'asar' suffix to allow symlinking in a directory
"../webapp" ,
// from a packaged application
"../../webapp" ,
] ) ;
}
2020-04-16 14:08:16 +02:00
2023-03-16 11:31:06 +01:00
return asarPathPromise ;
}
// Loads the config from asar, and applies a config.json from userData atop if one exists
// Writes config to `global.vectorConfig`. Does nothing if `global.vectorConfig` is already set.
async function loadConfig ( ) : Promise < void > {
if ( global . vectorConfig ) return ;
const asarPath = await getAsarPath ( ) ;
2019-12-10 18:40:17 +01:00
try {
2021-07-01 10:22:57 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2022-12-15 12:00:58 +01:00
global . vectorConfig = require ( asarPath + "config.json" ) ;
2019-12-10 18:40:17 +01:00
} catch ( e ) {
// it would be nice to check the error code here and bail if the config
2020-11-14 22:54:07 +01:00
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
2019-12-10 18:40:17 +01:00
// file or invalid json, so node is just very unhelpful.
// Continue with the defaults (ie. an empty config)
2022-07-01 21:17:40 +02:00
global . vectorConfig = { } ;
2019-12-06 19:17:34 +01:00
}
2019-12-10 18:40:17 +01:00
try {
// Load local config and use it to override values from the one baked with the build
2021-07-01 10:24:02 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2022-12-15 12:00:58 +01:00
const localConfig = require ( path . join ( app . getPath ( "userData" ) , "config.json" ) ) ;
2019-12-10 18:40:17 +01:00
// If the local config has a homeserver defined, don't use the homeserver from the build
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
// defined, and panics as a result.
2022-12-15 12:00:58 +01:00
if ( Object . keys ( localConfig ) . find ( ( k ) = > homeserverProps . includes ( < any > k ) ) ) {
2019-12-10 18:40:17 +01:00
// Rip out all the homeserver options from the vector config
2022-07-01 21:17:40 +02:00
global . vectorConfig = Object . keys ( global . vectorConfig )
2022-12-15 12:00:58 +01:00
. filter ( ( k ) = > ! homeserverProps . includes ( < any > k ) )
2022-11-30 14:51:54 +01:00
. reduce ( ( obj , key ) = > {
obj [ key ] = global . vectorConfig [ key ] ;
return obj ;
2023-03-16 11:31:06 +01:00
} , { } as Omit < Partial < ( typeof global ) [ " vectorConfig " ] > , keyof typeof homeserverProps > ) ;
2019-12-10 18:40:17 +01:00
}
2022-07-01 21:17:40 +02:00
global . vectorConfig = Object . assign ( global . vectorConfig , localConfig ) ;
2019-12-10 18:40:17 +01:00
} catch ( e ) {
2021-06-04 06:39:29 +02:00
if ( e instanceof SyntaxError ) {
dialog . showMessageBox ( {
type : "error" ,
2022-12-15 12:00:58 +01:00
title : ` Your ${ global . vectorConfig . brand || "Element" } is misconfigured ` ,
message :
` Your custom ${ global . vectorConfig . brand || "Element" } configuration contains invalid JSON. ` +
` Please correct the problem and reopen ${ global . vectorConfig . brand || "Element" } . ` ,
2021-06-04 06:39:29 +02:00
detail : e.message || "" ,
} ) ;
}
2019-12-10 18:40:17 +01:00
// Could not load local config, this is expected in most cases.
}
2023-03-16 11:31:06 +01:00
}
// Configure Electron Sentry and crashReporter using sentry.dsn in config.json if one is present.
async function configureSentry ( ) : Promise < void > {
await loadConfig ( ) ;
const { dsn , environment } = global . vectorConfig . sentry || { } ;
if ( dsn ) {
console . log ( ` Enabling Sentry with dsn= ${ dsn } environment= ${ environment } ` ) ;
Sentry . init ( {
dsn ,
environment ,
// We don't actually use this IPC, but we do not want Sentry injecting preloads
ipcMode : Sentry.IPCMode.Classic ,
} ) ;
}
}
// Set up globals for Tray and AutoLaunch
async function setupGlobals ( ) : Promise < void > {
const asarPath = await getAsarPath ( ) ;
await loadConfig ( ) ;
// we assume the resources path is in the same place as the asar
const resPath = await tryPaths ( "res" , path . dirname ( asarPath ) , [
// If run from the source checkout
"res" ,
// if run from packaged application
"" ,
] ) ;
2019-12-10 18:40:17 +01:00
// The tray icon
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
2022-12-15 12:00:58 +01:00
const iconFile = ` element. ${ process . platform === "win32" ? "ico" : "png" } ` ;
2022-07-01 21:17:40 +02:00
global . trayConfig = {
2023-03-16 11:31:06 +01:00
icon_path : path.join ( resPath , "img" , iconFile ) ,
2022-12-15 12:00:58 +01:00
brand : global.vectorConfig.brand || "Element" ,
2019-12-10 18:40:17 +01:00
} ;
// launcher
2022-07-01 21:17:40 +02:00
global . launcher = new AutoLaunch ( {
2022-12-15 12:00:58 +01:00
name : global.vectorConfig.brand || "Element" ,
2019-12-10 18:40:17 +01:00
isHidden : true ,
mac : {
useLaunchAgent : true ,
} ,
} ) ;
2019-12-06 19:17:34 +01:00
}
2023-03-16 11:31:06 +01:00
// Look for an auto-launcher under 'Riot' and if we find one,
// port its enabled/disabled-ness over to the new 'Element' launcher
2022-05-23 16:44:29 +02:00
async function moveAutoLauncher ( ) : Promise < void > {
2022-12-15 12:00:58 +01:00
if ( ! global . vectorConfig . brand || global . vectorConfig . brand === "Element" ) {
2020-07-02 14:30:11 +02:00
const oldLauncher = new AutoLaunch ( {
2022-12-15 12:00:58 +01:00
name : "Riot" ,
2020-07-02 14:30:11 +02:00
isHidden : true ,
mac : {
useLaunchAgent : true ,
} ,
} ) ;
const wasEnabled = await oldLauncher . isEnabled ( ) ;
if ( wasEnabled ) {
await oldLauncher . disable ( ) ;
2022-07-01 21:17:40 +02:00
await global . launcher . enable ( ) ;
2020-07-02 14:30:11 +02:00
}
}
}
2022-07-01 21:17:40 +02:00
global . store = new Store ( { name : "electron-config" } ) ;
2019-12-06 19:17:34 +01:00
global . appQuitting = false ;
2022-05-23 16:44:29 +02:00
const exitShortcuts : Array < ( input : Input , platform : string ) = > boolean > = [
2022-12-15 12:00:58 +01:00
( input , platform ) : boolean = > platform !== "darwin" && input . alt && input . key . toUpperCase ( ) === "F4" ,
( input , platform ) : boolean = > platform !== "darwin" && input . control && input . key . toUpperCase ( ) === "Q" ,
( input , platform ) : boolean = > platform === "darwin" && input . meta && input . key . toUpperCase ( ) === "Q" ,
2021-03-31 09:58:24 +02:00
] ;
2022-05-23 16:44:29 +02:00
const warnBeforeExit = ( event : Event , input : Input ) : void = > {
2022-12-15 12:00:58 +01:00
const shouldWarnBeforeExit = global . store . get ( "warnBeforeExit" , true ) ;
2021-06-21 15:17:28 +02:00
const exitShortcutPressed =
2022-12-15 12:00:58 +01:00
input . type === "keyDown" && exitShortcuts . some ( ( shortcutFn ) = > shortcutFn ( input , process . platform ) ) ;
2021-03-31 09:58:24 +02:00
2022-11-30 14:51:54 +01:00
if ( shouldWarnBeforeExit && exitShortcutPressed && global . mainWindow ) {
2022-12-15 12:00:58 +01:00
const shouldCancelCloseRequest =
dialog . showMessageBoxSync ( global . mainWindow , {
type : "question" ,
buttons : [
_t ( "Cancel" ) ,
_t ( "Close %(brand)s" , {
brand : global.vectorConfig.brand || "Element" ,
} ) ,
] ,
message : _t ( "Are you sure you want to quit?" ) ,
defaultId : 1 ,
cancelId : 0 ,
} ) === 0 ;
2021-03-29 13:10:27 +02:00
if ( shouldCancelCloseRequest ) {
event . preventDefault ( ) ;
}
}
} ;
2020-06-23 16:42:27 +02:00
2023-03-16 11:31:06 +01:00
configureSentry ( ) ;
2019-12-06 19:17:34 +01:00
// handle uncaught errors otherwise it displays
// stack traces in popup dialogs, which is terrible (which
// it will do any time the auto update poke fails, and there's
// no other way to catch this error).
// Assuming we generally run from the console when developing,
// this is far preferable.
2022-12-15 12:00:58 +01:00
process . on ( "uncaughtException" , function ( error : Error ) : void {
console . log ( "Unhandled exception" , error ) ;
2019-12-06 19:17:34 +01:00
} ) ;
2022-12-15 12:00:58 +01:00
app . commandLine . appendSwitch ( "--enable-usermedia-screen-capturing" ) ;
if ( ! app . commandLine . hasSwitch ( "enable-features" ) ) {
app . commandLine . appendSwitch ( "enable-features" , "WebRTCPipeWireCapturer" ) ;
2021-09-06 16:18:16 +02:00
}
2019-12-06 19:17:34 +01:00
const gotLock = app . requestSingleInstanceLock ( ) ;
if ( ! gotLock ) {
2022-12-15 12:00:58 +01:00
console . log ( "Other instance detected: exiting" ) ;
2019-12-06 19:17:34 +01:00
app . exit ( ) ;
}
2020-03-02 16:04:51 +01:00
// do this after we know we are the primary instance of the app
protocolInit ( ) ;
2019-12-06 19:17:34 +01:00
// Register the scheme the app is served from as 'standard'
// which allows things like relative URLs and IndexedDB to
// work.
// Also mark it as secure (ie. accessing resources from this
// protocol and HTTPS won't trigger mixed content warnings).
2022-12-15 12:00:58 +01:00
protocol . registerSchemesAsPrivileged ( [
{
scheme : "vector" ,
privileges : {
standard : true ,
secure : true ,
supportFetchAPI : true ,
} ,
2019-12-06 19:17:34 +01:00
} ,
2022-12-15 12:00:58 +01:00
] ) ;
2019-12-06 19:17:34 +01:00
2020-05-21 00:16:57 +02:00
// Turn the sandbox on for *all* windows we might generate. Doing this means we don't
// have to specify a `sandbox: true` to each BrowserWindow.
//
// This also fixes an issue with window.open where if we only specified the sandbox
// on the main window we'd run into cryptic "ipc_renderer be broke" errors. Turns out
// it's trying to jump the sandbox and make some calls into electron, which it can't
// do when half of it is sandboxed. By turning on the sandbox for everything, the new
// window (no matter how temporary it may be) is also sandboxed, allowing for a clean
// transition into the user's browser.
app . enableSandbox ( ) ;
2021-04-03 14:10:11 +02:00
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
2022-12-15 12:00:58 +01:00
app . commandLine . appendSwitch ( "disable-features" , "HardwareMediaKeyHandling,MediaSessionService" ) ;
2020-05-21 00:16:57 +02:00
2022-05-20 14:26:16 +02:00
// Disable hardware acceleration if the setting has been set.
2022-12-15 12:00:58 +01:00
if ( global . store . get ( "disableHardwareAcceleration" , false ) === true ) {
2022-05-20 14:37:58 +02:00
console . log ( "Disabling hardware acceleration." ) ;
2022-05-20 14:26:16 +02:00
app . disableHardwareAcceleration ( ) ;
}
2022-12-15 12:00:58 +01:00
app . on ( "ready" , async ( ) = > {
2023-03-16 11:31:06 +01:00
let asarPath : string ;
2019-12-10 18:40:17 +01:00
try {
2023-03-16 11:31:06 +01:00
asarPath = await getAsarPath ( ) ;
2019-12-10 18:40:17 +01:00
await setupGlobals ( ) ;
2020-07-02 14:30:11 +02:00
await moveAutoLauncher ( ) ;
2019-12-10 18:40:17 +01:00
} catch ( e ) {
console . log ( "App setup failed: exiting" , e ) ;
process . exit ( 1 ) ;
// process.exit doesn't cause node to stop running code immediately,
// so return (we could let the exception propagate but then we end up
// with node printing all sorts of stuff about unhandled exceptions
// when we want the actual error to be as obvious as possible).
return ;
}
2022-12-15 12:00:58 +01:00
if ( argv [ "devtools" ] ) {
2019-12-06 19:17:34 +01:00
try {
2021-07-01 10:24:02 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2022-12-15 12:00:58 +01:00
const { default : installExt , REACT_DEVELOPER_TOOLS , REACT_PERF } = require ( "electron-devtools-installer" ) ;
2019-12-06 19:17:34 +01:00
installExt ( REACT_DEVELOPER_TOOLS )
2022-11-30 14:51:54 +01:00
. then ( ( name : string ) = > console . log ( ` Added Extension: ${ name } ` ) )
2022-12-15 12:00:58 +01:00
. catch ( ( err : unknown ) = > console . log ( "An error occurred: " , err ) ) ;
2019-12-06 19:17:34 +01:00
installExt ( REACT_PERF )
2022-11-30 14:51:54 +01:00
. then ( ( name : string ) = > console . log ( ` Added Extension: ${ name } ` ) )
2022-12-15 12:00:58 +01:00
. catch ( ( err : unknown ) = > console . log ( "An error occurred: " , err ) ) ;
2019-12-06 19:17:34 +01:00
} catch ( e ) {
console . log ( e ) ;
}
}
2022-12-15 12:00:58 +01:00
protocol . registerFileProtocol ( "vector" , ( request , callback ) = > {
if ( request . method !== "GET" ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 322 } ) ; // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
2019-12-06 19:17:34 +01:00
return null ;
}
const parsedUrl = new URL ( request . url ) ;
2022-12-15 12:00:58 +01:00
if ( parsedUrl . protocol !== "vector:" ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 302 } ) ; // UNKNOWN_URL_SCHEME
2019-12-06 19:17:34 +01:00
return ;
}
2022-12-15 12:00:58 +01:00
if ( parsedUrl . host !== "vector" ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 105 } ) ; // NAME_NOT_RESOLVED
2019-12-06 19:17:34 +01:00
return ;
}
2022-12-15 12:00:58 +01:00
const target = parsedUrl . pathname . split ( "/" ) ;
2019-12-06 19:17:34 +01:00
// path starts with a '/'
2022-12-15 12:00:58 +01:00
if ( target [ 0 ] !== "" ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 6 } ) ; // FILE_NOT_FOUND
2019-12-06 19:17:34 +01:00
return ;
}
2022-12-15 12:00:58 +01:00
if ( target [ target . length - 1 ] == "" ) {
target [ target . length - 1 ] = "index.html" ;
2019-12-06 19:17:34 +01:00
}
2022-05-23 16:44:29 +02:00
let baseDir : string ;
2022-12-15 12:00:58 +01:00
if ( target [ 1 ] === "webapp" ) {
2019-12-10 18:40:17 +01:00
baseDir = asarPath ;
2019-12-06 19:17:34 +01:00
} else {
2021-05-27 15:39:26 +02:00
callback ( { error : - 6 } ) ; // FILE_NOT_FOUND
2019-12-06 19:17:34 +01:00
return ;
}
// Normalise the base dir and the target path separately, then make sure
// the target path isn't trying to back out beyond its root
baseDir = path . normalize ( baseDir ) ;
const relTarget = path . normalize ( path . join ( . . . target . slice ( 2 ) ) ) ;
2022-12-15 12:00:58 +01:00
if ( relTarget . startsWith ( ".." ) ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 6 } ) ; // FILE_NOT_FOUND
2019-12-06 19:17:34 +01:00
return ;
}
const absTarget = path . join ( baseDir , relTarget ) ;
callback ( {
path : absTarget ,
} ) ;
} ) ;
2022-12-15 12:00:58 +01:00
if ( argv [ "no-update" ] ) {
2019-12-06 19:17:34 +01:00
console . log ( 'Auto update disabled via command line flag "--no-update"' ) ;
2022-12-15 12:00:58 +01:00
} else if ( global . vectorConfig [ "update_base_url" ] ) {
console . log ( ` Starting auto update with base URL: ${ global . vectorConfig [ "update_base_url" ] } ` ) ;
updater . start ( global . vectorConfig [ "update_base_url" ] ) ;
2019-12-06 19:17:34 +01:00
} else {
2022-12-15 12:00:58 +01:00
console . log ( "No update_base_url is defined: auto update is disabled" ) ;
2019-12-06 19:17:34 +01:00
}
// Load the previous window state with fallback to defaults
const mainWindowState = windowStateKeeper ( {
defaultWidth : 1024 ,
defaultHeight : 768 ,
} ) ;
const preloadScript = path . normalize ( ` ${ __dirname } /preload.js ` ) ;
2022-07-01 21:17:40 +02:00
global . mainWindow = new BrowserWindow ( {
2020-02-21 12:17:18 +01:00
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
2022-12-15 12:00:58 +01:00
backgroundColor : "#fff" ,
2020-02-21 12:17:18 +01:00
2023-03-16 11:31:06 +01:00
icon : global.trayConfig.icon_path ,
2019-12-06 19:17:34 +01:00
show : false ,
2022-12-15 12:00:58 +01:00
autoHideMenuBar : global.store.get ( "autoHideMenuBar" , true ) ,
2019-12-06 19:17:34 +01:00
x : mainWindowState.x ,
y : mainWindowState.y ,
width : mainWindowState.width ,
height : mainWindowState.height ,
webPreferences : {
preload : preloadScript ,
nodeIntegration : false ,
2020-05-21 00:16:57 +02:00
//sandbox: true, // We enable sandboxing from app.enableSandbox() above
2021-01-13 16:21:00 +01:00
contextIsolation : true ,
2021-12-10 15:55:35 +01:00
webgl : true ,
2019-12-06 19:17:34 +01:00
} ,
} ) ;
2022-12-15 12:00:58 +01:00
global . mainWindow . loadURL ( "vector://vector/webapp/" ) ;
2019-12-06 19:17:34 +01:00
2021-04-02 12:07:33 +02:00
// Handle spellchecker
2022-07-01 21:17:40 +02:00
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
global . mainWindow . webContents . session . setSpellCheckerEnabled ( global . store . get ( "spellCheckerEnabled" , true ) ) ;
2021-04-02 12:07:33 +02:00
2019-12-06 19:17:34 +01:00
// Create trayIcon icon
2022-12-15 12:00:58 +01:00
if ( global . store . get ( "minimizeToTray" , true ) ) tray . create ( global . trayConfig ) ;
2019-12-06 19:17:34 +01:00
2022-12-15 12:00:58 +01:00
global . mainWindow . once ( "ready-to-show" , ( ) = > {
2022-11-30 14:51:54 +01:00
if ( ! global . mainWindow ) return ;
2022-07-01 21:17:40 +02:00
mainWindowState . manage ( global . mainWindow ) ;
2019-12-06 19:17:34 +01:00
2022-12-15 12:00:58 +01:00
if ( ! argv [ "hidden" ] ) {
2022-07-01 21:17:40 +02:00
global . mainWindow . show ( ) ;
2019-12-06 19:17:34 +01:00
} else {
// hide here explicitly because window manage above sometimes shows it
2022-07-01 21:17:40 +02:00
global . mainWindow . hide ( ) ;
2019-12-06 19:17:34 +01:00
}
} ) ;
2022-12-15 12:00:58 +01:00
global . mainWindow . webContents . on ( "before-input-event" , warnBeforeExit ) ;
2021-03-29 13:10:27 +02:00
2022-12-15 12:00:58 +01:00
global . mainWindow . on ( "closed" , ( ) = > {
2022-07-01 21:17:40 +02:00
global . mainWindow = null ;
2019-12-06 19:17:34 +01:00
} ) ;
2022-12-15 12:00:58 +01:00
global . mainWindow . on ( "close" , async ( e ) = > {
2021-03-25 15:50:33 +01:00
// If we are not quitting and have a tray icon then minimize to tray
2022-12-15 12:00:58 +01:00
if ( ! global . appQuitting && ( tray . hasTray ( ) || process . platform === "darwin" ) ) {
2021-03-25 15:50:33 +01:00
// On Mac, closing the window just hides it
// (this is generally how single-window Mac apps
// behave, eg. Mail.app)
e . preventDefault ( ) ;
2021-05-05 04:55:28 +02:00
2022-11-30 14:51:54 +01:00
if ( global . mainWindow ? . isFullScreen ( ) ) {
2022-12-15 12:00:58 +01:00
global . mainWindow . once ( "leave-full-screen" , ( ) = > global . mainWindow ? . hide ( ) ) ;
2021-05-05 04:55:28 +02:00
2022-07-01 21:17:40 +02:00
global . mainWindow . setFullScreen ( false ) ;
2021-05-05 04:55:28 +02:00
} else {
2022-11-30 14:51:54 +01:00
global . mainWindow ? . hide ( ) ;
2021-05-05 04:55:28 +02:00
}
2021-03-25 15:50:33 +01:00
return false ;
}
2019-12-06 19:17:34 +01:00
} ) ;
2022-12-15 12:00:58 +01:00
if ( process . platform === "win32" ) {
2019-12-06 19:17:34 +01:00
// Handle forward/backward mouse buttons in Windows
2022-12-15 12:00:58 +01:00
global . mainWindow . on ( "app-command" , ( e , cmd ) = > {
if ( cmd === "browser-backward" && global . mainWindow ? . webContents . canGoBack ( ) ) {
2022-07-01 21:17:40 +02:00
global . mainWindow . webContents . goBack ( ) ;
2022-12-15 12:00:58 +01:00
} else if ( cmd === "browser-forward" && global . mainWindow ? . webContents . canGoForward ( ) ) {
2022-07-01 21:17:40 +02:00
global . mainWindow . webContents . goForward ( ) ;
2019-12-06 19:17:34 +01:00
}
} ) ;
}
2022-07-01 21:17:40 +02:00
webContentsHandler ( global . mainWindow . webContents ) ;
2021-04-26 14:58:29 +02:00
2022-07-01 21:17:40 +02:00
global . appLocalization = new AppLocalization ( {
store : global.store ,
2022-12-15 12:00:58 +01:00
components : [ ( ) : void = > tray . initApplicationMenu ( ) , ( ) : void = > Menu . setApplicationMenu ( buildMenuTemplate ( ) ) ] ,
2021-04-26 14:58:29 +02:00
} ) ;
2019-12-06 19:17:34 +01:00
} ) ;
2022-12-15 12:00:58 +01:00
app . on ( "window-all-closed" , ( ) = > {
2019-12-06 19:17:34 +01:00
app . quit ( ) ;
} ) ;
2022-12-15 12:00:58 +01:00
app . on ( "activate" , ( ) = > {
2022-11-30 14:51:54 +01:00
global . mainWindow ? . show ( ) ;
2019-12-06 19:17:34 +01:00
} ) ;
2022-05-23 16:44:29 +02:00
function beforeQuit ( ) : void {
2019-12-06 19:17:34 +01:00
global . appQuitting = true ;
2022-12-15 12:00:58 +01:00
global . mainWindow ? . webContents . send ( "before-quit" ) ;
2020-05-23 12:52:49 +02:00
}
2022-12-15 12:00:58 +01:00
app . on ( "before-quit" , beforeQuit ) ;
autoUpdater . on ( "before-quit-for-update" , beforeQuit ) ;
2019-12-06 19:17:34 +01:00
2022-12-15 12:00:58 +01:00
app . on ( "second-instance" , ( ev , commandLine , workingDirectory ) = > {
2019-12-06 19:17:34 +01:00
// If other instance launched with --hidden then skip showing window
2022-12-15 12:00:58 +01:00
if ( commandLine . includes ( "--hidden" ) ) return ;
2019-12-06 19:17:34 +01:00
// Someone tried to run a second instance, we should focus our window.
2022-07-01 21:17:40 +02:00
if ( global . mainWindow ) {
if ( ! global . mainWindow . isVisible ( ) ) global . mainWindow . show ( ) ;
if ( global . mainWindow . isMinimized ( ) ) global . mainWindow . restore ( ) ;
global . mainWindow . focus ( ) ;
2019-12-06 19:17:34 +01:00
}
} ) ;
// Set the App User Model ID to match what the squirrel
// installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is
// a noop on other platforms).
2022-12-15 12:00:58 +01:00
app . setAppUserModelId ( "com.squirrel.element-desktop.Element" ) ;