123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- import {
- MESSAGE_TYPE_EVENT,
- MESSAGE_TYPE_REQUEST,
- MESSAGE_TYPE_RESPONSE
- } from './constants';
-
- /**
- * Stores the currnet transport backend that have to be used. Also implements
- * request/response mechanism.
- */
- export default class Transport {
- /**
- * Creates new instance.
- *
- * @param {Object} options - Optional parameters for configuration of the
- * transport backend.
- */
- constructor({ backend } = {}) {
- /**
- * Maps an event name and listener that have been added to the Transport
- * instance.
- *
- * @type {Map<string, Function>}
- */
- this._listeners = new Map();
-
- /**
- * 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}
- */
- this._requestID = 0;
-
- /**
- * Maps an IDs of the requests and handlers that will process the
- * responses of those requests.
- *
- * @type {Map<number, Function>}
- */
- this._responseHandlers = new Map();
-
- /**
- * 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>}
- */
- this._unprocessedMessages = new Set();
-
- /**
- * Alias.
- */
- this.addListener = this.on;
-
- if (backend) {
- this.setBackend(backend);
- }
- }
-
- /**
- * Disposes the current transport backend.
- *
- * @returns {void}
- */
- _disposeBackend() {
- if (this._backend) {
- this._backend.dispose();
- this._backend = null;
- }
- }
-
- /**
- * Handles incoming messages from the transport backend.
- *
- * @param {Object} message - The message.
- * @returns {void}
- */
- _onMessageReceived(message) {
- if (message.type === MESSAGE_TYPE_RESPONSE) {
- const handler = this._responseHandlers.get(message.id);
-
- if (handler) {
- handler(message);
- this._responseHandlers.delete(message.id);
- }
- } else if (message.type === MESSAGE_TYPE_REQUEST) {
- this.emit('request', message.data, (result, error) => {
- this._backend.send({
- type: MESSAGE_TYPE_RESPONSE,
- error,
- id: message.id,
- result
- });
- });
- } else {
- this.emit('event', message.data);
- }
- }
-
- /**
- * Disposes the allocated resources.
- *
- * @returns {void}
- */
- dispose() {
- this._responseHandlers.clear();
- this._unprocessedMessages.clear();
- this.removeAllListeners();
- this._disposeBackend();
- }
-
- /**
- * 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 has been processed by any listener,
- * false otherwise.
- */
- emit(eventName, ...args) {
- const listenersForEvent = this._listeners.get(eventName);
- let isProcessed = false;
-
- if (listenersForEvent && listenersForEvent.size) {
- listenersForEvent.forEach(listener => {
- isProcessed = listener(...args) || isProcessed;
- });
- }
-
- if (!isProcessed) {
- this._unprocessedMessages.add(args);
- }
-
- return isProcessed;
- }
-
- /**
- * 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. If this parameter is
- * not specified all listeners will be removed.
- * @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 event.
- *
- * @param {Object} event - The event to be sent.
- * @returns {void}
- */
- sendEvent(event = {}) {
- if (this._backend) {
- this._backend.send({
- type: MESSAGE_TYPE_EVENT,
- data: event
- });
- }
- }
-
- /**
- * Sending request.
- *
- * @param {Object} request - The request to be sent.
- * @returns {Promise}
- */
- sendRequest(request) {
- if (!this._backend) {
- return Promise.reject(new Error('No transport backend defined!'));
- }
-
- this._requestID++;
-
- const id = this._requestID;
-
- return new Promise((resolve, reject) => {
- this._responseHandlers.set(id, ({ error, result }) => {
- if (typeof result !== 'undefined') {
- resolve(result);
-
- // eslint-disable-next-line no-negated-condition
- } else if (typeof error !== 'undefined') {
- reject(error);
- } else { // no response
- reject(new Error('Unexpected response format!'));
- }
- });
-
- this._backend.send({
- type: MESSAGE_TYPE_REQUEST,
- data: request,
- id
- });
- });
- }
-
- /**
- * Changes the current backend transport.
- *
- * @param {Object} backend - The new transport backend that will be used.
- * @returns {void}
- */
- setBackend(backend) {
- this._disposeBackend();
-
- this._backend = backend;
- this._backend.setReceiveCallback(this._onMessageReceived.bind(this));
- }
- }
|