From 6ca0b3ad038a942deab2c42606e5d4b0923272eb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 11 Dec 2018 16:13:46 +0000 Subject: [PATCH 01/25] Update the tests to match https://github.com/matrix-org/matrix-react-sdk/pull/2340 See the react-sdk PR for some context. --- test/app-tests/loading.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/app-tests/loading.js b/test/app-tests/loading.js index 676475b687..43e72b9355 100644 --- a/test/app-tests/loading.js +++ b/test/app-tests/loading.js @@ -43,6 +43,17 @@ import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils'; const DEFAULT_HS_URL='http://my_server'; const DEFAULT_IS_URL='http://my_is'; +expect.extend({ + toStartWith(prefix) { + expect.assert( + this.actual.startsWith(prefix), + 'expected %s to start with %s', + this.actual, prefix, + ); + return this; + } +}); + describe('loading:', function() { let parentDiv; let httpBackend; @@ -437,10 +448,7 @@ describe('loading:', function() { }).done(done, done); }); - it('uses the last known homeserver to register with', function(done) { - localStorage.setItem("mx_hs_url", "https://homeserver" ); - localStorage.setItem("mx_is_url", "https://idserver" ); - + it('uses the default homeserver to register with', function(done) { loadApp(); Promise.delay(1).then(() => { @@ -449,7 +457,7 @@ describe('loading:', function() { assertAtLoadingSpinner(matrixChat); httpBackend.when('POST', '/register').check(function(req) { - expect(req.path).toMatch(new RegExp("^https://homeserver/")); + expect(req.path).toStartWith(DEFAULT_HS_URL); expect(req.queryParams.kind).toEqual('guest'); }).respond(200, { user_id: "@guest:localhost", @@ -462,15 +470,15 @@ describe('loading:', function() { }).then(() => { return expectAndAwaitSync(); }).then((req) => { - expect(req.path).toMatch(new RegExp("^https://homeserver/")); + expect(req.path).toStartWith(DEFAULT_HS_URL); // once the sync completes, we should have a home page httpBackend.verifyNoOutstandingExpectation(); ReactTestUtils.findRenderedComponentWithType( matrixChat, sdk.getComponent('structures.HomePage')); expect(windowLocation.hash).toEqual("#/home"); - expect(MatrixClientPeg.get().baseUrl).toEqual("https://homeserver"); - expect(MatrixClientPeg.get().idBaseUrl).toEqual("https://idserver"); + expect(MatrixClientPeg.get().baseUrl).toEqual(DEFAULT_HS_URL); + expect(MatrixClientPeg.get().idBaseUrl).toEqual(DEFAULT_IS_URL); }).done(done, done); }); From 17983c47d8e09ed17b7a0065b29061b281c6d3fc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 18 Dec 2018 00:30:16 +0000 Subject: [PATCH 02/25] Link to CONTRIBUTING from JS SDK The JS SDK's CONTRIBUTING file is a bit simpler to read. The Synapse version previously used includes mentions of Python lint tools that don't apply here. Signed-off-by: J. Ryan Stinnett --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2a9bab5759..f084b474bd 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing code to Riot ========================= -Riot follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst. +Riot follows the same pattern as https://github.com/matrix-org/matrix-js-sdk/blob/master/CONTRIBUTING.rst. From 19f1489c9284171e5ea3e69121864697a4e97e8a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Dec 2018 17:42:55 +0000 Subject: [PATCH 03/25] Run the Desktop app in a sandbox * Turn off node integration in the electron renderer process * Enable the chromium sandbox to put the renderer into its own process * Expose just the ipc module with a preload script * Introduce a little IPC call wrapper so we can call into the renderer process and await on the result. * Use this in a bunch of places we previously used direct calls to electron modules. * Convert other uses of node, eg. use of process to derive the platform (just look at the user agent) * Strip out the desktopCapturer integration which doesn't appear to have ever worked (probably best to just wait until getDisplayMedia() is available in chrome at this point: https://github.com/vector-im/riot-web/issues/4880). --- electron_app/src/electron-main.js | 106 +++++++---- .../index.js => electron_app/src/preload.js | 16 +- src/vector/index.js | 15 +- src/vector/platform/ElectronPlatform.js | 177 +++++++++--------- src/vector/platform/VectorBasePlatform.js | 19 +- src/vector/platform/WebPlatform.js | 4 + webpack.config.js | 5 - 7 files changed, 198 insertions(+), 144 deletions(-) rename src/vector/platform/index.js => electron_app/src/preload.js (61%) diff --git a/electron_app/src/electron-main.js b/electron_app/src/electron-main.js index 85955392aa..e11b3233ba 100644 --- a/electron_app/src/electron-main.js +++ b/electron_app/src/electron-main.js @@ -2,6 +2,7 @@ Copyright 2016 Aviral Dasgupta Copyright 2016 OpenMarket Ltd Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2018 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. @@ -23,8 +24,9 @@ const checkSquirrelHooks = require('./squirrelhooks'); if (checkSquirrelHooks()) return; const argv = require('minimist')(process.argv); -const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu} = require('electron'); +const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater} = require('electron'); const AutoLaunch = require('auto-launch'); +const path = require('path'); const tray = require('./tray'); const vectorMenu = require('./vectormenu'); @@ -97,6 +99,61 @@ ipcMain.on('app_onAction', function(ev, payload) { } }); +autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => { + if (!mainWindow) return; + // forward to renderer + mainWindow.webContents.send('update-downloaded', { + releaseNotes, + releaseName, + releaseDate, + updateURL, + }); +}); + +ipcMain.on('ipcCall', function(ev, payload) { + if (!mainWindow) return; + + const args = payload.args || []; + let ret; + + switch (payload.name) { + case 'getUpdateFeedUrl': + ret = autoUpdater.getFeedURL(); + break; + case 'getAutoLaunchEnabled': + ret = launcher.isEnabled; + break; + case 'setAutoLaunchEnabled': + if (args[0]) { + launcher.enable(); + } else { + launcher.disable(); + } + break; + case 'getAppVersion': + ret = app.getVersion(); + break; + case 'focusWindow': + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } else if (!mainWindow.isVisible()) { + mainWindow.show(); + } else { + mainWindow.focus(); + } + default: + mainWindow.webContents.send('ipcReply', { + id: payload.id, + error: new Error("Unknown IPC Call: "+payload.name), + }); + return; + } + + mainWindow.webContents.send('ipcReply', { + id: payload.id, + reply: ret, + }); +}); app.commandLine.appendSwitch('--enable-usermedia-screen-capturing'); @@ -126,40 +183,6 @@ const launcher = new AutoLaunch({ }, }); -const settings = { - 'auto-launch': { - get: launcher.isEnabled, - set: function(bool) { - if (bool) { - return launcher.enable(); - } else { - return launcher.disable(); - } - }, - }, -}; - -ipcMain.on('settings_get', async function(ev) { - const data = {}; - - try { - await Promise.all(Object.keys(settings).map(async function (setting) { - data[setting] = await settings[setting].get(); - })); - - ev.sender.send('settings', data); - } catch (e) { - console.error(e); - } -}); - -ipcMain.on('settings_set', function(ev, key, value) { - console.log(key, value); - if (settings[key] && settings[key].set) { - settings[key].set(value); - } -}); - app.on('ready', () => { if (argv['devtools']) { try { @@ -191,6 +214,7 @@ app.on('ready', () => { defaultHeight: 768, }); + const preloadScript = path.normalize(`${__dirname}/preload.js`); mainWindow = global.mainWindow = new BrowserWindow({ icon: iconPath, show: false, @@ -200,6 +224,18 @@ app.on('ready', () => { y: mainWindowState.y, width: mainWindowState.width, height: mainWindowState.height, + webPreferences: { + preload: preloadScript, + nodeIntegration: false, + sandbox: true, + enableRemoteModule: false, + // We don't use this: it's useful for the preload script to + // share a context with the main page so we can give select + // objects to the main page. The sandbox option isolates the + // main page from the background script. + contextIsolation: false, + webgl: false, + }, }); mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`); Menu.setApplicationMenu(vectorMenu); diff --git a/src/vector/platform/index.js b/electron_app/src/preload.js similarity index 61% rename from src/vector/platform/index.js rename to electron_app/src/preload.js index 9071420015..4c926d2145 100644 --- a/src/vector/platform/index.js +++ b/electron_app/src/preload.js @@ -1,8 +1,5 @@ -// @flow - /* -Copyright 2016 Aviral Dasgupta -Copyright 2016 OpenMarket Ltd +Copyright 2018 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. @@ -17,13 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -let Platform = null; +const { ipcRenderer } = require('electron'); -if (window && window.process && window.process && window.process.type === 'renderer') { - // we're running inside electron - Platform = require('./ElectronPlatform'); -} else { - Platform = require('./WebPlatform'); -} +// expose ipcRenderer to the renderer process +window.ipcRenderer = ipcRenderer; -export default Platform; diff --git a/src/vector/index.js b/src/vector/index.js index 32a88f363d..9d05aa347c 100644 --- a/src/vector/index.js +++ b/src/vector/index.js @@ -47,7 +47,9 @@ import * as languageHandler from 'matrix-react-sdk/lib/languageHandler'; import url from 'url'; import {parseQs, parseQsFromFragment} from './url_utils'; -import Platform from './platform'; + +import ElectronPlatform from './platform/ElectronPlatform'; +import WebPlatform from './platform/WebPlatform'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore"; @@ -219,8 +221,15 @@ async function loadApp() { const fragparts = parseQsFromFragment(window.location); const params = parseQs(window.location); - // set the platform for react sdk (our Platform object automatically picks the right one) - PlatformPeg.set(new Platform()); + // set the platform for react sdk + //if (navigator.userAgent.toLowerCase().indexOf('electron') > 0) { + if (window.ipcRenderer) { + console.log("Using Electron platform"); + PlatformPeg.set(new ElectronPlatform()); + } else { + console.log("Using Web platform"); + PlatformPeg.set(new WebPlatform()); + } // Load the config file. First try to load up a domain-specific config of the // form "config.$domain.json" and if that fails, fall back to config.json. diff --git a/src/vector/platform/ElectronPlatform.js b/src/vector/platform/ElectronPlatform.js index 8b384b20d1..b116cd6370 100644 --- a/src/vector/platform/ElectronPlatform.js +++ b/src/vector/platform/ElectronPlatform.js @@ -3,6 +3,7 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2016 OpenMarket Ltd +Copyright 2018 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. @@ -21,44 +22,24 @@ import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform'; import dis from 'matrix-react-sdk/lib/dispatcher'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; import Promise from 'bluebird'; -import {remote, ipcRenderer, desktopCapturer} from 'electron'; import rageshake from 'matrix-react-sdk/lib/rageshake/rageshake'; -remote.autoUpdater.on('update-downloaded', onUpdateDownloaded); - -// try to flush the rageshake logs to indexeddb before quit. -ipcRenderer.on('before-quit', function() { - console.log('riot-desktop closing'); - rageshake.flush(); -}); - -function onUpdateDownloaded(ev: Event, releaseNotes: string, ver: string, date: Date, updateURL: string) { - dis.dispatch({ - action: 'new_version', - currentVersion: remote.app.getVersion(), - newVersion: ver, - releaseNotes: releaseNotes, - }); -} - function platformFriendlyName(): string { - console.log(window.process); - switch (window.process.platform) { - case 'darwin': - return 'macOS'; - case 'freebsd': - return 'FreeBSD'; - case 'openbsd': - return 'OpenBSD'; - case 'sunos': - return 'SunOS'; - case 'win32': - return 'Windows'; - default: - // Sorry, Linux users: you get lumped into here, - // but only because Linux's capitalisation is - // normal. We do care about you. - return window.process.platform[0].toUpperCase() + window.process.platform.slice(1); + // used to use window.process but the same info is available here + if (navigator.userAgent.indexOf('Macintosh')) { + return 'macOS'; + } else if (navigator.userAgent.indexOf('FreeBSD')) { + return 'FreeBSD'; + } else if (navigator.userAgent.indexOf('OpenBSD')) { + return 'OpenBSD'; + } else if (navigator.userAgent.indexOf('SunOS')) { + return 'SunOS'; + } else if (navigator.userAgent.indexOf('Windows')) { + return 'Windows'; + } else if (navigator.userAgent.indexOf('Linux')) { + return 'Linux'; + } else { + return 'Unknown'; } } @@ -85,9 +66,11 @@ function getUpdateCheckStatus(status) { export default class ElectronPlatform extends VectorBasePlatform { constructor() { super(); - dis.register(_onAction); - this.updatable = Boolean(remote.autoUpdater.getFeedURL()); + this._pendingIpcCalls = {}; + this._nextIpcCallId = 0; + + dis.register(_onAction); /* IPC Call `check_updates` returns: true if there is an update available @@ -103,10 +86,28 @@ export default class ElectronPlatform extends VectorBasePlatform { this.showUpdateCheck = false; }); + // try to flush the rageshake logs to indexeddb before quit. + ipcRenderer.on('before-quit', function() { + console.log('riot-desktop closing'); + rageshake.flush(); + }); + + ipcRenderer.on('ipcReply', this._onIpcReply.bind(this)); + ipcRenderer.on('update-downloaded', this.onUpdateDownloaded.bind(this)); + this.startUpdateCheck = this.startUpdateCheck.bind(this); this.stopUpdateCheck = this.stopUpdateCheck.bind(this); } + async onUpdateDownloaded(ev, updateInfo) { + dis.dispatch({ + action: 'new_version', + currentVersion: await this.getAppVersion(), + newVersion: updateInfo, + releaseNotes: updateInfo.releaseNotes, + }); + } + getHumanReadableName(): string { return 'Electron Platform'; // no translation required: only used for analytics } @@ -133,7 +134,7 @@ export default class ElectronPlatform extends VectorBasePlatform { // maybe we should pass basic styling (italics, bold, underline) through from MD // we only have to strip out < and > as the spec doesn't include anything about things like & // so we shouldn't assume that all implementations will treat those properly. Very basic tag parsing is done. - if (window.process.platform === 'linux') { + if (navigator.userAgent.indexOf('Linux')) { msg = msg.replace(//g, '>'); } @@ -147,17 +148,13 @@ export default class ElectronPlatform extends VectorBasePlatform { }, ); - notification.onclick = function() { + notification.onclick = () => { dis.dispatch({ action: 'view_room', room_id: room.roomId, }); global.focus(); - const win = remote.getCurrentWindow(); - - if (win.isMinimized()) win.restore(); - else if (!win.isVisible()) win.show(); - else win.focus(); + this._ipcCall('focusWindow'); }; return notification; @@ -171,8 +168,25 @@ export default class ElectronPlatform extends VectorBasePlatform { notif.close(); } - getAppVersion(): Promise { - return Promise.resolve(remote.app.getVersion()); + async getAppVersion(): Promise { + return await this._ipcCall('getAppVersion'); + } + + supportsAutoLaunch() { + return true; + } + + async getAutoLaunchEnabled() { + return await this._ipcCall('getAutoLaunchEnabled'); + } + + async setAutoLaunchEnabled(enabled) { + return await this._ipcCall('setAutoLaunchEnabled', enabled); + } + + async canSelfUpdate(): boolean { + const feedUrl = await this._ipcCall('getUpdateFeedUrl'); + return Boolean(feedUrl); } startUpdateCheck() { @@ -197,52 +211,43 @@ export default class ElectronPlatform extends VectorBasePlatform { return null; } - isElectron(): boolean { return true; } - requestNotificationPermission(): Promise { return Promise.resolve('granted'); } reload() { - remote.getCurrentWebContents().reload(); + // we used to remote to the main process to get it to + // reload the webcontents, but in practice this is unnecessary: + // the normal way works fine. + window.location.reload(false); } - /* BEGIN copied and slightly-modified code - * setupScreenSharingForIframe function from: - * https://github.com/jitsi/jitsi-meet-electron-utils - * Copied directly here to avoid the need for a native electron module for - * 'just a bit of JavaScript' - * NOTE: Apache v2.0 licensed - */ - setupScreenSharingForIframe(iframe: Object) { - iframe.contentWindow.JitsiMeetElectron = { - /** - * Get sources available for screensharing. The callback is invoked - * with an array of DesktopCapturerSources. - * - * @param {Function} callback - The success callback. - * @param {Function} errorCallback - The callback for errors. - * @param {Object} options - Configuration for getting sources. - * @param {Array} options.types - Specify the desktop source types - * to get, with valid sources being "window" and "screen". - * @param {Object} options.thumbnailSize - Specify how big the - * preview images for the sources should be. The valid keys are - * height and width, e.g. { height: number, width: number}. By - * default electron will return images with height and width of - * 150px. - */ - obtainDesktopStreams(callback, errorCallback, options = {}) { - desktopCapturer.getSources(options, - (error, sources) => { - if (error) { - errorCallback(error); - return; - } - - callback(sources); - }); - }, - }; + async _ipcCall(name, ...args) { + const ipcCallId = ++this._nextIpcCallId; + return new Promise((resolve, reject) => { + this._pendingIpcCalls[ipcCallId] = {resolve, reject}; + window.ipcRenderer.send('ipcCall', {id: ipcCallId, name, args}); + // Maybe add a timeout to these? Probably not necessary. + }); + } + + _onIpcReply(ev, payload) { + if (payload.id === undefined) { + console.warn("Ignoring IPC reply with no ID"); + return; + } + + if (this._pendingIpcCalls[payload.id] === undefined) { + console.warn("Unknown IPC payload ID: " + payload.id); + return; + } + + const callbacks = this._pendingIpcCalls[payload.id]; + delete this._pendingIpcCalls[payload.id]; + if (payload.error) { + callbacks.reject(payload.error); + } else { + callbacks.resolve(payload.reply); + } } - /* END of copied and slightly-modified code */ } diff --git a/src/vector/platform/VectorBasePlatform.js b/src/vector/platform/VectorBasePlatform.js index 16b9d17801..7523462cfc 100644 --- a/src/vector/platform/VectorBasePlatform.js +++ b/src/vector/platform/VectorBasePlatform.js @@ -3,6 +3,7 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2016 OpenMarket Ltd +Copyright 2018 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. @@ -45,7 +46,6 @@ export default class VectorBasePlatform extends BasePlatform { this.favicon = new Favico({animation: 'none'}); this.showUpdateCheck = false; this._updateFavicon(); - this.updatable = true; this.startUpdateCheck = this.startUpdateCheck.bind(this); this.stopUpdateCheck = this.stopUpdateCheck.bind(this); @@ -88,6 +88,19 @@ export default class VectorBasePlatform extends BasePlatform { this._updateFavicon(); } + supportsAutoLaunch() { + return false; + } + + // XXX: Surely this should be a setting like any other? + async getAutoLaunchEnabled() { + return false; + } + + async setAutoLaunchEnabled(enabled) { + throw new Error("Unimplemented"); + } + /** * Begin update polling, if applicable */ @@ -97,8 +110,8 @@ export default class VectorBasePlatform extends BasePlatform { /** * Whether we can call checkForUpdate on this platform build */ - canSelfUpdate(): boolean { - return this.updatable; + async canSelfUpdate(): boolean { + return false; } startUpdateCheck() { diff --git a/src/vector/platform/WebPlatform.js b/src/vector/platform/WebPlatform.js index 2955b84a3b..d850dd6ddd 100644 --- a/src/vector/platform/WebPlatform.js +++ b/src/vector/platform/WebPlatform.js @@ -142,6 +142,10 @@ export default class WebPlatform extends VectorBasePlatform { setInterval(this.pollForUpdate.bind(this), POKE_RATE_MS); } + async canSelfUpdate(): boolean { + return true; + } + pollForUpdate() { return this._getVersion().then((ver) => { if (this.runningVersion === null) { diff --git a/webpack.config.js b/webpack.config.js index f335aa3c1b..82972adf44 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -108,11 +108,6 @@ module.exports = { "matrix-js-sdk": path.resolve('./node_modules/matrix-js-sdk'), }, }, - externals: { - // Don't try to bundle electron: leave it as a commonjs dependency - // (the 'commonjs' here means it will output a 'require') - "electron": "commonjs electron", - }, plugins: [ new webpack.DefinePlugin({ 'process.env': { From 0c428efea0a6b4e26bcf88b62a59475732e5a1fb Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Dec 2018 18:03:47 +0000 Subject: [PATCH 04/25] lint --- src/vector/platform/ElectronPlatform.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vector/platform/ElectronPlatform.js b/src/vector/platform/ElectronPlatform.js index b116cd6370..0bf0068a43 100644 --- a/src/vector/platform/ElectronPlatform.js +++ b/src/vector/platform/ElectronPlatform.js @@ -24,6 +24,8 @@ import { _t } from 'matrix-react-sdk/lib/languageHandler'; import Promise from 'bluebird'; import rageshake from 'matrix-react-sdk/lib/rageshake/rageshake'; +const ipcRenderer = window.ipcRenderer; + function platformFriendlyName(): string { // used to use window.process but the same info is available here if (navigator.userAgent.indexOf('Macintosh')) { From 60d307603873b4a6e9630fafea986e3d1589d5f8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Dec 2018 18:10:09 +0000 Subject: [PATCH 05/25] Update to new electron single instance API --- electron_app/src/electron-main.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/electron_app/src/electron-main.js b/electron_app/src/electron-main.js index 85955392aa..986cc7070f 100644 --- a/electron_app/src/electron-main.js +++ b/electron_app/src/electron-main.js @@ -100,24 +100,12 @@ ipcMain.on('app_onAction', function(ev, payload) { app.commandLine.appendSwitch('--enable-usermedia-screen-capturing'); -const shouldQuit = app.makeSingleInstance((commandLine, workingDirectory) => { - // If other instance launched with --hidden then skip showing window - if (commandLine.includes('--hidden')) return; - - // Someone tried to run a second instance, we should focus our window. - if (mainWindow) { - if (!mainWindow.isVisible()) mainWindow.show(); - if (mainWindow.isMinimized()) mainWindow.restore(); - mainWindow.focus(); - } -}); - -if (shouldQuit) { +const gotLock = app.requestSingleInstanceLock(); +if (!gotLock) { console.log('Other instance detected: exiting'); app.exit(); } - const launcher = new AutoLaunch({ name: vectorConfig.brand || 'Riot', isHidden: true, @@ -268,6 +256,18 @@ app.on('before-quit', () => { } }); +app.on('second-instance', (ev, commandLine, workingDirectory) => { + // If other instance launched with --hidden then skip showing window + if (commandLine.includes('--hidden')) return; + + // Someone tried to run a second instance, we should focus our window. + if (mainWindow) { + if (!mainWindow.isVisible()) mainWindow.show(); + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + } +}); + // 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 From 0e580635eb6a2ed4b63a626d4e920d61f398e875 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Dec 2018 18:13:51 +0000 Subject: [PATCH 06/25] Update tests for new platform layout --- test/app-tests/joining.js | 4 ++-- test/app-tests/loading.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/app-tests/joining.js b/test/app-tests/joining.js index 60bf3fd3b4..5b4eae4213 100644 --- a/test/app-tests/joining.js +++ b/test/app-tests/joining.js @@ -17,7 +17,7 @@ limitations under the License. /* joining.js: tests for the various paths when joining a room */ import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg'; -import Platform from '../../src/vector/platform'; +import WebPlatform from '../../src/vector/platform/WebPlatform'; require('skin-sdk'); @@ -88,7 +88,7 @@ describe('joining a room', function() { localStorage.setItem("mx_access_token", ACCESS_TOKEN ); localStorage.setItem("mx_user_id", USER_ID); - PlatformPeg.set(new Platform()); + PlatformPeg.set(new WebPlatform()); const mc = ( Date: Wed, 19 Dec 2018 09:29:05 +0000 Subject: [PATCH 07/25] Remove unused commented line --- src/vector/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vector/index.js b/src/vector/index.js index 9d05aa347c..36ae067c63 100644 --- a/src/vector/index.js +++ b/src/vector/index.js @@ -222,7 +222,6 @@ async function loadApp() { const params = parseQs(window.location); // set the platform for react sdk - //if (navigator.userAgent.toLowerCase().indexOf('electron') > 0) { if (window.ipcRenderer) { console.log("Using Electron platform"); PlatformPeg.set(new ElectronPlatform()); From fc4e1485ad745a89e8d43f9d35fb1462484ae357 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Dec 2018 12:04:40 +0000 Subject: [PATCH 08/25] Electron: Load app from custom protocol This puts the app into its own origin so it doesn't have access to the filesystem via file:// URIs. Next step: migrate over localstorage & indexeddb data from the old origin... --- electron_app/src/electron-main.js | 50 +++++++++++++++++++++++++++++-- electron_app/src/preload.js | 8 +++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/electron_app/src/electron-main.js b/electron_app/src/electron-main.js index dbd2ff670c..b8201070a0 100644 --- a/electron_app/src/electron-main.js +++ b/electron_app/src/electron-main.js @@ -24,7 +24,7 @@ const checkSquirrelHooks = require('./squirrelhooks'); if (checkSquirrelHooks()) return; const argv = require('minimist')(process.argv); -const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater} = require('electron'); +const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol} = require('electron'); const AutoLaunch = require('auto-launch'); const path = require('path'); @@ -171,6 +171,13 @@ const launcher = new AutoLaunch({ }, }); +// 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). +protocol.registerStandardSchemes(['vector'], {secure: true}); + app.on('ready', () => { if (argv['devtools']) { try { @@ -186,6 +193,45 @@ app.on('ready', () => { } } + protocol.registerFileProtocol('vector', (request, callback) => { + if (request.method !== 'GET') { + callback({error: -322}); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h + return null; + } + + const parsedUrl = new URL(request.url); + if (parsedUrl.protocol !== 'vector:') { + callback({error: -302}); // UNKNOWN_URL_SCHEME + return; + } + if (parsedUrl.host !== 'vector') { + callback({error: -105}); // NAME_NOT_RESOLVED + return; + } + + const target = parsedUrl.pathname.split('/'); + if (target[target.length - 1] == '') { + target[target.length - 1] = 'index.html'; + } + + // Normalise the base dir and the target path separately, then make sure + // the target path isn't trying to back out beyond its root + const appBaseDir = path.normalize(__dirname + "/../../webapp"); + const relTarget = path.normalize(path.join(...target)); + if (relTarget.startsWith('..')) { + callback({error: -6}); // FILE_NOT_FOUND + return; + } + const absTarget = path.join(appBaseDir, relTarget); + + callback({ + path: absTarget, + }); + }, (error) => { + if (error) console.error('Failed to register protocol') + }); + + if (vectorConfig['update_base_url']) { console.log(`Starting auto update with base URL: ${vectorConfig['update_base_url']}`); @@ -225,7 +271,7 @@ app.on('ready', () => { webgl: false, }, }); - mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`); + mainWindow.loadURL('vector://vector/'); Menu.setApplicationMenu(vectorMenu); // explicitly hide because setApplicationMenu on Linux otherwise shows... diff --git a/electron_app/src/preload.js b/electron_app/src/preload.js index 4c926d2145..bf6e23bbaa 100644 --- a/electron_app/src/preload.js +++ b/electron_app/src/preload.js @@ -19,3 +19,11 @@ const { ipcRenderer } = require('electron'); // expose ipcRenderer to the renderer process window.ipcRenderer = ipcRenderer; +// Allow the fetch API to load resources from this +// protocol: this is necessary to load olm.wasm. +// (Also mark it a secure although we've already +// done this in the main process). +webFrame.registerURLSchemeAsPrivileged('vector', { + secure: true, + supportFetchAPI: true, +}); From b6d70f443419e468251fd27991fb0ba72c15242d Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Dec 2018 12:25:32 +0000 Subject: [PATCH 09/25] Missing include --- electron_app/src/preload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron_app/src/preload.js b/electron_app/src/preload.js index bf6e23bbaa..3a4f7c9a4b 100644 --- a/electron_app/src/preload.js +++ b/electron_app/src/preload.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { ipcRenderer } = require('electron'); +const { ipcRenderer, webFrame } = require('electron'); // expose ipcRenderer to the renderer process window.ipcRenderer = ipcRenderer; From 8cd37d28daa6476a2142c164090ca121b029cf8b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Dec 2018 14:52:17 +0000 Subject: [PATCH 10/25] Clarify line number instructions Signed-off-by: J. Ryan Stinnett --- src/vector/rageshakesetup.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vector/rageshakesetup.js b/src/vector/rageshakesetup.js index 2ea438ef5b..7992df2b47 100644 --- a/src/vector/rageshakesetup.js +++ b/src/vector/rageshakesetup.js @@ -30,7 +30,9 @@ import SdkConfig from "matrix-react-sdk/src/SdkConfig"; function initRageshake() { rageshake.init().then(() => { - console.log("Initialised rageshake: See https://bugs.chromium.org/p/chromium/issues/detail?id=583193 to fix line numbers on Chrome."); + console.log("Initialised rageshake."); + console.log("To fix line numbers in Chrome: " + + "Meatball menu → Settings → Blackboxing → Add /rageshake\\.js$"); window.addEventListener('beforeunload', (e) => { console.log('riot-web closing'); From f7bdc9339c95e66cd1f9bd3a2da42f078200851d Mon Sep 17 00:00:00 2001 From: Remi Rampin Date: Wed, 19 Dec 2018 15:50:16 -0500 Subject: [PATCH 11/25] Make clear that the Debian package is for desktop --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 72def22803..a07e48709c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ Note that Chrome does not allow microphone or webcam access for sites served over http (except localhost), so for working VoIP you will need to serve Riot over https. -### Installation Steps for Debian Stretch +### Desktop Installation for Debian Stretch + 1. Add the repository to your sources.list using either of the following two options: - Directly to sources.list: `echo "deb https://riot.im/packages/debian/ stretch main" | sudo tee -a /etc/apt/sources.list` - As a separate entry in sources.list.d: `echo "deb https://riot.im/packages/debian/ stretch main" | sudo tee /etc/apt/sources.list.d/riot.list` From 7527bdd2c7d824fd061778917c5cfa0953e2d33c Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Dec 2018 22:05:04 +0000 Subject: [PATCH 12/25] Fix the IndexedDB worker Looks like this was broken in the webpack 4 upgrade due to the worker script setter and the bundle being re-ordered in index.html. * Remove the loop: we only use two scripts now, so import them explicitly * Remove outdated olm import code. * Stop generating a script import for each theme: we were pulling in 3 js files that did absolutely nothing. * Fix worker 'onmessage' scope (set it as a global rather than trying to make it an ES6 module which it isn't). * Fail hard if the indexeddb worker script isn't set to avoid this happening again. --- src/vector/index.html | 26 ++++---------------------- src/vector/index.js | 7 +++++++ src/vector/indexeddb-worker.js | 2 +- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/vector/index.html b/src/vector/index.html index 4ba65c3e06..a5f3af9b81 100644 --- a/src/vector/index.html +++ b/src/vector/index.html @@ -36,28 +36,10 @@
- <% for (var i=0; i < htmlWebpackPlugin.files.js.length; i++) { - if (_.endsWith(htmlWebpackPlugin.files.js[i], 'olm.js')) { - var array = htmlWebpackPlugin.files.js; - htmlWebpackPlugin.files.js.unshift(htmlWebpackPlugin.files.js[i]); - htmlWebpackPlugin.files.js.splice(i, 1); - } - } - - for (var i=0; i < htmlWebpackPlugin.files.js.length; i++) { - // Not a particularly graceful way of not putting the indexeddb worker script - // into the main page - if (_.endsWith(htmlWebpackPlugin.files.js[i], 'indexeddb-worker.js')) { - %> - - <% - continue; - } - %> - - <% } %> + +