@omneedia/socketcluster
Version:
SocketCluster - A Highly parallelized WebSocket server cluster to make the most of multi-core machines/instances.
1,135 lines (985 loc) • 29.3 kB
JavaScript
var fork = require('child_process').fork;
var EventEmitter = require('events').EventEmitter;
var ComSocket = require('ncom').ComSocket;
var FlexiMap = require('fleximap').FlexiMap;
var uuid = require('uuid');
var scErrors = require('sc-errors');
var BrokerError = scErrors.BrokerError;
var TimeoutError = scErrors.TimeoutError;
var DEFAULT_PORT = 9435;
var HOST = '127.0.0.1';
var DEFAULT_CONNECT_RETRY_ERROR_THRESHOLD = 20;
var DEFAULT_IPC_ACK_TIMEOUT = 10000;
var Server = function (options) {
EventEmitter.call(this);
var self = this;
var defaultBrokerControllerPath = __dirname + '/default-broker-controller.js';
var serverOptions = {
id: options.id,
debug: options.debug,
socketPath: options.socketPath,
port: options.port,
expiryAccuracy: options.expiryAccuracy,
downgradeToUser: options.downgradeToUser,
brokerControllerPath: options.brokerControllerPath || defaultBrokerControllerPath,
processTermTimeout: options.processTermTimeout
};
self.options = options;
self._pendingResponseHandlers = {};
var stringArgs = JSON.stringify(serverOptions);
self.socketPath = options.socketPath;
if (!self.socketPath) {
self.port = options.port;
}
if (options.ipcAckTimeout == null) {
self.ipcAckTimeout = DEFAULT_IPC_ACK_TIMEOUT;
} else {
self.ipcAckTimeout = options.ipcAckTimeout;
}
if (!options.brokerOptions) {
options.brokerOptions = {};
}
options.brokerOptions.secretKey = options.secretKey;
options.brokerOptions.instanceId = options.instanceId;
var debugRegex = /^--debug(=[0-9]*)?$/;
var debugBrkRegex = /^--debug-brk(=[0-9]*)?$/;
var inspectRegex = /^--inspect(=[0-9]*)?$/;
var inspectBrkRegex = /^--inspect-brk(=[0-9]*)?$/;
// Brokers should not inherit the master --debug argument
// because they have their own --debug-brokers option.
var execOptions = {
execArgv: process.execArgv.filter(function (arg) {
return !debugRegex.test(arg) && !debugBrkRegex.test(arg) && !inspectRegex.test(arg) && !inspectBrkRegex.test(arg);
}),
env: {}
};
Object.keys(process.env).forEach(function (key) {
execOptions.env[key] = process.env[key];
});
execOptions.env.brokerInitOptions = JSON.stringify(options.brokerOptions);
if (options.debug) {
execOptions.execArgv.push('--debug=' + options.debug);
}
if (options.inspect) {
execOptions.execArgv.push('--inspect=' + options.inspect);
}
self._server = fork(serverOptions.brokerControllerPath, [stringArgs], execOptions);
var formatError = function (error) {
var err = scErrors.hydrateError(error, true);
if (typeof err === 'object') {
if (err.name == null || err.name === 'Error') {
err.name = 'BrokerError';
}
err.brokerPid = self._server.pid;
}
return err;
};
self._server.on('error', function (error) {
var err = formatError(error);
self.emit('error', err);
});
self._server.on('message', function (value) {
if (value.type === 'error') {
var err = formatError(value.data);
self.emit('error', err);
} else if (value.type === 'brokerMessage') {
self.emit('brokerMessage', value.brokerId, value.data, function (err, data) {
if (value.cid) {
self._server.send({
type: 'masterResponse',
error: scErrors.dehydrateError(err, true),
data: data,
rid: value.cid
});
}
});
} else if (value.type === 'brokerResponse') {
var responseHandler = self._pendingResponseHandlers[value.rid];
if (responseHandler) {
clearTimeout(responseHandler.timeout);
delete self._pendingResponseHandlers[value.rid];
var properError = scErrors.hydrateError(value.error, true);
responseHandler.callback(properError, value.data, value.brokerId);
}
} else if (value.type === 'listening') {
self.emit('ready', value.data);
}
});
self._server.on('exit', function (code, signal) {
self.emit('exit', {
id: options.id,
pid: self._server.pid,
code: code,
signal: signal
});
});
self.destroy = function () {
self._server.kill('SIGTERM');
};
self._createIPCResponseHandler = function (callback) {
var cid = uuid.v4();
var responseTimeout = setTimeout(function () {
var responseHandler = self._pendingResponseHandlers[cid];
delete self._pendingResponseHandlers[cid];
var timeoutError = new TimeoutError('IPC response timed out');
responseHandler.callback(timeoutError);
}, self.ipcAckTimeout);
self._pendingResponseHandlers[cid] = {
callback: callback,
timeout: responseTimeout
};
return cid;
};
self.sendToBroker = function (data, callback) {
var messagePacket = {
type: 'masterMessage',
data: data
};
if (callback) {
messagePacket.cid = self._createIPCResponseHandler(callback);
}
self._server.send(messagePacket);
};
};
Server.prototype = Object.create(EventEmitter.prototype);
module.exports.createServer = function (options) {
if (!options) {
options = {};
}
if (!options.socketPath && !options.port) {
options.port = DEFAULT_PORT;
}
return new Server(options);
};
var Client = function (options) {
var self = this;
var secretKey = options.secretKey || null;
var timeout = options.timeout;
self.socketPath = options.socketPath;
self.port = options.port;
self.host = options.host;
if (options.autoReconnect == null) {
self.autoReconnect = true;
} else {
self.autoReconnect = options.autoReconnect;
}
if (self.autoReconnect) {
if (options.autoReconnectOptions == null) {
options.autoReconnectOptions = {};
}
var reconnectOptions = options.autoReconnectOptions;
if (reconnectOptions.initialDelay == null) {
reconnectOptions.initialDelay = 200;
}
if (reconnectOptions.randomness == null) {
reconnectOptions.randomness = 100;
}
if (reconnectOptions.multiplier == null) {
reconnectOptions.multiplier = 1.3;
}
if (reconnectOptions.maxDelay == null) {
reconnectOptions.maxDelay = 1000;
}
self.autoReconnectOptions = reconnectOptions;
}
if (options.connectRetryErrorThreshold == null) {
self.connectRetryErrorThreshold = DEFAULT_CONNECT_RETRY_ERROR_THRESHOLD;
} else {
self.connectRetryErrorThreshold = options.connectRetryErrorThreshold;
}
self.CONNECTED = 'connected';
self.CONNECTING = 'connecting';
self.DISCONNECTED = 'disconnected';
self.state = self.DISCONNECTED;
if (timeout) {
self._timeout = timeout;
} else {
self._timeout = 10000;
}
self._subscriptionMap = {};
self._commandMap = {};
self._pendingBuffer = [];
self._pendingSubscriptionBuffer = [];
self.connectAttempts = 0;
self.pendingReconnect = false;
self.pendingReconnectTimeout = null;
var createSocket = function () {
if (self._socket) {
self._socket.removeAllListeners();
}
self._socket = new ComSocket();
if (options.pubSubBatchDuration != null) {
self._socket.batchDuration = options.pubSubBatchDuration;
}
self._socket.on('connect', self._connectHandler);
self._socket.on('error', function (err) {
var isConnectionFailure = err.code === 'ENOENT' || err.code === 'ECONNREFUSED';
var isBelowRetryThreshold = self.connectAttempts < self.connectRetryErrorThreshold;
// We can tolerate a few missed reconnections without emitting a full error.
if (isConnectionFailure && isBelowRetryThreshold && err.address === options.socketPath) {
self.emit('warning', err);
} else {
self.emit('error', err);
}
});
self._socket.on('close', handleDisconnection);
self._socket.on('end', handleDisconnection);
self._socket.on('message', function (response) {
var id = response.id;
var rawError = response.error;
var error = null;
if (rawError != null) {
error = scErrors.hydrateError(rawError, true);
}
if (response.type === 'response') {
if (self._commandMap.hasOwnProperty(id)) {
clearTimeout(self._commandMap[id].timeout);
var action = response.action;
var callback = self._commandMap[id].callback;
delete self._commandMap[id];
if (response.value !== undefined) {
callback(error, response.value);
} else {
callback(error);
}
}
} else if (response.type === 'message') {
self.emit('message', response.channel, response.value);
}
});
};
self._curID = 1;
self.MAX_ID = Math.pow(2, 53) - 2;
self.setMaxListeners(0);
self._tryReconnect = function (initialDelay) {
var exponent = self.connectAttempts++;
var reconnectOptions = self.autoReconnectOptions;
var timeout;
if (initialDelay == null || exponent > 0) {
var initialTimeout = Math.round(reconnectOptions.initialDelay + (reconnectOptions.randomness || 0) * Math.random());
timeout = Math.round(initialTimeout * Math.pow(reconnectOptions.multiplier, exponent));
} else {
timeout = initialDelay;
}
if (timeout > reconnectOptions.maxDelay) {
timeout = reconnectOptions.maxDelay;
}
clearTimeout(self._reconnectTimeoutRef);
self.pendingReconnect = true;
self.pendingReconnectTimeout = timeout;
self._reconnectTimeoutRef = setTimeout(function () {
self._connect();
}, timeout);
};
self._genID = function () {
self._curID = (self._curID + 1) % self.MAX_ID;
return 'n' + self._curID;
};
self._flushPendingBuffers = function () {
var subBufLen = self._pendingSubscriptionBuffer.length;
for (var i = 0; i < subBufLen; i++) {
var subCommandData = self._pendingSubscriptionBuffer[i];
self._execCommand(subCommandData.command, subCommandData.options);
}
self._pendingSubscriptionBuffer = [];
var bufLen = self._pendingBuffer.length;
for (var j = 0; j < bufLen; j++) {
var commandData = self._pendingBuffer[j];
self._execCommand(commandData.command, commandData.options);
}
self._pendingBuffer = [];
};
self._flushPendingBuffersIfConnected = function () {
if (self.state === self.CONNECTED) {
self._flushPendingBuffers();
}
};
self._prepareAndTrackCommand = function (command, callback) {
command.id = self._genID();
if (callback) {
var request = {callback: callback, command: command};
self._commandMap[command.id] = request;
request.timeout = setTimeout(function () {
var error = new TimeoutError('Broker Error - The ' + command.action + ' action timed out');
delete request.callback;
if (self._commandMap.hasOwnProperty(command.id)) {
delete self._commandMap[command.id];
}
callback(error);
}, self._timeout);
}
};
self._bufferSubscribeCommand = function (command, callback, options) {
self._prepareAndTrackCommand(command, callback);
// Clone the command argument to prevent the user from modifying the data
// whilst the command is still pending in the buffer.
var commandData = {
command: JSON.parse(JSON.stringify(command)),
options: options
};
self._pendingSubscriptionBuffer.push(commandData);
};
self._bufferCommand = function (command, callback, options) {
self._prepareAndTrackCommand(command, callback);
// Clone the command argument to prevent the user from modifying the data
// whilst the command is still pending in the buffer.
var commandData = {
command: JSON.parse(JSON.stringify(command)),
options: options
};
self._pendingBuffer.push(commandData);
};
// Recovers subscriptions after Broker server crash
self._resubscribeAll = function () {
var hasFailed = false;
var handleResubscribe = function (channel, err) {
if (err) {
if (!hasFailed) {
hasFailed = true;
self.emit('error', new BrokerError('Failed to resubscribe to Broker server channels'));
}
}
};
var channels = self._subscriptionMap;
for (var i in channels) {
if (channels.hasOwnProperty(i)) {
self.subscribe(i, handleResubscribe.bind(self, i), true);
}
}
};
self._connectHandler = function () {
var command = {
action: 'init',
secretKey: secretKey
};
var initHandler = function (err, brokerInfo) {
if (err) {
self.emit('error', err);
} else {
self.state = self.CONNECTED;
self.connectAttempts = 0;
self._resubscribeAll();
self._flushPendingBuffers();
self.emit('ready', brokerInfo);
}
};
self._prepareAndTrackCommand(command, initHandler);
self._execCommand(command);
};
self._connect = function () {
if (self.state === self.DISCONNECTED) {
self.pendingReconnect = false;
self.pendingReconnectTimeout = null;
clearTimeout(self._reconnectTimeoutRef);
self.state = self.CONNECTING;
createSocket();
if (self.socketPath) {
self._socket.connect(self.socketPath);
} else {
self._socket.connect(self.port, self.host);
}
}
};
var handleDisconnection = function () {
self.state = self.DISCONNECTED;
self.pendingReconnect = false;
self.pendingReconnectTimeout = null;
clearTimeout(self._reconnectTimeoutRef);
self._pendingBuffer = [];
self._pendingSubscriptionBuffer = [];
self._tryReconnect();
};
self._connect();
self._execCommand = function (command, options) {
self._socket.write(command, options);
};
self.isConnected = function () {
return self.state === self.CONNECTED;
};
self.extractKeys = function (object) {
return Object.keys(object);
};
self.extractValues = function (object) {
var array = [];
for (var i in object) {
if (object.hasOwnProperty(i)) {
array.push(object[i]);
}
}
return array;
};
self._getPubSubExecOptions = function () {
var execOptions = {};
if (options.pubSubBatchDuration != null) {
execOptions.batch = true;
}
return execOptions;
};
self.subscribe = function (channel, ackCallback, force) {
if (!force && self.isSubscribed(channel)) {
ackCallback && ackCallback();
} else {
self._subscriptionMap[channel] = 'pending';
var command = {
channel: channel,
action: 'subscribe'
};
var callback = function (err) {
if (err) {
ackCallback && ackCallback(err);
self.emit('subscribeFail', err, channel);
} else {
self._subscriptionMap[channel] = 'subscribed';
ackCallback && ackCallback();
self.emit('subscribe', channel);
}
};
var execOptions = self._getPubSubExecOptions();
self._connect();
self._bufferSubscribeCommand(command, callback, execOptions);
self._flushPendingBuffersIfConnected();
}
};
self.unsubscribe = function (channel, ackCallback) {
// No need to unsubscribe if the server is disconnected
// The server cleans up automatically in case of disconnection
if (self.isSubscribed(channel) && self.state === self.CONNECTED) {
delete self._subscriptionMap[channel];
var command = {
action: 'unsubscribe',
channel: channel
};
var cb = function (err) {
// Unsubscribe can never fail because TCP guarantees
// delivery for the life of the connection. If the
// connection fails then all subscriptions
// will be cleared automatically anyway.
ackCallback && ackCallback();
self.emit('unsubscribe');
};
var execOptions = self._getPubSubExecOptions();
self._bufferCommand(command, cb, execOptions);
self._flushPendingBuffers();
} else {
delete self._subscriptionMap[channel];
ackCallback && ackCallback();
}
};
self.subscriptions = function (includePending) {
var allSubs = Object.keys(self._subscriptionMap || {});
if (includePending) {
return allSubs;
}
var activeSubs = [];
var len = allSubs.length;
for (var i = 0; i < len; i++) {
var sub = allSubs[i];
if (self._subscriptionMap[sub] === 'subscribed') {
activeSubs.push(sub);
}
}
return activeSubs;
};
self.isSubscribed = function (channel, includePending) {
if (includePending) {
return !!self._subscriptionMap[channel];
}
return self._subscriptionMap[channel] === 'subscribed';
};
self.publish = function (channel, value, callback) {
var command = {
action: 'publish',
channel: channel,
value: value,
getValue: 1
};
var execOptions = self._getPubSubExecOptions();
self._connect();
self._bufferCommand(command, callback, execOptions);
self._flushPendingBuffersIfConnected();
};
self.send = function (data, callback) {
var command = {
action: 'send',
value: data
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
set(key, value,[ options, callback])
*/
self.set = function () {
var key = arguments[0];
var value = arguments[1];
var options = {
getValue: 0
};
var callback;
if (arguments[2] instanceof Function) {
callback = arguments[2];
} else {
options.getValue = arguments[2];
callback = arguments[3];
}
var command = {
action: 'set',
key: key,
value: value
};
if (options.getValue) {
command.getValue = 1;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
expire(keys, seconds,[ callback])
*/
self.expire = function (keys, seconds, callback) {
var command = {
action: 'expire',
keys: keys,
value: seconds
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
unexpire(keys,[ callback])
*/
self.unexpire = function (keys, callback) {
var command = {
action: 'unexpire',
keys: keys
};
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
getExpiry(key,[ callback])
*/
self.getExpiry = function (key, callback) {
var command = {
action: 'getExpiry',
key: key
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
add(key, value,[ options, callback])
*/
self.add = function () {
var key = arguments[0];
var value = arguments[1];
var callback;
if (arguments[2] instanceof Function) {
callback = arguments[2];
} else {
callback = arguments[3];
}
var command = {
action: 'add',
key: key,
value: value
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
concat(key, value,[ options, callback])
*/
self.concat = function () {
var key = arguments[0];
var value = arguments[1];
var options = {
getValue: 0
};
var callback;
if (arguments[2] instanceof Function) {
callback = arguments[2];
} else {
options.getValue = arguments[2];
callback = arguments[3];
}
var command = {
action: 'concat',
key: key,
value: value
};
if (options.getValue) {
command.getValue = 1;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self.get = function (key, callback) {
var command = {
action: 'get',
key: key
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
getRange(key, fromIndex,[ toIndex,] callback)
*/
self.getRange = function () {
var key = arguments[0];
var fromIndex = arguments[1];
var toIndex = null;
var callback;
if (arguments[2] instanceof Function) {
callback = arguments[2];
} else {
toIndex = arguments[2];
callback = arguments[3];
}
var command = {
action: 'getRange',
key: key,
fromIndex: fromIndex
};
if (toIndex) {
command.toIndex = toIndex;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self.getAll = function (callback) {
var command = {
action: 'getAll'
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self.count = function (key, callback) {
var command = {
action: 'count',
key: key
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self._stringifyQuery = function (query, data) {
query = query.toString();
var validVarNameRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
var headerString = '';
for (var i in data) {
if (data.hasOwnProperty(i)) {
if (!validVarNameRegex.test(i)) {
throw new BrokerError("The variable name '" + i + "' is invalid");
}
headerString += 'var ' + i + '=' + JSON.stringify(data[i]) + ';';
}
}
query = query.replace(/^(function *[(][^)]*[)] *{)/, function (match) {
return match + headerString;
});
return query;
};
/*
registerDeathQuery(query,[ data, callback])
*/
self.registerDeathQuery = function () {
var data;
var callback = null;
if (arguments[1] instanceof Function) {
data = arguments[0].data || {};
callback = arguments[1];
} else if (arguments[1]) {
data = arguments[1];
callback = arguments[2];
} else {
data = arguments[0].data || {};
}
var query = self._stringifyQuery(arguments[0], data);
if (query) {
var command = {
action: 'registerDeathQuery',
value: query
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
} else {
callback && callback('Invalid query format - Query must be a string or a function');
}
};
/*
exec(query,[ options, callback])
*/
self.exec = function () {
var data;
var baseKey = null;
var noAck = null;
var callback = null;
if (arguments[0].data) {
data = arguments[0].data;
} else {
data = {};
}
if (arguments[1] instanceof Function) {
callback = arguments[1];
} else if (arguments[1]) {
baseKey = arguments[1].baseKey;
noAck = arguments[1].noAck;
if (arguments[1].data) {
data = arguments[1].data;
}
callback = arguments[2];
}
var query = self._stringifyQuery(arguments[0], data);
if (query) {
var command = {
action: 'exec',
value: query
};
if (baseKey) {
command.baseKey = baseKey;
}
if (noAck) {
command.noAck = noAck;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
} else {
callback && callback('Invalid query format - Query must be a string or a function');
}
};
/*
query(query,[ data, callback])
*/
self.query = function () {
if (arguments[1] && !(arguments[1] instanceof Function)) {
var options = {data: arguments[1]};
self.exec(arguments[0], options, arguments[2]);
} else {
self.exec.apply(self, arguments);
}
};
/*
remove(key,[ options, callback])
*/
self.remove = function () {
var key = arguments[0];
var options = {
getValue: 0
};
var callback;
if (arguments[1] instanceof Function) {
callback = arguments[1];
} else {
if (arguments[1] instanceof Object) {
options = arguments[1];
} else {
options.getValue = arguments[1];
}
callback = arguments[2];
}
var command = {
action: 'remove',
key: key
};
if (options.getValue) {
command.getValue = 1;
}
if (options.noAck) {
command.noAck = 1;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
removeRange(key, fromIndex,[ options, callback])
*/
self.removeRange = function () {
var key = arguments[0];
var fromIndex = arguments[1];
var options = {
toIndex: null,
getValue: 0
};
var callback;
if (arguments[2] instanceof Function) {
callback = arguments[2];
} else if (arguments[3] instanceof Function) {
if (arguments[2] instanceof Object) {
options = arguments[2];
} else {
options.toIndex = arguments[2];
}
callback = arguments[3];
} else {
options.toIndex = arguments[2];
options.getValue = arguments[3];
callback = arguments[4];
}
var command = {
action: 'removeRange',
fromIndex: fromIndex,
key: key
};
if (options.toIndex) {
command.toIndex = options.toIndex;
}
if (options.getValue) {
command.getValue = 1;
}
if (options.noAck) {
command.noAck = 1;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self.removeAll = function (callback) {
var command = {
action: 'removeAll'
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
splice(key,[ options, callback])
The following options are supported:
- fromIndex
- count // Number of items to delete
- items // Must be an Array of items to insert as part of splice
*/
self.splice = function () {
var key = arguments[0];
var index = arguments[1];
var options = {};
var callback;
if (arguments[2] instanceof Function) {
options = arguments[1];
callback = arguments[2];
} else if (arguments[1] instanceof Function) {
callback = arguments[1];
} else if (arguments[1]) {
options = arguments[1];
}
var command = {
action: 'splice',
key: key
};
if (options.index != null) {
command.index = options.index;
}
if (options.count != null) {
command.count = options.count;
}
if (options.items != null) {
command.items = options.items;
}
if (options.getValue) {
command.getValue = 1;
}
if (options.noAck) {
command.noAck = 1;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
/*
pop(key,[ options, callback])
*/
self.pop = function () {
var key = arguments[0];
var options = {
getValue: 0
};
var callback;
if (arguments[1] instanceof Function) {
callback = arguments[1];
} else {
options.getValue = arguments[1];
callback = arguments[2];
}
var command = {
action: 'pop',
key: key
};
if (options.getValue) {
command.getValue = 1;
}
if (options.noAck) {
command.noAck = 1;
}
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self.hasKey = function (key, callback) {
var command = {
action: 'hasKey',
key: key
};
self._connect();
self._bufferCommand(command, callback);
self._flushPendingBuffersIfConnected();
};
self.end = function (callback) {
clearTimeout(self._reconnectTimeoutRef);
self.unsubscribe(null, function () {
if (callback) {
var disconnectCallback = function () {
if (disconnectTimeout) {
clearTimeout(disconnectTimeout);
}
setTimeout(callback, 0);
self._socket.removeListener('end', disconnectCallback);
};
var disconnectTimeout = setTimeout(function () {
self._socket.removeListener('end', disconnectCallback);
callback('Disconnection timed out');
}, self._timeout);
if (self._socket.connected) {
self._socket.on('end', disconnectCallback);
} else {
disconnectCallback();
}
}
var setDisconnectStatus = function () {
self._socket.removeListener('end', setDisconnectStatus);
self.state = self.DISCONNECTED;
};
if (self._socket.connected) {
self._socket.on('end', setDisconnectStatus);
self._socket.end();
} else {
self._socket.destroy();
self.state = self.DISCONNECTED;
}
});
};
};
Client.prototype = Object.create(EventEmitter.prototype);
module.exports.createClient = function (options) {
if (!options) {
options = {};
}
if (!options.socketPath && !options.port) {
options.port = DEFAULT_PORT;
}
if (!options.socketPath && !options.host) {
options.host = HOST;
}
return new Client(options);
};