Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

Transport.js 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import {
  2. MESSAGE_TYPE_EVENT,
  3. MESSAGE_TYPE_REQUEST,
  4. MESSAGE_TYPE_RESPONSE
  5. } from './constants';
  6. /**
  7. * Stores the currnet transport backend that have to be used. Also implements
  8. * request/response mechanism.
  9. */
  10. export default class Transport {
  11. /**
  12. * Creates new instance.
  13. *
  14. * @param {Object} options - Optional parameters for configuration of the
  15. * transport backend.
  16. */
  17. constructor({ backend } = {}) {
  18. /**
  19. * Maps an event name and listener that have been added to the Transport
  20. * instance.
  21. *
  22. * @type {Map<string, Function>}
  23. */
  24. this._listeners = new Map();
  25. /**
  26. * The request ID counter used for the id property of the request. This
  27. * property is used to match the responses with the request.
  28. *
  29. * @type {number}
  30. */
  31. this._requestID = 0;
  32. /**
  33. * Maps an IDs of the requests and handlers that will process the
  34. * responses of those requests.
  35. *
  36. * @type {Map<number, Function>}
  37. */
  38. this._responseHandlers = new Map();
  39. /**
  40. * A set with the events and requests that were received but not
  41. * processed by any listener. They are later passed on every new
  42. * listener until they are processed.
  43. *
  44. * @type {Set<Object>}
  45. */
  46. this._unprocessedMessages = new Set();
  47. /**
  48. * Alias.
  49. */
  50. this.addListener = this.on;
  51. if (backend) {
  52. this.setBackend(backend);
  53. }
  54. }
  55. /**
  56. * Disposes the current transport backend.
  57. *
  58. * @returns {void}
  59. */
  60. _disposeBackend() {
  61. if (this._backend) {
  62. this._backend.dispose();
  63. this._backend = null;
  64. }
  65. }
  66. /**
  67. * Handles incoming messages from the transport backend.
  68. *
  69. * @param {Object} message - The message.
  70. * @returns {void}
  71. */
  72. _onMessageReceived(message) {
  73. if (message.type === MESSAGE_TYPE_RESPONSE) {
  74. const handler = this._responseHandlers.get(message.id);
  75. if (handler) {
  76. handler(message);
  77. this._responseHandlers.delete(message.id);
  78. }
  79. } else if (message.type === MESSAGE_TYPE_REQUEST) {
  80. this.emit('request', message.data, (result, error) => {
  81. this._backend.send({
  82. type: MESSAGE_TYPE_RESPONSE,
  83. error,
  84. id: message.id,
  85. result
  86. });
  87. });
  88. } else {
  89. this.emit('event', message.data);
  90. }
  91. }
  92. /**
  93. * Disposes the allocated resources.
  94. *
  95. * @returns {void}
  96. */
  97. dispose() {
  98. this._responseHandlers.clear();
  99. this._unprocessedMessages.clear();
  100. this.removeAllListeners();
  101. this._disposeBackend();
  102. }
  103. /**
  104. * Calls each of the listeners registered for the event named eventName, in
  105. * the order they were registered, passing the supplied arguments to each.
  106. *
  107. * @param {string} eventName - The name of the event.
  108. * @returns {boolean} True if the event has been processed by any listener,
  109. * false otherwise.
  110. */
  111. emit(eventName, ...args) {
  112. const listenersForEvent = this._listeners.get(eventName);
  113. let isProcessed = false;
  114. if (listenersForEvent && listenersForEvent.size) {
  115. listenersForEvent.forEach(listener => {
  116. isProcessed = listener(...args) || isProcessed;
  117. });
  118. }
  119. if (!isProcessed) {
  120. this._unprocessedMessages.add(args);
  121. }
  122. return isProcessed;
  123. }
  124. /**
  125. * Adds the listener function to the listeners collection for the event
  126. * named eventName.
  127. *
  128. * @param {string} eventName - The name of the event.
  129. * @param {Function} listener - The listener that will be added.
  130. * @returns {Transport} References to the instance of Transport class, so
  131. * that calls can be chained.
  132. */
  133. on(eventName, listener) {
  134. let listenersForEvent = this._listeners.get(eventName);
  135. if (!listenersForEvent) {
  136. listenersForEvent = new Set();
  137. this._listeners.set(eventName, listenersForEvent);
  138. }
  139. listenersForEvent.add(listener);
  140. this._unprocessedMessages.forEach(args => {
  141. if (listener(...args)) {
  142. this._unprocessedMessages.delete(args);
  143. }
  144. });
  145. return this;
  146. }
  147. /**
  148. * Removes all listeners, or those of the specified eventName.
  149. *
  150. * @param {string} [eventName] - The name of the event. If this parameter is
  151. * not specified all listeners will be removed.
  152. * @returns {Transport} References to the instance of Transport class, so
  153. * that calls can be chained.
  154. */
  155. removeAllListeners(eventName) {
  156. if (eventName) {
  157. this._listeners.delete(eventName);
  158. } else {
  159. this._listeners.clear();
  160. }
  161. return this;
  162. }
  163. /**
  164. * Removes the listener function from the listeners collection for the event
  165. * named eventName.
  166. *
  167. * @param {string} eventName - The name of the event.
  168. * @param {Function} listener - The listener that will be removed.
  169. * @returns {Transport} References to the instance of Transport class, so
  170. * that calls can be chained.
  171. */
  172. removeListener(eventName, listener) {
  173. const listenersForEvent = this._listeners.get(eventName);
  174. if (listenersForEvent) {
  175. listenersForEvent.delete(listener);
  176. }
  177. return this;
  178. }
  179. /**
  180. * Sends the passed event.
  181. *
  182. * @param {Object} event - The event to be sent.
  183. * @returns {void}
  184. */
  185. sendEvent(event = {}) {
  186. if (this._backend) {
  187. this._backend.send({
  188. type: MESSAGE_TYPE_EVENT,
  189. data: event
  190. });
  191. }
  192. }
  193. /**
  194. * Sending request.
  195. *
  196. * @param {Object} request - The request to be sent.
  197. * @returns {Promise}
  198. */
  199. sendRequest(request) {
  200. if (!this._backend) {
  201. return Promise.reject(new Error('No transport backend defined!'));
  202. }
  203. this._requestID++;
  204. const id = this._requestID;
  205. return new Promise((resolve, reject) => {
  206. this._responseHandlers.set(id, ({ error, result }) => {
  207. if (typeof result !== 'undefined') {
  208. resolve(result);
  209. // eslint-disable-next-line no-negated-condition
  210. } else if (typeof error !== 'undefined') {
  211. reject(error);
  212. } else { // no response
  213. reject(new Error('Unexpected response format!'));
  214. }
  215. });
  216. this._backend.send({
  217. type: MESSAGE_TYPE_REQUEST,
  218. data: request,
  219. id
  220. });
  221. });
  222. }
  223. /**
  224. * Changes the current backend transport.
  225. *
  226. * @param {Object} backend - The new transport backend that will be used.
  227. * @returns {void}
  228. */
  229. setBackend(backend) {
  230. this._disposeBackend();
  231. this._backend = backend;
  232. this._backend.setReceiveCallback(this._onMessageReceived.bind(this));
  233. }
  234. }