sbis3-node-ws
Version:
Модуль позволяет использовать ядро интерфейсного фреймворка sbis3-ws(`core.js`) и модуль работы с данными (`Source.js`) в приложении на nodejs.
233 lines (206 loc) • 7.89 kB
JavaScript
define('js!SBIS3.CORE.ServerEventBus/resources/reconnect', [
'browser!cdn!/sockjs/1.0.3-p1/sockjs-min.js'
], function(SockJS) {
/**
* Производим переподключение только в том случае, если код указан в этом хэше и содержит значение true
* @see http://tools.ietf.org/html/rfc6455#section-7.4.1
*/
var RECONNECT_BY_CODE = {
// connection cleanly closed
1000: false,
// endpoint going down
1001: false,
// some protocol error (STOMP 502)
1002: true,
// WTF
1005: true,
// "to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame."
1006: true,
// message too big
1009: true,
// All transports failed
2000: true,
// Transport timeouted
2007: true,
// Transport timeout in reconnect
3007: true
};
var PROTOCOLS = [
'xhr-streaming',
'xhr-polling',
'xdr-streaming',
'xdr-polling',
'iframe-xhr-polling'
];
if (typeof window !== 'undefined' && window.WebSocket) {
PROTOCOLS.unshift('websocket');
}
function ReconnectingSockJS(url) {
// These can be altered by calling code.
this.debug = false;
this.timeoutInterval = 5000;
this.reconnectFailures = 0;
var self = this;
var ws;
var forcedClose = false;
var timedOut = false;
this.url = url;
this.transports = PROTOCOLS;
this.readyState = 0;
this.URL = url; // Public API
this.transport = '';
this.getReconnectInterval = function() {
// Максимальный интервал переподключения - 1 час
return Math.min(Math.exp(this.reconnectFailures / 2) * 300 + 1000, 60 * 60 * 1000);
};
/**
* Reconnect only on unclean connection shutdown
* @param event
* @returns {*|boolean}
*/
this.isNeedReconnect = function(event) {
return (event && RECONNECT_BY_CODE[event.code] || false);
};
this.onopen = function(event) {
};
this.onclose = function(event) {
};
this.onconnecting = function(event) {
};
this.onmessage = function(event) {
};
this.onerror = function(event) {
};
function connect(reconnectAttempt) {
if (forcedClose && (self.debug || ReconnectingSockJS.debugAll)) {
console.error('ReconnectingSockJS', 'connection when forcedClose', url);
}
var config = PROTOCOLS.length ? {
transports: PROTOCOLS
} : undefined;
ws = new SockJS(url, null, config);
self.onconnecting && self.onconnecting();
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'attempt-connect', url);
}
var localWs = ws;
var timeout = setTimeout(function() {
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'connection-timeout', url);
}
timedOut = true;
localWs.close(3007, "Transport timeout");
timedOut = false;
}, self.timeoutInterval);
ws.onopen = function(event) {
clearTimeout(timeout);
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'onopen', url);
}
self.readyState = 1;
self.transport = ws.transport;
reconnectAttempt = false;
self.reconnectFailures = 0;
self.onopen && self.onopen(event);
};
ws.onclose = function(event) {
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'underlyingSocket.onclose', JSON.stringify(event));
}
clearTimeout(timeout);
if (forcedClose) {
self.readyState = 3;
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'manually closed', url);
}
self.onclose && self.onclose(event);
} else {
self.readyState = 0;
if (!reconnectAttempt && !timedOut) {
// Между onclose и onopen все другие onclose игнорируются пока не удастся подключиться
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'onclose', url);
}
self.onclose && self.onclose(event);
// Если кто то решил закрыть сокет во время onclose
if (forcedClose) {
forcedClose = false;
}
}
self.reconnectFailures++;
if (self.isNeedReconnect(event)) {
var interval = self.getReconnectInterval();
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'Planning to reconnect in ' + interval, url);
}
setTimeout(function() {
connect(true);
}, interval);
} else {
self.pausingToReconnectCloseEvent = {
event: event,
stack: (new Error().stack)
};
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'needReconnect=false. Give up reconnecting.', url);
}
}
}
ws = null;
};
ws.onmessage = function(event) {
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'onmessage', url, event.data);
}
self.onmessage && self.onmessage(event);
};
ws.onerror = function(event) {
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'onerror', url, event);
}
self.onerror && self.onerror(event);
};
}
connect();
this.send = function(data) {
if (ws) {
if (self.debug || ReconnectingSockJS.debugAll) {
console.log('ReconnectingSockJS', 'send', url, data);
}
try {
return ws.send(data);
} catch(e) {
if (e.message != 'InvalidStateError: The connection has not been established yet') {
throw e;
}
}
} else if (!!self.pausingToReconnectCloseEvent) {
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket. Close code: ' +
self.pausingToReconnectCloseEvent.event.code + '. \n' +
self.pausingToReconnectCloseEvent.stack;
} else {
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket' + '. \n' + (new Error().stack);
}
};
this.close = function() {
if (ws) {
forcedClose = true;
ws.close();
}
};
/**
* Additional public API method to refresh the connection if still open (close, re-open).
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
*/
this.refresh = function() {
if (ws) {
ws.close();
}
};
}
/**
* Setting this to true is the equivalent of setting all instances of ReconnectingSockJS.debug to true.
*/
ReconnectingSockJS.debugAll = false;
return ReconnectingSockJS;
});