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;
|
||
});
|