2017-04-17 15:52:31 +00:00
|
|
|
import {
|
|
|
|
MESSAGE_TYPE_EVENT,
|
2017-05-02 22:39:36 +00:00
|
|
|
MESSAGE_TYPE_REQUEST,
|
|
|
|
MESSAGE_TYPE_RESPONSE
|
2017-04-17 15:52:31 +00:00
|
|
|
} from './constants';
|
|
|
|
|
|
|
|
/**
|
2017-04-28 20:24:20 +00:00
|
|
|
* Stores the currnet transport backend that have to be used. Also implements
|
|
|
|
* request/response mechanism.
|
2017-04-17 15:52:31 +00:00
|
|
|
*/
|
|
|
|
export default class Transport {
|
|
|
|
/**
|
|
|
|
* Creates new instance.
|
|
|
|
*
|
|
|
|
* @param {Object} options - Optional parameters for configuration of the
|
2017-04-28 20:24:20 +00:00
|
|
|
* transport backend.
|
2017-04-17 15:52:31 +00:00
|
|
|
*/
|
2017-04-28 20:24:20 +00:00
|
|
|
constructor({ backend } = {}) {
|
2017-05-02 22:39:36 +00:00
|
|
|
/**
|
|
|
|
* Maps an event name and listener that have been added to the Transport
|
|
|
|
* instance.
|
|
|
|
*
|
|
|
|
* @type {Map<string, Function>}
|
|
|
|
*/
|
|
|
|
this._listeners = new Map();
|
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
/**
|
|
|
|
* The request ID counter used for the id property of the request. This
|
|
|
|
* property is used to match the responses with the request.
|
|
|
|
*
|
|
|
|
* @type {number}
|
|
|
|
*/
|
2017-04-17 15:52:31 +00:00
|
|
|
this._requestID = 0;
|
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
/**
|
|
|
|
* Maps an IDs of the requests and handlers that will process the
|
|
|
|
* responses of those requests.
|
|
|
|
*
|
|
|
|
* @type {Map<number, Function>}
|
|
|
|
*/
|
2017-04-17 15:52:31 +00:00
|
|
|
this._responseHandlers = new Map();
|
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
/**
|
|
|
|
* A set with the events and requests that were received but not
|
|
|
|
* processed by any listener. They are later passed on every new
|
|
|
|
* listener until they are processed.
|
|
|
|
*
|
|
|
|
* @type {Set<Object>}
|
|
|
|
*/
|
2017-04-17 15:52:31 +00:00
|
|
|
this._unprocessedMessages = new Set();
|
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
/**
|
|
|
|
* Alias.
|
|
|
|
*/
|
2017-04-17 15:52:31 +00:00
|
|
|
this.addListener = this.on;
|
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
if (backend) {
|
|
|
|
this.setBackend(backend);
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-28 20:24:20 +00:00
|
|
|
* Disposes the current transport backend.
|
2017-04-17 15:52:31 +00:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2017-04-28 20:24:20 +00:00
|
|
|
_disposeBackend() {
|
|
|
|
if (this._backend) {
|
|
|
|
this._backend.dispose();
|
|
|
|
this._backend = null;
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-28 21:03:36 +00:00
|
|
|
* Handles incoming messages from the transport backend.
|
2017-04-17 15:52:31 +00:00
|
|
|
*
|
2017-04-28 21:03:36 +00:00
|
|
|
* @param {Object} message - The message.
|
2017-04-17 15:52:31 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2017-04-28 21:03:36 +00:00
|
|
|
_onMessageReceived(message) {
|
|
|
|
if (message.type === MESSAGE_TYPE_RESPONSE) {
|
|
|
|
const handler = this._responseHandlers.get(message.id);
|
2017-04-17 15:52:31 +00:00
|
|
|
|
|
|
|
if (handler) {
|
2017-04-28 21:03:36 +00:00
|
|
|
handler(message);
|
|
|
|
this._responseHandlers.delete(message.id);
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
2017-04-28 21:03:36 +00:00
|
|
|
} else if (message.type === MESSAGE_TYPE_REQUEST) {
|
|
|
|
this.emit('request', message.data, (result, error) => {
|
2017-04-28 20:24:20 +00:00
|
|
|
this._backend.send({
|
2017-04-17 15:52:31 +00:00
|
|
|
type: MESSAGE_TYPE_RESPONSE,
|
|
|
|
error,
|
2017-04-28 21:03:36 +00:00
|
|
|
id: message.id,
|
2017-04-27 20:21:01 +00:00
|
|
|
result
|
2017-04-17 15:52:31 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
2017-04-28 21:03:36 +00:00
|
|
|
this.emit('event', message.data);
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disposes the allocated resources.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
dispose() {
|
|
|
|
this._responseHandlers.clear();
|
|
|
|
this._unprocessedMessages.clear();
|
|
|
|
this.removeAllListeners();
|
2017-04-28 20:24:20 +00:00
|
|
|
this._disposeBackend();
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls each of the listeners registered for the event named eventName, in
|
|
|
|
* the order they were registered, passing the supplied arguments to each.
|
|
|
|
*
|
|
|
|
* @param {string} eventName - The name of the event.
|
2017-04-28 20:24:20 +00:00
|
|
|
* @returns {boolean} True if the event has been processed by any listener,
|
|
|
|
* false otherwise.
|
2017-04-17 15:52:31 +00:00
|
|
|
*/
|
|
|
|
emit(eventName, ...args) {
|
|
|
|
const listenersForEvent = this._listeners.get(eventName);
|
|
|
|
let isProcessed = false;
|
|
|
|
|
2017-04-27 20:21:01 +00:00
|
|
|
if (listenersForEvent && listenersForEvent.size) {
|
|
|
|
listenersForEvent.forEach(listener => {
|
|
|
|
isProcessed = listener(...args) || isProcessed;
|
|
|
|
});
|
|
|
|
}
|
2017-04-17 15:52:31 +00:00
|
|
|
|
|
|
|
if (!isProcessed) {
|
|
|
|
this._unprocessedMessages.add(args);
|
|
|
|
}
|
2017-04-27 20:21:01 +00:00
|
|
|
|
|
|
|
return isProcessed;
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the listener function to the listeners collection for the event
|
|
|
|
* named eventName.
|
|
|
|
*
|
|
|
|
* @param {string} eventName - The name of the event.
|
|
|
|
* @param {Function} listener - The listener that will be added.
|
|
|
|
* @returns {Transport} References to the instance of Transport class, so
|
|
|
|
* that calls can be chained.
|
|
|
|
*/
|
|
|
|
on(eventName, listener) {
|
|
|
|
let listenersForEvent = this._listeners.get(eventName);
|
|
|
|
|
|
|
|
if (!listenersForEvent) {
|
|
|
|
listenersForEvent = new Set();
|
|
|
|
this._listeners.set(eventName, listenersForEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
listenersForEvent.add(listener);
|
|
|
|
|
|
|
|
this._unprocessedMessages.forEach(args => {
|
|
|
|
if (listener(...args)) {
|
|
|
|
this._unprocessedMessages.delete(args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all listeners, or those of the specified eventName.
|
|
|
|
*
|
2017-04-28 20:24:20 +00:00
|
|
|
* @param {string} [eventName] - The name of the event. If this parameter is
|
|
|
|
* not specified all listeners will be removed.
|
2017-04-17 15:52:31 +00:00
|
|
|
* @returns {Transport} References to the instance of Transport class, so
|
|
|
|
* that calls can be chained.
|
|
|
|
*/
|
|
|
|
removeAllListeners(eventName) {
|
|
|
|
if (eventName) {
|
|
|
|
this._listeners.delete(eventName);
|
|
|
|
} else {
|
|
|
|
this._listeners.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the listener function from the listeners collection for the event
|
|
|
|
* named eventName.
|
|
|
|
*
|
|
|
|
* @param {string} eventName - The name of the event.
|
|
|
|
* @param {Function} listener - The listener that will be removed.
|
|
|
|
* @returns {Transport} References to the instance of Transport class, so
|
|
|
|
* that calls can be chained.
|
|
|
|
*/
|
|
|
|
removeListener(eventName, listener) {
|
|
|
|
const listenersForEvent = this._listeners.get(eventName);
|
|
|
|
|
|
|
|
if (listenersForEvent) {
|
|
|
|
listenersForEvent.delete(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-28 21:03:36 +00:00
|
|
|
* Sends the passed event.
|
2017-04-17 15:52:31 +00:00
|
|
|
*
|
2017-04-28 21:03:36 +00:00
|
|
|
* @param {Object} event - The event to be sent.
|
2017-04-17 15:52:31 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2017-04-28 21:03:36 +00:00
|
|
|
sendEvent(event = {}) {
|
2017-04-28 20:24:20 +00:00
|
|
|
if (this._backend) {
|
|
|
|
this._backend.send({
|
2017-04-17 15:52:31 +00:00
|
|
|
type: MESSAGE_TYPE_EVENT,
|
2017-04-28 21:03:36 +00:00
|
|
|
data: event
|
2017-04-17 15:52:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sending request.
|
|
|
|
*
|
2017-04-28 21:03:36 +00:00
|
|
|
* @param {Object} request - The request to be sent.
|
2017-04-17 15:52:31 +00:00
|
|
|
* @returns {Promise}
|
|
|
|
*/
|
2017-04-28 21:03:36 +00:00
|
|
|
sendRequest(request) {
|
2017-04-28 20:24:20 +00:00
|
|
|
if (!this._backend) {
|
|
|
|
return Promise.reject(new Error('No transport backend defined!'));
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
2017-04-27 20:21:01 +00:00
|
|
|
|
2017-04-17 15:52:31 +00:00
|
|
|
this._requestID++;
|
2017-04-27 20:21:01 +00:00
|
|
|
|
2017-04-17 15:52:31 +00:00
|
|
|
const id = this._requestID;
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2017-05-01 22:11:26 +00:00
|
|
|
this._responseHandlers.set(id, ({ error, result }) => {
|
2017-06-13 00:12:29 +00:00
|
|
|
if (typeof result !== 'undefined') {
|
2017-04-17 15:52:31 +00:00
|
|
|
resolve(result);
|
2017-06-13 00:12:29 +00:00
|
|
|
|
|
|
|
// eslint-disable-next-line no-negated-condition
|
|
|
|
} else if (typeof error !== 'undefined') {
|
2017-04-17 15:52:31 +00:00
|
|
|
reject(error);
|
|
|
|
} else { // no response
|
|
|
|
reject(new Error('Unexpected response format!'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
this._backend.send({
|
2017-04-17 15:52:31 +00:00
|
|
|
type: MESSAGE_TYPE_REQUEST,
|
2017-04-28 21:03:36 +00:00
|
|
|
data: request,
|
2017-04-27 20:21:01 +00:00
|
|
|
id
|
2017-04-17 15:52:31 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-28 20:24:20 +00:00
|
|
|
* Changes the current backend transport.
|
2017-04-17 15:52:31 +00:00
|
|
|
*
|
2017-04-28 20:24:20 +00:00
|
|
|
* @param {Object} backend - The new transport backend that will be used.
|
2017-04-17 15:52:31 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2017-04-28 20:24:20 +00:00
|
|
|
setBackend(backend) {
|
|
|
|
this._disposeBackend();
|
2017-04-27 20:21:01 +00:00
|
|
|
|
2017-04-28 20:24:20 +00:00
|
|
|
this._backend = backend;
|
2017-04-28 21:03:36 +00:00
|
|
|
this._backend.setReceiveCallback(this._onMessageReceived.bind(this));
|
2017-04-17 15:52:31 +00:00
|
|
|
}
|
|
|
|
}
|