pandora-hub
Version:
pandora.js messenge hub
361 lines (323 loc) • 9.88 kB
text/typescript
import uuid = require('uuid');
import {
Location, Selector, MessagePackage, ReplyPackage, PublishPackage, ForceReplyFn,
DispatchHandler, HubMessage, ClientOptions
} from '../domain';
import {MessengerClient} from 'pandora-messenger';
import {
HUB_SOCKET_NAME, PANDORA_HUB_ACTION_MSG_UP, PANDORA_HUB_ACTION_PUBLISH_UP,
PANDORA_HUB_ACTION_UNPUBLISH_UP, PANDORA_HUB_ACTION_MSG_DOWN, PANDORA_HUB_ACTION_ONLINE_UP,
PANDORA_HUB_ACTION_OFFLINE_UP, TIMEOUT_OF_RESPONSE
} from '../const';
import {SelectorUtils} from './SelectorUtils';
import {format} from 'util';
import {DefaultDispatchHandler} from './DefaultDispatchHandler';
import {EventEmitter} from 'events';
export class HubClient extends EventEmitter {
protected messengerClient: MessengerClient = null;
protected location: Location;
protected publishedSelectors: Array<Selector> = [];
protected logger;
protected dispatchHandlers: DispatchHandler[];
constructor (options: ClientOptions) {
super();
this.location = {
...options.location,
clientId: uuid.v4()
};
this.logger = options.logger || console;
this.dispatchHandlers = [
new DefaultDispatchHandler()
];
}
/**
* Set a handler to hand HUB Dispatching message
* @param {DispatchHandler} dispatchHandler
*/
public pushDispatchHandler(dispatchHandler: DispatchHandler) {
this.dispatchHandlers.push(dispatchHandler);
}
async handleHubDispatch(message: HubMessage): Promise<any> {
for(const dispatchHandler of this.dispatchHandlers) {
const ret = await dispatchHandler.dispatch(message);
if(ret) {
return ret;
}
}
this.emit(message.action, message);
}
isReady() {
return !!this.messengerClient;
}
/**
* Let this client online
* @return {Promise<void>}
*/
async start() {
if(this.messengerClient) {
throw new Error('HubClient already started');
}
await new Promise((resolve, reject) => {
this.messengerClient = new MessengerClient({
name: HUB_SOCKET_NAME,
reConnectTimes: 10,
responseTimeout: TIMEOUT_OF_RESPONSE,
unref: true
});
this.messengerClient.once('error', reject);
this.messengerClient.ready(resolve);
});
this.startListen();
await this.sendOnline();
// When reconnected
this.messengerClient.on('connect', () => {
this.resendPublishedSelectors().catch((err) => {
this.logger.error(err);
this.logger.error('resendPublishedSelectors() went wrong');
});
});
}
protected async sendOnline() {
await this.sendToHubAndWaitReply(PANDORA_HUB_ACTION_ONLINE_UP);
}
/**
* Publish a selector to Hub, so Hub will set a relation in RouteTable between client and selector
* @param {Selector} selector
* @return {Promise<ReplyPackage>}
*/
async publish(selector: Selector): Promise<ReplyPackage> {
// Make sure each selector are unique.
this.assertExistSelector(selector);
const res = await this.sendPublishToHub(selector);
this.publishedSelectors.push(selector);
return res;
}
/**
* Unpublish a selector to Hub, so Hub will forget the relation in RouteTable between client and selector
* @param {Selector} selector
* @return {Promise<ReplyPackage>}
*/
async unpublish(selector: Selector): Promise<ReplyPackage> {
const filteredSelectors: Array<Selector> = [];
const batchReply = [];
for(const targetSelector of this.publishedSelectors) {
if(!SelectorUtils.match(selector, targetSelector)) {
filteredSelectors.push(targetSelector);
continue;
}
const res = await this.sendToHubAndWaitReply<PublishPackage>(PANDORA_HUB_ACTION_UNPUBLISH_UP, {
data: {
selector: targetSelector
}
});
batchReply.push(res);
if(!res.success) {
throw new Error(format('Unpublish selector %j went wrong, cause from Hub: %s', selector, res.error));
}
}
this.publishedSelectors = filteredSelectors;
return {
success: true,
batchReply
};
}
/**
* Resend all published selectors to HUB when reconnected
* @return {Promise<void>}
*/
protected async resendPublishedSelectors () {
await this.sendOnline();
for(const selector of this.publishedSelectors) {
await this.sendPublishToHub(selector);
}
}
// /**
// * Get all route relations within Hub
// * @return {Promise<any>}
// */
// async discover() {
// const res = await this.sendToHubAndWaitReply(PANDORA_HUB_ACTION_DISCOVER_UP);
// if(!res.success) {
// throw new Error(format('discover whole hub went wrong, cause from Hub: %j', res.error));
// }
// return res.data;
// }
//
// /**
// * Lookup route relations by a certain selector
// * @param {Selector} selector
// * @return {Promise<any>}
// */
// async lookup(selector: Selector) {
// const res = await this.sendToHubAndWaitReply<LookupPackage>(PANDORA_HUB_ACTION_DISCOVER_UP, {
// data: {
// selector: selector
// }
// });
// if(!res.success) {
// throw new Error(format('lookup selector %j went wrong, cause from Hub: %j', selector, res.error));
// }
// return res.data;
// }
/**
* Invoke a remote Object only from a random one of all selected clients
* @return {Promise<any>}
*/
async invoke(remote: Selector, action, message): Promise<ReplyPackage> {
const res = await this.sendToHubAndWaitReply<HubMessage>(PANDORA_HUB_ACTION_MSG_UP, {
remote: remote,
action,
broadcast: false,
...message
});
return res;
}
/**
* Invoke a remote Object from all selected clients
* @param {Selector} remote
* @param message
* @return {Promise<Array<ReplyPackage>>}
*/
async multipleInvoke(remote: Selector, action, message): Promise<Array<ReplyPackage>> {
const res = await this.sendToHubAndWaitReply<HubMessage>(PANDORA_HUB_ACTION_MSG_UP, {
remote: remote,
action,
broadcast: true,
...message
});
return res.batchReply;
}
/**
* Send a message to a random one of all selected clients
* @param remote
* @param data
* @return {Promise<void>}
*/
send(remote: Selector, action, message): void {
this.sendToHub<HubMessage>(PANDORA_HUB_ACTION_MSG_UP, {
remote: remote,
action,
broadcast: false,
...message
});
}
/**
* Send a message to all selected clients
* @param remote
* @param message
* @return {Promise<void>}
*/
multipleSend(remote: Selector, action, message): void {
this.sendToHub<HubMessage>(PANDORA_HUB_ACTION_MSG_UP, {
remote: remote,
action,
broadcast: true,
...message
});
}
/**
* Get location of this client
* @return {Location}
*/
getLocation () {
return this.location;
}
/**
* Send a message to Hub
*/
protected sendToHub<MessageType extends MessagePackage>(action, message?: MessageType): void {
message = <any> (message || {});
message.host = this.location;
this.messengerClient.send(action, message);
}
/**
* Send a message to Hub and wait reply
* @param action
* @param {MessageType} message
* @return {Promise<ReplyPackage>}
*/
protected async sendToHubAndWaitReply<MessageType extends MessagePackage>(action, message?: MessageType): Promise<ReplyPackage> {
message = <any> (message || {});
message.host = this.location;
message.needReply = true;
return new Promise(((resolve, reject) => {
this.messengerClient.send(action, message, (err, message: ReplyPackage) => {
if(err) {
reject(err);
return;
}
resolve(message);
}, message.timeout);
}));
}
/**
* only send publish message to Hub without state keeping
* @param {Selector} selector
* @return {Promise<ReplyPackage>}
*/
protected async sendPublishToHub(selector: Selector): Promise<ReplyPackage> {
const res = await this.sendToHubAndWaitReply<PublishPackage>(PANDORA_HUB_ACTION_PUBLISH_UP, {
data: {
selector: selector
}
});
if(!res.success) {
throw new Error(format('Publish selector %j went wrong, cause from Hub: %s', selector, res.error));
}
return res;
}
/**
* Make sure each selector are unique
* @param selector
*/
protected assertExistSelector (selector) {
for(const targetSelector of this.publishedSelectors) {
if(SelectorUtils.match(selector, targetSelector)) {
throw new Error(format('Selector %j already exist', selector));
}
}
}
startListen() {
this.messengerClient.on(PANDORA_HUB_ACTION_MSG_DOWN, async (message: HubMessage, reply: ForceReplyFn) => {
try {
let replyPkg: ReplyPackage = null;
try {
const data = await this.handleHubDispatch(message);
replyPkg = {
host: this.location,
remote: message.host,
success: true,
data: data
};
} catch (error) {
replyPkg = {
host: this.location,
remote: message.host,
success: false,
error: error
};
}
if(message.needReply) {
reply(replyPkg);
}
} catch (err) {
this.logger.error(err);
this.logger.error(format('Handing PANDORA_HUB_ACTION_MSG_DOWN went wrong, remote message: %j', message));
}
});
}
/**
* Close this client
*/
async stop() {
if(!this.messengerClient) {
throw new Error('HubClient has not started yet');
}
await this.sendToHubAndWaitReply(PANDORA_HUB_ACTION_OFFLINE_UP);
this.messengerClient.close();
this.messengerClient = null;
}
getMessengerClient() {
return this.messengerClient;
}
}