'use strict';
/**
* Creates event emitter, which will pass connection to listeners,
* when connect function callback will be called.
*
* @constructor
* @param {Object} [options] - Options for establishing connection
* @param {Number} [options.retries=5] - Number of retries before emitting error
* @param {Number} [options.reconnectWait=1000] - Milliseconds between reconnect trys.
* @param {Function} [options.heartbeat] - Function, that will check connection from time to time.
* @param {Number} [options.heartbeatTimeout=800] - Milliseconds before heartbeat function counts as failed.
* @param {Number} [options.heartbeatInterval=1000] - Milliseconds between heartbeats calls.
* @param {Function} connect - Function to be called with following arguments to get connection
* @param {...Object} [arguments] - Arguments for connect function
*/
function Connection() {
var args = Array.prototype.slice.apply(arguments);
this.options = (args.length > 1 && typeof args[0] === 'object') ? args.shift() : {};
this.options.retries = this.options.retries || 5;
this.retries = this.options.retries;
this.options.reconnectWait = this.options.reconnectWait || 1000;
if (this.options.heartbeat && typeof this.options.heartbeat !== 'function') { throw new Error('Provided heartbeat is not a function'); }
this.options.heartbeatTimeout = this.options.heartbeatTimeout || 800;
this.options.heartbeatInterval = this.options.heartbeatInterval || 1000;
this.connect = args.shift();
if (typeof this.connect !== 'function') { throw new Error('Provided callback is not a function'); }
this.arguments = args || [];
this.connect.apply(
this.connect,
args.concat([this.retry.bind(this)])
);
}
Connection.prototype = Object.create(require('events').EventEmitter.prototype);
/**
* Callback, that will be passed to connect function. When connection is available
* it will store results of connect function and emit `available` event with them.
* If error returned from connect function it will retry to connect accordingly to
* options and emit `available` with error as first argument on retries == 0.
*
* @method
*/
Connection.prototype.retry = function retry() {
var args = Array.prototype.slice.call(arguments);
var error = args[0];
this.retries --;
if (!error || this.retries <= 0) {
this.retries = this.options.retries;
this.result = args;
if (!error && this.options.heartbeat) {
this.checkPulse(args);
}
/**
* Emitted once, when connection is available. For retrieving saved results use `when` method.
* Contains all arguments that was called by connect function callback.
*
* @event Connection#available
*/
return this.emit.apply(this,
['available']
.concat(this.result)
);
}
/**
* Emitted on each reconnection try. Contains error, that happened on connection try
*
* @event Connection#reconnect
*/
this.emit('reconnect', error);
return setTimeout(function () {
this.connect.apply(
this.connect,
this.arguments.concat([this.retry.bind(this)])
);
}.bind(this), this.options.reconnectWait);
};
/**
* This method starts checking connection with heartbeat function.
*
* @method
*/
Connection.prototype.checkPulse = function checkPulse(args) {
var timer = setTimeout(function () {
this.retry(new Error('Connection is dead'));
}.bind(this), this.options.heartbeatTimeout);
this.options.heartbeat.apply(
this,
args.concat([function () {
clearTimeout(timer);
setTimeout(this.checkPulse.bind(this), this.options.heartbeatInterval, args);
}.bind(this)])
);
};
/**
* This is wrapper around `once` method. If event is equal `available` -
* then it will check saved results from callback, and they absent attach
* `callback` with `once` method on event `available`.
*
* @method
*/
Connection.prototype.when = function when() {
var args = Array.prototype.slice.call(arguments);
if (this.result && // if it when('available', funciton)
args.length === 2 &&
args[0] === 'available' &&
typeof args[1] === 'function') { // then apply cached results
return args[1].apply(null, this.result);
}
this.once.apply(this, args);
};
module.exports = Connection;