From 2627de964dc5a39daf5fec9d7ee482ac6a3d0685 Mon Sep 17 00:00:00 2001 From: David Kebler Date: Fri, 1 Mar 2019 18:10:57 -0800 Subject: [PATCH] 0.1.6 major refactor of websocket client remove auto reconnect dependency and wrote zero dep one. Has promise for initial connect and then will reconnect emmiting disconnet/reconnect to server events --- package.json | 7 ++-- src/WSConsumer.js | 101 ++++++++++++++++++++++++++++------------------ 2 files changed, 66 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 6255594..b5e4b05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uci/websocket-client", - "version": "0.1.5", + "version": "0.1.6", "description": "JSON packet browser client over web socket", "main": "src", "scripts": { @@ -29,7 +29,8 @@ "devDependencies": {}, "dependencies": { "auto-bind": "^2.0.0", - "eventemitter3": "^3.1.0", - "reconnecting-websocket": "^4.1.10" + "await-to-js": "^2.1.1", + "better-try-catch": "^0.6.2", + "eventemitter3": "^3.1.0" } } diff --git a/src/WSConsumer.js b/src/WSConsumer.js index 4d19aa1..0a4ceb2 100644 --- a/src/WSConsumer.js +++ b/src/WSConsumer.js @@ -1,10 +1,9 @@ // Websocket is a native global for vanilla JS -// /* globals WebSocket:true */ +/* globals WebSocket:true */ import btc from 'better-try-catch' import EventEmitter from 'eventemitter3' import autoBind from 'auto-bind' -import WS from 'reconnecting-websocket' /** * Web Socket Consumer - An in browser consumer/client that can communicate via UCI packets @@ -12,6 +11,9 @@ import WS from 'reconnecting-websocket' * uses the browser built in vanilla js global {@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket Websocket client class} * @extends EventEmitter */ + +let count = 1 + class WSConsumer extends EventEmitter { /** * constructor - Description @@ -24,9 +26,11 @@ class WSConsumer extends EventEmitter { this.name = opts.name || 'browser' this.instanceID = new Date().getTime() this.url = url - this.wsopts = opts.ws || { maxReconnectionDelay: 10000,minReconnectionDelay: 1000 + Math.random() * 4000,reconnectionDelayGrowFactor: 1.3,minUptime: 5000,connectionTimeout: 4000,maxRetries: Infinity,debug: false,} + this.rsLimit = opts.rsLimit || 5 + this.initTimeout = opts.initTimeout || 30000 + this.rsDelay = opts.rsDelay || 5000 // in large production you'd want a more robust delay calculation this.protocol = opts.protocol // available if needed but not documented - this.socket = {} + this.started = false autoBind(this) } @@ -38,44 +42,58 @@ class WSConsumer extends EventEmitter { * but opted to use https://www.npmjs.com/package/reconnecting-websocket */ - // async connect () { - // return new Promise((resolve, reject) => { - // const init = (socket) => { - // if (socket) console.error('Disconnected from Server') - // this.socket = new WebSocket(this.url, this.protocol) - // this.socket.addEventListener('close', init) - // this.socket.addEventListener('open', open.bind(this)) - // } - // - // setTimeout(function () { - // reject(new Error('Socket did not initially connect in 20 seconds')) - // }, 20000) - // - // init() - // - // function open () { - // this.listen() - // resolve(`socket open to server at : ${this.url}`) - // } - // }) - // } - async connect () { + // console.log('--- initial connect to websocket at', this.url) return new Promise((resolve, reject) => { - this.socket = new WS(this.url, this.protocol, this.wsopts) - this.socket.onopen = open.bind(this) + if(!this.url) reject('no url provided!') + let timeout + let connect = con.bind(this) - setTimeout(function () { - reject(new Error('Socket did not initially connect in 20 seconds')) - }, 20000) - // this.socket.onerror = (ev) => { reject(`could not connect/reconnect to server : ${ev}`)} + function con () { + // console.log(this.started, this.socket) + if (this.socket) delete this.socket // make sure previous socket is garabage collected + this.socket = new WebSocket(this.url, this.protocol) + // console.log('ready after create', this.socket.readyState, this.socket.onopen, this.socket.onclose) + this.socket.onopen = open.bind(this) - function open () { - this.listen() - resolve(`socket open to server at : ${this.url}`) + timeout = setTimeout(function () { + if (!this.started && count===1) console.log('original connection connect failed - retrying') + console.log(`socket has not ${this.started?'re':''}connected in ${this.rsDelay*count/1000} seconds`) + count += 1 + if (!this.started && this.rsDelay*count > this.initTimeout) { + let err = `unable to make a connection to websocket server at ${this.url} within ${this.initTimeout/1000}s` + console.log(err) + reject({url:this.url, msg:err}) + } + else connect() + }.bind(this), this.rsDelay) } - }) + function open () { + this.listen() // this handles messages + console.log(`socket open to server at : ${this.url}`) + if (!this.started) this.emit('connected') + else this.emit('reconnected') + clearTimeout(timeout) + // this.socket.onerror = error.bind(this) + this.socket.onclose = close.bind(this) + console.log(this.socket) + count = 0 + this.started = true + resolve({url:this.url, msg:`socket open to server at : ${this.url}`}) + } + + connect() // get the ball rolling + + function close () { + this.socket.onclose = null + console.error('Socket has closed, attempting reconnect') + this.removeAllListeners('push') + this.socket.onmessage = null + this.emit('disconnected') + connect() + } + }) // end promise } /** @@ -86,7 +104,9 @@ class WSConsumer extends EventEmitter { * @returns {type} Description */ listen (func) { - this.socket.addEventListener('message', handler.bind(this)) + this.socket.onmessage = packetHandler.bind(this) + + // process 'pushed' packets this.on('pushed', async function (packet) { // TODO do some extra security here for 'evil' pushed packets let res = await this._packetProcess(packet) @@ -98,7 +118,7 @@ class WSConsumer extends EventEmitter { } }) - function handler (event) { + function packetHandler (event) { let packet = {} if (this.socket.readyState === 1) { let [err, parsed] = btc(JSON.parse)(event.data) @@ -110,7 +130,9 @@ class WSConsumer extends EventEmitter { } } // console.log('in the handler', event.data) - if (func) func(packet) + if (func) func(packet) // extra processing if enabled + // this is response to a packet send command listener and is processed below + // will also emit 'pushed' via id which can be listened for in app this.emit(packet._header.id, packet) } } @@ -165,6 +187,7 @@ class WSConsumer extends EventEmitter { this._packetProcess = func } + // do nothing async _packetProcess (packet) { return Promise.resolve(packet) }