tyo-mq
Version:
Distributed Message Pub/Sub Service with socket.io
377 lines (303 loc) • 9.5 kB
JavaScript
/**
* @file socket.js
*
* A Socket.io connection
*
*/
const Constants = require('./constants');
/**
* Socket Class
*/
function Socket() {
/**
* Autoconnect
*/
this.autoreconnect = true;
/**
* SocketIO instance
*/
this.io = require('socket.io-client');
/**
* Socket Instance from socket.io
*/
this.socket = null;
this.connected = false;
this.auth = null;
this.authInfo = null;
/**
* Socket Id
*/
this.id = function () {
return this.socket.id;
};
this.logger = this.logger || /* (process.env.NODE_ENV === 'production') ? null : */console;
this.onConnectListeners = null;
/**
* Add on connect listener
*/
this.addConnectionListener = function (listener) {
this.onConnectListeners = this.onConnectListeners || [];
this.onConnectListeners.push(listener);
};
var self = this;
this.onDisconnectListener = function(socket) {
self.connected = false;
if (self.logger)
self.logger.log("connection lost");
};
// Only available from the server side
// this.disable = function (what) {
// this.io.disable(what);
// }
/**
* The name of the socket such as a name of an App
*/
this.name = Constants.ANONYMOUS;
/**
* Alias
*/
this.alias = null;
/**
this.serial_id = -1;
this.sendIdentificationInfo = function () {
// do nothing yet
}
*/
/**
* Check if it is connected
*/
this.isConnected = function () {
return this.connected;
}
/**
* On Error
*/
this.onError = function (message) {
if (this.logger && this.logger.error && this.logger.error.apply)
this.logger.error(message);
}
/**
* On Connect
*/
this.onConnect = function (callback) {
this.on("ERROR", function (message) {
if (self.onError)
self.onError.call(self, message);
});
this.authenticate(function (err) {
if (!err)
self.sendIdentificationInfo();
if (callback)
callback(err);
});
}
this.sendIdentificationInfo = function () {};
/**
* On Disconnect
*/
this.onDisconnect = function () {
this.connected = false;
this.logger.log("Socket (" + this.getId() + ") is disconnected");
}
}
Socket.prototype.authenticate = function (callback) {
var self = this;
var auth = this.auth;
if (!auth || (!auth.token && !auth.key && !auth.role && !auth.realm)) {
callback();
return;
}
var done = false;
var timeout = setTimeout(function () {
finish(new Error("Authentication timeout"));
}, auth.timeout || 5000);
function finish (err, info) {
if (done)
return;
done = true;
clearTimeout(timeout);
self.socket.off('AUTH_OK', onAuthOk);
self.socket.off('AUTH_FAIL', onAuthFail);
if (info)
self.authInfo = info;
if (err && self.onError)
self.onError.call(self, err);
callback(err);
}
function onAuthOk (info) {
finish(null, info);
}
function onAuthFail (message) {
finish(new Error((message && message.message) || "Authentication failed"));
}
this.socket.once('AUTH_OK', onAuthOk);
this.socket.once('AUTH_FAIL', onAuthFail);
var payload = {};
if (auth.token) payload.token = auth.token;
if (auth.realm) payload.realm = auth.realm;
if (auth.role) payload.role = auth.role;
if (auth.key) payload.key = auth.key;
this.socket.emit('AUTHENTICATION', payload);
};
/**
*
*/
Socket.prototype.generateConnectionUrl = function() {
var host_url = this.protocol + "://" + this.host + ":" + this.port + "/";
return host_url;
};
/**
* Disconnect
*/
Socket.prototype.disconnect = function (callback) {
if (this.socket && this.socket.connected) {
this.socket.disconnect();
}
};
/**
* Flush
*/
Socket.prototype.flush = function (callback) {
this.socket.flush();
setTimeout(function() {
callback();
}, 10);
};
/**
* Connect to the Socket.io server
*/
Socket.prototype.connect = function (callback, port, host, protocol, args) {
var self = this;
if (this.socket && this.socket.connected) {
if (callback) {
return callback();
}
return;
}
this.host = this.host || host || process.env.TYO_MQ_HOST || 'localhost';
this.port = this.port || port || process.env.TYO_MQ_PORT || '17352';
this.protocol = this.protocol || protocol || 'http';
/**
*/
this.connectString = this.connectString || (this.protocol + "://" + this.host + ':' + this.port);
this.connectWith(callback, this.connectString, args);
};
/**
* Connect to ther server with connection string
*/
Socket.prototype.connectWith = function (callback, connectString, args) {
var self = this;
this.callback = callback;
if (!this.connecting) {
this.socket = this.io.connect(connectString, args || { transports: ["websocket"] });
this.connecting = true;
if (self.logger)
self.logger.log(this.name + " connecting to " + connectString + "...");
this.socket.on('connect', function(socket) {
self.connected = true;
self.connecting = false;
if (self.logger)
self.logger.log(self.name + " connected to message queue server: socket id - " + self.socket.id);
self.onConnect(function (err) {
if (err) {
if (self.callback)
self.callback(err);
return;
}
if (self.onConnectListeners && self.onConnectListeners.length > 0) {
self.onConnectListeners.forEach(function (listener) {
listener();
});
}
if (self.callback) {
self.callback();
}
});
});
// let self has a chance to register a custom onDisconnectListener
this.socket.on('disconnect', (() => {
self.connecting = false;
self.connected = false;
// Drop any in-flight inbound chunk transfers — they will never complete
Object.keys(inboundChunks).forEach(function (key) { delete inboundChunks[key]; });
if (self.onDisconnectListener && typeof self.onDisconnectListener === 'function')
self.onDisconnectListener();
}));
// Reassemble large inbound messages that the server split into CONSUME_CHUNK frames
var inboundChunks = {};
this.socket.on('CONSUME_CHUNK', function (chunk) {
var key = chunk.transferId;
if (!inboundChunks[key]) {
inboundChunks[key] = { parts: new Array(chunk.total), received: 0, event: chunk.event };
}
var transfer = inboundChunks[key];
transfer.parts[chunk.index] = chunk.data;
transfer.received++;
if (transfer.received === chunk.total) {
delete inboundChunks[key];
var assembled;
try {
assembled = JSON.parse(transfer.parts.join(''));
} catch (e) {
if (self.logger) self.logger.warn('CONSUME_CHUNK: reassembly parse failed: ' + e);
return;
}
// Dispatch directly to listeners registered for this consume event
self.socket.listeners(transfer.event).forEach(function (fn) { fn(assembled); });
}
});
}
};
/**
* Send Event Message
*/
Socket.prototype.sendMessage = function (event, msg, from, callback) {
if (!this.socket)
throw new Error("Socket isn't initialized yet");
if (!this.socket.connected) {
/**
* The following code could potentially cause lots of recursive calls
* if the main thread is blocked due to intensive computation
* so let the main thread to handle the reconnection
*/
// var futureFunc = this.socket.emit.bind(this.socket, event, msg);
// if (this.autoreconnect)
// this.connect(function (){
// futureFunc.call();
// });
// else
if (this.logger && this.logger.warn && this.logger.warn.apply)
this.logger.warn("Socket is created but not connected");
if (callback && typeof callback === 'function')
callback(new Error("Socket is created but not connected"));
return;
}
this.socket.emit(event, msg);
if (callback && typeof callback === 'function') {
callback();
}
};
/**
* On Event
*/
Socket.prototype.on = function (event, callback) {
if (event === 'connect') {
this.addConnectionListener(callback);
return;
}
this.socket.on(event, callback);
};
/**
* On Event
*/
Socket.prototype.off = function (event) {
this.socket.off(event);
};
/**
* Get Socket Id
*/
Socket.prototype.getSocketId = function () {
return this.socket ? this.socket.id : null;
};
Socket.prototype.getId = Socket.prototype.getSocketId;
module.exports = Socket;