WebSocket Improvments #3544
This commit is contained in:
		
							parent
							
								
									a22469ade5
								
							
						
					
					
						commit
						f800dd1eee
					
				| @ -29,21 +29,33 @@ export default class Ws extends JsonRpcBase { | |||||||
|     this._token = token; |     this._token = token; | ||||||
|     this._messages = {}; |     this._messages = {}; | ||||||
| 
 | 
 | ||||||
|     this._connecting = true; |     this._connecting = false; | ||||||
|  |     this._connected = false; | ||||||
|     this._lastError = null; |     this._lastError = null; | ||||||
|     this._autoConnect = false; |     this._autoConnect = true; | ||||||
|  |     this._retries = 0; | ||||||
|  |     this._reconnectTimeoutId = null; | ||||||
| 
 | 
 | ||||||
|     this._connect(); |     this._connect(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updateToken (token) { |   updateToken (token) { | ||||||
|     this._token = token; |     this._token = token; | ||||||
|     this._autoConnect = false; |     this._autoConnect = true; | ||||||
| 
 | 
 | ||||||
|     this._connect(); |     this._connect(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _connect () { |   _connect () { | ||||||
|  |     if (this._connecting) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (this._reconnectTimeoutId) { | ||||||
|  |       window.clearTimeout(this._reconnectTimeoutId); | ||||||
|  |       this._reconnectTimeoutId = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const time = parseInt(new Date().getTime() / 1000, 10); |     const time = parseInt(new Date().getTime() / 1000, 10); | ||||||
|     const sha3 = keccak_256(`${this._token}:${time}`); |     const sha3 = keccak_256(`${this._token}:${time}`); | ||||||
|     const hash = `${sha3}_${time}`; |     const hash = `${sha3}_${time}`; | ||||||
| @ -53,6 +65,7 @@ export default class Ws extends JsonRpcBase { | |||||||
|       this._ws.onopen = null; |       this._ws.onopen = null; | ||||||
|       this._ws.onclose = null; |       this._ws.onclose = null; | ||||||
|       this._ws.onmessage = null; |       this._ws.onmessage = null; | ||||||
|  |       this._ws.close(); | ||||||
|       this._ws = null; |       this._ws = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -65,6 +78,42 @@ export default class Ws extends JsonRpcBase { | |||||||
|     this._ws.onopen = this._onOpen; |     this._ws.onopen = this._onOpen; | ||||||
|     this._ws.onclose = this._onClose; |     this._ws.onclose = this._onClose; | ||||||
|     this._ws.onmessage = this._onMessage; |     this._ws.onmessage = this._onMessage; | ||||||
|  | 
 | ||||||
|  |     // Get counts in dev mode
 | ||||||
|  |     if (process.env.NODE_ENV === 'development') { | ||||||
|  |       this._count = 0; | ||||||
|  |       this._lastCount = { | ||||||
|  |         timestamp: Date.now(), | ||||||
|  |         count: 0 | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|  |       window.setInterval(() => { | ||||||
|  |         const n = this._count - this._lastCount.count; | ||||||
|  |         const t = (Date.now() - this._lastCount.timestamp) / 1000; | ||||||
|  |         const s = Math.round(1000 * n / t) / 1000; | ||||||
|  | 
 | ||||||
|  |         if (this._debug) { | ||||||
|  |           console.log('::parityWS', `speed: ${s} req/s`, `count: ${this._count}`); | ||||||
|  |         } | ||||||
|  |       }, 5000); | ||||||
|  | 
 | ||||||
|  |       window._parityWS = this; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _checkNodeUp () { | ||||||
|  |     const url = process.env.PARITY_URL || window.location.host; | ||||||
|  | 
 | ||||||
|  |     return fetch( | ||||||
|  |         `http://${url}/api/ping`, | ||||||
|  |         { method: 'HEAD' } | ||||||
|  |       ) | ||||||
|  |       .then((r) => { | ||||||
|  |         return r.status === 200; | ||||||
|  |       }, () => { | ||||||
|  |         return false; | ||||||
|  |       }) | ||||||
|  |       .catch(() => false); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onOpen = (event) => { |   _onOpen = (event) => { | ||||||
| @ -72,6 +121,7 @@ export default class Ws extends JsonRpcBase { | |||||||
|     this._connected = true; |     this._connected = true; | ||||||
|     this._connecting = false; |     this._connecting = false; | ||||||
|     this._autoConnect = true; |     this._autoConnect = true; | ||||||
|  |     this._retries = 0; | ||||||
| 
 | 
 | ||||||
|     Object.keys(this._messages) |     Object.keys(this._messages) | ||||||
|       .filter((id) => this._messages[id].queued) |       .filter((id) => this._messages[id].queued) | ||||||
| @ -79,18 +129,50 @@ export default class Ws extends JsonRpcBase { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onClose = (event) => { |   _onClose = (event) => { | ||||||
|     console.log('ws:onClose', event); |  | ||||||
|     this._connected = false; |     this._connected = false; | ||||||
|     this._connecting = false; |     this._connecting = false; | ||||||
| 
 | 
 | ||||||
|     if (this._autoConnect) { |     this._checkNodeUp() | ||||||
|       setTimeout(() => this._connect(), 500); |       .then((up) => { | ||||||
|     } |         // If the connection has been closed and the node
 | ||||||
|  |         // is up, it means we have a wrong token
 | ||||||
|  |         // Event code 1006 for WS means there is an error
 | ||||||
|  |         // (not just closed by server)
 | ||||||
|  |         if (up && event.code === 1006) { | ||||||
|  |           event.status = 403; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this._lastError = event; | ||||||
|  | 
 | ||||||
|  |         if (this._autoConnect) { | ||||||
|  |           const timeout = this.retryTimeout; | ||||||
|  | 
 | ||||||
|  |           const time = timeout < 1000 | ||||||
|  |             ? Math.round(timeout) + 'ms' | ||||||
|  |             : (Math.round(timeout / 10) / 100) + 's'; | ||||||
|  | 
 | ||||||
|  |           console.log('ws:onClose', `trying again in ${time}...`); | ||||||
|  | 
 | ||||||
|  |           this._reconnectTimeoutId = setTimeout(() => { | ||||||
|  |             this._connect(); | ||||||
|  |           }, timeout); | ||||||
|  | 
 | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         console.log('ws:onClose', event); | ||||||
|  |       }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onError = (event) => { |   _onError = (event) => { | ||||||
|     console.error('ws:onError', event); |     // Only print error if the WS is connected
 | ||||||
|     this._lastError = event; |     // ie. don't print if error == closed
 | ||||||
|  |     window.setTimeout(() => { | ||||||
|  |       if (this._connected) { | ||||||
|  |         console.error('ws:onError', event); | ||||||
|  |         this._lastError = event; | ||||||
|  |       } | ||||||
|  |     }, 50); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onMessage = (event) => { |   _onMessage = (event) => { | ||||||
| @ -127,11 +209,16 @@ export default class Ws extends JsonRpcBase { | |||||||
|   _send = (id) => { |   _send = (id) => { | ||||||
|     const message = this._messages[id]; |     const message = this._messages[id]; | ||||||
| 
 | 
 | ||||||
|     message.queued = !this._connected; |  | ||||||
| 
 |  | ||||||
|     if (this._connected) { |     if (this._connected) { | ||||||
|       this._ws.send(message.json); |       if (process.env.NODE_ENV === 'development') { | ||||||
|  |         this._count++; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return this._ws.send(message.json); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     message.queued = !this._connected; | ||||||
|  |     message.timestamp = Date.now(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   execute (method, ...params) { |   execute (method, ...params) { | ||||||
| @ -159,4 +246,27 @@ export default class Ws extends JsonRpcBase { | |||||||
|   get lastError () { |   get lastError () { | ||||||
|     return this._lastError; |     return this._lastError; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Exponential Timeout for Retries | ||||||
|  |    * | ||||||
|  |    * @see http://dthain.blogspot.de/2009/02/exponential-backoff-in-distributed.html
 | ||||||
|  |    */ | ||||||
|  |   get retryTimeout () { | ||||||
|  |     // R between 1 and 2
 | ||||||
|  |     const R = Math.random() + 1; | ||||||
|  |     // Initial timeout (100ms)
 | ||||||
|  |     const T = 100; | ||||||
|  |     // Exponential Factor
 | ||||||
|  |     const F = 2; | ||||||
|  |     // Max timeout (4s)
 | ||||||
|  |     const M = 4000; | ||||||
|  |     // Current number of retries
 | ||||||
|  |     const N = this._retries; | ||||||
|  | 
 | ||||||
|  |     // Increase retries number
 | ||||||
|  |     this._retries++; | ||||||
|  | 
 | ||||||
|  |     return Math.min(R * T * Math.pow(F, N), M); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,25 +21,39 @@ export default class Registry { | |||||||
|     this._api = api; |     this._api = api; | ||||||
|     this._contracts = []; |     this._contracts = []; | ||||||
|     this._instance = null; |     this._instance = null; | ||||||
|  |     this._fetching = false; | ||||||
|  |     this._queue = []; | ||||||
| 
 | 
 | ||||||
|     this.getInstance(); |     this.getInstance(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getInstance () { |   getInstance () { | ||||||
|     return new Promise((resolve, reject) => { |     if (this._instance) { | ||||||
|       if (this._instance) { |       return Promise.resolve(this._instance); | ||||||
|         resolve(this._instance); |     } | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       this._api.parity |     if (this._fetching) { | ||||||
|         .registryAddress() |       return new Promise((resolve) => { | ||||||
|         .then((address) => { |         this._queue.push({ resolve }); | ||||||
|           this._instance = this._api.newContract(abis.registry, address).instance; |       }); | ||||||
|           resolve(this._instance); |     } | ||||||
|         }) | 
 | ||||||
|         .catch(reject); |     this._fetching = true; | ||||||
|     }); | 
 | ||||||
|  |     return this._api.parity | ||||||
|  |       .registryAddress() | ||||||
|  |       .then((address) => { | ||||||
|  |         this._fetching = false; | ||||||
|  |         this._instance = this._api.newContract(abis.registry, address).instance; | ||||||
|  | 
 | ||||||
|  |         this._queue.forEach((queued) => { | ||||||
|  |           queued.resolve(this._instance); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this._queue = []; | ||||||
|  | 
 | ||||||
|  |         return this._instance; | ||||||
|  |       }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getContract (_name) { |   getContract (_name) { | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ | |||||||
| import { getBalances, getTokens } from './balancesActions'; | import { getBalances, getTokens } from './balancesActions'; | ||||||
| import { setAddressImage } from './imagesActions'; | import { setAddressImage } from './imagesActions'; | ||||||
| 
 | 
 | ||||||
|  | import Contracts from '../../contracts'; | ||||||
| import * as abis from '../../contracts/abi'; | import * as abis from '../../contracts/abi'; | ||||||
| 
 | 
 | ||||||
| import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; | import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; | ||||||
| @ -84,15 +85,9 @@ export default class Balances { | |||||||
|       return Promise.resolve(this._tokenreg); |       return Promise.resolve(this._tokenreg); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return this._api.parity |     return Contracts.get().tokenReg | ||||||
|       .registryAddress() |       .getContract() | ||||||
|       .then((registryAddress) => { |       .then((tokenreg) => { | ||||||
|         const registry = this._api.newContract(abis.registry, registryAddress); |  | ||||||
| 
 |  | ||||||
|         return registry.instance.getAddress.call({}, [this._api.util.sha3('tokenreg'), 'A']); |  | ||||||
|       }) |  | ||||||
|       .then((tokenregAddress) => { |  | ||||||
|         const tokenreg = this._api.newContract(abis.tokenreg, tokenregAddress); |  | ||||||
|         this._tokenreg = tokenreg; |         this._tokenreg = tokenreg; | ||||||
|         this.attachToTokens(); |         this.attachToTokens(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -71,8 +71,8 @@ export default class Status { | |||||||
|    * @see src/views/Connection/connection.js |    * @see src/views/Connection/connection.js | ||||||
|    */ |    */ | ||||||
|   _shouldPing = () => { |   _shouldPing = () => { | ||||||
|     const { isConnected, isConnecting } = this._apiStatus; |     const { isConnected } = this._apiStatus; | ||||||
|     return isConnecting || !isConnected; |     return !isConnected; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _stopPollPing = () => { |   _stopPollPing = () => { | ||||||
| @ -102,7 +102,7 @@ export default class Status { | |||||||
|       }, timeout); |       }, timeout); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     fetch('/', { method: 'HEAD' }) |     fetch('/api/ping', { method: 'HEAD' }) | ||||||
|       .then((response) => dispatch(!!response.ok)) |       .then((response) => dispatch(!!response.ok)) | ||||||
|       .catch(() => dispatch(false)); |       .catch(() => dispatch(false)); | ||||||
|   } |   } | ||||||
| @ -119,7 +119,7 @@ export default class Status { | |||||||
| 
 | 
 | ||||||
|   _pollStatus = () => { |   _pollStatus = () => { | ||||||
|     const nextTimeout = (timeout = 1000) => { |     const nextTimeout = (timeout = 1000) => { | ||||||
|       setTimeout(this._pollStatus, timeout); |       setTimeout(() => this._pollStatus(), timeout); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     const { isConnected, isConnecting, needsToken, secureToken } = this._api; |     const { isConnected, isConnecting, needsToken, secureToken } = this._api; | ||||||
| @ -134,7 +134,8 @@ export default class Status { | |||||||
|     const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected; |     const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected; | ||||||
| 
 | 
 | ||||||
|     if (gotReconnected) { |     if (gotReconnected) { | ||||||
|       this._pollLongStatus(); |       this._pollLongStatus(true); | ||||||
|  |       this._store.dispatch(statusCollection({ isPingable: true })); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!isEqual(apiStatus, this._apiStatus)) { |     if (!isEqual(apiStatus, this._apiStatus)) { | ||||||
| @ -175,13 +176,12 @@ export default class Status { | |||||||
|           this._store.dispatch(statusCollection(status)); |           this._store.dispatch(statusCollection(status)); | ||||||
|           this._status = status; |           this._status = status; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         nextTimeout(); |  | ||||||
|       }) |       }) | ||||||
|       .catch((error) => { |       .catch((error) => { | ||||||
|         console.error('_pollStatus', error); |         console.error('_pollStatus', error); | ||||||
|         nextTimeout(250); |  | ||||||
|       }); |       }); | ||||||
|  | 
 | ||||||
|  |     nextTimeout(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
| @ -223,7 +223,11 @@ export default class Status { | |||||||
|    * fetched every 30s just in case, and whenever |    * fetched every 30s just in case, and whenever | ||||||
|    * the client got reconnected. |    * the client got reconnected. | ||||||
|    */ |    */ | ||||||
|   _pollLongStatus = () => { |   _pollLongStatus = (newConnection = false) => { | ||||||
|  |     if (!this._api.isConnected) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const nextTimeout = (timeout = 30000) => { |     const nextTimeout = (timeout = 30000) => { | ||||||
|       if (this._longStatusTimeoutId) { |       if (this._longStatusTimeoutId) { | ||||||
|         clearTimeout(this._longStatusTimeoutId); |         clearTimeout(this._longStatusTimeoutId); | ||||||
| @ -242,7 +246,7 @@ export default class Status { | |||||||
|         this._api.parity.netChain(), |         this._api.parity.netChain(), | ||||||
|         this._api.parity.netPort(), |         this._api.parity.netPort(), | ||||||
|         this._api.parity.rpcSettings(), |         this._api.parity.rpcSettings(), | ||||||
|         this._api.parity.enode() |         newConnection ? Promise.resolve(null) : this._api.parity.enode() | ||||||
|       ]) |       ]) | ||||||
|       .then(([ |       .then(([ | ||||||
|         clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode |         clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode | ||||||
| @ -255,21 +259,23 @@ export default class Status { | |||||||
|           netChain, |           netChain, | ||||||
|           netPort, |           netPort, | ||||||
|           rpcSettings, |           rpcSettings, | ||||||
|           enode, |  | ||||||
|           isTest |           isTest | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  |         if (enode) { | ||||||
|  |           longStatus.enode = enode; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (!isEqual(longStatus, this._longStatus)) { |         if (!isEqual(longStatus, this._longStatus)) { | ||||||
|           this._store.dispatch(statusCollection(longStatus)); |           this._store.dispatch(statusCollection(longStatus)); | ||||||
|           this._longStatus = longStatus; |           this._longStatus = longStatus; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         nextTimeout(); |  | ||||||
|       }) |       }) | ||||||
|       .catch((error) => { |       .catch((error) => { | ||||||
|         console.error('_pollLongStatus', error); |         console.error('_pollLongStatus', error); | ||||||
|         nextTimeout(250); |  | ||||||
|       }); |       }); | ||||||
|  | 
 | ||||||
|  |     nextTimeout(newConnection ? 5000 : 30000); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _pollLogs = () => { |   _pollLogs = () => { | ||||||
|  | |||||||
| @ -25,12 +25,13 @@ export default class SecureApi extends Api { | |||||||
|     this._isConnecting = true; |     this._isConnecting = true; | ||||||
|     this._connectState = sysuiToken === 'initial' ? 1 : 0; |     this._connectState = sysuiToken === 'initial' ? 1 : 0; | ||||||
|     this._needsToken = false; |     this._needsToken = false; | ||||||
|     this._nextToken = nextToken; |  | ||||||
|     this._dappsPort = 8080; |     this._dappsPort = 8080; | ||||||
|     this._dappsInterface = null; |     this._dappsInterface = null; | ||||||
|     this._signerPort = 8180; |     this._signerPort = 8180; | ||||||
|  |     this._followConnectionTimeoutId = null; | ||||||
| 
 | 
 | ||||||
|     console.log('SecureApi:constructor', sysuiToken); |     // Try tokens from hash, then from localstorage
 | ||||||
|  |     this._tokensToTry = [ nextToken, sysuiToken ].filter((t) => t && t.length); | ||||||
| 
 | 
 | ||||||
|     this._followConnection(); |     this._followConnection(); | ||||||
|   } |   } | ||||||
| @ -42,13 +43,19 @@ export default class SecureApi extends Api { | |||||||
| 
 | 
 | ||||||
|   _followConnection = () => { |   _followConnection = () => { | ||||||
|     const nextTick = () => { |     const nextTick = () => { | ||||||
|       setTimeout(() => this._followConnection(), 250); |       if (this._followConnectionTimeoutId) { | ||||||
|  |         clearTimeout(this._followConnectionTimeoutId); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this._followConnectionTimeoutId = setTimeout(() => this._followConnection(), 250); | ||||||
|     }; |     }; | ||||||
|  | 
 | ||||||
|     const setManual = () => { |     const setManual = () => { | ||||||
|       this._connectState = 100; |       this._connectState = 100; | ||||||
|       this._needsToken = true; |       this._needsToken = true; | ||||||
|       this._isConnecting = false; |       this._isConnecting = false; | ||||||
|     }; |     }; | ||||||
|  | 
 | ||||||
|     const lastError = this._transport.lastError; |     const lastError = this._transport.lastError; | ||||||
|     const isConnected = this._transport.isConnected; |     const isConnected = this._transport.isConnected; | ||||||
| 
 | 
 | ||||||
| @ -58,11 +65,17 @@ export default class SecureApi extends Api { | |||||||
|         if (isConnected) { |         if (isConnected) { | ||||||
|           return this.connectSuccess(); |           return this.connectSuccess(); | ||||||
|         } else if (lastError) { |         } else if (lastError) { | ||||||
|           const nextToken = this._nextToken || 'initial'; |           const nextToken = this._tokensToTry[0] || 'initial'; | ||||||
|           const nextState = this._nextToken ? 0 : 1; |           const nextState = nextToken !== 'initial' ? 0 : 1; | ||||||
| 
 | 
 | ||||||
|           this._nextToken = null; |           // If previous token was wrong, delete it
 | ||||||
|           this.updateToken(nextToken, nextState); |           if (lastError.status === 403) { | ||||||
|  |             this._tokensToTry = this._tokensToTry.slice(1); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           if (nextToken !== this._transport.token) { | ||||||
|  |             this.updateToken(nextToken, nextState); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user