mirror of https://github.com/vector-im/riot-web
				
				
				
			Electron support
From https://github.com/vector-im/vector-web/pull/2511 but with just the actual electron changespull/2535/head
							parent
							
								
									8c3fed7559
								
							
						
					
					
						commit
						caa3cb7d89
					
				|  | @ -13,3 +13,4 @@ | |||
| /vector/olm.* | ||||
| .DS_Store | ||||
| npm-debug.log | ||||
| electron/dist | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 21 KiB | 
|  | @ -0,0 +1,157 @@ | |||
| // @flow
 | ||||
| 
 | ||||
| /* | ||||
| Copyright 2016 Aviral Dasgupta and OpenMarket 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. | ||||
| */ | ||||
| 
 | ||||
| const electron = require('electron'); | ||||
| const url = require('url'); | ||||
| 
 | ||||
| const VectorMenu = require('./vectormenu'); | ||||
| 
 | ||||
| const PERMITTED_URL_SCHEMES = [ | ||||
|     'http:', | ||||
|     'https:', | ||||
|     'mailto:', | ||||
| ]; | ||||
| 
 | ||||
| const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 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 parsed_url = url.parse(target); | ||||
|     if (PERMITTED_URL_SCHEMES.indexOf(parsed_url.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 new_target = url.format(parsed_url); | ||||
|         electron.shell.openExternal(new_target); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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 popup_menu = new electron.Menu(); | ||||
|     popup_menu.append(new electron.MenuItem({ | ||||
|         label: params.linkURL, | ||||
|         click() { | ||||
|             safeOpenURL(params.linkURL); | ||||
|         }, | ||||
|     })); | ||||
|     popup_menu.popup(); | ||||
|     ev.preventDefault(); | ||||
| } | ||||
| 
 | ||||
| function installUpdate() { | ||||
|     // for some reason, quitAndInstall does not fire the
 | ||||
|     // before-quit event, so we need to set the flag here.
 | ||||
|     appQuitting = true; | ||||
|     electron.autoUpdater.quitAndInstall(); | ||||
| } | ||||
| 
 | ||||
| function pollForUpdates() { | ||||
|     try { | ||||
|         electron.autoUpdater.checkForUpdates(); | ||||
|     } catch (e) { | ||||
|         console.log("Couldn't check for update", e); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| electron.ipcMain.on('install_update', installUpdate); | ||||
| 
 | ||||
| electron.app.on('ready', () => { | ||||
|     try { | ||||
|         // For reasons best known to Squirrel, the way it checks for updates
 | ||||
|         // is completely different between macOS and windows. On macOS, it
 | ||||
|         // 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') { | ||||
|             electron.autoUpdater.setFeedURL("https://riot.im/autoupdate/desktop/"); | ||||
|         } else if (process.platform == 'win32') { | ||||
|             electron.autoUpdater.setFeedURL("https://riot.im/download/desktop/win32/"); | ||||
|         } 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"); | ||||
|         } | ||||
|         // 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:
 | ||||
|         // every hour should be just fine for a desktop app)
 | ||||
|         // However, we still let the main window listen for the update events.
 | ||||
|         pollForUpdates(); | ||||
|         setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS); | ||||
|     } catch (err) { | ||||
|         // will fail if running in debug mode
 | ||||
|         console.log("Couldn't enable update checking", err); | ||||
|     } | ||||
| 
 | ||||
|     mainWindow = new electron.BrowserWindow({ | ||||
|         icon: `${__dirname}/../../vector/img/logo.png`, | ||||
|         width: 1024, height: 768, | ||||
|     }); | ||||
|     mainWindow.loadURL(`file://${__dirname}/../../vector/index.html`); | ||||
|     electron.Menu.setApplicationMenu(VectorMenu); | ||||
| 
 | ||||
|     mainWindow.on('closed', () => { | ||||
|         mainWindow = null; | ||||
|     }); | ||||
|     mainWindow.on('close', (e) => { | ||||
|         if (process.platform == 'darwin' && !appQuitting) { | ||||
|             // On Mac, closing the window just hides it
 | ||||
|             // (this is generally how single-window Mac apps
 | ||||
|             // behave, eg. Mail.app)
 | ||||
|             e.preventDefault(); | ||||
|             mainWindow.hide(); | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     mainWindow.webContents.on('new-window', onWindowOrNavigate); | ||||
|     mainWindow.webContents.on('will-navigate', onWindowOrNavigate); | ||||
| 
 | ||||
|     mainWindow.webContents.on('context-menu', function(ev, params) { | ||||
|         if (params.linkURL) { | ||||
|             onLinkContextMenu(ev, params); | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| electron.app.on('window-all-closed', () => { | ||||
|     electron.app.quit(); | ||||
| }); | ||||
| 
 | ||||
| electron.app.on('activate', () => { | ||||
|     mainWindow.show(); | ||||
| }); | ||||
| 
 | ||||
| electron.app.on('before-quit', () => { | ||||
|     appQuitting = true; | ||||
| }); | ||||
|  | @ -0,0 +1,184 @@ | |||
| /* | ||||
| Copyright 2016 OpenMarket 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. | ||||
| */ | ||||
| 
 | ||||
| const electron = 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' | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     label: 'View', | ||||
|     submenu: [ | ||||
|       { | ||||
|         type: 'separator' | ||||
|       }, | ||||
|       { | ||||
|         role: 'resetzoom' | ||||
|       }, | ||||
|       { | ||||
|         role: 'zoomin' | ||||
|       }, | ||||
|       { | ||||
|         role: 'zoomout' | ||||
|       }, | ||||
|       { | ||||
|         type: 'separator' | ||||
|       }, | ||||
|       { | ||||
|         role: 'togglefullscreen' | ||||
|       }, | ||||
|       { | ||||
|         label: 'Toggle Developer Tools', | ||||
|         accelerator: process.platform == 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', | ||||
|         click: function(item, focusedWindow) { | ||||
|           if (focusedWindow) focusedWindow.toggleDevTools(); | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     role: 'window', | ||||
|     submenu: [ | ||||
|       { | ||||
|         role: 'minimize' | ||||
|       }, | ||||
|       { | ||||
|         role: 'close' | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     role: 'help', | ||||
|     submenu: [ | ||||
|       { | ||||
|         label: 'riot.im', | ||||
|         click () { electron.shell.openExternal('https://riot.im/') } | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| ]; | ||||
| 
 | ||||
| if (process.platform === 'darwin') { | ||||
|   const name = electron.app.getName() | ||||
|   template.unshift({ | ||||
|     label: name, | ||||
|     submenu: [ | ||||
|       { | ||||
|         role: 'about' | ||||
|       }, | ||||
|       { | ||||
|         type: 'separator' | ||||
|       }, | ||||
|       { | ||||
|         role: 'services', | ||||
|         submenu: [] | ||||
|       }, | ||||
|       { | ||||
|         type: 'separator' | ||||
|       }, | ||||
|       { | ||||
|         role: 'hide' | ||||
|       }, | ||||
|       { | ||||
|         role: 'hideothers' | ||||
|       }, | ||||
|       { | ||||
|         role: 'unhide' | ||||
|       }, | ||||
|       { | ||||
|         type: 'separator' | ||||
|       }, | ||||
|       { | ||||
|         role: 'quit' | ||||
|       } | ||||
|     ] | ||||
|   }) | ||||
|   // Edit menu.
 | ||||
|   template[1].submenu.push( | ||||
|     { | ||||
|       type: 'separator' | ||||
|     }, | ||||
|     { | ||||
|       label: 'Speech', | ||||
|       submenu: [ | ||||
|         { | ||||
|           role: 'startspeaking' | ||||
|         }, | ||||
|         { | ||||
|           role: 'stopspeaking' | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ) | ||||
|   // Window menu.
 | ||||
|   template[3].submenu = [ | ||||
|     { | ||||
|       label: 'Close', | ||||
|       accelerator: 'CmdOrCtrl+W', | ||||
|       role: 'close' | ||||
|     }, | ||||
|     { | ||||
|       label: 'Minimize', | ||||
|       accelerator: 'CmdOrCtrl+M', | ||||
|       role: 'minimize' | ||||
|     }, | ||||
|     { | ||||
|       label: 'Zoom', | ||||
|       role: 'zoom' | ||||
|     }, | ||||
|     { | ||||
|       type: 'separator' | ||||
|     }, | ||||
|     { | ||||
|       label: 'Bring All to Front', | ||||
|       role: 'front' | ||||
|     } | ||||
|   ] | ||||
| }; | ||||
| 
 | ||||
| module.exports = electron.Menu.buildFromTemplate(template) | ||||
| 
 | ||||
							
								
								
									
										27
									
								
								package.json
								
								
								
								
							
							
						
						
									
										27
									
								
								package.json
								
								
								
								
							|  | @ -1,8 +1,10 @@ | |||
| { | ||||
|   "name": "vector-web", | ||||
|   "productName": "Riot", | ||||
|   "main": "electron/src/electron-main.js", | ||||
|   "version": "0.8.4-rc.2", | ||||
|   "description": "Vector webapp", | ||||
|   "author": "matrix.org", | ||||
|   "description": "A feature-rich client for Matrix.org", | ||||
|   "author": "Vector Creations Ltd.", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/vector-im/vector-web" | ||||
|  | @ -31,6 +33,7 @@ | |||
|     "build:compile": "babel --source-maps -d lib src", | ||||
|     "build:bundle": "NODE_ENV=production webpack -p --progress", | ||||
|     "build:bundle:dev": "webpack --optimize-occurence-order --progress", | ||||
|     "build:electron": "build -lwm", | ||||
|     "build": "node scripts/babelcheck.js && npm run build:emojione && npm run build:css && npm run build:bundle", | ||||
|     "build:dev": "npm run build:emojione && npm run build:css && npm run build:bundle:dev", | ||||
|     "dist": "scripts/package.sh", | ||||
|  | @ -91,6 +94,7 @@ | |||
|     "catw": "^1.0.1", | ||||
|     "cpx": "^1.3.2", | ||||
|     "css-raw-loader": "^0.1.1", | ||||
|     "electron-builder": "^7.10.2", | ||||
|     "emojione": "^2.2.3", | ||||
|     "expect": "^1.16.0", | ||||
|     "fs-extra": "^0.30.0", | ||||
|  | @ -117,5 +121,24 @@ | |||
|   }, | ||||
|   "optionalDependencies": { | ||||
|     "olm": "https://matrix.org/packages/npm/olm/olm-2.0.0.tgz" | ||||
|   }, | ||||
|   "build": { | ||||
|     "appId": "im.riot.app", | ||||
|     "category": "Network", | ||||
|     "electronVersion": "1.4.2", | ||||
|     "//asar=false": "https://github.com/electron-userland/electron-builder/issues/675", | ||||
|     "asar": false, | ||||
|     "dereference": true, | ||||
|     "//files": "We bundle everything, so we only need to include vector/", | ||||
|     "files": [ | ||||
|       "!**/*", | ||||
|       "electron/src/**", | ||||
|       "vector/**", | ||||
|       "package.json" | ||||
|     ] | ||||
|   }, | ||||
|   "directories": { | ||||
|     "buildResources": "electron/build", | ||||
|     "output": "electron/dist" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,102 @@ | |||
| // @flow
 | ||||
| 
 | ||||
| /* | ||||
| Copyright 2016 Aviral Dasgupta | ||||
| Copyright 2016 OpenMarket 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 VectorBasePlatform from './VectorBasePlatform'; | ||||
| import dis from 'matrix-react-sdk/lib/dispatcher'; | ||||
| 
 | ||||
| function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) { | ||||
|     dis.dispatch({ | ||||
|         action: 'new_version', | ||||
|         currentVersion: electron.remote.app.getVersion(), | ||||
|         newVersion: ver, | ||||
|         releaseNotes: releaseNotes, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // index.js imports us unconditionally, so we need this check here as well
 | ||||
| let electron = null, remote = null; | ||||
| if (window && window.process && window.process && window.process.type === 'renderer') { | ||||
|     electron = require('electron'); | ||||
|     electron.remote.autoUpdater.on('update-downloaded', onUpdateDownloaded); | ||||
|     remote = electron.remote; | ||||
| } | ||||
| 
 | ||||
| export default class ElectronPlatform extends VectorBasePlatform { | ||||
|     setNotificationCount(count: number) { | ||||
|         super.setNotificationCount(count); | ||||
|         // this sometimes throws because electron is made of fail:
 | ||||
|         // https://github.com/electron/electron/issues/7351
 | ||||
|         // For now, let's catch the error, but I suspect it may
 | ||||
|         // continue to fail and we might just have to accept that
 | ||||
|         // electron's remote RPC is a non-starter for now and use IPC
 | ||||
|         try { | ||||
|             remote.app.setBadgeCount(count); | ||||
|         } catch (e) { | ||||
|             console.error("Failed to set notification count", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     supportsNotifications() : boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     maySendNotifications() : boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     displayNotification(title: string, msg: string, avatarUrl: string): Notification { | ||||
|         // Notifications in Electron use the HTML5 notification API
 | ||||
|         const notification = new global.Notification( | ||||
|             title, | ||||
|             { | ||||
|                 body: msg, | ||||
|                 icon: avatarUrl, | ||||
|                 tag: "vector", | ||||
|                 silent: true, // we play our own sounds
 | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         notification.onclick = function() { | ||||
|             dis.dispatch({ | ||||
|                 action: 'view_room', | ||||
|                 room_id: room.roomId | ||||
|             }); | ||||
|             global.focus(); | ||||
|         }; | ||||
| 
 | ||||
|         return notification; | ||||
|     } | ||||
| 
 | ||||
|     clearNotification(notif: Notification) { | ||||
|         notif.close(); | ||||
|     } | ||||
| 
 | ||||
|     pollForUpdate() { | ||||
|         // In electron we control the update process ourselves, since
 | ||||
|         // it needs to run in the main process, so we just run the timer
 | ||||
|         // loop in the main electron process instead.
 | ||||
|     } | ||||
| 
 | ||||
|     installUpdate() { | ||||
|         // IPC to the main process to install the update, since quitAndInstall
 | ||||
|         // doesn't fire the before-quit event so the main process needs to know
 | ||||
|         // it should exit.
 | ||||
|         electron.ipcRenderer.send('install_update'); | ||||
|     } | ||||
| } | ||||
|  | @ -16,8 +16,16 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import ElectronPlatform from './ElectronPlatform'; | ||||
| import WebPlatform from './WebPlatform'; | ||||
| 
 | ||||
| let Platform = WebPlatform; | ||||
| let Platform = null; | ||||
| 
 | ||||
| if (window && window.process && window.process && window.process.type === 'renderer') { | ||||
|     // we're running inside electron
 | ||||
|     Platform = ElectronPlatform; | ||||
| } else { | ||||
|     Platform = WebPlatform; | ||||
| } | ||||
| 
 | ||||
| export default Platform; | ||||
|  |  | |||
|  | @ -75,6 +75,9 @@ module.exports = { | |||
|     }, | ||||
|     externals: { | ||||
|         "olm": "Olm", | ||||
|         // 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({ | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker