const tutorial = require('../utils/Bundle').tutorial; const Message = tutorial.Message; /** * 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 (0 == arguments.length) { this._callbacks = {}; return this; } // specific event var callbacks = this._callbacks['$' + event]; if (!callbacks) return this; // remove all handlers if (1 == arguments.length) { 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), 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 ('string' == typeof fn) fn = obj[fn]; if ('function' != typeof fn) 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}?uid=${Global.user.uid}&token=${Global.user.token}&channel=${Global.channel}&ver=${Global.os}&os=${Global.ver}` 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(null == opts.timeout ? 20000 : opts.timeout) this.logStyle = 'color:blue; font-size:16px;font-weight:bold;' this.keepAliveInterval = 15000 //心跳包发送间隔 this.keepAliveTimeout = null //心跳包计时器 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') { return; } 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.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); } 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) } } // WsManager.prototype.onOpen = function(res) { // if(CC_WECHATGAME) { // this.socket.onOpen((res) => { // this.readyState = 'open' // callback && callback(res) // console.log(`%c [Socket连接成功: ${this.url.split("&token")[0]}]`, this.logStyle); // }) // } else { // this.socket.onopen = () => { // this.readyState = 'open' // callback && callback() // console.log(`%c [Socket连接成功: ${this.url.split("&token")[0]}]`, this.logStyle); // } // } // } // WsManager.prototype.onClose = function(callback) { // if(CC_WECHATGAME) { // this.socket.onClose((res) => { // this.readyState = 'closed' // callback && callback(res) // console.log(`%c [Socket连接关闭: ${res}]`, this.logStyle) // }) // } else { // this.socket.onclose = (event) => { // this.readyState = 'closed'; // let code = event.code // let reason = event.reason // let wasClean = event.wasClean // callback && callback(event) // console.log(`%c [Socket连接关闭: ${reason}]`, this.logStyle) // } // } // } // WsManager.prototype.onMessage = function(callback) { // if(CC_WECHATGAME) { // this.socket.onMessage((res) => { // let data = res.data // callback && callback(data) // console.log(`%c [接收到Socket消息: ${JSON.stringify(data)}]`, this.logStyle); // }) // } else { // this.socket.onmessage = (event) => { // let data = event.data // callback && callback(data) // console.log(`%c [接收到Socket消息: ${JSON.stringify(data)}]`, this.logStyle); // } // } // } // WsManager.prototype.onError = function(callback) { // if(CC_WECHATGAME) { // this.socket.onError((res) => { // callback && callback(res.errMsg) // console.log(`%c [Socket错误: ${res.errMsg}]`, this.logStyle); // }) // } else { // this.socket.onerror = (event) => { // callback && callback(event.data) // console.log(`%c [Socket错误: ${event.data}]`, this.logStyle); // } // } // } /** * 配置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 } module.exports = WsManager // var Ws = function() { // this.socket = new io(Api.SocketLocal, { // autoConnect: false, //自动连接 // reconnection: true, //断开自动重连 // reconnectionDelay: 2000, //自动重连时间,默认为2000毫秒 // reconnectionAttempts: 10, //最大重连尝试次数,默认为Infinity // forceNew: true, // // }) // this.socket.on("connect", () => { // console.log(`Socket connected: ${Api.SocketLocal}`); // }) // } // Ws.prototype.open = function() { // this.socket.open() // } // /** // * 监听Websocket事件 // * @param {String} eventName 监听事件名称 // * @param {Function} callback 事件触发回调 // */ // Ws.prototype.on = function(eventName, callback) { // if(eventName) { // this.socket.on(eventName, data => { // console.log(`Socket On Event: ${eventName}`); // callback && callback(data) // }) // } // } // /** // * 上报Websocket报文 // * @param {String} eventName 上报事件名 // * @param {Object} data 上报数据 // */ // Ws.prototype.emit = function(eventName, data) { // if(eventName) { // console.log(`Socket Do Emit: ${eventName}`, data); // this.socket.emit(eventName, data); // } // } // module.exports = new Ws();