irc-framework
Version:
A better IRC framework for node.js
295 lines (286 loc) • 12.4 kB
JavaScript
;
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.symbol.to-primitive.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.date.to-primitive.js");
require("core-js/modules/es.number.constructor.js");
require("core-js/modules/es.object.create.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.object.get-prototype-of.js");
require("core-js/modules/es.reflect.construct.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/es.array.for-each.js");
require("core-js/modules/es.array.slice.js");
require("core-js/modules/es.date.now.js");
require("core-js/modules/es.date.to-string.js");
require("core-js/modules/es.function.bind.js");
require("core-js/modules/es.object.set-prototype-of.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.regexp.to-string.js");
require("core-js/modules/web.dom-collections.for-each.js");
require("core-js/modules/web.timers.js");
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
var _ = {
pull: require('lodash/pull')
};
var EventEmitter = require('eventemitter3');
var ircLineParser = require('./irclineparser');
module.exports = /*#__PURE__*/function (_EventEmitter) {
function Connection(options) {
var _this;
_classCallCheck(this, Connection);
_this = _callSuper(this, Connection);
_this.options = options || {};
_this.connected = false;
_this.requested_disconnect = false;
_this.reconnect_attempts = 0;
// When an IRC connection was successfully registered.
_this.registered = false;
_this.transport = null;
_this._timers = [];
return _this;
}
_inherits(Connection, _EventEmitter);
return _createClass(Connection, [{
key: "debugOut",
value: function debugOut(out) {
this.emit('debug', out);
}
}, {
key: "registeredSuccessfully",
value: function registeredSuccessfully() {
this.registered = Date.now();
}
}, {
key: "connect",
value: function connect(options) {
var that = this;
if (options) {
this.options = options;
}
options = this.options;
this.auto_reconnect = options.auto_reconnect || false;
this.auto_reconnect_max_retries = options.auto_reconnect_max_retries || 3;
this.auto_reconnect_max_wait = options.auto_reconnect_max_wait || 300000;
if (this.transport) {
this.clearTimers();
this.transport.removeAllListeners();
this.transport.disposeSocket();
}
this.transport = new options.transport(options);
if (!options.encoding || !this.setEncoding(options.encoding)) {
this.setEncoding('utf8');
}
bindTransportEvents(this.transport);
this.registered = false;
this.requested_disconnect = false;
this.emit('connecting');
this.transport.connect();
function bindTransportEvents(transport) {
transport.on('open', socketOpen);
transport.on('line', socketLine);
transport.on('close', socketClose);
transport.on('debug', transportDebug);
transport.on('extra', transportExtra);
}
function transportDebug(out) {
that.debugOut(out);
}
function transportExtra() {
// Some transports may emit extra events
that.emit.apply(that, arguments);
}
// Called when the socket is connected and ready to start sending/receiving data.
function socketOpen() {
that.debugOut('Socket fully connected');
that.reconnect_attempts = 0;
that.connected = true;
that.emit('socket connected');
}
function socketLine(line) {
that.addReadBuffer(line);
}
function socketClose(err) {
var was_connected = that.connected;
var should_reconnect = false;
var safely_registered = false;
var registered_ms_ago = Date.now() - that.registered;
// Some networks use aKills which kill a user after succesfully
// registering instead of a ban, so we must wait some time after
// being registered to be sure that we are connected properly.
safely_registered = that.registered !== false && registered_ms_ago > 5000;
that.debugOut('Socket closed. was_connected=' + was_connected + ' safely_registered=' + safely_registered + ' requested_disconnect=' + that.requested_disconnect);
that.connected = false;
that.clearTimers();
that.emit('socket close', err);
if (that.requested_disconnect || !that.auto_reconnect) {
should_reconnect = false;
// If trying to reconnect, continue with it
} else if (that.reconnect_attempts && that.reconnect_attempts < that.auto_reconnect_max_retries) {
should_reconnect = true;
// If we were originally connected OK, reconnect
} else if (was_connected && safely_registered) {
should_reconnect = true;
} else {
should_reconnect = false;
}
if (should_reconnect) {
var reconnect_wait = that.calculateExponentialBackoff();
that.reconnect_attempts++;
that.emit('reconnecting', {
attempt: that.reconnect_attempts,
max_retries: that.auto_reconnect_max_retries,
wait: reconnect_wait
});
that.debugOut('Scheduling reconnect. Attempt: ' + that.reconnect_attempts + '/' + that.auto_reconnect_max_retries + ' Wait: ' + reconnect_wait + 'ms');
that.setTimeout(function () {
return that.connect();
}, reconnect_wait);
} else {
that.transport.removeAllListeners();
that.emit('close', !!err);
that.reconnect_attempts = 0;
}
}
}
}, {
key: "calculateExponentialBackoff",
value: function calculateExponentialBackoff() {
var jitter = 1000 + Math.floor(Math.random() * 5000);
var attempts = Math.min(this.reconnect_attempts, 30);
var time = 1000 * Math.pow(2, attempts);
return Math.min(time, this.auto_reconnect_max_wait) + jitter;
}
}, {
key: "addReadBuffer",
value: function addReadBuffer(line) {
if (!line) {
// Empty line
return;
}
this.emit('raw', {
line: line,
from_server: true
});
var message = ircLineParser(line);
if (!message) {
return;
}
this.emit('message', message, line);
}
}, {
key: "write",
value: function write(data, callback) {
if (!this.connected || this.requested_disconnect) {
this.debugOut('write() called when not connected');
if (callback) {
setTimeout(callback, 0); // fire in next tick
}
return false;
}
this.emit('raw', {
line: data,
from_server: false
});
return this.transport.writeLine(data, callback);
}
/**
* Create and keep track of all timers so they can be easily removed
*/
}, {
key: "setTimeout",
value: function (_setTimeout) {
function setTimeout() {
return _setTimeout.apply(this, arguments);
}
setTimeout.toString = function () {
return _setTimeout.toString();
};
return setTimeout;
}(function /* fn, length, argN */
() {
var that = this;
var tmr = null;
var args = Array.prototype.slice.call(arguments, 0);
var callback = args[0];
args[0] = function () {
_.pull(that._timers, tmr);
callback.apply(null, args);
};
tmr = setTimeout.apply(null, args);
this._timers.push(tmr);
return tmr;
})
}, {
key: "clearTimeout",
value: function (_clearTimeout) {
function clearTimeout(_x) {
return _clearTimeout.apply(this, arguments);
}
clearTimeout.toString = function () {
return _clearTimeout.toString();
};
return clearTimeout;
}(function (tmr) {
clearTimeout(tmr);
_.pull(this._timers, tmr);
})
}, {
key: "clearTimers",
value: function clearTimers() {
this._timers.forEach(function (tmr) {
clearTimeout(tmr);
});
this._timers = [];
}
/**
* Close the connection to the IRCd after forcing one last line
*/
}, {
key: "end",
value: function end(data, had_error) {
var that = this;
this.debugOut('Connection.end() connected=' + this.connected + ' with data=' + !!data + ' had_error=' + !!had_error);
if (this.connected && data) {
// Once the last bit of data has been sent, then re-run this function to close the socket
this.write(data, function () {
that.end(null, had_error);
});
return;
}
// Shutdowns of the connection may be caused by errors like ping timeouts, which
// are not requested by the user so we leave requested_disconnect as false to make sure any
// reconnects happen.
if (!had_error) {
this.requested_disconnect = true;
this.clearTimers();
}
if (this.transport) {
this.transport.close(!!had_error);
}
}
}, {
key: "setEncoding",
value: function setEncoding(encoding) {
this.debugOut('Connection.setEncoding() encoding=' + encoding);
if (this.transport) {
return this.transport.setEncoding(encoding);
}
}
}]);
}(EventEmitter);