const CC_WECHATGAME = false /** * Initialize a new `Emitter`. * * @api public */ function Emitter (obj) { if (obj) return mixin(obj) }; /** * Mixin the emitter properties. * * @param {Object} obj * @return {Object} * @api private */ function mixin (obj) { for (var key in Emitter.prototype) { obj[key] = Emitter.prototype[key] } return obj } /** * Listen on the given `event` with `fn`. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.on = Emitter.prototype.addEventListener = function (event, fn) { this._callbacks = this._callbacks || {}; (this._callbacks['$' + event] = this._callbacks['$' + event] || []) .push(fn) return this } /** * Adds an `event` listener that will be invoked a single * time then automatically removed. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.once = function (event, fn) { function on () { this.off(event, on) fn.apply(this, arguments) } on.fn = fn this.on(event, on) return this } /** * Remove the given callback for `event` or all * registered callbacks. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.off = Emitter.prototype.removeListener = Emitter.prototype.removeAllListeners = Emitter.prototype.removeEventListener = function (event, fn) { this._callbacks = this._callbacks || {} // all if (arguments.length === 0) { this._callbacks = {} return this } // specific event var callbacks = this._callbacks['$' + event] if (!callbacks) return this // remove all handlers if (arguments.length === 1) { delete this._callbacks['$' + event] return this } // remove specific handler var cb for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i] if (cb === fn || cb.fn === fn) { callbacks.splice(i, 1) break } } return this } /** * Emit `event` with the given args. * * @param {String} event * @param {Mixed} ... * @return {Emitter} */ Emitter.prototype.emit = function (event) { this._callbacks = this._callbacks || {} var args = [].slice.call(arguments, 1) var callbacks = this._callbacks['$' + event] if (callbacks) { callbacks = callbacks.slice(0) for (var i = 0, len = callbacks.length; i < len; ++i) { callbacks[i].apply(this, args) } } return this } /** * Return array of callbacks for `event`. * * @param {String} event * @return {Array} * @api public */ Emitter.prototype.listeners = function (event) { this._callbacks = this._callbacks || {} return this._callbacks['$' + event] || [] } /** * Check if this emitter has `event` handlers. * * @param {String} event * @return {Boolean} * @api public */ Emitter.prototype.hasListeners = function (event) { return !!this.listeners(event).length } // function bind (obj, fn) { // if (typeof fn === 'string') fn = obj[fn] // if (typeof fn !== 'function') throw new Error('bind() requires a function') // var args = [].slice.call(arguments, 2) // return function () { // return fn.apply(obj, args.concat([].slice.call(arguments))) // } // }; /** * WebSocket管理器 * 支持最多同时实例化5个socket连接(微信环境限制目前最多为5个) * @param {String} url socket地址 * @param {Object} opts 配置 * */ function WsManager (url, opts) { if (!(this instanceof WsManager)) { return new WsManager(url, opts) } if (url && (typeof url === 'object')) { opts = url url = undefined } Emitter(this) opts = opts || {} opts.path = opts.path || '/' this.opts = opts this.url = url this.lastPing = null this.socketCache = [] // 缓存socket队列 this.socketMaxCache = 5 // 最大缓存socket实例数量 [暂时没有使用] this.readyState = 'closed' // 当前socket状态 this.binaryType = opts.binaryType || 'blob' // 数据传输类型 this._reconnectTimes = 0 // 重连次数 this._reconnectionDelay = opts.reconnectionDelay || 1000 // 重连延迟 this.reconnection(opts.reconnection !== false) // 是否自动重连 this.reconnectionAttempts(opts.reconnectionAttempts || Infinity) // 重连最大尝试次数 this.timeout(opts.timeout === null ? 20000 : opts.timeout) this.logStyle = 'color:blue; font-size:16px;font-weight:bold;' this.keepAliveInterval = 15000 // 心跳包发送间隔 this.keepAliveTimeout = null // 心跳包计时器 this.keepAliveContent = opts.keepAliveContent || 1 // 心跳包内容 this.autoConnect = opts.autoConnect !== false // 是否自动连接 if (this.autoConnect) { this.connect() } } WsManager.prototype.connect = function (fn) { if (~this.readyState.indexOf('open')) { return this } this.readyState = 'opening' let _socket = CC_WECHATGAME ? this.openWxConnect() : this.openH5Connect() this.socketCache.push(_socket) this.socket = _socket } WsManager.prototype.reconnect = function () { clearInterval(this.keepAliveTimeout) if (this.readyState === 'open' || this.readyState === 'opening') { } else { if (this._reconnectTimes < this._reconnectionAttempts) { if (this.socket) { this.socket.close() } this._reconnectTimes += 1 this.readyState = 'reconnecting' // console.log(`%c [Socket正在尝试第${this._reconnectTimes}次重连]`, this.logStyle); setTimeout(() => { this.socket = CC_WECHATGAME ? this.openWxConnect() : this.openH5Connect() }, this._reconnectionDelay) } else { // console.log(`%c [达到最大连续重连失败次数,已重置,30秒后重试]`, this.logStyle); if (this.socket) { this.socket.close() } this._reconnectTimes = 1 this.readyState = 'reconnecting' setTimeout(() => { this.socket = CC_WECHATGAME ? this.openWxConnect() : this.openH5Connect() }, 30000) } } } WsManager.prototype.destroy = function () { this._reconnection = false this.socket.close() } WsManager.prototype.keepAlive = function () { this.keepAliveTimeout = setInterval(() => { if (this.readyState === 'open') { // let payload = { // type: 6 // } // let msg = Message.create(payload); // let buffer = Message.encode(msg).finish() // this.send(buffer); this.send(this.keepAliveContent) } else if (this.readyState === 'closed') { this.reconnect() } }, this.keepAliveInterval) } WsManager.prototype.openWxConnect = function () { let _header = {} let _socket = wx.connectSocket({ url: this.url, header: _header, success: function (ret) {} }) _socket.onOpen((res) => { this.readyState = 'open' this.emit('open', res) // console.log(`%c [Socket连接成功: ${this.url.split("&token")[0]}]`, this.logStyle); // 连接成功,重置重连次数 this._reconnectTimes = 1 // 每隔一段时间发一个心跳包保持连接状态 this.keepAlive() }) _socket.onClose((res) => { // console.log(`%c [Socket连接被关闭: ${res}]`, this.logStyle) clearInterval(this.keepAliveTimeout) this.readyState = 'closed' // 只要关闭就重连(暂时性处理) if (this._reconnection) { this.reconnect() } this.emit('close', res) }) _socket.onMessage((res) => { if (res.data !== 1) { this.emit('message', res.data) } // console.log(`%c [接收到Socket消息: ${JSON.stringify(res.data)}]`, this.logStyle); }) _socket.onError((res) => { // console.log(`%c [Socket错误: ${res.errMsg}]`, this.logStyle); clearInterval(this.keepAliveTimeout) if (this._reconnection) { if (this.readyState !== 'reconnecting') { this.readyState = 'error' this.reconnect() } } else { _socket.close() } this.emit('error', res.errMsg) }) return _socket } WsManager.prototype.openH5Connect = function () { let _socket = new WebSocket(this.url) // _socket.binaryType = "arraybuffer"; _socket.onopen = (event) => { this.readyState = 'open' this.emit('open', event) // console.log(`%c [Socket连接成功: ${this.url.split("&token")[0]}]`, this.logStyle); // 连接成功,重置重连次数 this._reconnectTimes = 1 // 每隔一段时间发一个心跳包保持连接状态 this.keepAlive() } _socket.onclose = (event) => { clearInterval(this.keepAliveTimeout) this.readyState = 'closed' // let code = event.code let reason = event.reason // let wasClean = event.wasClean console.log(`%c [Socket连接被关闭: ${reason}]`, this.logStyle) // 只要关闭就重连(暂时性处理) if (this._reconnection) { this.reconnect() } this.emit('close', event) } _socket.onmessage = (event) => { if (event.data !== 1) { this.emit('message', event.data) } // console.log(`%c [接收到Socket消息: ${event.data}]`, this.logStyle); } _socket.onerror = (event) => { // console.log(`%c [Socket错误: ${JSON.stringify(event)}]`, this.logStyle); clearInterval(this.keepAliveTimeout) if (this._reconnection) { if (this.readyState !== 'reconnecting') { this.readyState = 'error' this.reconnect() } } else { _socket.close() } this.emit(event) } return _socket } WsManager.prototype.send = function (data) { // console.log(`%c [发送Socket数据: ${data}]`, this.logStyle); if (CC_WECHATGAME) { let buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) this.socket.send({ data: buffer, success: function (res) { // console.log('Success: ', JSON.stringify(res)); }, fail: function (res) { // console.log('Fail: ', JSON.stringify(res)); }, complete: function (res) { // console.log('Complete: ', JSON.stringify(res)); } }) } else { this.socket.binaryType = this.binaryType this.socket.send(data) } } /** * 配置socket连接超时时间 * @param {Number} v 连接超时时间 * @return {WsManager} WsManager对象实例 * @api public */ WsManager.prototype.timeout = function (v) { if (!arguments.length) { return this._timeout } this._timeout = v return this } /** * 自动重连配置 * @param {Boolean} v 是否自动重连true / false * @return {WsManager} WsManager对象实例 * @api public */ WsManager.prototype.reconnection = function (v) { if (!arguments.length) { return this._reconnection } this._reconnection = !!v return this } /** * 配置最大重连次数 * @param {Number} v 最大重连次数 * @return {WsManager} WsManager对象实例 * @api public */ WsManager.prototype.reconnectionAttempts = function (v) { if (!arguments.length) { return this._reconnectionAttempts } this._reconnectionAttempts = v return this } export default WsManager