-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Work around WS in UI #3587
Work around WS in UI #3587
Changes from 5 commits
a22469a
f800dd1
abcccbe
ab212fc
bb6fe16
0c3d87f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,21 +29,33 @@ export default class Ws extends JsonRpcBase { | |
this._token = token; | ||
this._messages = {}; | ||
|
||
this._connecting = true; | ||
this._connecting = false; | ||
this._connected = false; | ||
this._lastError = null; | ||
this._autoConnect = false; | ||
this._autoConnect = true; | ||
this._retries = 0; | ||
this._reconnectTimeoutId = null; | ||
|
||
this._connect(); | ||
} | ||
|
||
updateToken (token) { | ||
this._token = token; | ||
this._autoConnect = false; | ||
this._autoConnect = true; | ||
|
||
this._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 sha3 = keccak_256(`${this._token}:${time}`); | ||
const hash = `${sha3}_${time}`; | ||
|
@@ -53,6 +65,7 @@ export default class Ws extends JsonRpcBase { | |
this._ws.onopen = null; | ||
this._ws.onclose = null; | ||
this._ws.onmessage = null; | ||
this._ws.close(); | ||
this._ws = null; | ||
} | ||
|
||
|
@@ -65,32 +78,96 @@ export default class Ws extends JsonRpcBase { | |
this._ws.onopen = this._onOpen; | ||
this._ws.onclose = this._onClose; | ||
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 () { | ||
return fetch('/', { method: 'HEAD' }) | ||
.then((r) => { | ||
return r.status === 200; | ||
}, () => { | ||
return false; | ||
}) | ||
.catch(() => false); | ||
} | ||
|
||
_onOpen = (event) => { | ||
console.log('ws:onOpen', event); | ||
this._connected = true; | ||
this._connecting = false; | ||
this._autoConnect = true; | ||
this._retries = 0; | ||
|
||
Object.keys(this._messages) | ||
.filter((id) => this._messages[id].queued) | ||
.forEach(this._send); | ||
} | ||
|
||
_onClose = (event) => { | ||
console.log('ws:onClose', event); | ||
this._connected = false; | ||
this._connecting = false; | ||
|
||
if (this._autoConnect) { | ||
setTimeout(() => this._connect(), 500); | ||
} | ||
this._checkNodeUp() | ||
.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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dangerous, but better than what we have atm. (Apart from the up check that doesn't belong) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the thing is that there is no way to know why a WS connection has been closed. It's like this in the specs. I will just remove this and put the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeap. That is the issue - if we had a 403 like gets displayed in the console, it would be so much easier. |
||
} | ||
|
||
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) => { | ||
console.error('ws:onError', event); | ||
this._lastError = event; | ||
// Only print error if the WS is connected | ||
// ie. don't print if error == closed | ||
window.setTimeout(() => { | ||
if (this._connected) { | ||
console.error('ws:onError', event); | ||
this._lastError = event; | ||
} | ||
}, 50); | ||
} | ||
|
||
_onMessage = (event) => { | ||
|
@@ -127,11 +204,16 @@ export default class Ws extends JsonRpcBase { | |
_send = (id) => { | ||
const message = this._messages[id]; | ||
|
||
message.queued = !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) { | ||
|
@@ -159,4 +241,27 @@ export default class Ws extends JsonRpcBase { | |
get 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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't really belong in the WS transport layer, the purpose it to manage the connection, not rely on how the rest is stitched together. Here we are now assuming that there is always a capable /api/ping available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better now with the HEAD /, however we are assuming that the content is served from the same location as the WS now in the WS interface. Very specific...