UNPKG

pandora-hub

Version:

pandora.js messenge hub

256 lines (227 loc) 7.55 kB
import {ForceReplyFn, MessagePackage, PublishPackage, ReplyPackage} from '../domain'; import {MessengerClient, MessengerServer} from 'pandora-messenger'; import { HUB_SOCKET_NAME, PANDORA_HUB_ACTION_MSG_UP, PANDORA_HUB_ACTION_MSG_DOWN, PANDORA_HUB_ACTION_OFFLINE_UP, PANDORA_HUB_ACTION_ONLINE_UP, PANDORA_HUB_ACTION_PUBLISH_UP, PANDORA_HUB_ACTION_UNPUBLISH_UP, TIMEOUT_OF_RESPONSE } from '../const'; import {RouteTable} from './RouteTable'; import {Balancer} from './Balancer'; import {format} from 'util'; import {EventEmitter} from 'events'; /** * IPC-Hub */ export class HubServer extends EventEmitter { protected messengerServer: MessengerServer; protected routeTable: RouteTable = new RouteTable; /** * Count of pending reply transactions * @type {number} */ public pendingReplyCount = 0; /** * Start Hub * @return {Promise<void>} */ async start(): Promise<void> { if(this.messengerServer) { throw new Error('Hub already started'); } this.messengerServer = new MessengerServer({ name: HUB_SOCKET_NAME, responseTimeout: TIMEOUT_OF_RESPONSE }); this.startListen(); await new Promise((resolve) => { this.messengerServer.ready(resolve); }); } /** * Handle message from clients * @param {MessagePackage} message * @param {ForceReplyFn} reply */ protected handleMessageIn(message: MessagePackage, reply?: ForceReplyFn) { if(message.needReply) { this.pendingReplyCount++; } const originReply = reply; reply = (replyData) => { this.pendingReplyCount--; originReply(replyData); }; // Broadcast to all clients if message.broadcast be true and no message.remote // Only broadcast, working in very low level if(message.broadcast && !message.remote) { try { this.messengerServer.broadcast(PANDORA_HUB_ACTION_MSG_DOWN, message); if(message.needReply) { reply(<ReplyPackage> {success: true}); } } catch (error) { if(message.needReply) { reply(<ReplyPackage> {success: false, error}); } } return; } try { const clients = this.routeTable.selectClients(message.remote); if(!clients.length) { throw new Error(format('Cannot found any clients by selector: %j', message.remote)); } if(message.broadcast) { this.broadcastToClients(clients, message, reply); } else { this.balanceToClients(clients, message, reply); } } catch (error) { if(message.needReply) { reply(<ReplyPackage> {success: false, error}); } } } /** * Handle kind of message as {remote: {}, broadcast: false} * @param clients * @param message * @param reply */ protected balanceToClients(clients, message, reply) { const balancer = new Balancer(clients); const {client, selector: hitSelector} = balancer.pick(); const callback = message.needReply ? (error, res: ReplyPackage) => { if(error) { reply({ host: hitSelector, success: false, error: error }); } else { reply(res); } } : null; // Dispatch the message to a random client of all selected clients client.send(PANDORA_HUB_ACTION_MSG_DOWN, message, callback, message && message.timeout); } /** * Handle kind of message as {remote: {}, broadcast: true} * @param clients * @param message * @param reply */ protected broadcastToClients(clients, message, reply) { const expectFoundNumber = clients.length; const batchReply: Array<ReplyPackage> = []; for (const {selector: hitSelector, client} of clients) { const callback = message.needReply ? (error, res: ReplyPackage) => { if(error) { batchReply.push({ host: hitSelector, success: false, error: error }); } else { batchReply.push(res); } if(batchReply.length === expectFoundNumber) { reply({ success: true, remote: message.host, batchReply }); } } : null; // Dispatch the message to all selected clients client.send(PANDORA_HUB_ACTION_MSG_DOWN, message, callback, message && message.timeout); } } /** * Start listen on this.messengerServer */ protected startListen() { this.messengerServer.on('connected', (client: MessengerClient) => { // this.messengerServer will ignore error this.routeTable.setRelation(client, {initialization: true}); }); this.messengerServer.on('disconnected', (client: MessengerClient) => { // this.messengerServer will ignore error const selectors = this.routeTable.getSelectorsByClient(client); this.routeTable.forgetClient(client); if(selectors) { this.emit('client_disconnected', selectors); } }); this.messengerServer.on(PANDORA_HUB_ACTION_ONLINE_UP, (message: MessagePackage, reply: ForceReplyFn, client: MessengerClient) => { try { this.routeTable.setRelation(client, message.host); reply(<ReplyPackage> {success: true}); } catch (error) { reply(<ReplyPackage> {success: false, error}); } }); this.messengerServer.on(PANDORA_HUB_ACTION_OFFLINE_UP, (message: MessagePackage, reply: ForceReplyFn, client: MessengerClient) => { try { const selectors = this.routeTable.getSelectorsByClient(client); this.routeTable.forgetClient(client); if(selectors) { this.emit('client_disconnected', selectors); } reply(<ReplyPackage> {success: true}); } catch (error) { reply(<ReplyPackage> {success: false, error}); } }); this.messengerServer.on(PANDORA_HUB_ACTION_PUBLISH_UP, (message: PublishPackage, reply: ForceReplyFn, client: MessengerClient) => { try { this.routeTable.setRelation(client, message.data.selector); reply(<ReplyPackage> {success: true}); } catch (error) { reply(<ReplyPackage> {success: false, error}); } }); this.messengerServer.on(PANDORA_HUB_ACTION_UNPUBLISH_UP, (message: PublishPackage, reply: ForceReplyFn, client: MessengerClient) => { try { this.routeTable.forgetRelation(client, message.data.selector); reply(<ReplyPackage> {success: true}); } catch (error) { reply(<ReplyPackage> {success: false, error}); } }); this.messengerServer.on(PANDORA_HUB_ACTION_MSG_UP, this.handleMessageIn.bind(this)); } /** * Stop listen on this.messengerServer */ protected stopListen() { this.messengerServer.removeAllListeners('connected'); this.messengerServer.removeAllListeners('disconnected'); this.messengerServer.removeAllListeners(PANDORA_HUB_ACTION_ONLINE_UP); this.messengerServer.removeAllListeners(PANDORA_HUB_ACTION_OFFLINE_UP); this.messengerServer.removeAllListeners(PANDORA_HUB_ACTION_MSG_UP); } /** * Stop Hub * @return {Promise<void>} */ async stop (): Promise<void> { if(!this.messengerServer) { throw new Error('Hub has not started yet'); } await new Promise((resolve, reject) => { this.stopListen(); this.messengerServer.close((err) => { this.messengerServer = null; if(err) { reject(err); return; } resolve(); }); }); } public getMessengerServer() { return this.messengerServer; } }