diff --git a/electron_app/src/electron-main.js b/electron_app/src/electron-main.js
index 6f712050..ca8c3a1b 100644
--- a/electron_app/src/electron-main.js
+++ b/electron_app/src/electron-main.js
@@ -1,5 +1,3 @@
-// @flow
-
 /*
 Copyright 2016 Aviral Dasgupta
 Copyright 2016 OpenMarket Ltd
@@ -20,14 +18,13 @@ 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.
-const check_squirrel_hooks = require('./squirrelhooks');
-if (check_squirrel_hooks()) return;
+const checkSquirrelHooks = require('./squirrelhooks');
+if (checkSquirrelHooks()) return;
 
 const electron = require('electron');
 
 const tray = require('./tray');
-
-const VectorMenu = require('./vectormenu');
+const vectorMenu = require('./vectormenu');
 const webContentsHandler = require('./webcontents-handler');
 
 const windowStateKeeper = require('electron-window-state');
@@ -48,6 +45,47 @@ const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
 let mainWindow = null;
 let appQuitting = false;
 
+function safeOpenURL(target) {
+    // openExternal passes the target to open/start/xdg-open,
+    // so put fairly stringent limits on what can be opened
+    // (for instance, open /bin/sh does indeed open a terminal
+    // with a shell, albeit with no arguments)
+    const parsedUrl = url.parse(target);
+    if (PERMITTED_URL_SCHEMES.indexOf(parsedUrl.protocol) > -1) {
+        // explicitly use the URL re-assembled by the url library,
+        // so we know the url parser has understood all the parts
+        // of the input string
+        const newTarget = url.format(parsedUrl);
+        electron.shell.openExternal(newTarget);
+    }
+}
+
+function onWindowOrNavigate(ev, target) {
+    // always prevent the default: if something goes wrong,
+    // we don't want to end up opening it in the electron
+    // app, as we could end up opening any sort of random
+    // url in a window that has node scripting access.
+    ev.preventDefault();
+    safeOpenURL(target);
+}
+
+function onLinkContextMenu(ev, params) {
+    const popupMenu = new electron.Menu();
+
+    popupMenu.append(new electron.MenuItem({
+        label: params.linkURL,
+        click() { safeOpenURL(params.linkURL); },
+    }));
+
+    popupMenu.append(new electron.MenuItem({
+        label: 'Copy Link Address',
+        click() { electron.clipboard.writeText(params.linkURL); },
+    }));
+
+    popupMenu.popup();
+    ev.preventDefault();
+}
+
 function installUpdate() {
     // for some reason, quitAndInstall does not fire the
     // before-quit event, so we need to set the flag here.
@@ -59,13 +97,13 @@ function pollForUpdates() {
     try {
         electron.autoUpdater.checkForUpdates();
     } catch (e) {
-        console.log("Couldn't check for update", e);
+        console.log('Couldn\'t check for update', e);
     }
 }
 
-function startAutoUpdate(update_base_url) {
-    if (update_base_url.slice(-1) !== '/') {
-        update_base_url = update_base_url + '/';
+function startAutoUpdate(updateBaseUrl) {
+    if (updateBaseUrl.slice(-1) !== '/') {
+        updateBaseUrl = updateBaseUrl + '/';
     }
     try {
         // For reasons best known to Squirrel, the way it checks for updates
@@ -73,7 +111,7 @@ function startAutoUpdate(update_base_url) {
         // hits a URL that either gives it a 200 with some json or
         // 204 No Content. On windows it takes a base path and looks for
         // files under that path.
-        if (process.platform == 'darwin') {
+        if (process.platform === 'darwin') {
             // include the current version in the URL we hit. Electron doesn't add
             // it anywhere (apart from the User-Agent) so it's up to us. We could
             // (and previously did) just use the User-Agent, but this doesn't
@@ -81,16 +119,15 @@ function startAutoUpdate(update_base_url) {
             // and also acts as a convenient cache-buster to ensure that when the
             // app updates it always gets a fresh value to avoid update-looping.
             electron.autoUpdater.setFeedURL(
-                update_base_url +
-                'macos/?localVersion=' + encodeURIComponent(electron.app.getVersion())
-            );
-        } else if (process.platform == 'win32') {
-            electron.autoUpdater.setFeedURL(update_base_url + 'win32/' + process.arch + '/');
+                `${updateBaseUrl}macos/?localVersion=${encodeURIComponent(electron.app.getVersion())}`);
+
+        } else if (process.platform === 'win32') {
+            electron.autoUpdater.setFeedURL(`${updateBaseUrl}win32/${process.arch}/`);
         } else {
             // Squirrel / electron only supports auto-update on these two platforms.
             // I'm not even going to try to guess which feed style they'd use if they
             // implemented it on Linux, or if it would be different again.
-            console.log("Auto update not supported on this platform");
+            console.log('Auto update not supported on this platform');
         }
         // We check for updates ourselves rather than using 'updater' because we need to
         // do it in the main process (and we don't really need to check every 10 minutes:
@@ -103,7 +140,7 @@ function startAutoUpdate(update_base_url) {
         setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
     } catch (err) {
         // will fail if running in debug mode
-        console.log("Couldn't enable update checking", err);
+        console.log('Couldn\'t enable update checking', err);
     }
 }
 
@@ -114,7 +151,7 @@ function startAutoUpdate(update_base_url) {
 // Assuming we generally run from the console when developing,
 // this is far preferable.
 process.on('uncaughtException', function(error) {
-    console.log("Unhandled exception", error);
+    console.log('Unhandled exception', error);
 });
 
 electron.ipcMain.on('install_update', installUpdate);
@@ -168,30 +205,28 @@ const shouldQuit = electron.app.makeSingleInstance((commandLine, workingDirector
 });
 
 if (shouldQuit) {
-    console.log("Other instance detected: exiting");
+    console.log('Other instance detected: exiting');
     electron.app.quit();
 }
 
 electron.app.on('ready', () => {
     if (vectorConfig.update_base_url) {
-        console.log("Starting auto update with base URL: " + vectorConfig.update_base_url);
+        console.log(`Starting auto update with base URL: ${vectorConfig.update_base_url}`);
         startAutoUpdate(vectorConfig.update_base_url);
     } else {
-        console.log("No update_base_url is defined: auto update is disabled");
+        console.log('No update_base_url is defined: auto update is disabled');
     }
 
-    const icon_path = `${__dirname}/../img/riot.` + (
-        process.platform == 'win32' ? 'ico' : 'png'
-    );
+    const iconPath = `${__dirname}/../img/riot.${process.platform === 'win32' ? 'ico' : 'png'}`;
 
     // Load the previous window state with fallback to defaults
-    let mainWindowState = windowStateKeeper({
+    const mainWindowState = windowStateKeeper({
         defaultWidth: 1024,
         defaultHeight: 768,
     });
 
     mainWindow = new electron.BrowserWindow({
-        icon: icon_path,
+        icon: iconPath,
         show: false,
         autoHideMenuBar: true,
 
@@ -201,12 +236,12 @@ electron.app.on('ready', () => {
         height: mainWindowState.height,
     });
     mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`);
-    electron.Menu.setApplicationMenu(VectorMenu);
+    electron.Menu.setApplicationMenu(vectorMenu);
 
     // Create trayIcon icon
     tray.create(mainWindow, {
-        icon_path: icon_path,
-        brand: vectorConfig.brand || 'Riot'
+        icon_path: iconPath,
+        brand: vectorConfig.brand || 'Riot',
     });
 
     if (!process.argv.includes('--hidden')) {
@@ -219,7 +254,7 @@ electron.app.on('ready', () => {
         mainWindow = null;
     });
     mainWindow.on('close', (e) => {
-        if (!appQuitting && (tray.hasTray() || process.platform == 'darwin')) {
+        if (!appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
             // On Mac, closing the window just hides it
             // (this is generally how single-window Mac apps
             // behave, eg. Mail.app)
diff --git a/electron_app/src/squirrelhooks.js b/electron_app/src/squirrelhooks.js
index 15ed670f..728c9cfb 100644
--- a/electron_app/src/squirrelhooks.js
+++ b/electron_app/src/squirrelhooks.js
@@ -16,30 +16,30 @@ limitations under the License.
 
 const path = require('path');
 const spawn = require('child_process').spawn;
-const app = require('electron').app;
+const {app} = require('electron');
 
-function run_update_exe(args, done) {
+function runUpdateExe(args, done) {
     // Invokes Squirrel's Update.exe which will do things for us like create shortcuts
     // Note that there's an Update.exe in the app-x.x.x directory and one in the parent
     // directory: we need to run the one in the parent directory, because it discovers
     // information about the app by inspecting the directory it's run from.
     const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
-    console.log('Spawning `%s` with args `%s`', updateExe, args);
+    console.log(`Spawning '${updateExe}' with args '${args}'`);
     spawn(updateExe, args, {
-      detached: true
+      detached: true,
     }).on('close', done);
-};
+}
 
-function check_squirrel_hooks() {
-    if (process.platform != 'win32') return false;
+function checkSquirrelHooks() {
+    if (process.platform !== 'win32') return false;
 
     const cmd = process.argv[1];
     const target = path.basename(process.execPath);
     if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
-        run_update_exe(['--createShortcut=' + target + ''], app.quit);
+        runUpdateExe(['--createShortcut=' + target + ''], app.quit);
         return true;
     } else if (cmd === '--squirrel-uninstall') {
-        run_update_exe(['--removeShortcut=' + target + ''], app.quit);
+        runUpdateExe(['--removeShortcut=' + target + ''], app.quit);
         return true;
     } else if (cmd === '--squirrel-obsolete') {
         app.quit();
@@ -48,4 +48,4 @@ function check_squirrel_hooks() {
     return false;
 }
 
-module.exports = check_squirrel_hooks;
+module.exports = checkSquirrelHooks;
diff --git a/electron_app/src/tray.js b/electron_app/src/tray.js
index 5409194d..9df1a0fb 100644
--- a/electron_app/src/tray.js
+++ b/electron_app/src/tray.js
@@ -25,9 +25,7 @@ exports.hasTray = function hasTray() {
 
 exports.create = function(win, config) {
     // no trays on darwin
-    if (process.platform === 'darwin' || trayIcon) {
-        return;
-    }
+    if (process.platform === 'darwin' || trayIcon) return;
 
     const toggleWin = function() {
         if (win.isVisible() && !win.isMinimized()) {
@@ -41,12 +39,10 @@ exports.create = function(win, config) {
 
     const contextMenu = Menu.buildFromTemplate([
         {
-            label: 'Show/Hide ' + config.brand,
+            label: `Show/Hide ${config.brand}`,
             click: toggleWin,
         },
-        {
-            type: 'separator',
-        },
+        { type: 'separator' },
         {
             label: 'Quit',
             click: function() {
diff --git a/electron_app/src/vectormenu.js b/electron_app/src/vectormenu.js
index 70ed3ac3..ab30b376 100644
--- a/electron_app/src/vectormenu.js
+++ b/electron_app/src/vectormenu.js
@@ -14,170 +14,112 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-const electron = require('electron');
+const {app, shell, Menu} = require('electron');
 
 // Menu template from http://electron.atom.io/docs/api/menu/, edited
 const template = [
     {
         label: 'Edit',
         submenu: [
-            {
-                role: 'undo'
-            },
-            {
-                role: 'redo'
-            },
-            {
-                type: 'separator'
-            },
-            {
-                role: 'cut'
-            },
-            {
-                role: 'copy'
-            },
-            {
-                role: 'paste'
-            },
-            {
-                role: 'pasteandmatchstyle'
-            },
-            {
-                role: 'delete'
-            },
-            {
-                role: 'selectall'
-            }
-        ]
+            { role: 'undo' },
+            { role: 'redo' },
+            { type: 'separator' },
+            { role: 'cut' },
+            { role: 'copy' },
+            { role: 'paste' },
+            { role: 'pasteandmatchstyle' },
+            { role: 'delete' },
+            { role: 'selectall' },
+        ],
     },
     {
         label: 'View',
         submenu: [
-            {
-                type: 'separator'
-            },
-            {
-                role: 'resetzoom'
-            },
-            {
-                role: 'zoomin'
-            },
-            {
-                role: 'zoomout'
-            },
-            {
-                type: 'separator'
-            },
-            {
-                role: 'togglefullscreen'
-            },
-            {
-                role: 'toggledevtools'
-            }
-        ]
+            { type: 'separator' },
+            { role: 'resetzoom' },
+            { role: 'zoomin' },
+            { role: 'zoomout' },
+            { type: 'separator' },
+            { role: 'togglefullscreen' },
+            { role: 'toggledevtools' },
+        ],
     },
     {
         role: 'window',
         submenu: [
-            {
-                role: 'minimize'
-            },
-            {
-                role: 'close'
-            }
-        ]
+            { role: 'minimize' },
+            { role: 'close' },
+        ],
     },
     {
         role: 'help',
         submenu: [
             {
                 label: 'riot.im',
-                click () { electron.shell.openExternal('https://riot.im/') }
-            }
-        ]
-    }
+                click() { shell.openExternal('https://riot.im/'); },
+            },
+        ],
+    },
 ];
 
 // macOS has specific menu conventions...
 if (process.platform === 'darwin') {
     // first macOS menu is the name of the app
-    const name = electron.app.getName()
+    const name = app.getName();
     template.unshift({
         label: name,
         submenu: [
-            {
-                role: 'about'
-            },
-            {
-                type: 'separator'
-            },
+            { role: 'about' },
+            { type: 'separator' },
             {
                 role: 'services',
-                submenu: []
+                submenu: [],
             },
-            {
-                type: 'separator'
-            },
-            {
-                role: 'hide'
-            },
-            {
-                role: 'hideothers'
-            },
-            {
-                role: 'unhide'
-            },
-            {
-                type: 'separator'
-            },
-            {
-                role: 'quit'
-            }
-        ]
-    })
+            { type: 'separator' },
+            { role: 'hide' },
+            { role: 'hideothers' },
+            { role: 'unhide' },
+            { type: 'separator' },
+            { role: 'quit' },
+        ],
+    });
     // Edit menu.
     // This has a 'speech' section on macOS
     template[1].submenu.push(
-        {
-            type: 'separator'
-        },
+        { type: 'separator' },
         {
             label: 'Speech',
             submenu: [
-                {
-                    role: 'startspeaking'
-                },
-                {
-                    role: 'stopspeaking'
-                }
-            ]
-        }
-    )
+                { role: 'startspeaking' },
+                { role: 'stopspeaking' },
+            ],
+        });
+
     // Window menu.
     // This also has specific functionality on macOS
     template[3].submenu = [
         {
             label: 'Close',
             accelerator: 'CmdOrCtrl+W',
-            role: 'close'
+            role: 'close',
         },
         {
             label: 'Minimize',
             accelerator: 'CmdOrCtrl+M',
-            role: 'minimize'
+            role: 'minimize',
         },
         {
             label: 'Zoom',
-            role: 'zoom'
+            role: 'zoom',
         },
         {
-            type: 'separator'
+            type: 'separator',
         },
         {
             label: 'Bring All to Front',
-            role: 'front'
-        }
-    ]
+            role: 'front',
+        },
+    ];
 } else {
     template.unshift({
         label: 'File',
@@ -186,12 +128,10 @@ if (process.platform === 'darwin') {
             /*{
                 role: 'about'
             },*/
-            {
-                role: 'quit'
-            }
-        ]
+            { role: 'quit' },
+        ],
     });
 }
 
-module.exports = electron.Menu.buildFromTemplate(template)
+module.exports = Menu.buildFromTemplate(template);