Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electron: Load app from custom protocol #7943

Merged
merged 4 commits into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 82 additions & 4 deletions electron_app/src/electron-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,23 @@ const checkSquirrelHooks = require('./squirrelhooks');
if (checkSquirrelHooks()) return;

const argv = require('minimist')(process.argv);
const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater} = require('electron');
const {app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol} = require('electron');
const AutoLaunch = require('auto-launch');
const path = require('path');

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']}`);
}
Expand Down Expand Up @@ -110,7 +116,7 @@ autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate,
});
});

ipcMain.on('ipcCall', function(ev, payload) {
ipcMain.on('ipcCall', async function(ev, payload) {
if (!mainWindow) return;

const args = payload.args || [];
Expand Down Expand Up @@ -141,10 +147,15 @@ ipcMain.on('ipcCall', function(ev, payload) {
} else {
mainWindow.focus();
}
case 'origin_migrate':
migratingOrigin = true;
await migrateFromOldOrigin();
migratingOrigin = false;
break;
default:
mainWindow.webContents.send('ipcReply', {
id: payload.id,
error: new Error("Unknown IPC Call: "+payload.name),
error: "Unknown IPC Call: " + payload.name,
});
return;
}
Expand All @@ -171,6 +182,13 @@ const launcher = new AutoLaunch({
},
});

// Register the scheme the app is served from as 'standard'
// which allows things like relative URLs and IndexedDB to
// work.
// Also mark it as secure (ie. accessing resources from this
// protocol and HTTPS won't trigger mixed content warnings).
protocol.registerStandardSchemes(['vector'], {secure: true});

app.on('ready', () => {
if (argv['devtools']) {
try {
Expand All @@ -186,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";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be better to use path.join for this and the one above, because Windows. Not sure how badly it'll complain though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, path.normalize fixes it all

} 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']}`);
Expand Down Expand Up @@ -225,7 +303,7 @@ app.on('ready', () => {
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...
Expand Down
62 changes: 62 additions & 0 deletions electron_app/src/originMigrator.js
Original file line number Diff line number Diff line change
@@ -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,
};
10 changes: 9 additions & 1 deletion electron_app/src/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

const { ipcRenderer } = require('electron');
const { ipcRenderer, webFrame } = require('electron');

// expose ipcRenderer to the renderer process
window.ipcRenderer = ipcRenderer;

// 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,
});
19 changes: 19 additions & 0 deletions origin_migrator/dest/browser-matrix.min.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions origin_migrator/dest/dest.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<body>
<script src="browser-matrix.min.js"></script>
<script src="dest.js"></script>
</body>
</html>
125 changes: 125 additions & 0 deletions origin_migrator/dest/dest.js
Original file line number Diff line number Diff line change
@@ -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);

7 changes: 7 additions & 0 deletions origin_migrator/source.html
Original file line number Diff line number Diff line change
@@ -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>
Loading