244 lines
6.2 KiB
JavaScript
244 lines
6.2 KiB
JavaScript
import {
|
|
MESSAGE_TYPE_EVENT,
|
|
MESSAGE_TYPE_RESPONSE,
|
|
MESSAGE_TYPE_REQUEST
|
|
} from './constants';
|
|
|
|
/**
|
|
* Stores the currnet transport that have to be used.
|
|
*/
|
|
export default class Transport {
|
|
/**
|
|
* Creates new instance.
|
|
*
|
|
* @param {Object} options - Optional parameters for configuration of the
|
|
* transport.
|
|
*/
|
|
constructor(options = {}) {
|
|
const { transport } = options;
|
|
|
|
this._requestID = 0;
|
|
|
|
this._responseHandlers = new Map();
|
|
|
|
this._listeners = new Map();
|
|
|
|
this._unprocessedMessages = new Set();
|
|
|
|
this.addListener = this.on;
|
|
|
|
if (transport) {
|
|
this.setTransport(transport);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disposes the current transport.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
_disposeTransport() {
|
|
if (this._transport) {
|
|
this._transport.dispose();
|
|
this._transport = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles incomming data from the transport.
|
|
*
|
|
* @param {Object} data - The data.
|
|
* @returns {void}
|
|
*/
|
|
_onDataReceived(data) {
|
|
if (data.type === MESSAGE_TYPE_RESPONSE) {
|
|
const handler = this._responseHandlers.get(data.id);
|
|
|
|
if (handler) {
|
|
handler(data);
|
|
this._responseHandlers.delete(data.id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (data.type === MESSAGE_TYPE_REQUEST) {
|
|
this.emit('request', data.data, (result, error) => {
|
|
this._transport.send({
|
|
type: MESSAGE_TYPE_RESPONSE,
|
|
result,
|
|
error,
|
|
id: data.id
|
|
});
|
|
});
|
|
} else {
|
|
this.emit('event', data.data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disposes the allocated resources.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
dispose() {
|
|
this._responseHandlers.clear();
|
|
this._unprocessedMessages.clear();
|
|
this.removeAllListeners();
|
|
this._disposeTransport();
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @returns {boolean} True if the event had listeners, false otherwise.
|
|
*/
|
|
emit(eventName, ...args) {
|
|
const listenersForEvent = this._listeners.get(eventName);
|
|
|
|
if (!listenersForEvent || listenersForEvent.size === 0) {
|
|
this._unprocessedMessages.add(args);
|
|
|
|
return false;
|
|
}
|
|
|
|
let isProcessed = false;
|
|
|
|
listenersForEvent.forEach(listener => {
|
|
isProcessed = listener(...args) || isProcessed;
|
|
});
|
|
|
|
if (!isProcessed) {
|
|
this._unprocessedMessages.add(args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param {string} [eventName] - The name of the event.
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* Sends the passed data.
|
|
*
|
|
* @param {Object} data - The data to be sent.
|
|
* @returns {void}
|
|
*/
|
|
sendEvent(data = {}) {
|
|
if (this._transport) {
|
|
this._transport.send({
|
|
type: MESSAGE_TYPE_EVENT,
|
|
data
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sending request.
|
|
*
|
|
* @param {Object} data - The data for the request.
|
|
* @returns {Promise}
|
|
*/
|
|
sendRequest(data) {
|
|
if (!this._transport) {
|
|
return Promise.reject(new Error('No transport defined!'));
|
|
}
|
|
this._requestID++;
|
|
const id = this._requestID;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this._responseHandlers.set(this._requestID, response => {
|
|
const { result, error } = response;
|
|
|
|
if (result) {
|
|
resolve(result);
|
|
} else if (error) {
|
|
reject(error);
|
|
} else { // no response
|
|
reject(new Error('Unexpected response format!'));
|
|
}
|
|
});
|
|
|
|
this._transport.send({
|
|
id,
|
|
type: MESSAGE_TYPE_REQUEST,
|
|
data
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Changes the current transport.
|
|
*
|
|
* @param {Object} transport - The new transport that will be used.
|
|
* @returns {void}
|
|
*/
|
|
setTransport(transport) {
|
|
this._disposeTransport();
|
|
this._transport = transport;
|
|
this._transport.setDataReceivedCallback(
|
|
this._onDataReceived.bind(this));
|
|
}
|
|
}
|