From e50281f11c7f4896c47e780d951913f2f141791c Mon Sep 17 00:00:00 2001 From: TheraNinjaCat <23611633+TheraNinjaCat@users.noreply.github.com> Date: Tue, 31 Mar 2020 21:25:51 +1300 Subject: [PATCH 1/4] Added the ability to specify a proxy for electron to connect through in the config.json file. --- src/electron-main.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/electron-main.js b/src/electron-main.js index 2cd6c66ae7..f2dc4707e6 100644 --- a/src/electron-main.js +++ b/src/electron-main.js @@ -696,7 +696,17 @@ app.on('ready', async () => { spellcheck: true, }, }); - mainWindow.loadURL('vector://vector/webapp/'); + + if (vectorConfig['proxy']) { + console.log(`Starting electron through proxy: ${vectorConfig['proxy']}`); + mainWindow.webContents.session.setProxy({proxyRules: vectorConfig['proxy']}) + .then(() => { + mainWindow.loadURL('vector://vector/webapp/'); + }); + } else { + mainWindow.loadURL('vector://vector/webapp/'); + } + Menu.setApplicationMenu(vectorMenu); // Create trayIcon icon From 462669727e3d6b23f00401fc5623e2709edfe8a4 Mon Sep 17 00:00:00 2001 From: TheraNinjaCat <236111633+TheraNinjaCat@users.noreply.github.com> Date: Fri, 17 Sep 2021 15:37:04 +1200 Subject: [PATCH 2/4] Added proxy support from the electron-config.json file. --- README.md | 43 +++++++++++++++++ src/electron-main.ts | 12 +++++ src/proxy-helper.ts | 108 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 src/proxy-helper.ts diff --git a/README.md b/README.md index d2fe695f6d..4db69e914a 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,49 @@ $PROFILE` in which case it becomes `Element-$PROFILE`, or it is using one of the above created by a pre-1.7 install, in which case it will be `Riot` or `Riot-$PROFILE`. +See https://github.com/vector-im/element-web/blob/develop/docs/config.md + +Electron Config +=============== + +Electron config is stored in the same folder as the [user specified config](#user-specified-configjson) +in `electron-config.json`. +This is for configuring Electron options as opposed to Matrix/Element options from `config.json`. + +This has the following config options: + +* `warnBeforeExit`: boolean, optional (default: `true`) + * `true`: Element will display a confirmation box before exiting. +* `minimizeToTray`: boolean, optional (default: `true`) + * `true`: Element will enable the tray. +* `spellCheckerEnabled`: boolean, optional (default: `true`) + * `true`: Element spell checking is enabled. +* `autoHideMenuBar`: boolean, optional (default: `true`) + * `true`: Element will automatically hide the menu bar. +* `locale`: string[], optional + * A list of locales for Element to enable. +* `proxy`: object, optional (default: `null`) + * Proxy configuration for Electron to use. [More info](https://www.electronjs.org/docs/api/session#sessetproxyconfig). + * `mode`: string, optional (default: `null`) + * Proxy mode. If it's unspecified, it will be automatically determined based on other specified options. + * `direct`: All connections go direct without a proxy involved. + * `auto_detect`: The proxy configuration is determined by a PAC script that will be downloaded from `wpad/wpad.dat`. + * `pac_script`: The proxy configuration is determined by a PAC script specified in `pacScript`. + This is the default mode if `pacScript` is specified. + * `fixed_servers`: The proxy configuration is specified in `proxyRules`. + This is the default mode if `proxyRules` is specified and `pacScript` is not specified. + * `system`: The proxy configuration is taken from the operating system. + * `pacScript`: string, optional (default: null) + * The URL for a PAC file. + * This supports local files through the `file:` URI scheme, + e.g. `file:///home/$USER/proxy.pac` or `file://c:/Users/Username/Documents/proxy.pac`, + as well as `data:`, `http:`, and `https:` URI schemes. + * A `data:` URI should follow the format `data:application/x-javascript-config;base64,$BASE64_PAC_CONTENT`; + * `proxyRules`: string, optional (default: `null`) + * Rules indicating which proxies to use. + * `proxyBypassRules`: string, optional (default: `null`) + * Rules indication which URLs should bypass the proxy settings. + Translations ========================== diff --git a/src/electron-main.ts b/src/electron-main.ts index 33f6a3f36b..eb098fa26e 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -35,6 +35,7 @@ import webContentsHandler from './webcontents-handler'; import * as updater from './updater'; import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol'; import { _t, AppLocalization } from './language-helper'; +import { ProxyConfig, Proxy } from './proxy-helper'; const argv = minimist(process.argv, { alias: { help: "h" }, @@ -81,6 +82,7 @@ let iconPath; let trayConfig; let launcher; let appLocalization; +let proxy; if (argv["help"]) { console.log("Options:"); @@ -261,6 +263,7 @@ const store = new Store<{ spellCheckerEnabled?: boolean; autoHideMenuBar?: boolean; locale?: string | string[]; + proxy?: ProxyConfig; }>({ name: "electron-config" }); let eventIndex = null; @@ -938,6 +941,14 @@ app.on('ready', async () => { webgl: false, }, }); + + proxy = new Proxy({ + store: store, + session: mainWindow.webContents.session // apply proxy to main window + }); + + await proxy.applyProxy(); // wait for proxy settings to be applied + mainWindow.loadURL('vector://vector/webapp/'); // Handle spellchecker @@ -1018,6 +1029,7 @@ function beforeQuit() { if (mainWindow) { mainWindow.webContents.send('before-quit'); } + if (proxy) proxy.close(); } app.on('before-quit', beforeQuit); diff --git a/src/proxy-helper.ts b/src/proxy-helper.ts new file mode 100644 index 0000000000..24c015e730 --- /dev/null +++ b/src/proxy-helper.ts @@ -0,0 +1,108 @@ +/* +Copyright 2021 New Vector Ltd + +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. +*/ + +import { Session } from 'electron'; +import fs from 'fs'; +import type Store from 'electron-store'; + +export interface ProxyConfig { + mode?: "direct" | "auto_detect" | "pac_script" | "fixed_servers" | "system"; + pacScript?: string; + proxyRules?: string; + proxyBypassRules?: string; +} + +type TypedStore = Store<{ proxy?: ProxyConfig }>; + +export class Proxy { + public ready: Promise; + private static readonly STORE_KEY = "proxy"; + + private readonly store: TypedStore; + private readonly session: Session + private proxy: ProxyConfig; + private pacWatcher: fs.FSWatcher; + + constructor( { store, session }: { store: TypedStore, session: Session }) { + + this.store = store; + this.session = session; + + if (this.store.has(Proxy.STORE_KEY)) { + console.log("Setting up proxy."); + this.setProxy(this.store.get(Proxy.STORE_KEY)); + } + } + + public setProxy(proxy: ProxyConfig): void { + this.proxy = proxy; + this.store.set(Proxy.STORE_KEY, this.proxy); + if (this.proxy.pacScript) { + // Add custom handling for the file: URI handler as chromium does not support it + // https://bugs.chromium.org/p/chromium/issues/detail?id=839566#c40 + const pacURL = new URL(this.proxy.pacScript); + if (pacURL.protocol === 'file:') { + this.setProxyFromPACFile(pacURL.pathname); + this.watchProxyPACFile(pacURL.pathname); + } + } + } + + public async applyProxy(): Promise { + // Apply the proxy config to the session + if (!this.proxy) return; + + return this.session.closeAllConnections() // Ensure all in-progress connections are closed + .then(() => this.session.setProxy(this.proxy)) // Set the proxy settings + .then(() => this.session.forceReloadProxyConfig()); // Ensure the updated config has been reloaded + } + + private setProxyFromPACFile(pacFile: fs.PathLike): void { + // Convert PAC file path into a base64 data: URI + if (!this.proxy) return; + + const pacBuf = fs.readFileSync(pacFile); + this.proxy.pacScript = `data:application/x-javascript-config;base64,${pacBuf.toString('base64')}`; + } + + private watchProxyPACFile(pacFile: fs.PathLike): void { + // Watch the PAC file for changes and reapply config if a change is detected + if (this.pacWatcher) return; + + this.pacWatcher = fs.watch(pacFile, async (event) => { + console.log("Started watching PAC file.") + }); + + this.pacWatcher.on('change', (eventType: string) => { + console.log("PAC file changed, updating proxy settings."); + this.setProxyFromPACFile(pacFile); + this.applyProxy(); + + }); + + this.pacWatcher.on('close', () => { + console.log("Stopped watching PAC file.") + }); + } + + public close(): void { + // Cleanup the fs watcher + if (!this.pacWatcher) return; + + this.pacWatcher.close(); + this.pacWatcher = null; + } +} From 27c801fb75d062a817463843dbc436b20db6495e Mon Sep 17 00:00:00 2001 From: TheraNinjaCat <236111633+TheraNinjaCat@users.noreply.github.com> Date: Fri, 17 Sep 2021 15:47:26 +1200 Subject: [PATCH 3/4] Updated proxy support to apply to multiple sessions, and changed updater from using the built in updater to using electron-updater instead, as the built in updater does not obey proxy settings, and the electron-updater exposes it's session that can have proxy settings applied --- package.json | 1 + src/electron-main.ts | 8 ++++++-- src/proxy-helper.ts | 18 +++++++++++------- src/updater.ts | 4 +++- yarn.lock | 29 +++++++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 33df4ceef4..3a375757c3 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "auto-launch": "^5.0.5", "counterpart": "^0.18.6", "electron-store": "^6.0.1", + "electron-updater": "^4.3.9", "electron-window-state": "^5.0.3", "minimist": "^1.2.3", "png-to-ico": "^2.1.1", diff --git a/src/electron-main.ts b/src/electron-main.ts index eb098fa26e..066c3fed0f 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, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol, dialog } from "electron"; +import { app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, protocol, dialog } from "electron"; +import { autoUpdater } from "electron-updater"; import AutoLaunch from "auto-launch"; import path from "path"; import windowStateKeeper from 'electron-window-state'; @@ -944,7 +945,10 @@ app.on('ready', async () => { proxy = new Proxy({ store: store, - session: mainWindow.webContents.session // apply proxy to main window + sessions: [ + mainWindow.webContents.session, // apply proxy to main window + autoUpdater.netSession // apply proxy to autoUpdater + ] }); await proxy.applyProxy(); // wait for proxy settings to be applied diff --git a/src/proxy-helper.ts b/src/proxy-helper.ts index 24c015e730..6576a15e93 100644 --- a/src/proxy-helper.ts +++ b/src/proxy-helper.ts @@ -32,14 +32,14 @@ export class Proxy { private static readonly STORE_KEY = "proxy"; private readonly store: TypedStore; - private readonly session: Session + private readonly sessions: Array private proxy: ProxyConfig; private pacWatcher: fs.FSWatcher; - constructor( { store, session }: { store: TypedStore, session: Session }) { + constructor( { store, sessions = [] }: { store: TypedStore, sessions: Session[] }) { this.store = store; - this.session = session; + this.sessions = sessions; if (this.store.has(Proxy.STORE_KEY)) { console.log("Setting up proxy."); @@ -62,12 +62,16 @@ export class Proxy { } public async applyProxy(): Promise { - // Apply the proxy config to the session + // Apply the proxy config to the sessions if (!this.proxy) return; - return this.session.closeAllConnections() // Ensure all in-progress connections are closed - .then(() => this.session.setProxy(this.proxy)) // Set the proxy settings - .then(() => this.session.forceReloadProxyConfig()); // Ensure the updated config has been reloaded + return Promise.allSettled( + this.sessions.map((session) => + session.closeAllConnections() // Ensure all in-progress connections are closed + .then(() => session.setProxy(this.proxy)) // Set the proxy settings + .then(() => session.forceReloadProxyConfig()) // Ensure the updated config has been reloaded + ) + ); } private setProxyFromPACFile(pacFile: fs.PathLike): void { diff --git a/src/updater.ts b/src/updater.ts index f41e0cf9ed..1bfee709ba 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { app, autoUpdater, ipcMain } from "electron"; +import { app, ipcMain, autoUpdater } from "electron"; +// import { autoUpdater } from "electron-updater"; + const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000; const INITIAL_UPDATE_DELAY_MS = 30 * 1000; diff --git a/yarn.lock b/yarn.lock index fc81e6ab3a..a01856da9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -765,6 +765,11 @@ "@types/node" "*" xmlbuilder ">=11.0.1" +"@types/semver@^7.3.5": + version "7.3.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59" + integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now== + "@types/verror@^1.10.3": version "1.10.5" resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.5.tgz#2a1413aded46e67a1fe2386800e291123ed75eb1" @@ -1947,6 +1952,20 @@ electron-store@^6.0.1: conf "^7.1.2" type-fest "^0.16.0" +electron-updater@^4.3.9: + version "4.3.9" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.9.tgz#247c660bafad7c07935e1b81acd3e9a5fd733154" + integrity sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA== + dependencies: + "@types/semver" "^7.3.5" + builder-util-runtime "8.7.5" + fs-extra "^10.0.0" + js-yaml "^4.1.0" + lazy-val "^1.0.4" + lodash.escaperegexp "^4.1.2" + lodash.isequal "^4.5.0" + semver "^7.3.5" + electron-window-state@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8" @@ -3133,11 +3152,21 @@ lodash.difference@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" From f9a37e697ec9d709a00127a27ae98b3524957575 Mon Sep 17 00:00:00 2001 From: TheraNinjaCat <236111633+TheraNinjaCat@users.noreply.github.com> Date: Fri, 17 Sep 2021 16:41:15 +1200 Subject: [PATCH 4/4] Fixed linting, and correctly imported electron-updater in the updater.js file --- src/electron-main.ts | 4 ++-- src/proxy-helper.ts | 15 ++++++--------- src/updater.ts | 5 ++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/electron-main.ts b/src/electron-main.ts index 066c3fed0f..6ce3ab83b3 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -947,8 +947,8 @@ app.on('ready', async () => { store: store, sessions: [ mainWindow.webContents.session, // apply proxy to main window - autoUpdater.netSession // apply proxy to autoUpdater - ] + autoUpdater.netSession, // apply proxy to autoUpdater + ], }); await proxy.applyProxy(); // wait for proxy settings to be applied diff --git a/src/proxy-helper.ts b/src/proxy-helper.ts index 6576a15e93..7121e5833c 100644 --- a/src/proxy-helper.ts +++ b/src/proxy-helper.ts @@ -32,12 +32,11 @@ export class Proxy { private static readonly STORE_KEY = "proxy"; private readonly store: TypedStore; - private readonly sessions: Array + private readonly sessions: Array; private proxy: ProxyConfig; private pacWatcher: fs.FSWatcher; constructor( { store, sessions = [] }: { store: TypedStore, sessions: Session[] }) { - this.store = store; this.sessions = sessions; @@ -68,10 +67,9 @@ export class Proxy { return Promise.allSettled( this.sessions.map((session) => session.closeAllConnections() // Ensure all in-progress connections are closed - .then(() => session.setProxy(this.proxy)) // Set the proxy settings - .then(() => session.forceReloadProxyConfig()) // Ensure the updated config has been reloaded - ) - ); + .then(() => session.setProxy(this.proxy)) // Set the proxy settings + .then(() => session.forceReloadProxyConfig()), // Ensure the updated config has been reloaded + )); } private setProxyFromPACFile(pacFile: fs.PathLike): void { @@ -87,18 +85,17 @@ export class Proxy { if (this.pacWatcher) return; this.pacWatcher = fs.watch(pacFile, async (event) => { - console.log("Started watching PAC file.") + console.log("Started watching PAC file."); }); this.pacWatcher.on('change', (eventType: string) => { console.log("PAC file changed, updating proxy settings."); this.setProxyFromPACFile(pacFile); this.applyProxy(); - }); this.pacWatcher.on('close', () => { - console.log("Stopped watching PAC file.") + console.log("Stopped watching PAC file."); }); } diff --git a/src/updater.ts b/src/updater.ts index 1bfee709ba..8294eeaeb4 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { app, ipcMain, autoUpdater } from "electron"; -// import { autoUpdater } from "electron-updater"; - +import { app, ipcMain } from "electron"; +import { autoUpdater } from "electron-updater"; const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000; const INITIAL_UPDATE_DELAY_MS = 30 * 1000;