Skip to content
This repository has been archived by the owner on Sep 5, 2020. It is now read-only.

Added preloader isolation #2087

Merged
merged 21 commits into from
Jul 5, 2017
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ globals: # don't warn about missing declarations
Tabs: true
Tracker: true
_: true
window: true
location: true

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ To run mist in development you need:
- [Node.js](https://nodejs.org) `v6.x` (use the prefered installation method for your OS)
- [Meteor](https://www.meteor.com/install) javascript app framework
- [Yarn](https://yarnpkg.com/) package manager
- [Electron](https://electron.atom.io/) `v1.3.13` cross platform desktop app framework
- [Electron](http://electron.atom.io/) `v1.4.15` cross platform desktop app framework
- [Gulp](http://gulpjs.com/) build and automation system

Install the latter ones via:

$ curl https://install.meteor.com/ | sh
$ curl -o- -L https://yarnpkg.com/install.sh | bash
$ yarn global add electron@1.3.13
$ yarn global add electron@1.4.15
$ yarn global add gulp

### Initialisation
Expand Down
2 changes: 1 addition & 1 deletion interface/client/templates/views/webview.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{{#if $eq _id "tests"}}
<webview src="file://{{dirname}}/tests/mocha-in-browser/runner.html" data-id="{{_id}}" preload="{{preloaderFile}}" autosize="on"></webview>
{{else}}
<webview src="{{checkedUrl}}" data-id="{{_id}}" preload="{{preloaderFile}}" autosize="on"></webview>
<webview src="{{checkedUrl}}" data-id="{{_id}}" preload="{{preloaderFile}}" webpreferences="contextIsolation=yes" autosize="on"></webview>
{{/if}}
</div>
</template>
1 change: 0 additions & 1 deletion modules/ipc/ipcProviderBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ class IpcProviderBackend {
log.debug(`Destroy socket connection due to event: ${ev}, id=${ownerId}`);

socket.destroy().finally(() => {

if (!owner.isDestroyed()) { owner.send(`ipcProvider-${ev}`, JSON.stringify(data)); }
});

Expand Down
128 changes: 111 additions & 17 deletions modules/preloader/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,125 @@
@module preloader browser
*/
require('./include/common')('browser');
require('./include/ethereumProvider.js');
const { ipcRenderer } = require('electron');
const { ipcRenderer, webFrame, remote } = require('electron');
const mist = require('./include/mistAPI.js');
require('./include/getFavicon.js');
require('./include/getMetaTags.js');
require('./include/setBasePath')('interface');
const packageJson = require('./../../package.json');
const fs = remote.require('fs');
const path = remote.require('path');

// notifiy the tab to store the webview id
ipcRenderer.sendToHost('setWebviewId');

// destroy the old socket
ipcRenderer.send('ipcProvider-destroy');
// Wait for post messages
window.addEventListener('message', function message(event) {
var data;
try {
data = JSON.parse(event.data);
} catch(e){
data = event.data;
}


if (typeof data !== 'object') {
return;
}

// EthereumProvider: connect
if (data.type === 'create') {
ipcRenderer.send('ipcProvider-create');

// EthereumProvider: write
} else if (data.type === 'write') {
// only accept valid JSON rpc requests
if(!Object.prototype.hasOwnProperty.call(data.message, 'method') ||
!Object.prototype.hasOwnProperty.call(data.message, 'id') ||
!Object.prototype.hasOwnProperty.call(data.message, 'params') ||
!Object.prototype.hasOwnProperty.call(data.message, 'jsonrpc')) {
return;
}

// make sure we only send allowed properties
ipcRenderer.send('ipcProvider-write', JSON.stringify({
jsonrpc: data.message.jsonrpc,
id: data.message.id,
method: data.message.method,
params: data.message.params
}));

// mistAPI
} else if (/^mistAPI_[a-z]/i.test(data.type)) {

if (data.type === 'mistAPI_requestAccount') {
ipcRenderer.send(data.type, data.message);
} else {
ipcRenderer.sendToHost(data.type, data.message);
}
}


});

// Security
process.on('loaded', function () {
Object.freeze(window.JSON);
// Object.freeze(window.Function);
// Object.freeze(window.Function.prototype);
// Object.freeze(window.Array);
// Object.freeze(window.Array.prototype);
var postMessage = function(payload) {
if(typeof payload === 'object') {
payload = JSON.stringify(payload);
}

window.postMessage(payload, (!location.origin || location.origin === "null" ) ? '*' : location.origin);
};

// custom Events
['uiAction_windowMessage', 'mistAPI_callMenuFunction'].forEach(function (type) {
ipcRenderer.on(type, function onIpcRenderer(e, result) {

// this type needs special packaging
if(type === 'uiAction_windowMessage') {
result = {
type: arguments[1],
error: arguments[2],
value: arguments[3]
};
}

postMessage({
type: type,
message: result
});
});
});

// add IPCbackend events
['data', 'error', 'end', 'timeout', 'connect'].forEach(function (type) {
ipcRenderer.on(`ipcProvider-`+ type, function onIpcRenderer(e, result) {
postMessage({
type: type,
message: JSON.parse(result)
});
});
});


// load ethereumProvider
const bignumber = fs.readFileSync(path.resolve('./modules/preloader/injected/BigNumber.js')).toString();
const eventEmitter3 = fs.readFileSync(path.resolve('./modules/preloader/injected/EventEmitter3.js')).toString();
let mistAPI = fs.readFileSync(path.resolve('./modules/preloader/injected/mistAPI.js')).toString();
const ethereumProvider = fs.readFileSync(path.resolve('./modules/preloader/injected/EthereumProvider.js')).toString();

window.mist = mist();
mistAPI = mistAPI.replace('__version__', packageJson.version)
.replace('__license__', packageJson.license)
.replace('__platform__', process.platform)
.replace('__solidityVersion__', String(packageJson.dependencies.solc).match(/\d+\.\d+\.\d+/)[0]);

// prevent overwriting the Dapps Web3
delete global.Web3;
delete window.Web3;
webFrame.executeJavaScript(
mistAPI +
bignumber +
eventEmitter3 +
ethereumProvider
);


// notifiy the tab to store the webview id
ipcRenderer.sendToHost('setWebviewId');

// destroy the old socket
ipcRenderer.send('ipcProvider-destroy');
1 change: 1 addition & 0 deletions modules/preloader/include/mistAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ module.exports = () => {
});

ipcRenderer.on('uiAction_windowMessage', (e, type, error, value) => {
console.log('uiAction_windowMessage',type, error, value);
if (mist.callbacks[type]) {
mist.callbacks[type].forEach((cb) => {
cb(error, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const LegacyWeb3IpcProvider = require('./legacyWeb3IpcProvider.js');


// SET ETHEREUM PROVIDER
window.ethereumProvider = new Web3.providers.IpcProvider('', ipcProviderWrapper);
// window.ethereumProvider = new Web3.providers.IpcProvider('', ipcProviderWrapper);


// LEGACY
Expand All @@ -20,4 +20,4 @@ window.web3 = {
};

// for now still add this too: WILL BE REMOVED with web3 1.0
window.web3 = new Web3(new Web3.providers.IpcProvider('', ipcProviderWrapper));
window.web3 = new Web3(new Web3.providers.IpcProvider('', ipcProviderWrapper));
9 changes: 9 additions & 0 deletions modules/preloader/injected/BigNumber.js

Large diffs are not rendered by default.

175 changes: 175 additions & 0 deletions modules/preloader/injected/EthereumProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@

(function () {
var EventEmitter = window.EventEmitter;

var postMessage = function(payload) {
if(typeof payload === 'object') {
payload = JSON.stringify(payload);
}

window.postMessage(payload, (!location.origin || location.origin === "null" ) ? '*' : location.origin);
};

// on events are: "connect", "data", "error", "end", "timeout"
// "data" will get notifications

function EthereumProvider() {
var _this = this;
// Call constructor of superclass to initialize superclass-derived members.
EventEmitter.call(this);

this.responseCallbacks = {};

// fire the connect
this._connect();
this._reconnectCheck();



// Wait for response messages
window.addEventListener('message', function(event) {
var data;
try {
data = JSON.parse(event.data);
} catch(e){
data = event.data;
}


if(typeof data !== 'object' || (data.message && !Object.prototype.hasOwnProperty.call(data.message, 'jsonrpc'))) {
return;
}

if (data.type === 'data') {

var id = null;
var result = data.message;

// get the id which matches the returned id
if(typeof result === 'object' && result.forEach && isFinite(result.length)) {
result.forEach(function(load){
if(_this.responseCallbacks[load.id])
id = load.id;
});
} else {
id = result.id;
}

// notification
if(!id && result.method && result.method.indexOf('_subscription') !== -1) {
// _this.listeners('data').forEach(function(callback){
// if(typeof callback === 'function')
// callback(null, result);
// });
_this.emit('data', result);

// fire the callback
} else if(_this.responseCallbacks[id]) {
_this.responseCallbacks[id](null, result);
delete _this.responseCallbacks[id];
}

// make all other events listenable
} else if(data.type) {
// _this.listeners(data.type).forEach(function(callback){
// if(typeof callback === 'function')
// callback(null, data.message);
// });
// TODO check if secure
_this.emit('data.type', data.message);
}
});
}

EthereumProvider.prototype = Object.create(EventEmitter.prototype);
EthereumProvider.prototype.constructor = EthereumProvider;

/**
Get the adds a callback to the responseCallbacks object,
which will be called if a response matching the response Id will arrive.

@method _addResponseCallback
*/
EthereumProvider.prototype._addResponseCallback = function(payload, callback) {
var id = payload.id || payload[0].id;
var method = payload.method || payload[0].method;

if (typeof callback !== 'function') {
throw new Error('No callback given, sync calls are not possible anymore in Mist. Please use only async calls.');
}

this.responseCallbacks[id] = callback;
this.responseCallbacks[id].method = method;
};


/**
Will watch for connection drops and tries to reconnect.

@method _reconnectCheck
*/
EthereumProvider.prototype._reconnectCheck = function() {
var _this = this;
var reconnectIntervalId;

this.on('end', function () {
reconnectIntervalId = setInterval(function () {
_this._connect();
}, 500);
});

this.on('connect', function () {
clearInterval(reconnectIntervalId);
});
};



/**
Will try to make a connection

@method connect
*/
EthereumProvider.prototype._connect = function(payload, callback) {
postMessage({
type: 'create'
});
};

/**
Sends the request

@method send
@param {Object} payload example: {id: 1, jsonrpc: '2.0', 'method': 'eth_someMethod', params: []}
@param {Function} callback the callback to call
*/
// TODO transform to: send(method, params, callback)
EthereumProvider.prototype.send = function send(payload, callback) {

this._addResponseCallback(payload, callback);
postMessage({
type: 'write',
message: payload
}, this.origin);
};




delete window.EventEmitter;
// TODO set real ethereum provider
// window.ethereum = new EthereumProvider();


// For backwards compatibility of web3.currentProvider;
EthereumProvider.prototype.sendSync = function () {
return {jsonrpc: '2.0', error: {"code": -32603, message: 'Sync calls are not anymore supported in Mist :\\'}};
};
EthereumProvider.prototype.sendAsync = EthereumProvider.prototype.send;
EthereumProvider.prototype.isConnected = function () {
return true;
};
window.web3 = {
currentProvider: new EthereumProvider()
};
})();
Loading