Implemented reconnect logic

This commit is contained in:
Jonatan Nilsson 2022-05-12 07:20:16 +00:00
parent 37a3f85037
commit 8e5788b6e5
3 changed files with 170 additions and 22 deletions

View file

@ -54,6 +54,7 @@ body, h1, h2, h3, h4, h5, h6, p, ol, ul {
[hidden] { display: none !important; } [hidden] { display: none !important; }
/* ---------------------------- Header ---------------------------- */
header { header {
border-bottom: 1px solid black; border-bottom: 1px solid black;
background: var(--header-bg); background: var(--header-bg);
@ -78,10 +79,38 @@ header span {
font-size: 1.2rem; font-size: 1.2rem;
} }
main { header .overlay {
flex: 2 1 auto; background: var(--header-bg);
color: var(--foreground);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
z-index: 10;
} }
header .overlay h2 {
font-size: 2rem;
}
header .overlay .bar {
display: flex;
margin-top: 1rem;
}
header .overlay .bar .led {
margin: 0 0.5rem;
}
/* ---------------------------- Nav ---------------------------- */
nav { nav {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -133,6 +162,15 @@ nav::after {
border-top: 1px solid var(--nav-above-border-color); border-top: 1px solid var(--nav-above-border-color);
} }
/* ---------------------------- Main ---------------------------- */
main {
flex: 2 1 auto;
}
/* ---------------------------- Extra ---------------------------- */
/* Taken from https://github.com/aus/led.css */ /* Taken from https://github.com/aus/led.css */
.led { .led {
margin-top: 1px; margin-top: 1px;

View file

@ -1,10 +1,37 @@
class Animator {
constructor() {
this.running = null
this.runningInterval = null
}
begin(time) {
if (this.runningInterval === time && this.running) return
if (this.running) {
clearInterval(this.running)
}
this.runningInterval = time
this.running = setInterval(function() {
m.redraw()
}, time)
}
stop() {
clearInterval(this.running)
this.running = null
}
}
const animator = new Animator()
class WSClient class WSClient
{ {
constructor(url = 'ws://localhost:4040') { constructor(url = 'ws://localhost:4040') {
this.url = url this.url = url
this.socket = null this.socket = null
this.connected = false this.connected = false
this.connecting = false this.connecting = null
this.connectingAt = null
this.reconnecting = null
this.reconnectingAt = null this.reconnectingAt = null
this.retryDuration = 1000 this.retryDuration = 1000
this.open() this.open()
@ -12,22 +39,31 @@ class WSClient
open() { open() {
if (this.connected || this.connecting) return if (this.connected || this.connecting) return
this.connecting = true clearTimeout(this.reconnecting)
this.reconnecting = this.reconnectingAt = null
this.connectingAt = new Date()
this.socket = new WebSocket(this.url) this.socket = new WebSocket(this.url)
this.socket.addEventListener('open', this.onopen.bind(this)) this.socket.addEventListener('open', this.onopen.bind(this))
this.socket.addEventListener('message', this.onmessage.bind(this)) this.socket.addEventListener('message', this.onmessage.bind(this))
this.socket.addEventListener('error', this.onerror.bind(this)) this.socket.addEventListener('error', this.onerror.bind(this))
this.socket.addEventListener('close', this.onclose.bind(this)) this.socket.addEventListener('close', this.onclose.bind(this))
animator.begin(1000)
this.connecting = setTimeout(() => {
this.socket.close()
}, 5000)
m.redraw() m.redraw()
} }
onopen() { onopen() {
animator.stop()
this.retryDuration = 1000 this.retryDuration = 1000
this.connecting = false clearTimeout(this.connecting)
this.connecting = null
this.connectingAt = null
this.reconnectingAt = null this.reconnectingAt = null
this.connected = true this.connected = true
console.log('Sending hello server')
this.send({ msg: 'Hello Server!' }) this.send({ msg: 'Hello Server!' })
m.redraw() m.redraw()
} }
@ -37,8 +73,7 @@ class WSClient
this.socket.send(JSON.stringify(payload)) this.socket.send(JSON.stringify(payload))
} }
onerror(err) { onerror() {
console.error(err)
this.reconnect() this.reconnect()
} }
@ -48,14 +83,17 @@ class WSClient
reconnect() { reconnect() {
if (this.reconnectingAt) return if (this.reconnectingAt) return
animator.begin(100)
this.socket = null this.socket = null
this.connected = false this.connected = false
this.connecting = false clearTimeout(this.connecting)
this.connecting = null
this.connectingAt = null
this.reconnectingAt = new Date(new Date().getTime() + this.retryDuration) this.reconnectingAt = new Date(new Date().getTime() + this.retryDuration)
setTimeout(() => { clearTimeout(this.reconnecting)
this.reconnectingAt = null this.reconnecting = setTimeout(() => {
this.retryDuration *= 1.5 this.retryDuration = Math.min(this.retryDuration * 1.5, 60000)
this.open() this.open()
}, this.retryDuration) }, this.retryDuration)
m.redraw() m.redraw()
@ -73,20 +111,93 @@ const client = new WSClient()
class Header { class Header {
view(vnode) { view(vnode) {
console.log(client.connecting)
return [ return [
m('h1', 'Fíladelfíu streymi'), m('h1', 'Fíladelfíu streymi'),
m('div.status.green', { hidden: true }, 'No errors'), m('div.status.green', { hidden: true }, 'No errors'),
m('div.filler'), m('div.filler'),
m('span', 'Live:'),
m('div.led'),
client.connected client.connected
? null ? null
: m('div.overlay', [ : m('div.overlay', [
m('h2', client.connecting ? 'Connecting to server...' : 'Connection lost'), m('h2', client.connecting ? 'Connecting to server...' : 'Failed to connect, retrying in...'),
m('') client.connecting
]), ? this.viewConnectingBar()
: this.viewWaitingCircle(),
]),
m('span', 'Live:'),
m('div.led'),
] ]
} }
viewConnectingBar() {
let diff = Math.round((new Date() - client.connectingAt) / 1000)
let out = []
for (let i = 0; i < 5; i++) {
out.push(m('div.led', { class: diff >= i ? 'led-blue' : '' }))
}
return m('div.bar', out)
}
viewWaitingCircle() {
var diff = Math.min((new Date() - client.reconnectingAt + 200), client.retryDuration)
var percentage = Math.abs(Math.min(diff / client.retryDuration, 0))
return m('svg', {
onclick: function() { client.open() },
style: 'width: 150px; transform: rotate(-90deg);',
viewBox: '0 0 150 150',
preserveAspectRatio: 'xMidYMid meet',
}, [
m('filter', { id: 'darker' }, [
m('feFlood', { 'flood-color': '#000000', 'flood-opacity': '1', in: 'SourceGraphic' }),
m('feComposite', { operator: 'in', in2: 'SourceGraphic' }),
m('feGaussianBlur', { stdDeviation: '2' }), // approx size, with higher values meaning less intensity
m('feComponentTransfer', { result: 'glowd' }, m('feFuncA', { type: 'linear', slope: 1, intercept: 0 })),
m('feMerge', [
m('feMergeNode', { in: 'SourceGraphic' }),
m('feMergeNode', { in: 'glowd' }),
]),
]),
m('filter', { id: 'glower' }, [
m('feFlood', { 'flood-color': '#0066ff', 'flood-opacity': '1', in: 'SourceGraphic' }),
m('feComposite', { operator: 'in', in2: 'SourceGraphic' }),
m('feGaussianBlur', { stdDeviation: '10' }), // approx size, with higher values meaning less intensity
m('feComponentTransfer', { result: 'glow1' }, m('feFuncA', { type: 'linear', slope: 1, intercept: 0 })),
m('feFlood', { 'flood-color': '#0066ff', 'flood-opacity': '0.5', in: 'SourceGraphic' }),
m('feComposite', { operator: 'in', in2: 'SourceGraphic' }),
m('feGaussianBlur', { stdDeviation: '1' }), // approx size, with higher values meaning less intensity
m('feComponentTransfer', { result: 'glow2' }, m('feFuncA', { type: 'linear', slope: 2, intercept: 0 })),
m('feMerge', [
m('feMergeNode', { in: 'SourceGraphic' }),
m('feMergeNode', { in: 'glow2' }),
m('feMergeNode', { in: 'glow1' }),
]),
]),
m('g', { width: '150px', height: '150px', filter: 'url(#darker)',}, [
m('rect', { width: '100%', height: '100%', fill: 'transparent' }),
m('circle', {
cx: '50%',
cy: '50%',
fill: 'transparent',
stroke: '#000000',
style: `stroke-width: 12%; transform-origin: 50% 50% 0px; transition: stroke-dashoffset 100ms;`,
r: 45,
}),
]),
m('g', { width: '150px', height: '150px', filter: 'url(#glower)',}, [
m('rect', { width: '100%', height: '100%', fill: 'transparent' }),
m('circle', {
cx: '50%',
cy: '50%',
fill: 'transparent',
stroke: '#000000',
style: `stroke-dashoffset: ${Math.round(280 * percentage)}px; stroke-dasharray: 280px; stroke-width: 10%; transform-origin: 50% 50% 0px; transition: stroke-dashoffset 100ms;`,
r: 45,
})
]),
])
}
} }
class Nav { class Nav {
@ -94,7 +205,7 @@ class Nav {
let path = m.route.get() let path = m.route.get()
return [ return [
m(m.route.Link, { m(m.route.Link, {
class: path === '/' ? 'active' : '', class: path === '/' || path === '' ? 'active' : '',
href: '/', href: '/',
}, [ }, [
m('svg', { m('svg', {

View file

@ -42,7 +42,9 @@ export function run(http, port, core) {
flaska.get('/::file', serve.serve.bind(serve)) flaska.get('/::file', serve.serve.bind(serve))
return flaska.listenAsync(port).then(function() { return flaska.listenAsync(port).then(function() {
/*const wss = new WebSocketServer({ server: flaska.server }) core.log.info('Server is listening on port ' + port)
const wss = new WebSocketServer({ server: flaska.server })
wss.on('connection', function(ws) { wss.on('connection', function(ws) {
console.log('new connection') console.log('new connection')
@ -88,8 +90,5 @@ export function run(http, port, core) {
wss.on('close', function() { wss.on('close', function() {
clearInterval(interval) clearInterval(interval)
}) })
*/
core.log.info('Server is listening on port ' + port)
}) })
} }