291 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /**
 | ||
|  * Gdoo Realtime 连接库
 | ||
|  *
 | ||
|  * 参考自:https://github.com/joewalnes/reconnecting-websocket/
 | ||
|  *
 | ||
|  * 使用方法
 | ||
|  * ======
 | ||
|  * 选项可以在实例化时传递,也可以在实例化后设置:
 | ||
|  * var socket = new Realtime({url: '127.0.0.1', debug: true, reconnectInterval: 4000});
 | ||
|  * 或者
 | ||
|  * var socket = new Realtime();
 | ||
|  * socket.url = '';
 | ||
|  * socket.debug = true;
 | ||
|  * socket.reconnectInterval = 4000;
 | ||
|  */
 | ||
| (function (global, factory) {
 | ||
|     if (typeof define === 'function' && define.amd) {
 | ||
|         define([], factory);
 | ||
|     } else if (typeof module !== 'undefined' && module.exports){
 | ||
|         module.exports = factory();
 | ||
|     } else {
 | ||
|         global.Realtime = factory();
 | ||
|     }
 | ||
| })(this, function () {
 | ||
| 
 | ||
|     if (!('WebSocket' in window)) {
 | ||
|         return;
 | ||
|     }
 | ||
| 
 | ||
|     function Realtime(appKey, options) {
 | ||
|         // 默认设置
 | ||
|         var settings = {
 | ||
|             // 连接ws服务器地址
 | ||
|             host: null,
 | ||
|             // 连接服务器认证的url
 | ||
|             authEndpoint: '/realtime/auth',
 | ||
|             // 认证参数
 | ||
|             auth: {params:{}, headers:{}},
 | ||
|             // 此实例是否应记录调试消息
 | ||
|             debug: false,
 | ||
|             // WebSocket是否应在实例化后立即尝试连接
 | ||
|             automaticOpen: true,
 | ||
|             // 尝试重新连接之前要延迟的毫秒数
 | ||
|             reconnectInterval: 1000,
 | ||
|             // 延迟重新连接尝试的最大毫秒数
 | ||
|             maxReconnectInterval: 30000,
 | ||
|             // The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. 
 | ||
|             reconnectDecay: 1.5,
 | ||
|             // The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. 
 | ||
|             timeoutInterval: 2000,
 | ||
|             // The maximum number of reconnection attempts to make. Unlimited if null. */
 | ||
|             maxReconnectAttempts: null,
 | ||
|             // The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
 | ||
|             binaryType: 'blob',
 | ||
|             // websocket子协议定义
 | ||
|             protocols: [],
 | ||
|             // 当前连接Id
 | ||
|             socket_id: null,
 | ||
|         }
 | ||
|         if (!options) { options = {}; }
 | ||
| 
 | ||
|         // Overwrite and define settings with options if they exist.
 | ||
|         for (var key in settings) {
 | ||
|             if (typeof options[key] == 'undefined') {
 | ||
|                 this[key] = settings[key];
 | ||
|             } else {
 | ||
|                 this[key] = options[key];
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         /** The number of attempted reconnects since starting, or the last successful connection. Read only. */
 | ||
|         this.reconnectAttempts = 0;
 | ||
| 
 | ||
|         /**
 | ||
|          * The current state of the connection.
 | ||
|          * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
 | ||
|          * Read only.
 | ||
|          */
 | ||
|         this.readyState = WebSocket.CONNECTING;
 | ||
| 
 | ||
|         /**
 | ||
|          * A string indicating the name of the sub-protocol the server selected; this will be one of
 | ||
|          * the strings specified in the protocols parameter when creating the WebSocket object.
 | ||
|          * Read only.
 | ||
|          */
 | ||
|         this.protocol = null;
 | ||
| 
 | ||
|         // 私有变量
 | ||
|         var me = this;
 | ||
|         var ws;
 | ||
|         var forcedClose = false;
 | ||
|         var timedOut = false;
 | ||
| 
 | ||
|         var handlers = {};
 | ||
| 
 | ||
|         handlers.connect = function(data) {};
 | ||
|         handlers.disconnect = function(data) {};
 | ||
|         handlers.reconnecting = function(data) {};
 | ||
|         handlers.quit = function(data) {};
 | ||
|         handlers.error = function(data) {};
 | ||
| 
 | ||
|         this.on = function(name, fun) {
 | ||
|             handlers[name] = fun;
 | ||
|             return me;
 | ||
|         }
 | ||
| 
 | ||
|         var channel = function(channel_name) {
 | ||
|             var that = this;
 | ||
|             this.channel_name = channel_name;
 | ||
|             this.members = {};
 | ||
|             this.on = function(event_name, callback) {
 | ||
|                 handlers[that.channel_name + '.' + event_name] = callback;
 | ||
|                 return that;
 | ||
|             }
 | ||
|             this.emit = function(event_name, data) {
 | ||
|                 return me.emit(that.channel_name, event_name, data);
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         this.subscribe = function(channel_name) {
 | ||
|             return new channel(channel_name);
 | ||
|         }
 | ||
| 
 | ||
|         this.authorize = function() {
 | ||
|             $.ajax({
 | ||
|                 url: me.authEndpoint,
 | ||
|                 type: 'POST',
 | ||
|                 data: me.auth['params'],
 | ||
|                 beforeSend: function(req) {
 | ||
|                     headers = me.auth['headers'];
 | ||
|                     for (var key in headers) {
 | ||
|                         req.setRequestHeader(key, headers[key]);
 | ||
|                     }
 | ||
|                 },
 | ||
|                 dataType: 'json',
 | ||
|                 success: function (data) {
 | ||
|                     me.send({channel:'socket', event:'authorize', data:data});
 | ||
|                 }
 | ||
|             });
 | ||
|         }
 | ||
| 
 | ||
|         this.connect = function (reconnectAttempt) {
 | ||
|             ws = new WebSocket(me.host + '?key=' + appKey, me.protocols);
 | ||
|             ws.binaryType = this.binaryType;
 | ||
| 
 | ||
|             if (reconnectAttempt) {
 | ||
|                 if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
 | ||
|                     return;
 | ||
|                 }
 | ||
|             } else {
 | ||
|                 handlers.reconnecting.call(me);
 | ||
|                 this.reconnectAttempts = 0;
 | ||
|             }
 | ||
| 
 | ||
|             if (me.debug || Realtime.debugAll) {
 | ||
|                 console.log('realtime', 'attempt-connect', me.host);
 | ||
|             }
 | ||
| 
 | ||
|             var localWs = ws;
 | ||
|             var timeout = setTimeout(function() {
 | ||
|                 if (me.debug || Realtime.debugAll) {
 | ||
|                     console.log('realtime', 'connection-timeout', me.host);
 | ||
|                 }
 | ||
|                 timedOut = true;
 | ||
|                 localWs.close();
 | ||
|                 timedOut = false;
 | ||
|             }, me.timeoutInterval);
 | ||
| 
 | ||
|             ws.onopen = function(event) {
 | ||
|                 clearTimeout(timeout);
 | ||
|                 if (me.debug || Realtime.debugAll) {
 | ||
|                     console.log('realtime', 'onopen', me.host);
 | ||
|                 }
 | ||
|                 me.protocol = ws.protocol;
 | ||
|                 me.readyState = WebSocket.OPEN;
 | ||
|                 me.reconnectAttempts = 0;
 | ||
| 
 | ||
|                 handlers.connect.call(me);
 | ||
| 
 | ||
|                 reconnectAttempt = false;
 | ||
|             };
 | ||
| 
 | ||
|             ws.onclose = function(event) {
 | ||
|                 clearTimeout(timeout);
 | ||
|                 ws = null;
 | ||
|                 if (forcedClose) {
 | ||
|                     me.readyState = WebSocket.CLOSED;
 | ||
|                     handlers.disconnect.call(me, event);
 | ||
|                 } else {
 | ||
|                     me.readyState = WebSocket.CONNECTING;
 | ||
|                     handlers.reconnecting.call(me, event);
 | ||
| 
 | ||
|                     if (!reconnectAttempt && !timedOut) {
 | ||
|                         if (me.debug || Realtime.debugAll) {
 | ||
|                             console.log('realtime', 'onclose', me.host);
 | ||
|                         }
 | ||
|                         handlers.disconnect.call(me, event);
 | ||
|                     }
 | ||
| 
 | ||
|                     var timeout = me.reconnectInterval * Math.pow(me.reconnectDecay, me.reconnectAttempts);
 | ||
|                     setTimeout(function() {
 | ||
|                         me.reconnectAttempts++;
 | ||
|                         me.connect(true);
 | ||
|                     }, timeout > me.maxReconnectInterval ? me.maxReconnectInterval : timeout);
 | ||
|                 }
 | ||
|             };
 | ||
| 
 | ||
|             ws.onmessage = function(event) {
 | ||
|                 if (me.debug || Realtime.debugAll) {
 | ||
|                     console.log('realtime', 'onmessage', me.host, event.data);
 | ||
|                 }
 | ||
|                 var packet = JSON.parse(event.data);
 | ||
|                 
 | ||
|                 var channel_name = packet['channel'];
 | ||
|                 var event_name   = packet['event'];
 | ||
|                 var data         = packet['data'];
 | ||
|                 var key          = channel_name == 'socket' ? event_name : channel_name + '.' + event_name;
 | ||
|                 var handler      = handlers[key];
 | ||
| 
 | ||
|                 // 连接成功事件
 | ||
|                 if (key == 'connected') {
 | ||
|                     me.socket_Id = data.socket_Id;
 | ||
|                     me.authorize();
 | ||
|                 }
 | ||
|                 if (typeof handler == 'function') {
 | ||
|                     handler.call(me, data, packet);
 | ||
|                 }
 | ||
|             };
 | ||
| 
 | ||
|             ws.onerror = function(event) {
 | ||
|                 if (me.debug || Realtime.debugAll) {
 | ||
|                     console.log('realtime', 'onerror', me.host, event);
 | ||
|                 }
 | ||
|                 handlers.error.call(me, event);
 | ||
|             };
 | ||
|         }
 | ||
| 
 | ||
|         // 是否在实例化时创建WebSocket
 | ||
|         if (this.automaticOpen == true) {
 | ||
|             this.connect(false);
 | ||
|         }
 | ||
| 
 | ||
|         /**
 | ||
|          * 通过WebSocket连接将数据传输到服务器。
 | ||
|          * @param channel 频道名称
 | ||
|          * @param data    要发送到服务器的文本字符串、arrayBuffer或blob。
 | ||
|          */
 | ||
|         this.emit = function(channel_name, event_name, data) {
 | ||
|             return this.send({channel:channel_name, event:event_name, data:data});
 | ||
|         };
 | ||
| 
 | ||
|         this.send = function(data) {
 | ||
|             if (ws) {
 | ||
|                 if (me.debug || Realtime.debugAll) {
 | ||
|                     console.log('realtime', 'send', me.host, data.data);
 | ||
|                 }
 | ||
|                 return ws.send(JSON.stringify(data));
 | ||
|             } else {
 | ||
|                 throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
 | ||
|             }
 | ||
|         };
 | ||
| 
 | ||
|         /**
 | ||
|          * 关闭WebSocket连接或连接尝试。
 | ||
|          * 如果连接已关闭,则此方法不执行任何操作。
 | ||
|          */
 | ||
|         this.disconnect = function(code, reason) {
 | ||
|             // Default CLOSE_NORMAL code
 | ||
|             if (typeof code == 'undefined') {
 | ||
|                 code = 1000;
 | ||
|             }
 | ||
|             forcedClose = true;
 | ||
|             if (ws) {
 | ||
|                 ws.close(code, reason);
 | ||
|             }
 | ||
|         };
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 将此设置为true等同于将 Realtime.log 的所有实例设置为true。
 | ||
|      */
 | ||
|     Realtime.debugAll = false;
 | ||
| 
 | ||
|     Realtime.CONNECTING = WebSocket.CONNECTING;
 | ||
|     Realtime.OPEN = WebSocket.OPEN;
 | ||
|     Realtime.CLOSING = WebSocket.CLOSING;
 | ||
|     Realtime.CLOSED = WebSocket.CLOSED;
 | ||
| 
 | ||
|     return Realtime;
 | ||
| });
 |