UNPKG

jii

Version:

Jii - Full-Stack JavaScript Framework

298 lines (252 loc) 8.27 kB
/** * @author Vladimir Kozhin <affka@affka.ru> * @license MIT */ 'use strict'; const Jii = require('../../BaseJii'); const String = require('../../helpers/String'); const HubInterface = require('./hub/HubInterface'); const ChannelEvent = require('../ChannelEvent'); const Request = require('./Request'); const Connection = require('./Connection'); const Response = require('./Response'); const _extend = require('lodash/extend'); const Component = require('../../base/Component'); const RedisHub = require('./hub/Redis'); const RedisQueue = require('./queue/Redis'); class HubServer extends Component { preInit() { /** * @type {string} */ this._serverUid = null; /** * @type {QueueInterface} */ this.queue = { className: RedisQueue }; /** * @type {HubInterface} */ this.hub = { className: RedisHub }; /** * @type {boolean} */ this.listenActions = true; super.preInit(...arguments); } init() { super.init(); // Generate unique server uid this._serverUid = String.generateUid(); // Init hub this.hub = Jii.createObject(this.hub); this.hub.on(HubInterface.EVENT_MESSAGE, this._onHubMessage.bind(this)); // Init queue this.queue = Jii.createObject(this.queue); } /** * Start listen income comet connections */ start() { return Promise.all([ this.hub.start(), this.queue.start() ]).then(() => { if (this.listenActions) { this.hub.subscribe(HubServer.CHANNEL_NAME_ACTION); this._runActionFromQueue(); } }); } /** * Abort current connections and stop listen income comet connections */ stop() { return Promise.all([ this.hub.stop(), this.queue.stop() ]); } /** * Send data to channel * @param {string} channel * @param {*} data */ sendToChannel(channel, data) { if (typeof data !== 'string') { data = JSON.stringify(data); } Jii.trace('Comet server send to channel `' + channel + '` data: ' + data); this.hub.send(channel, data); this.hub.send(HubServer.CHANNEL_NAME_ALL, channel + ' ' + data); } /** * * @param {string} name * @param {function} handler * @param {*} [data] * @param {boolean} [isAppend] */ on(name, handler, data, isAppend) { // Subscribe on hub channels if (name === HubServer.EVENT_CHANNEL && !this.hasEventHandlers(name)) { this.hub.subscribe(HubServer.CHANNEL_NAME_ALL); } if (name.indexOf(HubServer.EVENT_CHANNEL_NAME) === 0) { var channel = name.substr(HubServer.EVENT_CHANNEL_NAME.length); if (!this.hasChannelHandlers(channel)) { this.hub.subscribe(channel); } } super.on(...arguments); } /** * @param {string} name * @param {function} [handler] * @return boolean */ off(name, handler) { super.off(...arguments); // Unsubscribe on hub channels if (name === HubServer.EVENT_CHANNEL && !this.hasEventHandlers(name)) { this.hub.unsubscribe(HubServer.CHANNEL_NAME_ALL); } if (name.indexOf(HubServer.EVENT_CHANNEL_NAME) === 0) { var channel = name.substr(HubServer.EVENT_CHANNEL_NAME.length); if (!this.hasChannelHandlers(channel)) { this.hub.unsubscribe(channel); } } } /** * * @param {string} name * @returns {boolean} */ hasChannelHandlers(name) { return this.hasEventHandlers(HubServer.EVENT_CHANNEL_NAME + name); } /** * Send data to connection * @param {number|string} id * @param {*} data */ sendToConnection(id, data) { if (typeof data !== 'string') { data = JSON.stringify(data); } Jii.trace('Comet server send to connection `' + id + '` data: ' + data); this.hub.send(HubServer.CHANNEL_NAME_CONNECTION + id, data); } /** * * @param {Connection} connection * @param {string} route * @param {object} data */ pushActionToQueue(connection, route, data) { var queueData = { route: route, connection: connection.toJSON(), request: connection.request.toJSON() }; queueData.request.queryParams = data; this.queue.push(JSON.stringify(queueData)).then(() => { // Notify hub servers about new action this.hub.send(HubServer.CHANNEL_NAME_ACTION, route); }); } /** * Income message from hub * @param {ChannelEvent} event * @private */ _onHubMessage(event) { Jii.trace('Comet hub income, channel `' + event.channel + '`: ' + event.message); this.trigger(HubServer.EVENT_MESSAGE, new ChannelEvent({ channel: event.channel, message: event.message })); switch (event.channel) { case HubServer.CHANNEL_NAME_ACTION: var route = event.message; if (route && Jii.app.existsRoute(route)) { this._runActionFromQueue(); } break; case HubServer.CHANNEL_NAME_ALL: var i2 = event.message.indexOf(' '); this.trigger(HubServer.EVENT_CHANNEL, new ChannelEvent({ channel: event.message.substr(0, i2), message: event.message.substr(i2 + 1) })); break; default: this.trigger(HubServer.EVENT_CHANNEL_NAME + event.channel, new ChannelEvent({ channel: event.channel, message: event.message })); } } _runActionFromQueue() { this.queue.pop().then(message => { // Empty queue - skip if (message === null) { return false; } Jii.trace('Run action from queue: ' + message); var data = JSON.parse(message); if (Jii.app.existsRoute(data.route)) { var context = Jii.createContext({ route: data.route }); data.request.uid = data.request.queryParams ? data.request.queryParams.requestUid : null; context.setComponent('request', new Request(data.request)); context.setComponent('connection', new Connection(_extend({}, data.connection, { request: context.get('request') }))); context.setComponent('response', new Response({ comet: this, requestUid: data.request.uid, connectionId: context.get('connection').id })); // @todo return Promise Jii.app.runAction(data.route, context); } // Run next action this._runActionFromQueue(); }).catch(Jii.catchHandler); } } /** * @type {string} */ HubServer.CHANNEL_NAME_CONNECTION = '__connectionN9a63w:'; /** * @type {string} */ HubServer.CHANNEL_NAME_ACTION = '__actionXZj1sf'; /** * @type {string} */ HubServer.CHANNEL_NAME_ALL = '__allVfcOS7'; /** * @event HubServer#message * @property {MessageEvent} event */ HubServer.EVENT_MESSAGE = 'message'; /** * @event HubServer#channel:%channel_name% * @property {ChannelEvent} event */ HubServer.EVENT_CHANNEL_NAME = 'channel:'; /** * @event HubServer#channel * @property {ChannelEvent} event */ HubServer.EVENT_CHANNEL = 'channel'; module.exports = HubServer;