diff --git a/public/Communication.js b/public/Communication.js index 77baff8..3e6be1a 100644 --- a/public/Communication.js +++ b/public/Communication.js @@ -1,35 +1,65 @@ "use strict" -import WsClient from './WsClient.js' +import { logger } from '../common/Util.js' import Protocol from './../common/Protocol.js' -/** @type WsClient */ -let conn +const log = logger('Communication.js') + +const CODE_GOING_AWAY = 1001 +const CODE_CUSTOM_DISCONNECT = 4000 + +const CONN_STATE_NOT_CONNECTED = 0 // not connected yet +const CONN_STATE_DISCONNECTED = 1 // not connected, but was connected before +const CONN_STATE_CONNECTED = 2 // connected +const CONN_STATE_CONNECTING = 3 // connecting +const CONN_STATE_CLOSED = 4 // not connected (closed on purpose) + +/** @type WebSocket */ +let ws let changesCallback = () => {} -let connectionLostCallback = () => {} +let connectionStateChangeCallback = () => {} // TODO: change these to something like on(EVT, cb) function onServerChange(callback) { changesCallback = callback } -function onConnectionLost(callback) { - connectionLostCallback = callback +function onConnectionStateChange(callback) { + connectionStateChangeCallback = callback } -function send(message) { - conn.send(JSON.stringify(message)) +let connectionState = CONN_STATE_NOT_CONNECTED +const setConnectionState = (v) => { + if (connectionState !== v) { + connectionState = v + connectionStateChangeCallback(v) + } } +function send(message) { + if (connectionState === CONN_STATE_CONNECTED) { + try { + ws.send(JSON.stringify(message)) + } catch (e) { + log.info('unable to send message.. maybe because ws is invalid?') + } + } +} + let clientSeq let events function connect(address, gameId, clientId) { clientSeq = 0 events = {} - conn = new WsClient(address, clientId + '|' + gameId) + setConnectionState(CONN_STATE_CONNECTING) return new Promise(resolve => { - conn.connect() - conn.onSocket('message', async ({ data }) => { - const msg = JSON.parse(data) + ws = new WebSocket(address, clientId + '|' + gameId) + ws.onopen = (e) => { + setConnectionState(CONN_STATE_CONNECTED) + connectionStateChangeCallback() + send([Protocol.EV_CLIENT_INIT]) + } + ws.onmessage = (e) => { + const msg = JSON.parse(e.data) const msgType = msg[0] if (msgType === Protocol.EV_SERVER_INIT) { const game = msg[1] @@ -46,22 +76,36 @@ function connect(address, gameId, clientId) { } else { throw `[ 2021-05-09 invalid connect msgType ${msgType} ]` } - }) - conn.onclose(() => { - connectionLostCallback() - }) - send([Protocol.EV_CLIENT_INIT]) + } + + ws.onerror = (e) => { + setConnectionState(CONN_STATE_DISCONNECTED) + throw `[ 2021-05-15 onerror ]` + } + + ws.onclose = (e) => { + if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { + setConnectionState(CONN_STATE_CLOSED) + } else { + setConnectionState(CONN_STATE_DISCONNECTED) + } + } }) } +// TOOD: change replay stuff function connectReplay(address, gameId, clientId) { clientSeq = 0 events = {} - conn = new WsClient(address, clientId + '|' + gameId) + setConnectionState(CONN_STATE_CONNECTING) return new Promise(resolve => { - conn.connect() - conn.onSocket('message', async ({ data }) => { - const msg = JSON.parse(data) + ws = new WebSocket(address, clientId + '|' + gameId) + ws.onopen = (e) => { + setConnectionState(CONN_STATE_CONNECTED) + send([Protocol.EV_CLIENT_INIT_REPLAY]) + } + ws.onmessage = (e) => { + const msg = JSON.parse(e.data) const msgType = msg[0] if (msgType === Protocol.EV_SERVER_INIT_REPLAY) { const game = msg[1] @@ -70,14 +114,26 @@ function connectReplay(address, gameId, clientId) { } else { throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]` } - }) - send([Protocol.EV_CLIENT_INIT_REPLAY]) + } + + ws.onerror = (e) => { + setConnectionState(CONN_STATE_DISCONNECTED) + throw `[ 2021-05-15 onerror ]` + } + + ws.onclose = (e) => { + if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { + setConnectionState(CONN_STATE_CLOSED) + } else { + setConnectionState(CONN_STATE_DISCONNECTED) + } + } }) } function disconnect() { - if (conn) { - conn.disconnect() + if (ws) { + ws.close(CODE_CUSTOM_DISCONNECT) } clientSeq = 0 events = {} @@ -97,5 +153,12 @@ export default { disconnect, sendClientEvent, onServerChange, - onConnectionLost, + onConnectionStateChange, + CODE_CUSTOM_DISCONNECT, + + CONN_STATE_NOT_CONNECTED, + CONN_STATE_DISCONNECTED, + CONN_STATE_CLOSED, + CONN_STATE_CONNECTED, + CONN_STATE_CONNECTING, } diff --git a/public/WsClient.js b/public/WsClient.js deleted file mode 100644 index 6db8c13..0000000 --- a/public/WsClient.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict" - -import Time from '../common/Time.js' - -const CODE_CUSTOM_DISCONNECT = 4000 - -/** - * Wrapper around ws that - * - buffers 'send' until a connection is available - * - automatically tries to reconnect on close - */ -export default class WsClient { - // actual ws handle - handle = null - - // timeout for automatic reconnect - reconnectTimeout = null - - // buffer for 'send' - sendBuffer = [] - - constructor(addr, protocols) { - this.addr = addr - this.protocols = protocols - - this.onopen = () => {} - this.onclose = () => {} - this.onmessage = () => {} - - this._on = {} - this.onopen = (e) => { - this._dispatch('socket', 'open', e) - } - this.onmessage = (e) => { - this._dispatch('socket', 'message', e) - if (!!this._on['message']) { - const d = this._parseMessageData(e.data) - if (d.event) { - this._dispatch('message', d.event, d.data) - } - } - } - this.onclose = (e) => { - this._dispatch('socket', 'close', e) - } - } - - send (txt) { - if (this.handle) { - this.handle.send(txt) - } else { - this.sendBuffer.push(txt) - } - } - - connect() { - let ws = new WebSocket(this.addr, this.protocols) - ws.onopen = (e) => { - if (this.reconnectTimeout) { - clearTimeout(this.reconnectTimeout) - } - this.handle = ws - // should have a queue worker - while (this.sendBuffer.length > 0) { - this.handle.send(this.sendBuffer.shift()) - } - this.onopen(e) - } - ws.onmessage = (e) => { - this.onmessage(e) - } - ws.onerror = (e) => { - this.handle = null - this.reconnectTimeout = setTimeout(() => { this.connect() }, 1 * Time.SEC) - this.onclose(e) - } - ws.onclose = (e) => { - this.handle = null - if (e.code !== CODE_CUSTOM_DISCONNECT) { - this.reconnectTimeout = setTimeout(() => { this.connect() }, 1 * Time.SEC) - } - this.onclose(e) - } - } - - disconnect() { - if (this.handle) { - this.handle.close(CODE_CUSTOM_DISCONNECT) - } - } - - onSocket(tag, callback) { - this.addEventListener('socket', tag, callback) - } - - onMessage(tag, callback) { - this.addEventListener('message', tag, callback) - } - - addEventListener(type, tag, callback) { - const tags = Array.isArray(tag) ? tag : [tag] - this._on[type] = this._on[type] || {} - for (const t of tags) { - this._on[type][t] = this._on[type][t] || [] - this._on[type][t].push(callback) - } - } - - _parseMessageData(data) { - try { - const d = JSON.parse(data) - if (d.event) { - return {event: d.event, data: d.data || null} - } - } catch { - } - return {event: null, data: null} - } - - _dispatch(type, tag, ...args) { - const t = this._on[type] || {} - const callbacks = (t[tag] || []) - if (callbacks.length === 0) { - return - } - - for (const callback of callbacks) { - callback(...args) - } - } -} diff --git a/public/components/ConnectionOverlay.vue.js b/public/components/ConnectionOverlay.vue.js new file mode 100644 index 0000000..91ecb82 --- /dev/null +++ b/public/components/ConnectionOverlay.vue.js @@ -0,0 +1,34 @@ +"use strict" + +import Communication from './../Communication.js' + +export default { + name: 'connection-overlay', + template: ` +
`, + emits: { + reconnect: null, + }, + props: { + connectionState: Number, + }, + computed: { + lostConnection () { + return this.connectionState === Communication.CONN_STATE_DISCONNECTED + }, + connecting () { + return this.connectionState === Communication.CONN_STATE_CONNECTING + }, + show () { + return this.lostConnection || this.connecting + }, + } +} diff --git a/public/components/HelpOverlay.vue.js b/public/components/HelpOverlay.vue.js index 79f77f2..b8c5966 100644 --- a/public/components/HelpOverlay.vue.js +++ b/public/components/HelpOverlay.vue.js @@ -6,7 +6,7 @@ export default { name: 'help-overlay', template: `