Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Commit 06937c9

Browse files
authored
Merge pull request #2504 from ethcore/jg-connection-prompts
signer & node connection prompts/indicators
2 parents 408e08d + 01909ae commit 06937c9

File tree

21 files changed

+503
-118
lines changed

21 files changed

+503
-118
lines changed

js/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
"store": "^1.3.20",
143143
"utf8": "^2.1.1",
144144
"validator": "^5.7.0",
145-
"web3": "^0.17.0-alpha"
145+
"web3": "^0.17.0-alpha",
146+
"whatwg-fetch": "^1.0.0"
146147
}
147148
}

js/src/api/rpc/personal/personal.js

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export default class Personal {
2828
.then(outAccountInfo);
2929
}
3030

31+
generateAuthorizationToken () {
32+
return this._transport
33+
.execute('personal_generateAuthorizationToken');
34+
}
35+
3136
listAccounts () {
3237
return this._transport
3338
.execute('personal_listAccounts')

js/src/api/transport/ws/ws.js

+46-11
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,49 @@ export default class Ws extends JsonRpcBase {
2828
this._token = token;
2929
this._messages = {};
3030

31+
this._connecting = true;
32+
this._lastError = null;
33+
this._autoConnect = false;
34+
3135
this._connect();
3236
}
3337

34-
_hash () {
35-
const time = parseInt(new Date().getTime() / 1000, 10);
36-
const sha3 = keccak_256(`${this._token}:${time}`);
38+
updateToken (token) {
39+
this._token = token;
40+
this._autoConnect = false;
3741

38-
return `${sha3}_${time}`;
42+
this._connect();
3943
}
4044

4145
_connect () {
42-
this._ws = new WebSocket(this._url, this._hash());
46+
const time = parseInt(new Date().getTime() / 1000, 10);
47+
const sha3 = keccak_256(`${this._token}:${time}`);
48+
const hash = `${sha3}_${time}`;
49+
50+
if (this._ws) {
51+
this._ws.onerror = null;
52+
this._ws.onopen = null;
53+
this._ws.onclose = null;
54+
this._ws.onmessage = null;
55+
this._ws = null;
56+
}
57+
58+
this._connecting = true;
59+
this._connected = false;
60+
this._lastError = null;
4361

62+
this._ws = new WebSocket(this._url, hash);
4463
this._ws.onerror = this._onError;
4564
this._ws.onopen = this._onOpen;
4665
this._ws.onclose = this._onClose;
4766
this._ws.onmessage = this._onMessage;
48-
49-
this._connected = false;
5067
}
5168

5269
_onOpen = (event) => {
5370
console.log('ws:onOpen', event);
5471
this._connected = true;
72+
this._connecting = false;
73+
this._autoConnect = true;
5574

5675
Object.keys(this._messages)
5776
.filter((id) => this._messages[id].queued)
@@ -61,11 +80,16 @@ export default class Ws extends JsonRpcBase {
6180
_onClose = (event) => {
6281
console.log('ws:onClose', event);
6382
this._connected = false;
64-
this._connect();
83+
this._connecting = false;
84+
85+
if (this._autoConnect) {
86+
this._connect();
87+
}
6588
}
6689

6790
_onError = (event) => {
6891
console.error('ws:onError', event);
92+
this._lastError = event;
6993
}
7094

7195
_onMessage = (event) => {
@@ -106,8 +130,19 @@ export default class Ws extends JsonRpcBase {
106130
});
107131
}
108132

109-
updateToken (token) {
110-
this._token = token;
111-
this._connect();
133+
get token () {
134+
return this._token;
135+
}
136+
137+
get isAutoConnect () {
138+
return this._autoConnect;
139+
}
140+
141+
get isConnecting () {
142+
return this._connecting;
143+
}
144+
145+
get lastError () {
146+
return this._lastError;
112147
}
113148
}

js/src/index.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
1616

1717
import 'babel-polyfill';
18-
import 'isomorphic-fetch';
18+
import 'whatwg-fetch';
1919

2020
import es6Promise from 'es6-promise';
2121
es6Promise.polyfill();
@@ -28,7 +28,7 @@ import { Redirect, Router, Route, useRouterHistory } from 'react-router';
2828

2929
import Web3 from 'web3';
3030

31-
import Api from './api';
31+
import SecureApi from './secureApi';
3232
import ContractInstances from './contracts';
3333

3434
import { initStore } from './redux';
@@ -47,23 +47,22 @@ import './index.html';
4747

4848
injectTapEventPlugin();
4949

50-
const initToken = window.localStorage.getItem('sysuiToken') || 'initial';
5150
const parityUrl = process.env.PARITY_URL ||
5251
(
5352
process.env.NODE_ENV === 'production'
5453
? window.location.host
5554
: '127.0.0.1:8180'
5655
);
5756

58-
const api = new Api(new Api.Transport.Ws(`ws://${parityUrl}`, initToken)); // new Api.Transport.Http('/rpc/'));
57+
const ws = new Ws(parityUrl);
58+
const api = new SecureApi(`ws://${parityUrl}`, ws.init);
5959
ContractInstances.create(api);
6060

6161
// signer
6262
function tokenSetter (token, cb) {
6363
window.localStorage.setItem('sysuiToken', token);
6464
}
6565

66-
const ws = new Ws(parityUrl);
6766
const web3ws = new Web3(new WebSocketsProvider(ws));
6867
statusWeb3Extension(web3ws).map((extension) => web3ws._extend(extension));
6968

@@ -73,7 +72,6 @@ store.dispatch({ type: 'initAll', api });
7372
// signer
7473
new WsDataProvider(store, ws); // eslint-disable-line no-new
7574
new SignerDataProvider(store, ws); // eslint-disable-line no-new
76-
ws.init(initToken);
7775

7876
const routerHistory = useRouterHistory(createHashHistory)({});
7977

js/src/parity.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
1616

1717
import 'babel-polyfill';
18-
import 'isomorphic-fetch';
18+
import 'whatwg-fetch';
1919

2020
import es6Promise from 'es6-promise';
2121
es6Promise.polyfill();

js/src/redux/providers/status.js

+18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default class Status {
2424

2525
start () {
2626
this._subscribeBlockNumber();
27+
this._pollPing();
2728
this._pollStatus();
2829
this._pollLogs();
2930
}
@@ -42,8 +43,25 @@ export default class Status {
4243
});
4344
}
4445

46+
_pollPing = () => {
47+
const dispatch = (status, timeout = 500) => {
48+
this._store.dispatch(statusCollection({ isPingable: status }));
49+
setTimeout(this._pollPing, timeout);
50+
};
51+
52+
fetch(`http://${window.location.host}/api/ping`, { method: 'GET' })
53+
.then((response) => dispatch(!!response.ok))
54+
.catch(() => dispatch(false));
55+
}
56+
4557
_pollStatus = () => {
4658
const nextTimeout = (timeout = 1000) => setTimeout(this._pollStatus, timeout);
59+
const { secureToken, isConnected, isConnecting, needsToken } = this._api;
60+
61+
this._store.dispatch(statusCollection({ isConnected, isConnecting, needsToken, secureToken }));
62+
if (!isConnected) {
63+
nextTimeout(250);
64+
}
4765

4866
Promise
4967
.all([

js/src/redux/providers/statusReducer.js

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const initialState = {
3939
nodeName: '',
4040
rpcSettings: {},
4141
syncing: false,
42+
isApiConnected: true,
43+
isPingConnected: true,
4244
isTest: true
4345
};
4446

js/src/secureApi.js

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2015, 2016 Ethcore (UK) Ltd.
2+
// This file is part of Parity.
3+
4+
// Parity is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// Parity is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import Api from './api';
18+
19+
const sysuiToken = window.localStorage.getItem('sysuiToken');
20+
21+
export default class SecureApi extends Api {
22+
constructor (url, updateTokenCallback) {
23+
super(new Api.Transport.Ws(url, sysuiToken));
24+
25+
this._isConnecting = true;
26+
this._connectState = 0;
27+
this._needsToken = false;
28+
this._updateTokenCallback = (token) => updateTokenCallback(token);
29+
30+
this._followConnection();
31+
}
32+
33+
_followConnection = () => {
34+
const nextTick = () => setTimeout(this._followConnection, 250);
35+
const setToken = () => {
36+
window.localStorage.setItem('sysuiToken', this._transport.token);
37+
this._updateTokenCallback(this._transport.token);
38+
};
39+
const setManual = () => {
40+
this._connectedState = 100;
41+
this._needsToken = true;
42+
this._isConnecting = false;
43+
};
44+
const lastError = this._transport.lastError;
45+
const isConnected = this._transport.isConnected;
46+
47+
switch (this._connectState) {
48+
// token = <passed via constructor>
49+
case 0:
50+
if (isConnected) {
51+
this._isConnecting = false;
52+
return setToken();
53+
} else if (lastError) {
54+
this.updateToken('initial', 1);
55+
}
56+
break;
57+
58+
// token = 'initial'
59+
case 1:
60+
if (isConnected) {
61+
this._connectState = 2;
62+
this.personal
63+
.generateAuthorizationToken()
64+
.then((token) => {
65+
this.updateToken(token, 2);
66+
})
67+
.catch((error) => {
68+
console.error('_followConnection', error);
69+
setManual();
70+
});
71+
return;
72+
} else if (lastError) {
73+
return setManual();
74+
}
75+
break;
76+
77+
// token = <personal_generateAuthorizationToken>
78+
case 2:
79+
if (isConnected) {
80+
this._isConnecting = false;
81+
return setToken();
82+
} else if (lastError) {
83+
return setManual();
84+
}
85+
break;
86+
}
87+
88+
nextTick();
89+
}
90+
91+
updateToken (token, connectedState = 0) {
92+
this._connectState = connectedState;
93+
this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, ''));
94+
this._followConnection();
95+
}
96+
97+
get isConnecting () {
98+
return this._isConnecting;
99+
}
100+
101+
get isConnected () {
102+
return this._transport.isConnected;
103+
}
104+
105+
get needsToken () {
106+
return this._needsToken;
107+
}
108+
109+
get secureToken () {
110+
return this._transport.token;
111+
}
112+
}

js/src/ui/Container/container.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ export default class Container extends Component {
2323
static propTypes = {
2424
children: PropTypes.node,
2525
className: PropTypes.string,
26-
light: PropTypes.bool
26+
light: PropTypes.bool,
27+
style: PropTypes.object
2728
}
2829

2930
render () {
30-
const { children, className, light } = this.props;
31+
const { children, className, light, style } = this.props;
3132
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
3233

3334
return (
34-
<div className={ classes }>
35+
<div className={ classes } style={ style }>
3536
<Card className={ styles.padded }>
3637
{ children }
3738
</Card>

js/src/ui/Modal/modal.css

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
background: rgba(0, 0, 0, 0.5) !important;
4242
}
4343

44+
.content div {
45+
transition: none !important;
46+
}
47+
4448
.title {
4549
padding: 1em;
4650
margin-bottom: 0;
@@ -62,4 +66,5 @@
6266

6367
.overlay {
6468
background: rgba(255, 255, 255, 0.5) !important;
69+
transition: none !important;
6570
}

js/src/ui/Modal/modal.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,12 @@ class Modal extends Component {
7777
modal
7878
open={ visible }
7979
overlayClassName={ styles.overlay }
80+
overlayStyle={ { transition: 'none' } }
8081
repositionOnUpdate={ false }
8182
style={ DIALOG_STYLE }
8283
title={ header }
8384
titleStyle={ TITLE_STYLE }>
84-
<Container light>
85+
<Container light style={ { transition: 'none' } }>
8586
{ children }
8687
</Container>
8788
</Dialog>

0 commit comments

Comments
 (0)