Compare commits
40 commits
develop
...
aviraldg-e
Author | SHA1 | Date | |
---|---|---|---|
|
7e66fef17b | ||
|
56f080619e | ||
|
c8b39a1755 | ||
|
cbbbedc0cc | ||
|
d068499184 | ||
|
62b1282060 | ||
|
24610f4d18 | ||
|
02e85fcffb | ||
|
fac539c102 | ||
|
c2b487cc79 | ||
|
8edefe9e22 | ||
|
058d7c0ac7 | ||
|
752682220f | ||
|
c02f3a2230 | ||
|
3c44f8a2cb | ||
|
b6939b8138 | ||
|
7b4694c637 | ||
|
f62f2e8f25 | ||
|
e2b123b56a | ||
|
fd2a2f25ad | ||
|
1c7d859b2b | ||
|
dc1952a993 | ||
|
11aed8cf07 | ||
|
4e01bd7675 | ||
|
680f599df5 | ||
|
eef846e490 | ||
|
6c16ccce68 | ||
|
27604d2798 | ||
|
5e4f572a46 | ||
|
1b0c561ac1 | ||
|
ddb2ff5994 | ||
|
3c55629208 | ||
|
31607f0776 | ||
|
f20786226a | ||
|
da30338aa7 | ||
|
eef629137e | ||
|
2026142595 | ||
|
976c20a2f7 | ||
|
919cc8cffc | ||
|
4f185211ce |
15 changed files with 729 additions and 71 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@
|
|||
/vector/olm.*
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
electron/dist
|
||||
|
|
BIN
electron/build/icon.icns
Normal file
BIN
electron/build/icon.icns
Normal file
Binary file not shown.
BIN
electron/build/icon.ico
Normal file
BIN
electron/build/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
157
electron/src/electron-main.js
Normal file
157
electron/src/electron-main.js
Normal file
|
@ -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;
|
||||
});
|
184
electron/src/vectormenu.js
Normal file
184
electron/src/vectormenu.js
Normal file
|
@ -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)
|
||||
|
28
package.json
28
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",
|
||||
|
@ -53,6 +56,7 @@
|
|||
"classnames": "^2.1.2",
|
||||
"draft-js": "^0.8.1",
|
||||
"extract-text-webpack-plugin": "^0.9.1",
|
||||
"favico.js": "^0.3.10",
|
||||
"filesize": "^3.1.2",
|
||||
"flux": "~2.0.3",
|
||||
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
|
||||
|
@ -90,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",
|
||||
|
@ -116,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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk');
|
||||
import Modal from 'matrix-react-sdk/lib/Modal';
|
||||
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
|
||||
|
||||
/**
|
||||
* Check a version string is compatible with the Changelog
|
||||
|
@ -31,30 +32,50 @@ function checkVersion(ver) {
|
|||
|
||||
export default function NewVersionBar(props) {
|
||||
const onChangelogClicked = () => {
|
||||
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
|
||||
|
||||
Modal.createDialog(ChangelogDialog, {
|
||||
version: props.version,
|
||||
newVersion: props.newVersion,
|
||||
onFinished: (update) => {
|
||||
if(update) {
|
||||
window.location.reload();
|
||||
if (props.releaseNotes) {
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "What's New",
|
||||
description: <pre className="changelog_text">{props.releaseNotes}</pre>,
|
||||
button: "Update",
|
||||
onFinished: (update) => {
|
||||
if(update && PlatformPeg.get()) {
|
||||
PlatformPeg.get().installUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
|
||||
Modal.createDialog(ChangelogDialog, {
|
||||
version: props.version,
|
||||
newVersion: props.newVersion,
|
||||
releaseNotes: releaseNotes,
|
||||
onFinished: (update) => {
|
||||
if(update && PlatformPeg.get()) {
|
||||
PlatformPeg.get().installUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let changelog_button;
|
||||
if (checkVersion(props.version) && checkVersion(props.newVersion)) {
|
||||
changelog_button = <button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>Changelog</button>;
|
||||
const onUpdateClicked = () => {
|
||||
PlatformPeg.get().installUpdate();
|
||||
};
|
||||
|
||||
let action_button;
|
||||
if (props.releaseNotes || (checkVersion(props.version) && checkVersion(props.newVersion))) {
|
||||
action_button = <button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>What's new?</button>;
|
||||
} else if (PlatformPeg.get()) {
|
||||
action_button = <button className="mx_MatrixToolbar_action" onClick={onUpdateClicked}>Update</button>;
|
||||
}
|
||||
return (
|
||||
<div className="mx_MatrixToolbar">
|
||||
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
|
||||
<div className="mx_MatrixToolbar_content">
|
||||
A new version of Riot is available. Refresh your browser.
|
||||
A new version of Riot is available.
|
||||
</div>
|
||||
{changelog_button}
|
||||
{action_button}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -62,4 +83,5 @@ export default function NewVersionBar(props) {
|
|||
NewVersionBar.propTypes = {
|
||||
version: React.PropTypes.string.isRequired,
|
||||
newVersion: React.PropTypes.string.isRequired,
|
||||
releaseNotes: React.PropTypes.string,
|
||||
};
|
||||
|
|
|
@ -288,3 +288,7 @@ textarea {
|
|||
cursor: pointer;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.changelog_text {
|
||||
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ require('gfm.css/gfm.css');
|
|||
require('highlight.js/styles/github.css');
|
||||
require('draft-js/dist/Draft.css');
|
||||
|
||||
|
||||
// add React and ReactPerf to the global namespace, to make them easier to
|
||||
// access via the console
|
||||
global.React = require("react");
|
||||
|
@ -47,6 +46,7 @@ if (process.env.NODE_ENV !== 'production') {
|
|||
var RunModernizrTests = require("./modernizr"); // this side-effects a global
|
||||
var ReactDOM = require("react-dom");
|
||||
var sdk = require("matrix-react-sdk");
|
||||
var PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg");
|
||||
sdk.loadSkin(require('../component-index'));
|
||||
var VectorConferenceHandler = require('../VectorConferenceHandler');
|
||||
var UpdateChecker = require("./updater");
|
||||
|
@ -57,6 +57,7 @@ import UAParser from 'ua-parser-js';
|
|||
import url from 'url';
|
||||
|
||||
import {parseQs, parseQsFromFragment} from './url_utils';
|
||||
import Platform from './platform';
|
||||
|
||||
var lastLocationHashSet = null;
|
||||
|
||||
|
@ -111,10 +112,6 @@ function onHashChange(ev) {
|
|||
routeUrl(window.location);
|
||||
}
|
||||
|
||||
function onVersion(current, latest) {
|
||||
window.matrixChat.onVersion(current, latest);
|
||||
}
|
||||
|
||||
var loaded = false;
|
||||
var lastLoadedScreen = null;
|
||||
|
||||
|
@ -163,8 +160,7 @@ window.onload = function() {
|
|||
if (!validBrowser) {
|
||||
return;
|
||||
}
|
||||
UpdateChecker.setVersionListener(onVersion);
|
||||
UpdateChecker.run();
|
||||
UpdateChecker.start();
|
||||
routeUrl(window.location);
|
||||
loaded = true;
|
||||
if (lastLoadedScreen) {
|
||||
|
@ -177,14 +173,27 @@ function getConfig() {
|
|||
let deferred = q.defer();
|
||||
|
||||
request(
|
||||
{ method: "GET", url: "config.json", json: true },
|
||||
{ method: "GET", url: "config.json" },
|
||||
(err, response, body) => {
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
// Lack of a config isn't an error, we should
|
||||
// just use the defaults.
|
||||
// Also treat a blank config as no config because
|
||||
// we don't get 404s from file: URIs so this is the
|
||||
// only way we can not fail if the file doesn't exist
|
||||
// when loading from a file:// URI.
|
||||
if (( err && err.response.status == 404) || body == '') {
|
||||
deferred.resolve({});
|
||||
}
|
||||
deferred.reject({err: err, response: response});
|
||||
return;
|
||||
}
|
||||
|
||||
deferred.resolve(body);
|
||||
// We parse the JSON ourselves rather than use the JSON
|
||||
// parameter, since this throws a parse error on empty
|
||||
// which breaks if there's no config.json and we're
|
||||
// loading from the filesystem (see above).
|
||||
deferred.resolve(iJSON.parse(body));
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -210,6 +219,9 @@ 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());
|
||||
|
||||
// don't try to redirect to the native apps if we're
|
||||
// verifying a 3pid
|
||||
const preventRedirect = Boolean(fragparts.params.client_secret);
|
||||
|
@ -234,13 +246,7 @@ async function loadApp() {
|
|||
try {
|
||||
configJson = await getConfig();
|
||||
} catch (e) {
|
||||
// On 404 errors, carry on without a config,
|
||||
// but on other errors, fail, otherwise it will
|
||||
// lead to subtle errors where the app runs with
|
||||
// the default config if it fails to fetch config.json.
|
||||
if (e.response.status != 404) {
|
||||
configError = e;
|
||||
}
|
||||
configError = e;
|
||||
}
|
||||
|
||||
console.log("Vector starting at "+window.location);
|
||||
|
|
32
src/vector/platform/BasePlatform.js
Normal file
32
src/vector/platform/BasePlatform.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// @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.
|
||||
*/
|
||||
|
||||
export default class BasePlatform {
|
||||
constructor() {
|
||||
this.notificationCount = 0;
|
||||
this.errorDidOccur = false;
|
||||
}
|
||||
|
||||
setNotificationCount(count: number) {
|
||||
this.notificationCount = count;
|
||||
}
|
||||
|
||||
setErrorStatus(errorDidOccur: boolean) {
|
||||
this.errorDidOccur = errorDidOccur;
|
||||
}
|
||||
}
|
93
src/vector/platform/ElectronPlatform.js
Normal file
93
src/vector/platform/ElectronPlatform.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
// @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.
|
||||
*/
|
||||
|
||||
import BasePlatform from './BasePlatform';
|
||||
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 BasePlatform {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
131
src/vector/platform/WebPlatform.js
Normal file
131
src/vector/platform/WebPlatform.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
// @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.
|
||||
*/
|
||||
|
||||
import BasePlatform from './BasePlatform';
|
||||
import Favico from 'favico.js';
|
||||
import request from 'browser-request';
|
||||
import dis from 'matrix-react-sdk/lib/dispatcher.js';
|
||||
import q from 'q';
|
||||
|
||||
export default class WebPlatform extends BasePlatform {
|
||||
constructor() {
|
||||
super();
|
||||
this.runningVersion = null;
|
||||
// The 'animations' are really low framerate and look terrible.
|
||||
// Also it re-starts the animationb every time you set the badge,
|
||||
// and we set the state each time, even if the value hasn't changed,
|
||||
// so we'd need to fix that if enabling the animation.
|
||||
this.favicon = new Favico({animation: 'none'});
|
||||
this.updateFavicon();
|
||||
}
|
||||
|
||||
updateFavicon() {
|
||||
try {
|
||||
// This needs to be in in a try block as it will throw
|
||||
// if there are more than 100 badge count changes in
|
||||
// its internal queue
|
||||
let bgColor = "#d00",
|
||||
notif = this.notificationCount;
|
||||
|
||||
if (this.errorDidOccur) {
|
||||
notif = notif || "×";
|
||||
bgColor = "#f00";
|
||||
}
|
||||
|
||||
this.favicon.badge(notif, {
|
||||
bgColor: bgColor
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`Failed to set badge count: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
setNotificationCount(count: number) {
|
||||
super.setNotificationCount(count);
|
||||
this.updateFavicon();
|
||||
}
|
||||
|
||||
setErrorStatus(errorDidOccur: boolean) {
|
||||
super.setErrorStatus(errorDidOccur);
|
||||
this.updateFavicon();
|
||||
}
|
||||
|
||||
displayNotification(title: string, msg: string, avatarUrl: string) {
|
||||
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();
|
||||
};
|
||||
|
||||
// Chrome only dismisses notifications after 20s, which
|
||||
// is waaaaay too long
|
||||
global.setTimeout(function() {
|
||||
notification.close();
|
||||
}, 5 * 1000);
|
||||
}
|
||||
|
||||
_getVersion() {
|
||||
const deferred = q.defer();
|
||||
request(
|
||||
{ method: "GET", url: "version" },
|
||||
(err, response, body) => {
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
if (err == null) err = { status: response.status };
|
||||
deferred.reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const ver = body.trim();
|
||||
deferred.resolve(ver);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
pollForUpdate() {
|
||||
this._getVersion().done((ver) => {
|
||||
if (this.runningVersion == null) {
|
||||
this.runningVersion = ver;
|
||||
} else if (this.runningVersion != ver) {
|
||||
dis.dispatch({
|
||||
action: 'new_version',
|
||||
currentVersion: this.runningVersion,
|
||||
newVersion: ver,
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
console.error("Failed to poll for update", err);
|
||||
});
|
||||
}
|
||||
|
||||
installUpdate() {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
31
src/vector/platform/index.js
Normal file
31
src/vector/platform/index.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
// @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.
|
||||
*/
|
||||
|
||||
import ElectronPlatform from './ElectronPlatform';
|
||||
import WebPlatform from './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;
|
|
@ -13,48 +13,18 @@ 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 PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
|
||||
|
||||
var POKE_RATE_MS = 10 * 60 * 1000; // 10 min
|
||||
var currentVersion = null;
|
||||
var latestVersion = null;
|
||||
var listener = function(){}; // NOP
|
||||
|
||||
module.exports = {
|
||||
setVersionListener: function(fn) { // invoked with fn(currentVer, newVer)
|
||||
listener = fn;
|
||||
start: function() {
|
||||
module.exports.poll();
|
||||
setInterval(module.exports.poll, POKE_RATE_MS);
|
||||
},
|
||||
|
||||
run: function() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener("load", function() {
|
||||
if (!req.responseText) {
|
||||
return;
|
||||
}
|
||||
var ver = req.responseText.trim();
|
||||
if (!currentVersion) {
|
||||
currentVersion = ver;
|
||||
listener(currentVersion, currentVersion);
|
||||
}
|
||||
|
||||
if (ver !== latestVersion) {
|
||||
latestVersion = ver;
|
||||
if (module.exports.hasNewVersion()) {
|
||||
console.log("Current=%s Latest=%s", currentVersion, latestVersion);
|
||||
listener(currentVersion, latestVersion);
|
||||
}
|
||||
}
|
||||
});
|
||||
var cacheBuster = "?ts=" + new Date().getTime();
|
||||
req.open("GET", "version" + cacheBuster);
|
||||
req.send(); // can't suppress 404s from being logged.
|
||||
|
||||
setTimeout(module.exports.run, POKE_RATE_MS);
|
||||
},
|
||||
|
||||
getCurrentVersion: function() {
|
||||
return currentVersion;
|
||||
},
|
||||
|
||||
hasNewVersion: function() {
|
||||
return currentVersion && latestVersion && (currentVersion !== latestVersion);
|
||||
poll: function() {
|
||||
PlatformPeg.get().pollForUpdate();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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…
Add table
Reference in a new issue