diff --git a/package.json b/package.json index 85bbf1a3b..7cbdd1cb5 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "test": "jest" }, "dependencies": { + "@sentry/electron": "^4.3.0", "auto-launch": "^5.0.5", "counterpart": "^0.18.6", "electron-store": "^8.0.2", diff --git a/src/electron-main.ts b/src/electron-main.ts index a8f2d8ce3..6e8a14f94 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -19,7 +19,8 @@ limitations under the License. // Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc. import "./squirrelhooks"; -import { app, BrowserWindow, Menu, autoUpdater, protocol, dialog } from "electron"; +import { app, BrowserWindow, Menu, autoUpdater, protocol, dialog, Input } from "electron"; +import * as Sentry from "@sentry/electron/main"; import AutoLaunch from "auto-launch"; import path from "path"; import windowStateKeeper from "electron-window-state"; @@ -38,18 +39,11 @@ import webContentsHandler from "./webcontents-handler"; import * as updater from "./updater"; import { getProfileFromDeeplink, protocolInit } from "./protocol"; import { _t, AppLocalization } from "./language-helper"; -import Input = Electron.Input; const argv = minimist(process.argv, { alias: { help: "h" }, }); -// Things we need throughout the file but need to be created -// async to are initialised in setupGlobals() -let asarPath: string; -let resPath: string; -let iconPath: string; - if (argv["help"]) { console.log("Options:"); console.log(" --profile-dir {path}: Path to where to store the profile."); @@ -119,28 +113,32 @@ async function tryPaths(name: string, root: string, rawPaths: string[]): Promise const homeserverProps = ["default_is_url", "default_hs_url", "default_server_name", "default_server_config"] as const; -// Find the webapp resources and set up things that require them -async function setupGlobals(): Promise { - // find the webapp asar. - asarPath = await 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", - ]); +let asarPathPromise: Promise | undefined; +// Get the webapp resource file path, memoizes result +function getAsarPath(): Promise { + 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", + ]); + } - // we assume the resources path is in the same place as the asar - resPath = await tryPaths("res", path.dirname(asarPath), [ - // If run from the source checkout - "res", - // if run from packaged application - "", - ]); + 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 { + if (global.vectorConfig) return; + + const asarPath = await getAsarPath(); try { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -168,7 +166,7 @@ async function setupGlobals(): Promise { .reduce((obj, key) => { obj[key] = global.vectorConfig[key]; return obj; - }, {} as Omit, keyof typeof homeserverProps>); + }, {} as Omit, keyof typeof homeserverProps>); } global.vectorConfig = Object.assign(global.vectorConfig, localConfig); @@ -186,13 +184,41 @@ async function setupGlobals(): Promise { // Could not load local config, this is expected in most cases. } +} + +// Configure Electron Sentry and crashReporter using sentry.dsn in config.json if one is present. +async function configureSentry(): Promise { + 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 { + 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 + "", + ]); // The tray icon // It's important to call `path.join` so we don't end up with the packaged asar in the final path. const iconFile = `element.${process.platform === "win32" ? "ico" : "png"}`; - iconPath = path.join(resPath, "img", iconFile); global.trayConfig = { - icon_path: iconPath, + icon_path: path.join(resPath, "img", iconFile), brand: global.vectorConfig.brand || "Element", }; @@ -206,9 +232,9 @@ async function setupGlobals(): Promise { }); } +// Look for an auto-launcher under 'Riot' and if we find one, +// port its enabled/disabled-ness over to the new 'Element' launcher async function moveAutoLauncher(): Promise { - // Look for an auto-launcher under 'Riot' and if we find one, port it's - // enabled/disabled-ness over to the new 'Element' launcher if (!global.vectorConfig.brand || global.vectorConfig.brand === "Element") { const oldLauncher = new AutoLaunch({ name: "Riot", @@ -261,6 +287,8 @@ const warnBeforeExit = (event: Event, input: Input): void => { } }; +configureSentry(); + // 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 @@ -322,7 +350,10 @@ if (global.store.get("disableHardwareAcceleration", false) === true) { } app.on("ready", async () => { + let asarPath: string; + try { + asarPath = await getAsarPath(); await setupGlobals(); await moveAutoLauncher(); } catch (e) { @@ -422,7 +453,7 @@ app.on("ready", async () => { // https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do backgroundColor: "#fff", - icon: iconPath, + icon: global.trayConfig.icon_path, show: false, autoHideMenuBar: global.store.get("autoHideMenuBar", true), diff --git a/test/launch-test.ts b/test/launch-test.ts index dcaf2e454..173413099 100644 --- a/test/launch-test.ts +++ b/test/launch-test.ts @@ -23,7 +23,7 @@ import { ElectronApplication, Page } from "playwright-core"; describe("App launch", () => { const artifactsPath = path.join(process.cwd(), "test_artifacts"); - fs.mkdirSync(artifactsPath); + if (!fs.existsSync(artifactsPath)) fs.mkdirSync(artifactsPath); const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "element-desktop-tests")); console.log("Using temp profile directory: ", tmpDir); diff --git a/yarn.lock b/yarn.lock index c05097eff..e5d57991e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2629,6 +2629,74 @@ dependencies: "@octokit/openapi-types" "^12.11.0" +"@sentry/browser@7.37.1": + version "7.37.1" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.37.1.tgz#d452ebed7f974f20872d34744e67d856ab54a41b" + integrity sha512-MfVbKzEVHKVH6ZyMCKLtPXvMtRCvxqQzrnK735sYW6EyMpcMYhukBU0pq7ws1E/KaCZjAJi1wDx2nqf2yPIVdQ== + dependencies: + "@sentry/core" "7.37.1" + "@sentry/replay" "7.37.1" + "@sentry/types" "7.37.1" + "@sentry/utils" "7.37.1" + tslib "^1.9.3" + +"@sentry/core@7.37.1": + version "7.37.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.37.1.tgz#6d8d151b3d6ae0d6f81c7f4da92cd2e7cb5bf1fa" + integrity sha512-eS5hoFDjAOl7POZg6K77J0oiypiqR1782oVSB49UkjK+D8tCZzZ5PxPMv0b/O0310p7x4oZ3WGRJaWEN3vY4KQ== + dependencies: + "@sentry/types" "7.37.1" + "@sentry/utils" "7.37.1" + tslib "^1.9.3" + +"@sentry/electron@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/electron/-/electron-4.3.0.tgz#c38eefe5fad32122e55f7bde0147d3f9e625e3aa" + integrity sha512-LqFMvgycMd+Mcs4Km9S8YBtaHISHSiIUVUz6mgAr2khKY6SNhkW9A4GcoOKtzRJreqYLVeBSbaUVttQfQ4Ot7g== + dependencies: + "@sentry/browser" "7.37.1" + "@sentry/core" "7.37.1" + "@sentry/node" "7.37.1" + "@sentry/types" "7.37.1" + "@sentry/utils" "7.37.1" + deepmerge "4.3.0" + tslib "^2.5.0" + +"@sentry/node@7.37.1": + version "7.37.1" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.37.1.tgz#c234e8711090b7532358bb1d6ab3fcca75356e98" + integrity sha512-nGerngIo5JwinJgl7m0SaL/xI+YRBlhb53gbkuLSAAcnoitBFzbp7LjywsqYFTWuWDIyk7O2t124GNxtolBAgA== + dependencies: + "@sentry/core" "7.37.1" + "@sentry/types" "7.37.1" + "@sentry/utils" "7.37.1" + cookie "^0.4.1" + https-proxy-agent "^5.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/replay@7.37.1": + version "7.37.1" + resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.37.1.tgz#8ab4588b28baa07e35c417d3ffc6aaf934c58c45" + integrity sha512-3sHOE/oPirdvJbOn0IA/wpds12Sm2WaEtiAeC0+5Gg5mxQzFBLRrsA1Mz/ifzPGwr+ETn3sCyPCnd9b3PWaWMQ== + dependencies: + "@sentry/core" "7.37.1" + "@sentry/types" "7.37.1" + "@sentry/utils" "7.37.1" + +"@sentry/types@7.37.1": + version "7.37.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.37.1.tgz#269da7da39c1a5243bf5f9a35370291b5cc205bb" + integrity sha512-c2HWyWSgVA0V4+DSW2qVb0yjftrb1X/q2CzCom+ayjGHO72qyWC+9Tc+7ZfotU1mapRjqUWBgkXkbGmao8N8Ug== + +"@sentry/utils@7.37.1": + version "7.37.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.37.1.tgz#7695d6e30d6178723f3fa446a9553893bca85e96" + integrity sha512-/4mJOyDsfysx+5TXyJgSI+Ihw2/0EVJbrHjCyXPDXW5ADwbtU8VdBZ0unOmF0hk4QfftqwM9cyEu3BN4iBJsEA== + dependencies: + "@sentry/types" "7.37.1" + tslib "^1.9.3" + "@sinclair/typebox@^0.25.16": version "0.25.23" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.23.tgz#1c15b0d2b872d89cc0f47c7243eacb447df8b8bd" @@ -4064,6 +4132,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + core-js-compat@^3.25.1: version "3.28.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.28.0.tgz#c08456d854608a7264530a2afa281fadf20ecee6" @@ -4189,7 +4262,7 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: +deepmerge@4.3.0, deepmerge@^4.2.2: version "4.3.0" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== @@ -6494,6 +6567,11 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -8227,12 +8305,12 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.11.1, tslib@^1.8.1: +tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.1.0, tslib@^2.3.1: +tslib@^2.1.0, tslib@^2.3.1, tslib@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==