UNPKG

occaecatidicta

Version:
645 lines (574 loc) 20 kB
import { getLogger } from 'omelox-logger'; import { MqttServer, MqttSocket } from '../protocol/mqtt/mqttServer'; import { EventEmitter } from 'events'; import { MasterSocket } from './masterSocket'; import * as protocol from '../util/protocol'; import * as utils from '../util/utils'; import * as Util from 'util'; import { ConsoleService } from '../consoleService'; import { ServerInfo, AdminUserInfo, AdminServerInfo, Callback } from '../util/constants'; import * as path from 'path'; import { MqttConnection } from '../protocol/mqtt/mqttConnectorDefine'; let logger = getLogger('omelox-admin', path.basename(__filename)); let ST_INITED = 1; let ST_STARTED = 2; let ST_CLOSED = 3; export type WhiteList = string[]; export interface MasterAgentOptions { whitelist?: WhiteList; } export interface AgentClient { id: string; type: string; pid: string; info: AdminUserInfo | ServerInfo; socket: MqttSocket; } export interface AuthUserRequest { username: string; password: string; md5: string; id: string; type: string; } export interface AuthServerRequest { id: string; serverType: string; token: string; pid: string; info: ServerInfo; } /** * MasterAgent Constructor * * @class MasterAgent * @constructor * @param {Object} opts construct parameter * opts.consoleService {Object} consoleService * opts.id {String} server id * opts.type {String} server type, 'master', 'connector', etc. * opts.socket {Object} socket-io object * opts.reqId {Number} reqId add by 1 * opts.callbacks {Object} callbacks * opts.state {Number} MasterAgent state * @api public */ export class MasterAgent extends EventEmitter { reqId = 1; idMap: { [serverId: string]: AgentClient } = {}; msgMap: { [serverId: string]: { [reqId: number]: { moduleId: string, msg: any } } } = {}; typeMap: { [type: string]: AgentClient[] } = {}; clients: { [id: string]: AgentClient } = {}; sockets: { [id: string]: MqttConnection } = {}; slaveMap: { [serverId: string]: AgentClient[] } = {}; server: MqttServer = null; callbacks: { [reqId: number]: Callback } = {}; state = ST_INITED; whitelist: WhiteList; consoleService: ConsoleService; constructor(consoleService: ConsoleService, opts: MasterAgentOptions) { super(); this.whitelist = opts.whitelist; this.consoleService = consoleService; } /** * master listen to a port and handle register and request * * @param {String} port * @api public */ listen(port: number, cb: (err?: Error) => void) { if (this.state > ST_INITED) { logger.error('master agent has started or closed.'); return; } this.state = ST_STARTED; this.server = new MqttServer(); this.server.listen(port); // this.server = sio.listen(port); // this.server.set('log level', 0); cb = cb || function () { }; let self = this; this.server.on('error', function (err) { self.emit('error', err); cb(err); }); this.server.once('listening', function () { setImmediate(function () { cb(); }); }); this.server.on('connection', function (socket) { // let id, type, info, registered, username; let masterSocket = new MasterSocket(); masterSocket['agent'] = self; masterSocket['socket'] = socket; self.sockets[socket.id] = socket; socket.on('register', function (msg: any) { // register a new connection masterSocket.onRegister(msg); }); // end of on 'register' // message from monitor socket.on('monitor', function (msg: any) { masterSocket.onMonitor(msg); }); // end of on 'monitor' // message from client socket.on('client', function (msg: any) { masterSocket.onClient(msg); }); // end of on 'client' socket.on('reconnect', function (msg: any) { masterSocket.onReconnect(msg); }); socket.on('disconnect', function () { masterSocket.onDisconnect(); }); socket.on('close', function () { masterSocket.onDisconnect(); }); socket.on('error', function (err: Error) { masterSocket.onError(err); }); }); // end of on 'connection' } // end of listen /** * close master agent * * @api public */ close() { if (this.state > ST_STARTED) { return; } this.state = ST_CLOSED; this.server.close(); } /** * set module * * @param {String} moduleId module id/name * @param {Object} value module object * @api public */ set(moduleId: string, value: any) { this.consoleService.set(moduleId, value); } /** * get module * * @param {String} moduleId module id/name * @api public */ get(moduleId: string) { return this.consoleService.get(moduleId); } /** * getClientById * * @param {String} clientId * @api public */ getClientById(clientId: string) { return this.clients[clientId]; } /** * request monitor{master node} data from monitor * * @param {String} serverId * @param {String} moduleId module id/name * @param {Object} msg * @param {Function} callback function * @api public */ request(serverId: string, moduleId: string, msg: any, cb: (errOrResult?: Error | any, body?: any) => void) { if (this.state > ST_STARTED) { return false; } cb = cb || function () { }; let curId = this.reqId++; this.callbacks[curId] = cb; if (!this.msgMap[serverId]) { this.msgMap[serverId] = {}; } this.msgMap[serverId][curId] = { moduleId: moduleId, msg: msg }; let record = this.idMap[serverId]; if (!record) { cb(new Error('unknown server id:' + serverId)); return false; } this.sendToMonitor(record.socket, curId, moduleId, msg); return true; } /** * request server data from monitor by serverInfo{host:port} * * @param {String} serverId * @param {Object} serverInfo * @param {String} moduleId module id/name * @param {Object} msg * @param {Function} callback function * @api public */ requestServer(serverId: string, serverInfo: ServerInfo, moduleId: string, msg: any, cb: Callback) { if (this.state > ST_STARTED) { return false; } let record = this.idMap[serverId]; if (!record) { utils.invokeCallback(cb, new Error('unknown server id:' + serverId)); return false; } let curId = this.reqId++; this.callbacks[curId] = cb; if (utils.compareServer(record.info as ServerInfo, serverInfo)) { this.sendToMonitor(record.socket, curId, moduleId, msg); } else { let slaves = this.slaveMap[serverId]; for (let i = 0, l = slaves.length; i < l; i++) { if (utils.compareServer(slaves[i].info as ServerInfo, serverInfo)) { this.sendToMonitor(slaves[i].socket, curId, moduleId, msg); break; } } } return true; } /** * notify a monitor{master node} by id without callback * * @param {String} serverId * @param {String} moduleId module id/name * @param {Object} msg * @api public */ notifyById(serverId: string, moduleId: string, msg: any) { if (this.state > ST_STARTED) { return false; } let record = this.idMap[serverId]; if (!record) { logger.error('fail to notifyById for unknown server id:' + serverId); return false; } this.sendToMonitor(record.socket, null, moduleId, msg); return true; } /** * notify a monitor by server{host:port} without callback * * @param {String} serverId * @param {Object} serverInfo{host:port} * @param {String} moduleId module id/name * @param {Object} msg * @api public */ notifyByServer(serverId: string, serverInfo: ServerInfo, moduleId: string, msg: any) { if (this.state > ST_STARTED) { return false; } let record = this.idMap[serverId]; if (!record) { logger.error('fail to notifyByServer for unknown server id:' + serverId); return false; } if (utils.compareServer(record.info as ServerInfo, serverInfo)) { this.sendToMonitor(record.socket, null, moduleId, msg); } else { let slaves = this.slaveMap[serverId]; for (let i = 0, l = slaves.length; i < l; i++) { if (utils.compareServer(slaves[i].info as ServerInfo, serverInfo)) { this.sendToMonitor(slaves[i].socket, null, moduleId, msg); break; } } } return true; } /** * notify slaves by id without callback * * @param {String} serverId * @param {String} moduleId module id/name * @param {Object} msg * @api public */ notifySlavesById(serverId: string, moduleId: string, msg: any) { if (this.state > ST_STARTED) { return false; } let slaves = this.slaveMap[serverId]; if (!slaves || slaves.length === 0) { logger.error('fail to notifySlavesById for unknown server id:' + serverId); return false; } this.broadcastMonitors(slaves, moduleId, msg); return true; } /** * notify monitors by type without callback * * @param {String} type serverType * @param {String} moduleId module id/name * @param {Object} msg * @api public */ notifyByType(type: string, moduleId: string, msg: any) { if (this.state > ST_STARTED) { return false; } let list = this.typeMap[type]; if (!list || list.length === 0) { logger.error('fail to notifyByType for unknown server type:' + type); return false; } this.broadcastMonitors(list, moduleId, msg); return true; } /** * notify all the monitors without callback * * @param {String} moduleId module id/name * @param {Object} msg * @api public */ notifyAll(moduleId: string, msg?: any) { if (this.state > ST_STARTED) { return false; } this.broadcastMonitors(this.idMap, moduleId, msg); return true; } /** * notify a client by id without callback * * @param {String} clientId * @param {String} moduleId module id/name * @param {Object} msg * @api public */ notifyClient(clientId: string, moduleId: string, msg: any) { if (this.state > ST_STARTED) { return false; } let record = this.clients[clientId]; if (!record) { logger.error('fail to notifyClient for unknown client id:' + clientId); return false; } this.sendToClient(record.socket, null, moduleId, msg); } notifyCommand(command: string, moduleId: string, msg: any) { if (this.state > ST_STARTED) { return false; } this.broadcastCommand(this.idMap, command, moduleId, msg); return true; } doAuthUser(msg: AuthUserRequest, socket: MqttSocket, cb: Callback) { if (!msg.id) { // client should has a client id return cb(new Error('client should has a client id')); } let self = this; let username = msg.username; if (!username) { // client should auth with username this.doSend(socket, 'register', { code: protocol.PRO_FAIL, msg: 'client should auth with username' }); return cb(new Error('client should auth with username')); } let authUser = self.consoleService.authUser; let env = self.consoleService.env; authUser(msg, env, (user) => { if (!user) { // client should auth with username this.doSend(socket, 'register', { code: protocol.PRO_FAIL, msg: 'client auth failed with username or password error' }); return cb(new Error('client auth failed with username or password error')); } if (self.clients[msg.id]) { this.doSend(socket, 'register', { code: protocol.PRO_FAIL, msg: 'id has been registered. id:' + msg.id }); return cb(new Error('id has been registered. id:' + msg.id)); } logger.info('client user : ' + username + ' login to master'); this.addConnection(msg.id, msg.type, null, user, socket); this.doSend(socket, 'register', { code: protocol.PRO_OK, msg: 'ok' }); cb(); }); } doAuthServer(msg: AuthServerRequest, socket: MqttSocket, cb: Callback) { let self = this; let authServer = self.consoleService.authServer; let env = self.consoleService.env; authServer(msg, env, (status) => { if (status !== 'ok') { this.doSend(socket, 'register', { code: protocol.PRO_FAIL, msg: 'server auth failed,check config `adminServer`.' }); cb(new Error('server auth failed,check config `adminServer`.')); return; } let record = this.addConnection(msg.id, msg.serverType, msg.pid, msg.info, socket); this.doSend(socket, 'register', { code: protocol.PRO_OK, msg: 'ok' }); msg.info = msg.info; msg.info.pid = msg.pid; self.emit('register', msg.info); cb(null); }); } /** * add monitor,client to connection -- idMap * * @param {Object} agent agent object * @param {String} id * @param {String} type serverType * @param {Object} socket socket-io object * @api private */ addConnection(id: string, type: string, pid: string, info: AdminUserInfo | ServerInfo, socket: MqttSocket) { let record: AgentClient = { id: id, type: type, pid: pid, info: info, socket: socket }; if (type === 'client') { this.clients[id] = record; } else { if (!this.idMap[id]) { this.idMap[id] = record; let list = this.typeMap[type] = this.typeMap[type] || []; list.push(record); } else { let slaves = this.slaveMap[id] = this.slaveMap[id] || []; slaves.push(record); } } return record; } /** * remove monitor,client connection -- idMap * * @param {Object} agent agent object * @param {String} id * @param {String} type serverType * @api private */ removeConnection(id: string, type: string, info: ServerInfo) { if (type === 'client') { delete this.clients[id]; } else { // remove master node in idMap and typeMap let record = this.idMap[id]; if (!record) { return; } let _info = record['info']; // info {host, port} if (utils.compareServer(_info as ServerInfo, info)) { delete this.idMap[id]; let list = this.typeMap[type]; if (list) { for (let i = 0, l = list.length; i < l; i++) { if (list[i].id === id) { list.splice(i, 1); break; } } if (list.length === 0) { delete this.typeMap[type]; } } } else { // remove slave node in slaveMap let slaves = this.slaveMap[id]; if (slaves) { for (let i = 0, l = slaves.length; i < l; i++) { if (utils.compareServer(slaves[i]['info'] as ServerInfo, info)) { slaves.splice(i, 1); break; } } if (slaves.length === 0) { delete this.slaveMap[id]; } } } } } /** * send msg to monitor * * @param {Object} socket socket-io object * @param {Number} reqId request id * @param {String} moduleId module id/name * @param {Object} msg message * @api private */ sendToMonitor(socket: MqttSocket, reqId: number, moduleId: string, msg: any) { this.doSend(socket, 'monitor', protocol.composeRequest(reqId, moduleId, msg)); } /** * send msg to client * * @param {Object} socket socket-io object * @param {Number} reqId request id * @param {String} moduleId module id/name * @param {Object} msg message * @api private */ sendToClient(socket: MqttSocket, reqId: number, moduleId: string, msg: any) { this.doSend(socket, 'client', protocol.composeRequest(reqId, moduleId, msg)); } doSend(socket: MqttSocket, topic: string, msg: any) { socket.send(topic, msg); } /** * broadcast msg to monitor * * @param {Object} record registered modules * @param {String} moduleId module id/name * @param {Object} msg message * @api private */ broadcastMonitors(records: { [serverId: string]: AgentClient } | AgentClient[] , moduleId: string, msg: any) { msg = protocol.composeRequest(null, moduleId, msg); if (records instanceof Array) { for (let i = 0, l = records.length; i < l; i++) { let socket = records[i].socket; this.doSend(socket, 'monitor', msg); } } else { for (let id in records) { let record = (records as { [id: string]: AgentClient })[id]; let socket = record.socket; this.doSend(socket, 'monitor', msg); } } } broadcastCommand(records: AgentClient[] | { [id: string]: AgentClient }, command: string, moduleId: string, msg: any) { msg = protocol.composeCommand(null, command, moduleId, msg); if (records instanceof Array) { for (let i = 0, l = records.length; i < l; i++) { let socket = records[i].socket; this.doSend(socket, 'monitor', msg); } } else { for (let id in records) { let record = (records as { [id: string]: AgentClient })[id]; let socket = record.socket; this.doSend(socket, 'monitor', msg); } } } }