From 07328e1548af69dffc7118d468acb12bf3e764ea Mon Sep 17 00:00:00 2001 From: Viktor Date: Mon, 13 Nov 2017 19:52:29 +0300 Subject: [PATCH] Add reconnect (#25) * add ws reconnect * added in readme text about reconnect --- README.md | 22 +++++++++++++++++++++- dist/build.js | 2 +- package.json | 2 +- src/Main.js | 1 + src/Observer.js | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b40c4c..19aad9f 100755 --- a/README.md +++ b/README.md @@ -51,6 +51,16 @@ import store from './store' Vue.use(VueNativeSock, 'ws://localhost:9090', { store: store, format: 'json' }) ``` +Enable ws reconnect automatically: + +``` js +Vue.use(VueNativeSock, 'ws://localhost:9090', { + reconnection: true, // (Boolean) whether to reconnect automatically (false) + reconnectionAttempts: 5, // (Number) number of reconnection attempts before giving up (Infinity), + reconnectionDelay: 3000, // (Number) how long to initially wait before attempting a new (1000) +}) +``` + #### On Vuejs instance usage ``` js @@ -102,6 +112,8 @@ Update state in the open, close and error callbacks. You can also check the sock Handle all the data in the `SOCKET_ONMESSAGE` mutation. +Reconect events will commit mutations `SOCKET_RECONNECT` and `SOCKET_RECONNECT_ERROR`. + ``` js import Vue from 'vue' import Vuex from 'vuex' @@ -113,6 +125,7 @@ export default new Vuex.Store({ socket: { isConnected: false, message: '', + reconnectError: false, } }, mutations:{ @@ -128,7 +141,14 @@ export default new Vuex.Store({ // default handler called for all methods SOCKET_ONMESSAGE (state, message) { state.message = message - } + }, + // mutations for reconnect methods + [ws.WS_RECONNECT](state, count) { + console.info(state, count) + }, + [ws.WS_RECONNECT_ERROR](state) { + state.socket.reconnectError = true; + }, } }) ``` diff --git a/dist/build.js b/dist/build.js index 11ab402..1e9c401 100755 --- a/dist/build.js +++ b/dist/build.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.VueNativeSock=t():e.VueNativeSock=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,n){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n-1)&&(o.splice(r,1),this.listeners.set(e,o),!0)}},{key:"emit",value:function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),o=1;o2&&void 0!==arguments[2]?arguments[2]:{};if(!t)throw new Error("[vue-native-socket] cannot locate connection");var o=new i.default(t,n);e.prototype.$socket=o.WebSocket,e.mixin({created:function(){var e=this,t=this.$options.sockets;this.$options.sockets=new Proxy({},{set:function(e,t,n){return c.default.addListener(t,n,this),e[t]=n,!0},deleteProperty:function(e,t){return c.default.removeListener(t,this.$options.sockets[t],this),delete e.key,!0}}),t&&Object.keys(t).forEach(function(n){e.$options.sockets[n]=t[n]})},beforeDestroy:function(){var e=this,t=this.$options.sockets;t&&Object.keys(t).forEach(function(t){delete e.$options.sockets[t]})}})}}},function(e,t,n){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};o(this,e),this.format=n.format&&n.format.toLowerCase(),this.connect(t,n),n.store&&(this.store=n.store),this.onEvent()}return r(e,[{key:"connect",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=n.protocol||"";this.WebSocket=n.WebSocket||(""===o?new WebSocket(e):new WebSocket(e,o)),"json"===this.format&&("sendObj"in this.WebSocket||(this.WebSocket.sendObj=function(e){return t.WebSocket.send(JSON.stringify(e))}))}},{key:"onEvent",value:function(){var e=this;["onmessage","onclose","onerror","onopen"].forEach(function(t){e.WebSocket[t]=function(n){s.default.emit(t,n),e.store&&e.passToStore("SOCKET_"+t,n)}})}},{key:"passToStore",value:function(e,t){if(e.startsWith("SOCKET_")){var n="commit",o=e.toUpperCase(),r=t;"json"===this.format&&t.data&&(r=JSON.parse(t.data),r.mutation?o=[r.namespace||"",r.mutation].filter(function(e){return!!e}).join("/"):r.action&&(n="dispatch",o=r.action)),this.store[n](o,r)}}}]),e}();t.default=c}])}); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.VueNativeSock=t():e.VueNativeSock=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,n){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n-1)&&(o.splice(r,1),this.listeners.set(e,o),!0)}},{key:"emit",value:function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),o=1;o2&&void 0!==arguments[2]?arguments[2]:{};if(!t)throw new Error("[vue-native-socket] cannot locate connection");var o=new i.default(t,n);e.prototype.$socket=o.WebSocket,e.mixin({created:function(){var e=this,t=this.$options.sockets;this.$options.sockets=new Proxy({},{set:function(e,t,n){return s.default.addListener(t,n,this),e[t]=n,!0},deleteProperty:function(e,t){return s.default.removeListener(t,this.$options.sockets[t],this),delete e.key,!0}}),t&&Object.keys(t).forEach(function(n){e.$options.sockets[n]=t[n]})},beforeDestroy:function(){var e=this,t=this.$options.sockets;clearTimeout(o.reconnectTimeoutId),t&&Object.keys(t).forEach(function(t){delete e.$options.sockets[t]})}})}}},function(e,t,n){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};o(this,e),this.format=n.format&&n.format.toLowerCase(),this.connectionUrl=t,this.opts=n,this.reconnection=this.opts.reconnection||!1,this.reconnectionAttempts=this.opts.reconnectionAttempts||1/0,this.reconnectionDelay=this.opts.reconnectionDelay||1e3,this.reconnectTimeoutId=0,this.reconnectionCount=0,this.connect(t,n),n.store&&(this.store=n.store),this.onEvent()}return r(e,[{key:"connect",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=n.protocol||"";return this.WebSocket=n.WebSocket||(""===o?new WebSocket(e):new WebSocket(e,o)),"json"===this.format&&("sendObj"in this.WebSocket||(this.WebSocket.sendObj=function(e){return t.WebSocket.send(JSON.stringify(e))})),this.WebSocket}},{key:"reconnect",value:function(){var e=this;this.reconnectionCount<=this.reconnectionAttempts?(this.reconnectionCount++,clearTimeout(this.reconnectTimeoutId),this.reconnectTimeoutId=setTimeout(function(){e.store&&e.passToStore("SOCKET_RECONNECT",e.reconnectionCount),e.connect(e.connectionUrl,e.opts),e.onEvent()},this.reconnectionDelay)):this.store&&this.passToStore("SOCKET_RECONNECT_ERROR",!0)}},{key:"onEvent",value:function(){var e=this;["onmessage","onclose","onerror","onopen"].forEach(function(t){e.WebSocket[t]=function(n){c.default.emit(t,n),e.store&&e.passToStore("SOCKET_"+t,n),e.reconnection&&"onopen"===e.eventType&&(e.reconnectionCount=0),e.reconnection&&"onclose"===t&&e.reconnect(n)}})}},{key:"passToStore",value:function(e,t){if(e.startsWith("SOCKET_")){var n="commit",o=e.toUpperCase(),r=t;"json"===this.format&&t.data&&(r=JSON.parse(t.data),r.mutation?o=[r.namespace||"",r.mutation].filter(function(e){return!!e}).join("/"):r.action&&(n="dispatch",o=r.action)),this.store[n](o,r)}}}]),e}();t.default=s}])}); \ No newline at end of file diff --git a/package.json b/package.json index 047a3d4..6ecc34f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-native-websocket", - "version": "2.0.3", + "version": "2.0.4", "description": "native websocket implemantation for vuejs and vuex", "main": "dist/build.js", "scripts": { diff --git a/src/Main.js b/src/Main.js index d90cffe..364a1d1 100755 --- a/src/Main.js +++ b/src/Main.js @@ -35,6 +35,7 @@ export default { }, beforeDestroy () { let sockets = this.$options['sockets'] + clearTimeout(observer.reconnectTimeoutId) if (sockets) { Object.keys(sockets).forEach((key) => { diff --git a/src/Observer.js b/src/Observer.js index dd77cde..1efc63a 100755 --- a/src/Observer.js +++ b/src/Observer.js @@ -3,7 +3,17 @@ import Emitter from './Emitter' export default class { constructor (connectionUrl, opts = {}) { this.format = opts.format && opts.format.toLowerCase() + this.connectionUrl = connectionUrl + this.opts = opts + + this.reconnection = this.opts.reconnection || false + this.reconnectionAttempts = this.opts.reconnectionAttempts || Infinity + this.reconnectionDelay = this.opts.reconnectionDelay || 1000 + this.reconnectTimeoutId = 0 + this.reconnectionCount = 0 + this.connect(connectionUrl, opts) + if (opts.store) { this.store = opts.store } this.onEvent() } @@ -16,13 +26,36 @@ export default class { this.WebSocket.sendObj = (obj) => this.WebSocket.send(JSON.stringify(obj)) } } + + return this.WebSocket + } + + reconnect () { + if (this.reconnectionCount <= this.reconnectionAttempts) { + this.reconnectionCount++ + clearTimeout(this.reconnectTimeoutId) + + this.reconnectTimeoutId = setTimeout(() => { + if (this.store) { this.passToStore('SOCKET_RECONNECT', this.reconnectionCount) } + + this.connect(this.connectionUrl, this.opts) + this.onEvent() + }, this.reconnectionDelay) + } else { + if (this.store) { this.passToStore('SOCKET_RECONNECT_ERROR', true) } + } } onEvent () { ['onmessage', 'onclose', 'onerror', 'onopen'].forEach((eventType) => { this.WebSocket[eventType] = (event) => { Emitter.emit(eventType, event) + if (this.store) { this.passToStore('SOCKET_' + eventType, event) } + + if (this.reconnection && this.eventType === 'onopen') { this.reconnectionCount = 0 } + + if (this.reconnection && eventType === 'onclose') { this.reconnect(event) } } }) }