UNPKG

jii

Version:

Jii - Full-Stack JavaScript Framework

296 lines (251 loc) 9.09 kB
/** * @author Vladimir Kozhin <affka@affka.ru> * @license MIT */ 'use strict'; const Jii = require('../../BaseJii'); const Server = require('./Server'); const Component = require('../../base/Component'); const Event = require('../../base/Event'); const ActiveRecord = require('../../data/BaseActiveRecord'); const InvalidConfigException = require('../../exceptions/InvalidConfigException'); const _isFunction = require('lodash/isFunction'); const _isEmpty = require('lodash/isEmpty'); const _extend = require('lodash/extend'); const BaseObject = require('../../base/BaseObject'); const NeatComet = require('neatcomet'); class NeatServer extends BaseObject { preInit() { /** * @type {NeatComet.NeatCometServer} */ this.engine = null; /** * Note: onOpenProfileCommand() and onCloseProfileCommand must be called from actions explicitly. * There's no way to subscribe for them in Jii. * * @type {NeatComet.api.ICometServerEvents} */ this._events = null; /** * @type {boolean} */ this.listenModels = true; /** * @type {object|boolean} */ this.hasDynamicAttributes = false; /** * Callback function to be called when folder loaded from server. * @callback NeatServer~dataLoadHandlerCallback * @param {object} params * @returns {Promise} */ /** * @type {NeatServer~dataLoadHandlerCallback} */ this.dataLoadHandler = null; /** * @type {Server} **/ this.comet = null; /** * @type {object} */ this.bindings = null; /** * @type {string} * @deprecated */ this.configFileName = null; super.preInit(...arguments); } init() { super.init(); // Init transport this.comet = this.comet === null ? Jii.app.get('comet') : this.comet instanceof Component ? this.comet : Jii.createObject(this.comet); this.engine = new NeatComet.NeatCometServer(); this.engine.setup({ comet: this, ormLoader: this, configFileName: this.configFileName || this.bindings, externalDataLoader: this.dataLoadHandler }); Jii.app.inlineActions['neat/open'] = this._actionOpenProfile.bind(this); Jii.app.inlineActions['neat/close'] = this._actionCloseProfile.bind(this); if (this.listenModels && ActiveRecord) { Event.on(ActiveRecord, ActiveRecord.EVENT_AFTER_INSERT, this._onModelInsert.bind(this)); Event.on(ActiveRecord, ActiveRecord.EVENT_AFTER_UPDATE, this._onModelUpdate.bind(this)); Event.on(ActiveRecord, ActiveRecord.EVENT_AFTER_DELETE, this._onModelDelete.bind(this)); } } /** * Allowed to expect that it will be called only once per ICometServer instance * @param {NeatComet.api.ICometServerEvents} eventsHandler */ bindServerEvents(eventsHandler) { this._events = eventsHandler; this.comet.on(Server.EVENT_ADD_CONNECTION, event => { eventsHandler.onNewConnection(event.connection.id); }); this.comet.on(Server.EVENT_REMOVE_CONNECTION, event => { eventsHandler.onLostConnection(event.connection.id); }); } /** * @return {boolean} */ getSupportsForwardToClient() { // TODO: Implement return false; } /** * @param {String} channel * @param {*} message */ broadcast(channel, message) { this.comet.sendToChannel(this.constructor.ROUTE_PREFIX + channel, message); } /** * @param {String} channel * @param {Function} callback */ subscribe(channel, callback) { /** * @param ChannelEvent event */ callback.__jiiCallbackWrapper = event => { callback(event.channel.substr(this.constructor.ROUTE_PREFIX.length), JSON.parse(event.message)); }; this.comet.on('channel:' + this.constructor.ROUTE_PREFIX + channel, callback.__jiiCallbackWrapper); } /** * @param {String} channel * @param {Function} callback */ unsubscribe(channel, callback) { if (callback && callback.__jiiCallbackWrapper) { callback = callback.__jiiCallbackWrapper; } this.comet.off('channel:' + this.constructor.ROUTE_PREFIX + channel, callback); } /** * * @param connectionId * @param channel * @param data */ pushToClient(connectionId, channel, data) { this.comet.sendToConnection(connectionId, [ 'channel', this.constructor.ROUTE_PREFIX + channel, JSON.stringify(data) ].join(' ')); } /** * @param {string|ActiveQuery} modelClassName * @param {object|null} match * @param {string} whereType * @param {string|null} where * @param {object} attributes * @param {NeatComet.bindings.BindingServer} binding * @returns {Promise} Array of records data */ loadRecords(modelClassName, match, whereType, where, attributes, binding) { /** @typedef {BaseActiveRecord} modelClass */ var modelClass = Jii.namespace(modelClassName); if (!_isFunction(modelClass)) { throw new InvalidConfigException('Not found model `' + modelClassName + '` for binding'); } /** @typedef {ActiveQuery} query */ var query = modelClass.find(); // Apply match condition if (match) { query.where(match); } // Apply custom filter switch (whereType) { case NeatComet.api.IOrmLoader.WHERE_NONE: // Find all break; case NeatComet.api.IOrmLoader.WHERE_JS: where = NeatComet.bindings.BindingServer.convertWhereJsToSql(where); break; case NeatComet.api.IOrmLoader.WHERE_SQL: where = NeatComet.bindings.BindingServer.convertWhereJsToSql(where); query.from(modelClass.tableName() + ' ' + NeatComet.api.IOrmLoader.TABLE_ALIAS_IN_SQL).andWhere(where, NeatComet.bindings.BindingServer.filterAttributesBySqlParams(where, attributes)); break; default: throw new InvalidConfigException('Where type `' + whereType + '` is not implemented'); } // Query via model implementation if (!_isEmpty(this.hasDynamicAttributes)) { } else { if (binding.attributes !== null) { query.select(binding.attributes); } return query.asArray().all(); } } /** * * @param {AfterSaveEvent} event * @param {} event.sender * @private */ _onModelInsert(event) { /** @typedef {ActiveRecord} model */ var model = event.sender; this.engine.broadcastEvent(model.className(), 'sendAdd', model.getAttributes()); } /** * * @param {AfterSaveEvent} event * @param {ActiveRecord} event.sender * @private */ _onModelUpdate(event) { /** @typedef {ActiveRecord} model */ var model = event.sender; var oldAttributes = _extend({}, event.changedAttributes, model.getOldAttributes()); this.engine.broadcastEvent(model.className(), 'sendUpdate', model.getAttributes(), oldAttributes); } /** * * @param {Event} event * @param {ActiveRecord} event.sender * @private */ _onModelDelete(event) { /** @typedef {ActiveRecord} model */ var model = event.sender; this.engine.broadcastEvent(model.className(), 'sendRemove', model.getAttributes()); } /** * @param {Context} context * @param {Connection} context.connection * @param {Request} context.request * @param {Response} context.response */ _actionOpenProfile(context) { this._events.onOpenProfileCommand(context.connection.id, context.request.get('neat')).then(neatResponse => { context.response.data = { neat: neatResponse }; context.response.send(); }); } /** * @param {Context} context * @param {Connection} context.connection * @param {Request} context.request * @param {Response} context.response */ _actionCloseProfile(context) { context.response.send(); // No wait this._events.onCloseProfileCommand(context.connection.id, context.request.get('neat')); } } NeatServer.ROUTE_PREFIX = 'profiles:'; module.exports = NeatServer;