UNPKG

tyo-mq

Version:

Distributed Message Pub/Sub Service with socket.io

377 lines (303 loc) 9.5 kB
/** * @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;