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:
parent
a9f89b09e0
commit
aba38721b1
@ -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",
|
||||||
|
@ -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 () {
|
connect () {
|
||||||
|
if (this._connected) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 = () => {
|
this._needsToken = true;
|
||||||
if (this._followConnectionTimeoutId) {
|
this._isConnecting = false;
|
||||||
clearTimeout(this._followConnectionTimeoutId);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this._followConnectionTimeoutId = setTimeout(() => this._followConnection(), 250);
|
_tryNextToken () {
|
||||||
};
|
const nextTokenIndex = this._tokens.findIndex((t) => !t.tried);
|
||||||
|
|
||||||
const setManual = () => {
|
if (nextTokenIndex < 0) {
|
||||||
this._connectState = 100;
|
return this._setManual();
|
||||||
this._needsToken = true;
|
|
||||||
this._isConnecting = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const lastError = this._transport.lastError;
|
|
||||||
const isConnected = this._transport.isConnected;
|
|
||||||
|
|
||||||
switch (this._connectState) {
|
|
||||||
// token = <passed via constructor>
|
|
||||||
case 0:
|
|
||||||
if (isConnected) {
|
|
||||||
return this.connectSuccess();
|
|
||||||
} else if (lastError) {
|
|
||||||
return this
|
|
||||||
._checkNodeUp()
|
|
||||||
.then((isNodeUp) => {
|
|
||||||
const { timestamp } = lastError;
|
|
||||||
|
|
||||||
if ((Date.now() - timestamp) > 250) {
|
|
||||||
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()
|
|
||||||
.then((token) => {
|
|
||||||
this.updateToken(token, 2);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('SecureApi:generateAuthorizationToken', error);
|
|
||||||
setManual();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else if (lastError) {
|
|
||||||
return setManual();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// token = <personal_generateAuthorizationToken>
|
|
||||||
case 2:
|
|
||||||
if (isConnected) {
|
|
||||||
return this.connectSuccess();
|
|
||||||
} else if (lastError) {
|
|
||||||
return setManual();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick();
|
const nextToken = this._tokens[nextTokenIndex];
|
||||||
|
nextToken.tried = true;
|
||||||
|
|
||||||
|
this.updateToken(nextToken.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_followConnection = () => {
|
||||||
|
const token = this.transport.token;
|
||||||
|
|
||||||
|
return this
|
||||||
|
.transport
|
||||||
|
.connect()
|
||||||
|
.then(() => {
|
||||||
|
if (token === 'initial') {
|
||||||
|
return this.signer
|
||||||
|
.generateAuthorizationToken()
|
||||||
|
.then((token) => {
|
||||||
|
return this.updateToken(token);
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,5 +140,5 @@ function getCompiler (build) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.solc[longVersion];
|
return Promise.resolve(self.solc[longVersion]);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user