Merge pull request #8003 from vector-im/develop

Merge develop into experimental
pull/8109/head
David Baker 2019-01-03 18:56:03 +00:00 committed by GitHub
commit 373c587ef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 861 additions and 179 deletions

View File

@ -1,3 +1,28 @@
Changes in [0.17.8](https://github.com/vector-im/riot-web/releases/tag/v0.17.8) (2018-12-10)
============================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.17.8-rc.1...v0.17.8)
* No changes since rc.1
Changes in [0.17.8-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.17.8-rc.1) (2018-12-06)
======================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.17.7...v0.17.8-rc.1)
* Update from Weblate.
[\#7784](https://github.com/vector-im/riot-web/pull/7784)
* Add a function to send a rageshake from the console
[\#7755](https://github.com/vector-im/riot-web/pull/7755)
* Re-apply "Run lint on travis builds and use modern node versions"
[\#7738](https://github.com/vector-im/riot-web/pull/7738)
* Revert "Run lint on travis builds and use modern node versions"
[\#7737](https://github.com/vector-im/riot-web/pull/7737)
* Run lint on travis builds and use modern node versions
[\#7490](https://github.com/vector-im/riot-web/pull/7490)
* Fix missing js-sdk logging
[\#7736](https://github.com/vector-im/riot-web/pull/7736)
* Add $accent-color-50pct as a CSS variable to the Status theme
[\#7710](https://github.com/vector-im/riot-web/pull/7710)
Changes in [0.17.7](https://github.com/vector-im/riot-web/releases/tag/v0.17.7) (2018-11-22)
============================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.17.6...v0.17.7)

View File

@ -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.

View File

@ -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`

View File

@ -2,7 +2,7 @@
"name": "riot-web",
"productName": "Riot",
"main": "src/electron-main.js",
"version": "0.17.7",
"version": "0.17.8",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"dependencies": {

View File

@ -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,16 +24,23 @@ 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, protocol} = require('electron');
const AutoLaunch = require('auto-launch');
const path = require('path');
const tray = require('./tray');
const vectorMenu = require('./vectormenu');
const webContentsHandler = require('./webcontents-handler');
const updater = require('./updater');
const { migrateFromOldOrigin } = require('./originMigrator');
const windowStateKeeper = require('electron-window-state');
// boolean flag set whilst we are doing one-time origin migration
// We only serve the origin migration script while we're actually
// migrating to mitigate any risk of it being used maliciously.
let migratingOrigin = false;
if (argv['profile']) {
app.setPath('userData', `${app.getPath('userData')}-${argv['profile']}`);
}
@ -97,27 +105,75 @@ 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', async 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();
}
case 'origin_migrate':
migratingOrigin = true;
await migrateFromOldOrigin();
migratingOrigin = false;
break;
default:
mainWindow.webContents.send('ipcReply', {
id: payload.id,
error: "Unknown IPC Call: " + payload.name,
});
return;
}
mainWindow.webContents.send('ipcReply', {
id: payload.id,
reply: ret,
});
});
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,
@ -126,39 +182,12 @@ 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);
}
});
// 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']) {
@ -175,6 +204,66 @@ 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('/');
// path starts with a '/'
if (target[0] !== '') {
callback({error: -6}); // FILE_NOT_FOUND
return;
}
if (target[target.length - 1] == '') {
target[target.length - 1] = 'index.html';
}
let baseDir;
// first part of the path determines where we serve from
if (migratingOrigin && target[1] === 'origin_migrator_dest') {
// the origin migrator destination page
// (only the destination script needs to come from the
// custom protocol: the source part is loaded from a
// file:// as that's the origin we're migrating from).
baseDir = __dirname + "/../../origin_migrator/dest";
} else if (target[1] === 'webapp') {
baseDir = __dirname + "/../../webapp";
} else {
callback({error: -6}); // FILE_NOT_FOUND
return;
}
// Normalise the base dir and the target path separately, then make sure
// the target path isn't trying to back out beyond its root
baseDir = path.normalize(baseDir);
const relTarget = path.normalize(path.join(...target.slice(2)));
if (relTarget.startsWith('..')) {
callback({error: -6}); // FILE_NOT_FOUND
return;
}
const absTarget = path.join(baseDir, 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']}`);
@ -191,6 +280,7 @@ app.on('ready', () => {
defaultHeight: 768,
});
const preloadScript = path.normalize(`${__dirname}/preload.js`);
mainWindow = global.mainWindow = new BrowserWindow({
icon: iconPath,
show: false,
@ -200,8 +290,20 @@ 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`);
mainWindow.loadURL('vector://vector/webapp/');
Menu.setApplicationMenu(vectorMenu);
// explicitly hide because setApplicationMenu on Linux otherwise shows...
@ -268,6 +370,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

View File

@ -0,0 +1,62 @@
/*
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.
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 { BrowserWindow, ipcMain } = require('electron');
const path = require('path');
async function migrateFromOldOrigin() {
console.log("Attempting to migrate data between origins");
// We can use the same preload script: we just need ipcRenderer exposed
const preloadScript = path.normalize(`${__dirname}/preload.js`);
await new Promise(resolve => {
const migrateWindow = new BrowserWindow({
show: false,
webPreferences: {
preload: preloadScript,
nodeIntegration: false,
sandbox: true,
enableRemoteModule: false,
webgl: false,
},
});
ipcMain.on('origin_migration_complete', (e, success, sentSummary, storedSummary) => {
if (success) {
console.log("Origin migration completed successfully!");
} else {
console.error("Origin migration failed!");
}
console.error("Data sent", sentSummary);
console.error("Data stored", storedSummary);
migrateWindow.close();
resolve();
});
ipcMain.on('origin_migration_nodata', (e) => {
console.log("No session to migrate from old origin");
migrateWindow.close();
resolve();
});
// Normalise the path because in the distribution, __dirname will be inside the
// electron asar.
const sourcePagePath = path.normalize(__dirname + '/../../origin_migrator/source.html');
console.log("Loading path: " + sourcePagePath);
migrateWindow.loadURL('file://' + sourcePagePath);
});
}
module.exports = {
migrateFromOldOrigin,
};

View File

@ -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,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
let Platform = null;
const { ipcRenderer, webFrame } = 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;
// 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,
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
<html>
<body>
<script src="browser-matrix.min.js"></script>
<script src="dest.js"></script>
</body>
</html>

View File

@ -0,0 +1,125 @@
/*
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.
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 SOURCE_ORIGIN = 'file://';
const IndexedDBCryptoStore = window.matrixcs.IndexedDBCryptoStore;
const cryptoStore = new IndexedDBCryptoStore(window.indexedDB, 'matrix-js-sdk:crypto');
let accountStored = 0;
let sessionsStored = 0;
let inboundGroupSessionsStored = 0;
let deviceDataStored = 0;
let roomsStored = 0;
let localStorageKeysStored = 0;
const promises = [];
async function onMessage(e) {
if (e.origin !== SOURCE_ORIGIN) return;
const data = e.data.data; // bleh, naming clash
switch (e.data.cmd) {
case 'init':
// start with clean stores before we migrate data in
window.localStorage.clear();
await cryptoStore.deleteAllData();
e.source.postMessage({
cmd: 'initOK',
}, SOURCE_ORIGIN);
break;
case 'storeAccount':
promises.push(cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
cryptoStore.storeAccount(txn, data);
},
).then(() => {
++accountStored;
}));
break;
case 'storeSessions':
promises.push(cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
for (const sess of data) {
cryptoStore.storeEndToEndSession(sess.deviceKey, sess.sessionId, sess, txn);
}
},
).then(() => {
sessionsStored += data.length;
}));
break;
case 'storeInboundGroupSessions':
promises.push(cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS],
(txn) => {
for (const sess of data) {
cryptoStore.addEndToEndInboundGroupSession(
sess.senderKey, sess.sessionId, sess.sessionData, txn,
);
}
},
).then(() => {
inboundGroupSessionsStored += data.length;
}));
break;
case 'storeDeviceData':
promises.push(cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA],
(txn) => {
cryptoStore.storeEndToEndDeviceData(data, txn);
},
).then(() => {
++deviceDataStored;
}));
break;
case 'storeRooms':
promises.push(cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS],
(txn) => {
for (const [roomId, roomInfo] of Object.entries(data)) {
cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
}
},
).then(() => {
++roomsStored;
}));
break;
case 'storeLocalStorage':
window.localStorage.setItem(data.key, data.val);
++localStorageKeysStored;
break;
case 'getSummary':
await Promise.all(promises);
e.source.postMessage({
cmd: 'summary',
data: {
accountStored,
sessionsStored,
inboundGroupSessionsStored,
deviceDataStored,
roomsStored,
localStorageKeysStored,
},
}, SOURCE_ORIGIN);
break;
}
}
window.addEventListener('message', onMessage);

View File

@ -0,0 +1,7 @@
<html>
<body>
<script src="dest/browser-matrix.min.js"></script>
<script src="source.js"></script>
<iframe name="dest" src="vector://vector/origin_migrator_dest/dest.html" onload="doMigrate()"></iframe>
</body>
</html>

210
origin_migrator/source.js Normal file
View File

@ -0,0 +1,210 @@
/*
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.
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 TARGET_ORIGIN = 'vector://vector';
const BATCH_SIZE = 500;
let destFrame;
let initResolver = null;
let getSummaryResolver = null;
function onMessage(e) {
if (e.origin !== TARGET_ORIGIN) return;
if (e.data.cmd === 'initOK' && initResolver) {
initResolver();
initResolver = null;
} else if (e.data.cmd === 'summary' && getSummaryResolver) {
getSummaryResolver(e.data.data);
getSummaryResolver = null;
}
}
async function initDestFrame() {
return new Promise(resolve => {
initResolver = resolve;
destFrame.postMessage({
cmd: 'init',
}, TARGET_ORIGIN);
});
}
async function getSummary() {
return new Promise(resolve => {
getSummaryResolver = resolve;
destFrame.postMessage({
cmd: 'getSummary',
}, TARGET_ORIGIN);
});
}
async function doMigrate() {
let accountSent = 0;
let sessionsSent = 0;
let inboundGroupSessionsSent = 0;
let deviceDataSent = 0;
let roomsSent = 0;
let localStorageKeysSent = 0;
if (!window.ipcRenderer) {
console.error("ipcRenderer not found");
return;
}
if (window.localStorage.getItem('mx_user_id') === null) {
window.ipcRenderer.send("origin_migration_nodata");
return;
}
destFrame = window.parent.frames.dest;
await initDestFrame();
const IndexedDBCryptoStore = window.matrixcs.IndexedDBCryptoStore;
const cryptoStore = new IndexedDBCryptoStore(window.indexedDB, 'matrix-js-sdk:crypto');
await cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
cryptoStore.getAccount(txn, (account) => {
destFrame.postMessage({
cmd: 'storeAccount',
data: account,
}, TARGET_ORIGIN);
++accountSent;
});
},
);
await cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
let sessBatch = [];
cryptoStore.getAllEndToEndSessions(txn, (sessInfo) => {
if (sessInfo) {
++sessionsSent;
sessBatch.push(sessInfo);
}
if (sessBatch.length >= BATCH_SIZE || sessInfo === null) {
destFrame.postMessage({
cmd: 'storeSessions',
data: sessBatch,
}, TARGET_ORIGIN);
sessBatch = [];
}
});
},
);
await cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS],
(txn) => {
let sessBatch = [];
cryptoStore.getAllEndToEndInboundGroupSessions(txn, (sessInfo) => {
if (sessInfo) {
++inboundGroupSessionsSent;
sessBatch.push(sessInfo);
}
if (sessBatch.length >= BATCH_SIZE || sessInfo === null) {
destFrame.postMessage({
cmd: 'storeInboundGroupSessions',
data: sessBatch,
}, TARGET_ORIGIN);
sessBatch = [];
}
});
},
);
await cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA],
(txn) => {
cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
destFrame.postMessage({
cmd: 'storeDeviceData',
data: deviceData,
}, TARGET_ORIGIN);
++deviceDataSent;
});
},
);
await cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_ROOMS],
(txn) => {
cryptoStore.getEndToEndRooms(txn, (rooms) => {
destFrame.postMessage({
cmd: 'storeRooms',
data: rooms,
}, TARGET_ORIGIN);
++roomsSent;
});
},
);
// we don't bother migrating;
// * sync data (we can just initialsync again)
// * logs
// * key requests (worst case they'll just be re-sent)
// * sessions needing backup (feature isn't available on Electron)
for (let i = 0; i < window.localStorage.length; ++i) {
const key = window.localStorage.key(i);
const val = window.localStorage.getItem(key);
destFrame.postMessage({
cmd: 'storeLocalStorage',
data: { key, val },
}, TARGET_ORIGIN);
++localStorageKeysSent;
}
const summary = await getSummary();
let success = false;
if (
summary.accountStored === accountSent &&
summary.sessionsStored === sessionsSent &&
summary.inboundGroupSessionsStored === inboundGroupSessionsSent &&
summary.deviceDataStored === deviceDataSent &&
summary.roomsStored === roomsSent &&
summary.localStorageKeysStored === localStorageKeysSent
) {
success = true;
window.localStorage.clear();
await cryptoStore.deleteAllData();
// we don't bother migrating them, but also blow away the sync & logs db,
// otherwise they'll just hang about taking up space
await new Promise(resolve => {
const req = window.indexedDB.deleteDatabase('matrix-js-sdk:riot-web-sync');
req.onsuccess = resolve;
req.onerror = resolve;
});
await new Promise(resolve => {
const req = window.indexedDB.deleteDatabase('logs');
req.onsuccess = resolve;
req.onerror = resolve;
});
}
window.ipcRenderer.send("origin_migration_complete", success, {
accountSent, sessionsSent, inboundGroupSessionsSent,
deviceDataSent, roomsSent, localStorageKeysSent,
}, summary);
}
window.addEventListener('message', onMessage);

View File

@ -2,7 +2,7 @@
"name": "riot-web",
"productName": "Riot",
"main": "electron_app/src/electron-main.js",
"version": "0.17.7",
"version": "0.17.8",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {
@ -70,8 +70,8 @@
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#b302279",
"gfm.css": "^1.1.2",
"highlight.js": "^9.13.1",
"matrix-js-sdk": "0.14.1",
"matrix-react-sdk": "0.14.6",
"matrix-js-sdk": "0.14.2",
"matrix-react-sdk": "0.14.7",
"modernizr": "^3.6.0",
"prop-types": "^15.6.2",
"react": "^15.6.0",
@ -150,14 +150,15 @@
},
"build": {
"appId": "im.riot.app",
"electronVersion": "3.0.5",
"electronVersion": "3.0.10",
"files": [
"node_modules/**",
"src/**",
"img/**"
],
"extraResources": [
"webapp/**/*"
"webapp/**/*",
"origin_migrator/**/*"
],
"linux": {
"target": "deb",

View File

@ -1 +1,39 @@
{}
{
"Riot is not supported on mobile web. Install the app?": "रायट फ़ोन पर समर्थन नहीं हैं। एप्लिकेशन इनस्टॉल करना चाहेंगे ?",
"Riot Desktop on %(platformName)s": "%(platformName)s पर रायट डेस्कटॉप",
"Unknown device": "अज्ञात यन्त्र",
"%(appName)s via %(browserName)s on %(osName)s": "%(osName)s पर %(browserName)s के माध्यम से %(appName)s",
"You need to be using HTTPS to place a screen-sharing call.": "स्क्रीन साझा की कॉल करने के लिए आपको HTTPS का उपयोग करने की आवश्यकता है।",
"Custom Server Options": "कस्टम सर्वर विकल्प",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.<br/>This allows you to use Riot with an existing Matrix account on a different home server.<br/><br/>You can also set a custom identity server but you won't be able to invite users by email address, or be invited by email address yourself.": "आप एक अलग होम सर्वर यूआरएल निर्दिष्ट करके अन्य मैट्रिक्स सर्वरों में साइन इन करने के लिए कस्टम सर्वर विकल्प का उपयोग कर सकते हैं। <br/> यह आपको एक अलग होम सर्वर पर मौजूदा मैट्रिक्स खाते के साथ रायट का उपयोग करने की अनुमति देता है। <br/> <br/> आप अपना आइडेंटिटी सर्वर भी सेट कर सकते हैं लेकिन आप ईमेल पते से उपयोगकर्ताओं को आमंत्रित नहीं कर पाएंगे, या ईमेल पते से स्वयं आमंत्रित नहीं हों सकेंगे।",
"Dismiss": "खारिज",
"powered by Matrix": "मैट्रिक्स द्वारा संचालित",
"Welcome to Riot.im": "Riot.im में आपका स्वागत है",
"Decentralised, encrypted chat &amp; collaboration powered by [matrix]": "[मैट्रिक्स] द्वारा संचालित विकेंद्रीकृत, एन्क्रिप्टेड चैट और सहयोगिता",
"Search the room directory": "रूम डायरेक्टरी को खोजें",
"Lots of rooms already exist in Matrix, linked to existing networks (Slack, IRC, Gitter etc) or independent. Check out the directory!": "मौजूदा नेटवर्क से जुड़े मैट्रिक्स में बहुत से कमरे पहले से मौजूद हैं (स्लैक, आईआरसी, गिटर इत्यादि) या स्वतंत्र। डायरेक्टरी देखें!",
"Chat with Riot Bot": "रायट बॉट के साथ चैट करें",
"Get started with some tips from Riot Bot!": "रायट बॉट से कुछ सुझावों के साथ शुरू करें!",
"General discussion about Matrix and Riot": "मैट्रिक्स और रायट के बारे में सामान्य चर्चा",
"Discussion of all things Matrix!": "मैट्रिक्स की सभी चीजों की चर्चा!",
"Riot/Web &amp; Desktop chat": "रायट/वेब और डेस्कटॉप चैट",
"Riot/iOS &amp; matrix-ios-sdk chat": "रायट / iOS और matrix-ios-sdk चैट",
"Riot/Android &amp; matrix-android-sdk chat": "रायट / एंड्रॉइड और matrix-android-sdk चैट",
"Matrix technical discussions": "मैट्रिक्स तकनीकी चर्चाएं",
"Running Matrix services": "मैट्रिक्स सेवाएं चलाना",
"Community-run support for Synapse": "सामुदायिक चालित Synapse के लिए समर्थन",
"Admin support for Dendrite": "डेंडर्राइट के लिए व्यवस्थापक समर्थन",
"Announcements about Synapse releases": "Synapse रिलीज के बारे में घोषणाएं",
"Support for those using and running matrix-appservice-irc": "Matrix-appservice-irc का उपयोग और चलाने वाले लोगों के लिए समर्थन",
"Building services on Matrix": "मैट्रिक्स पर सेवाएं बनाना",
"Support for those using the Matrix spec": "मैट्रिक्स spec का उपयोग करने वालों के लिए समर्थन",
"Design and implementation of E2E in Matrix": "मैट्रिक्स में E2E के डिजाइन और कार्यान्वयन",
"Implementing VR services with Matrix": "मैट्रिक्स के साथ VR सेवाओं को लागू करना",
"Implementing VoIP services with Matrix": "मैट्रिक्स के साथ वीओआईपी सेवाओं को लागू करना",
"Discussion of the Identity Service API": "आइडेंटिटी सर्विस API की चर्चा",
"Support for those using, running and writing other bridges": "अन्य ब्रिज का उपयोग, चलाने और लिखने वालों के लिए समर्थन",
"Contributing code to Matrix and Riot": "मैट्रिक्स और रायट में कोड योगदान करना",
"Dev chat for the Riot/Web dev team": "रायट / वेब डेव टीम के लिए डेवलपर चैट",
"Dev chat for the Dendrite dev team": "डेन्ड्राइट देव टीम के लिए डेवलपर चैट",
"Co-ordination for Riot translators": "रायट अनुवादकों के लिए समन्वय"
}

View File

@ -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";
@ -226,8 +228,23 @@ 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 (window.ipcRenderer) {
console.log("Using Electron platform");
const plaf = new ElectronPlatform();
PlatformPeg.set(plaf);
// Electron only: see if we need to do a one-time data
// migration
if (window.localStorage.getItem('mx_user_id') === null) {
console.log("Migrating session from old origin...");
await plaf.migrateFromOldOrigin();
console.log("Origin migration complete");
}
} 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.

View File

@ -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,26 @@ 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,
});
}
const ipcRenderer = window.ipcRenderer;
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 +68,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 +88,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 +136,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 &amp;
// 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, '&lt;').replace(/>/g, '&gt;');
}
@ -147,17 +150,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 +170,25 @@ export default class ElectronPlatform extends VectorBasePlatform {
notif.close();
}
getAppVersion(): Promise<string> {
return Promise.resolve(remote.app.getVersion());
async getAppVersion(): Promise<string> {
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 +213,47 @@ export default class ElectronPlatform extends VectorBasePlatform {
return null;
}
isElectron(): boolean { return true; }
requestNotificationPermission(): Promise<string> {
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 migrateFromOldOrigin() {
return this._ipcCall('origin_migrate');
}
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 */
}

View File

@ -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() {
@ -136,4 +149,12 @@ export default class VectorBasePlatform extends BasePlatform {
getDefaultDeviceDisplayName(): string {
return _t("Unknown device");
}
/**
* Migrate account data from a previous origin
* Used only for the electron app
*/
async migrateFromOldOrigin() {
return false;
}
}

View File

@ -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) {

View File

@ -26,10 +26,13 @@ limitations under the License.
*/
import rageshake from "matrix-react-sdk/lib/rageshake/rageshake";
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');
@ -44,3 +47,18 @@ function initRageshake() {
}
initRageshake();
global.mxSendRageshake = function(text, withLogs) {
if (withLogs === undefined) withLogs = true;
require(['matrix-react-sdk/lib/rageshake/submit-rageshake'], (s) => {
s(SdkConfig.get().bug_report_endpoint_url, {
userText: text,
sendLogs: withLogs,
progressCallback: console.log.bind(console),
}).then(() => {
console.log("Bug report sent!");
}, (err) => {
console.error(err);
});
});
};

View File

@ -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 = (
<MatrixChat config={{}}

View File

@ -17,7 +17,7 @@ limitations under the License.
/* loading.js: test the myriad paths we have for loading the application */
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
import Platform from '../../src/vector/platform';
import WebPlatform from '../../src/vector/platform/WebPlatform';
import 'skin-sdk';
@ -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;
@ -140,7 +151,7 @@ describe('loading:', function() {
default_is_url: DEFAULT_IS_URL,
}, opts.config || {});
PlatformPeg.set(new Platform());
PlatformPeg.set(new WebPlatform());
const params = parseQs(windowLocation);
matrixChat = ReactDOM.render(
@ -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);
});

View File

@ -109,11 +109,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': {