Fix Secure API hangs (#3927)

* Use proxy for WS in dev

* Update SecureAPI

* Update webpack config

* Fix dev contract

* Update webpack

* Linting fixes

* Refactor Secure API logic : Promise based, no wastes of req

* Fix tests

* Add try 'intitial' token
This commit is contained in:
Nicolas Gotchac 2016-12-21 15:12:40 +01:00 committed by Gav Wood
parent a9f89b09e0
commit aba38721b1
15 changed files with 218 additions and 185 deletions

View File

@ -117,6 +117,7 @@
"react-hot-loader": "3.0.0-beta.6", "react-hot-loader": "3.0.0-beta.6",
"react-intl-aggregate-webpack-plugin": "0.0.1", "react-intl-aggregate-webpack-plugin": "0.0.1",
"rucksack-css": "0.9.1", "rucksack-css": "0.9.1",
"script-ext-html-webpack-plugin": "1.3.4",
"serviceworker-webpack-plugin": "0.1.7", "serviceworker-webpack-plugin": "0.1.7",
"sinon": "1.17.6", "sinon": "1.17.6",
"sinon-as-promised": "4.0.2", "sinon-as-promised": "4.0.2",
@ -125,7 +126,7 @@
"stylelint": "7.6.0", "stylelint": "7.6.0",
"stylelint-config-standard": "15.0.0", "stylelint-config-standard": "15.0.0",
"url-loader": "0.5.7", "url-loader": "0.5.7",
"webpack": "2.1.0-beta.27", "webpack": "2.2.0-rc.1",
"webpack-dev-middleware": "1.8.4", "webpack-dev-middleware": "1.8.4",
"webpack-error-notification": "0.1.6", "webpack-error-notification": "0.1.6",
"webpack-hot-middleware": "2.13.2", "webpack-hot-middleware": "2.13.2",

View File

@ -22,7 +22,7 @@ import TransportError from '../error';
/* global WebSocket */ /* global WebSocket */
export default class Ws extends JsonRpcBase { export default class Ws extends JsonRpcBase {
constructor (url, token) { constructor (url, token, connect = true) {
super(); super();
this._url = url; this._url = url;
@ -32,23 +32,34 @@ export default class Ws extends JsonRpcBase {
this._connecting = false; this._connecting = false;
this._connected = false; this._connected = false;
this._lastError = null; this._lastError = null;
this._autoConnect = true; this._autoConnect = false;
this._retries = 0; this._retries = 0;
this._reconnectTimeoutId = null; this._reconnectTimeoutId = null;
this._connect(); this._connectPromise = null;
this._connectPromiseFunctions = {};
if (connect) {
this.connect();
}
} }
updateToken (token) { updateToken (token, connect = true) {
this._token = token; this._token = token;
this._autoConnect = true; // this._autoConnect = true;
this._connect(); if (connect) {
this.connect();
}
}
connect () {
if (this._connected) {
return Promise.resolve();
} }
_connect () {
if (this._connecting) { if (this._connecting) {
return; return this._connectPromise || Promise.resolve();
} }
if (this._reconnectTimeoutId) { if (this._reconnectTimeoutId) {
@ -104,10 +115,17 @@ export default class Ws extends JsonRpcBase {
window._parityWS = this; window._parityWS = this;
} }
this._connectPromise = new Promise((resolve, reject) => {
this._connectPromiseFunctions = { resolve, reject };
});
return this._connectPromise;
} }
_onOpen = (event) => { _onOpen = (event) => {
console.log('ws:onOpen', event); console.log('ws:onOpen');
this._connected = true; this._connected = true;
this._connecting = false; this._connecting = false;
this._autoConnect = true; this._autoConnect = true;
@ -116,6 +134,11 @@ export default class Ws extends JsonRpcBase {
Object.keys(this._messages) Object.keys(this._messages)
.filter((id) => this._messages[id].queued) .filter((id) => this._messages[id].queued)
.forEach(this._send); .forEach(this._send);
this._connectPromiseFunctions.resolve();
this._connectPromise = null;
this._connectPromiseFunctions = {};
} }
_onClose = (event) => { _onClose = (event) => {
@ -135,13 +158,20 @@ export default class Ws extends JsonRpcBase {
console.log('ws:onClose', `trying again in ${time}...`); console.log('ws:onClose', `trying again in ${time}...`);
this._reconnectTimeoutId = setTimeout(() => { this._reconnectTimeoutId = setTimeout(() => {
this._connect(); this.connect();
}, timeout); }, timeout);
return; return;
} }
console.log('ws:onClose', event); if (this._connectPromise) {
this._connectPromiseFunctions.reject(event);
this._connectPromise = null;
this._connectPromiseFunctions = {};
}
console.log('ws:onClose');
} }
_onError = (event) => { _onError = (event) => {
@ -149,10 +179,17 @@ export default class Ws extends JsonRpcBase {
// ie. don't print if error == closed // ie. don't print if error == closed
window.setTimeout(() => { window.setTimeout(() => {
if (this._connected) { if (this._connected) {
console.error('ws:onError', event); console.error('ws:onError');
event.timestamp = Date.now(); event.timestamp = Date.now();
this._lastError = event; this._lastError = event;
if (this._connectPromise) {
this._connectPromiseFunctions.reject(event);
this._connectPromise = null;
this._connectPromiseFunctions = {};
}
} }
}, 50); }, 50);
} }

View File

@ -52,12 +52,7 @@ if (process.env.NODE_ENV === 'development') {
} }
const AUTH_HASH = '#/auth?'; const AUTH_HASH = '#/auth?';
const parityUrl = process.env.PARITY_URL || const parityUrl = process.env.PARITY_URL || window.location.host;
(
process.env.NODE_ENV === 'production'
? window.location.host
: '127.0.0.1:8180'
);
let token = null; let token = null;
if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) { if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {

View File

@ -22,13 +22,11 @@ export default class Status {
this._api = api; this._api = api;
this._store = store; this._store = store;
this._pingable = false;
this._apiStatus = {}; this._apiStatus = {};
this._status = {}; this._status = {};
this._longStatus = {}; this._longStatus = {};
this._minerSettings = {}; this._minerSettings = {};
this._pollPingTimeoutId = null;
this._longStatusTimeoutId = null; this._longStatusTimeoutId = null;
this._timestamp = Date.now(); this._timestamp = Date.now();
@ -36,7 +34,6 @@ export default class Status {
start () { start () {
this._subscribeBlockNumber(); this._subscribeBlockNumber();
this._pollPing();
this._pollStatus(); this._pollStatus();
this._pollLongStatus(); this._pollLongStatus();
this._pollLogs(); this._pollLogs();
@ -65,50 +62,6 @@ export default class Status {
}); });
} }
/**
* Pinging should be smart. It should only
* be used when the UI is connecting or the
* Node is deconnected.
*
* @see src/views/Connection/connection.js
*/
_shouldPing = () => {
const { isConnected } = this._apiStatus;
return !isConnected;
}
_stopPollPing = () => {
if (!this._pollPingTimeoutId) {
return;
}
clearTimeout(this._pollPingTimeoutId);
this._pollPingTimeoutId = null;
}
_pollPing = () => {
// Already pinging, don't try again
if (this._pollPingTimeoutId) {
return;
}
const dispatch = (pingable, timeout = 1000) => {
if (pingable !== this._pingable) {
this._pingable = pingable;
this._store.dispatch(statusCollection({ isPingable: pingable }));
}
this._pollPingTimeoutId = setTimeout(() => {
this._stopPollPing();
this._pollPing();
}, timeout);
};
fetch('/', { method: 'HEAD' })
.then((response) => dispatch(!!response.ok))
.catch(() => dispatch(false));
}
_pollTraceMode = () => { _pollTraceMode = () => {
return this._api.trace.block() return this._api.trace.block()
.then(blockTraces => { .then(blockTraces => {
@ -137,7 +90,6 @@ export default class Status {
if (gotConnected) { if (gotConnected) {
this._pollLongStatus(); this._pollLongStatus();
this._store.dispatch(statusCollection({ isPingable: true }));
} }
if (!isEqual(apiStatus, this._apiStatus)) { if (!isEqual(apiStatus, this._apiStatus)) {
@ -145,13 +97,6 @@ export default class Status {
this._apiStatus = apiStatus; this._apiStatus = apiStatus;
} }
// Ping if necessary, otherwise stop pinging
if (this._shouldPing()) {
this._pollPing();
} else {
this._stopPollPing();
}
if (!isConnected) { if (!isConnected) {
return nextTimeout(250); return nextTimeout(250);
} }

View File

@ -43,7 +43,6 @@ const initialState = {
syncing: true, syncing: true,
isConnected: false, isConnected: false,
isConnecting: false, isConnecting: false,
isPingable: false,
isTest: undefined, isTest: undefined,
refreshStatus: false, refreshStatus: false,
traceMode: undefined traceMode: undefined

View File

@ -14,35 +14,45 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { uniq } from 'lodash';
import Api from './api'; import Api from './api';
const sysuiToken = window.localStorage.getItem('sysuiToken'); const sysuiToken = window.localStorage.getItem('sysuiToken');
export default class SecureApi extends Api { export default class SecureApi extends Api {
constructor (url, nextToken) { constructor (url, nextToken) {
super(new Api.Transport.Ws(url, sysuiToken)); super(new Api.Transport.Ws(url, sysuiToken, false));
this._url = url;
this._isConnecting = true; this._isConnecting = true;
this._connectState = sysuiToken === 'initial' ? 1 : 0;
this._needsToken = false; this._needsToken = false;
this._dappsPort = 8080; this._dappsPort = 8080;
this._dappsInterface = null; this._dappsInterface = null;
this._signerPort = 8180; this._signerPort = 8180;
this._followConnectionTimeoutId = null;
// Try tokens from localstorage, then from hash // Try tokens from localstorage, then from hash
this._tokensToTry = [ sysuiToken, nextToken ].filter((t) => t && t.length); this._tokens = uniq([sysuiToken, nextToken, 'initial'])
.filter((token) => token)
.map((token) => ({ value: token, tried: false }));
this._followConnection(); this._tryNextToken();
} }
setToken = () => { saveToken = () => {
window.localStorage.setItem('sysuiToken', this._transport.token); window.localStorage.setItem('sysuiToken', this._transport.token);
// DEBUG: console.log('SecureApi:setToken', this._transport.token); // DEBUG: console.log('SecureApi:saveToken', this._transport.token);
} }
/**
* Returns a Promise that gets resolved with
* a boolean: `true` if the node is up, `false`
* otherwise
*/
_checkNodeUp () { _checkNodeUp () {
return fetch('/', { method: 'HEAD' }) const url = this._url.replace(/wss?/, 'http');
return fetch(url, { method: 'HEAD' })
.then( .then(
(r) => r.status === 200, (r) => r.status === 200,
() => false () => false
@ -50,92 +60,70 @@ export default class SecureApi extends Api {
.catch(() => false); .catch(() => false);
} }
_followConnection = () => { _setManual () {
const nextTick = () => {
if (this._followConnectionTimeoutId) {
clearTimeout(this._followConnectionTimeoutId);
}
this._followConnectionTimeoutId = setTimeout(() => this._followConnection(), 250);
};
const setManual = () => {
this._connectState = 100;
this._needsToken = true; this._needsToken = true;
this._isConnecting = false; this._isConnecting = false;
}; }
const lastError = this._transport.lastError; _tryNextToken () {
const isConnected = this._transport.isConnected; const nextTokenIndex = this._tokens.findIndex((t) => !t.tried);
if (nextTokenIndex < 0) {
return this._setManual();
}
const nextToken = this._tokens[nextTokenIndex];
nextToken.tried = true;
this.updateToken(nextToken.value);
}
_followConnection = () => {
const token = this.transport.token;
switch (this._connectState) {
// token = <passed via constructor>
case 0:
if (isConnected) {
return this.connectSuccess();
} else if (lastError) {
return this return this
._checkNodeUp() .transport
.then((isNodeUp) => { .connect()
const { timestamp } = lastError; .then(() => {
if (token === 'initial') {
if ((Date.now() - timestamp) > 250) { return this.signer
return nextTick();
}
const nextToken = this._tokensToTry[0] || 'initial';
const nextState = nextToken !== 'initial' ? 0 : 1;
// If previous token was wrong (error while node up), delete it
if (isNodeUp) {
this._tokensToTry = this._tokensToTry.slice(1);
}
if (nextToken !== this._transport.token) {
this.updateToken(nextToken, nextState);
}
return nextTick();
});
}
break;
// token = 'initial'
case 1:
if (isConnected) {
this.signer
.generateAuthorizationToken() .generateAuthorizationToken()
.then((token) => { .then((token) => {
this.updateToken(token, 2); return this.updateToken(token);
}) })
.catch((error) => { .catch((e) => console.error(e));
console.error('SecureApi:generateAuthorizationToken', error); }
setManual();
}); this.connectSuccess();
return true;
})
.catch((e) => {
this
._checkNodeUp()
.then((isNodeUp) => {
// Try again in a few...
if (!isNodeUp) {
this._isConnecting = false;
const timeout = this.transport.retryTimeout;
window.setTimeout(() => {
this._followConnection();
}, timeout);
return; return;
} else if (lastError) {
return setManual();
}
break;
// token = <personal_generateAuthorizationToken>
case 2:
if (isConnected) {
return this.connectSuccess();
} else if (lastError) {
return setManual();
}
break;
} }
nextTick(); this._tryNextToken();
return false;
});
});
} }
connectSuccess () { connectSuccess () {
this._isConnecting = false; this._isConnecting = false;
this._needsToken = false; this._needsToken = false;
this.setToken(); this.saveToken();
Promise Promise
.all([ .all([
@ -152,10 +140,9 @@ export default class SecureApi extends Api {
// DEBUG: console.log('SecureApi:connectSuccess', this._transport.token); // DEBUG: console.log('SecureApi:connectSuccess', this._transport.token);
} }
updateToken (token, connectState = 0) { updateToken (token) {
this._connectState = connectState; this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, ''), false);
this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, '')); return this._followConnection();
this._followConnection();
// DEBUG: console.log('SecureApi:updateToken', this._transport.token, connectState); // DEBUG: console.log('SecureApi:updateToken', this._transport.token, connectState);
} }

View File

@ -140,5 +140,5 @@ function getCompiler (build) {
}); });
} }
return self.solc[longVersion]; return Promise.resolve(self.solc[longVersion]);
} }

View File

@ -62,9 +62,17 @@ class BlockStatus extends Component {
); );
} }
let syncStatus = null;
if (syncing && syncing.currentBlock && syncing.highestBlock) {
syncStatus = (
<span>{ syncing.currentBlock.toFormat() }/{ syncing.highestBlock.toFormat() } syncing</span>
);
}
return ( return (
<div className={ styles.syncStatus }> <div className={ styles.syncStatus }>
<span>{ syncing.currentBlock.toFormat() }/{ syncing.highestBlock.toFormat() } syncing</span> { syncStatus }
{ warpStatus } { warpStatus }
</div> </div>
); );

View File

@ -34,27 +34,26 @@ class Connection extends Component {
static propTypes = { static propTypes = {
isConnected: PropTypes.bool, isConnected: PropTypes.bool,
isConnecting: PropTypes.bool, isConnecting: PropTypes.bool,
isPingable: PropTypes.bool,
needsToken: PropTypes.bool needsToken: PropTypes.bool
} }
state = { state = {
loading: false,
token: '', token: '',
validToken: false validToken: false
} }
render () { render () {
const { isConnected, isConnecting, isPingable } = this.props; const { isConnected, needsToken } = this.props;
const isOk = !isConnecting && isConnected && isPingable;
if (isOk) { if (isConnected) {
return null; return null;
} }
const typeIcon = isPingable const typeIcon = needsToken
? <NotificationVpnLock className={ styles.svg } /> ? <NotificationVpnLock className={ styles.svg } />
: <ActionDashboard className={ styles.svg } />; : <ActionDashboard className={ styles.svg } />;
const description = isPingable const description = needsToken
? this.renderSigner() ? this.renderSigner()
: this.renderPing(); : this.renderPing();
@ -82,7 +81,7 @@ class Connection extends Component {
} }
renderSigner () { renderSigner () {
const { token, validToken } = this.state; const { loading, token, validToken } = this.state;
const { isConnecting, needsToken } = this.props; const { isConnecting, needsToken } = this.props;
if (needsToken && !isConnecting) { if (needsToken && !isConnecting) {
@ -93,9 +92,11 @@ class Connection extends Component {
<Input <Input
label='secure token' label='secure token'
hint='a generated token from Parity' hint='a generated token from Parity'
disabled={ loading }
error={ validToken || (!token || !token.length) ? null : 'invalid signer token' } error={ validToken || (!token || !token.length) ? null : 'invalid signer token' }
value={ token } value={ token }
onChange={ this.onChangeToken } /> onChange={ this.onChangeToken }
/>
</div> </div>
</div> </div>
); );
@ -117,7 +118,7 @@ class Connection extends Component {
} }
onChangeToken = (event, token) => { onChangeToken = (event, token) => {
const validToken = /[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}/.test(token); const validToken = /[a-zA-Z0-9]{4}-?[a-zA-Z0-9]{4}-?[a-zA-Z0-9]{4}-?[a-zA-Z0-9]{4}/.test(token);
this.setState({ token, validToken }, () => { this.setState({ token, validToken }, () => {
validToken && this.setToken(); validToken && this.setToken();
}); });
@ -127,15 +128,20 @@ class Connection extends Component {
const { api } = this.context; const { api } = this.context;
const { token } = this.state; const { token } = this.state;
api.updateToken(token, 0); this.setState({ loading: true });
this.setState({ token: '', validToken: false });
api
.updateToken(token, 0)
.then((isValid) => {
this.setState({ loading: isValid || false, validToken: isValid });
});
} }
} }
function mapStateToProps (state) { function mapStateToProps (state) {
const { isConnected, isConnecting, isPingable, needsToken } = state.nodeStatus; const { isConnected, isConnecting, needsToken } = state.nodeStatus;
return { isConnected, isConnecting, isPingable, needsToken }; return { isConnected, isConnecting, needsToken };
} }
function mapDispatchToProps (dispatch) { function mapDispatchToProps (dispatch) {

View File

@ -105,6 +105,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-right: 0.5em; margin-right: 0.5em;
overflow: auto;
.panel { .panel {
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
padding: 1em; padding: 1em;

View File

@ -21,6 +21,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import CircularProgress from 'material-ui/CircularProgress'; import CircularProgress from 'material-ui/CircularProgress';
import moment from 'moment'; import moment from 'moment';
import { throttle } from 'lodash';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import SaveIcon from 'material-ui/svg-icons/content/save'; import SaveIcon from 'material-ui/svg-icons/content/save';
@ -60,6 +61,8 @@ class WriteContract extends Component {
if (worker !== undefined) { if (worker !== undefined) {
this.store.setWorker(worker); this.store.setWorker(worker);
} }
this.throttledResize = throttle(this.applyResize, 100, { leading: true });
} }
componentDidMount () { componentDidMount () {
@ -516,10 +519,16 @@ class WriteContract extends Component {
const x = pageX - left; const x = pageX - left;
this.setState({ size: 100 * x / width }); this.size = 100 * x / width;
this.throttledResize();
event.stopPropagation(); event.stopPropagation();
} }
applyResize = () => {
this.setState({ size: this.size });
}
} }
function mapStateToProps (state) { function mapStateToProps (state) {

View File

@ -288,7 +288,7 @@ export default class WriteContractStore {
const build = this.builds[this.selectedBuild]; const build = this.builds[this.selectedBuild];
const version = build.longVersion; const version = build.longVersion;
const sourcecode = this.sourcecode.replace(/\n+/g, '\n').replace(/\s(\s+)/g, ' '); const sourcecode = this.sourcecode.replace(/\s+/g, ' ');
const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize })); const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
let promise = Promise.resolve(null); let promise = Promise.resolve(null);
@ -302,7 +302,7 @@ export default class WriteContractStore {
} else { } else {
promise = this promise = this
.compile({ .compile({
sourcecode: sourcecode, sourcecode: this.sourcecode,
build: build, build: build,
optimize: this.optimize, optimize: this.optimize,
files: this.files files: this.files

View File

@ -23,6 +23,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const Shared = require('./shared'); const Shared = require('./shared');
const DAPPS = require('../src/dapps'); const DAPPS = require('../src/dapps');
@ -35,10 +36,13 @@ const isProd = ENV === 'production';
module.exports = { module.exports = {
cache: !isProd, cache: !isProd,
devtool: isProd ? '#eval' : '#eval-source-map', devtool: isProd ? '#hidden-source-map' : '#source-map',
context: path.join(__dirname, '../src'), context: path.join(__dirname, '../src'),
entry: Object.assign({}, Shared.dappsEntry, { entry: Object.assign({}, Shared.dappsEntry, {
modals: './modals/index.js',
views: './views/index.js',
ui: './ui/index.js',
index: './index.js' index: './index.js'
}), }),
output: { output: {
@ -162,13 +166,43 @@ module.exports = {
filename: 'index.html', filename: 'index.html',
template: './index.ejs', template: './index.ejs',
favicon: FAVICON, favicon: FAVICON,
chunks: [ isProd ? null : 'commons', 'index' ] chunks: [
isProd ? null : 'commons',
'common.modals', 'common.views', 'common.ui',
'modals', 'views', 'ui',
'index'
]
}),
new ScriptExtHtmlWebpackPlugin({
sync: [ 'commons', 'vendor.js' ],
defaultAttribute: 'defer'
}), }),
new ServiceWorkerWebpackPlugin({ new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '../src/serviceWorker.js') entry: path.join(__dirname, '../src/serviceWorker.js')
}), }),
new webpack.optimize.CommonsChunkPlugin({
filename: 'commons.modals.[hash:10].js',
name: 'common.modals',
minChunks: 2,
chunks: [ 'index', 'modals' ]
}),
new webpack.optimize.CommonsChunkPlugin({
filename: 'commons.views.[hash:10].js',
name: 'common.views',
minChunks: 2,
chunks: [ 'index', 'views' ]
}),
new webpack.optimize.CommonsChunkPlugin({
filename: 'commons.ui.[hash:10].js',
name: 'common.ui',
minChunks: 2
}),
DappsHTMLInjection DappsHTMLInjection
); );
@ -185,7 +219,7 @@ module.exports = {
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
filename: 'commons.[hash:10].js', filename: 'commons.[hash:10].js',
name: 'commons', name: 'commons',
minChunks: Infinity minChunks: 2
}) })
); );
} }

View File

@ -22,6 +22,7 @@ const webpackHotMiddleware = require('webpack-hot-middleware');
const http = require('http'); const http = require('http');
const express = require('express'); const express = require('express');
const ProgressBar = require('progress'); const ProgressBar = require('progress');
const proxy = require('http-proxy-middleware');
const webpackConfig = require('./app'); const webpackConfig = require('./app');
const Shared = require('./shared'); const Shared = require('./shared');
@ -33,6 +34,8 @@ let progressBar = { update: () => {} };
* and HMR to the plugins * and HMR to the plugins
*/ */
(function updateWebpackConfig () { (function updateWebpackConfig () {
webpackConfig.performance = { hints: false };
Object.keys(webpackConfig.entry).forEach((key) => { Object.keys(webpackConfig.entry).forEach((key) => {
const entry = webpackConfig.entry[key]; const entry = webpackConfig.entry[key];
@ -81,13 +84,18 @@ app.use(webpackDevMiddleware(compiler, {
} }
})); }));
app.use(express.static(webpackConfig.output.path)); var wsProxy = proxy('ws://127.0.0.1:8180', { changeOrigin: true });
// Add the dev proxies in the express App // Add the dev proxies in the express App
Shared.addProxies(app); Shared.addProxies(app);
app.use(express.static(webpackConfig.output.path));
app.use(wsProxy);
const server = http.createServer(app); const server = http.createServer(app);
server.listen(process.env.PORT || 3000, function () { server.listen(process.env.PORT || 3000, function () {
console.log('Listening on port', server.address().port); console.log('Listening on port', server.address().port);
progressBar = new ProgressBar('[:bar] :percent :etas', { total: 50 }); progressBar = new ProgressBar('[:bar] :percent :etas', { total: 50 });
}); });
server.on('upgrade', wsProxy.upgrade);

View File

@ -31,9 +31,11 @@ let modules = [
'ethereumjs-tx', 'ethereumjs-tx',
'lodash', 'lodash',
'material-ui', 'material-ui',
'material-ui-chip-input',
'mobx', 'mobx',
'mobx-react', 'mobx-react',
'moment', 'moment',
'phoneformat.js',
'react', 'react',
'react-dom', 'react-dom',
'react-redux', 'react-redux',