@oat-sa/tao-core-sdk
Version:
Core libraries of TAO
226 lines (213 loc) • 8.31 kB
JavaScript
define(['lodash', 'core/promise', 'core/providerRegistry', 'core/delegator', 'core/eventifier'], function (_, Promise, providerRegistry, delegator, eventifier) { 'use strict';
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
Promise = Promise && Object.prototype.hasOwnProperty.call(Promise, 'default') ? Promise['default'] : Promise;
providerRegistry = providerRegistry && Object.prototype.hasOwnProperty.call(providerRegistry, 'default') ? providerRegistry['default'] : providerRegistry;
delegator = delegator && Object.prototype.hasOwnProperty.call(delegator, 'default') ? delegator['default'] : delegator;
eventifier = eventifier && Object.prototype.hasOwnProperty.call(eventifier, 'default') ? eventifier['default'] : eventifier;
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2016-2019 (original work) Open Assessment Technologies SA ;
*/
/**
* Some default config values
* @type {Object}
* @private
*/
const defaults = {
timeout: 30 * 1000
};
/**
* Creates a communicator implementation.
* The communicator relies on a provider to execute the actions.
* Most of the delegated methods must return promises.
*
* Some standard channels are reserved, and must be implemented by the providers:
* - error: to carry on error purpose messages
* - malformed: to carry on malformed received messages
*
* @param {String} providerName - The name of the provider instance,
* which MUST be defined before through a `.registerProvider()` call.
* @param {Object} [config] - Optional config set
* @param {String} [config.service] - The address of the remote service to request
* @param {Number} [config.timeout] - The communication timeout, in milliseconds (default: 30000)
* @param {Object} [config.requestParams] - Extra params to override the defaults of the request
* @param {Object} [config.requestParams.jwtTokenHandler] - core/jwtTokenHandler instance to be used for JWT authentication
* @returns {communicator}
*/
function communicatorFactory(providerName, config) {
/**
* The communicator config set
* @type {Object}
*/
const extendedConfig = _(config || {}).defaults(defaults).value();
/**
* The function used to delegate the calls from the API to the provider.
* @type {Function}
*/
let delegate;
/**
* The current states of the communicator
* @type {Object}
*/
let states = {};
/**
* The selected communication provider
* @type {Object}
*/
const provider = communicatorFactory.getProvider(providerName);
/**
* The communicator implementation
* Creates the implementation by setting an API and delegating calls to the provider
* @type {Object}
*/
const communicator = eventifier({
/**
* Initializes the communication implementation.
* Sets the `ready` state.
* @returns {Promise} The delegated provider's method must return a promise
* @fires init
* @fires ready
*/
init() {
if (this.getState('ready')) {
return Promise.resolve();
}
return delegate('init').then(() => {
this.setState('ready').trigger('ready');
});
},
/**
* Tears down the communication implementation.
* Clears the states.
* @returns {Promise} The delegated provider's method must return a promise
* @fires destroy
* @fires destroyed
*/
destroy() {
let stepPromise;
if (this.getState('open')) {
stepPromise = this.close();
} else {
stepPromise = Promise.resolve();
}
return stepPromise.then(() => delegate('destroy')).then(() => {
this.trigger('destroyed');
states = {};
});
},
/**
* Opens the connection.
* Sets the `open` state.
* @returns {Promise} The delegated provider's method must return a promise
* @fires open
* @fires opened
*/
open() {
if (this.getState('open')) {
return Promise.resolve();
}
return delegate('open').then(() => {
this.setState('open').trigger('opened');
});
},
/**
* Closes the connection.
* Clears the `open` state.
* @returns {Promise} The delegated provider's method must return a promise
* @fires close
* @fires closed
*/
close() {
return delegate('close').then(() => {
this.setState('open', false).trigger('closed');
});
},
/**
* Sends an messages through the communication implementation.
* @param {String} channel - The name of the communication channel to use
* @param {Object} message - The message to send
* @returns {Promise} The delegated provider's method must return a promise
* @fires send
* @fires sent
*/
send(channel, message) {
if (!this.getState('open')) {
return Promise.reject();
}
return delegate('send', channel, message).then(response => {
this.trigger('sent', channel, message, response);
return response;
});
},
/**
* Registers a listener on a particular channel
* @param {String} name - The name of the channel to listen
* @param {Function} handler - The listener callback
* @returns {communicator}
* @throws TypeError if the name is missing or the handler is not a callback
*/
channel(name, handler) {
if (!_.isString(name) || name.length <= 0) {
throw new TypeError('A channel must have a name');
}
if (!_.isFunction(handler)) {
throw new TypeError('A handler must be attached to a channel');
}
this.on(`channel-${name}`, handler);
return this;
},
/**
* Gets the implementation config set
* @returns {Object}
*/
getConfig() {
return extendedConfig;
},
/**
* Sets a state
* @param {String} name - The name of the state to set
* @param {Boolean} [state] - The state itself (default: true)
* @returns {communicator}
*/
setState(name, state) {
if (_.isUndefined(state)) {
state = true;
}
states[name] = !!state;
return this;
},
/**
* Gets a state
* @param {String} name - The name of the state to get
* @returns {Boolean}
*/
getState(name) {
return !!states[name];
}
});
// all messages comes through a message event, then each is dispatched to the right channel
communicator.on('message', function (channel, message) {
this.trigger(`channel-${channel}`, message);
});
// use a delegate function to make a bridge between API and provider
delegate = delegator(communicator, provider, {
name: 'communicator'
});
return communicator;
}
var communicator = providerRegistry(communicatorFactory);
return communicator;
});