UNPKG

@jitsi/js-utils

Version:

Utilities for Jitsi JS projects

209 lines (208 loc) 7.1 kB
import { MessageType } from './constants'; /** * Stores the current transport backend that has to be used. Also implements * request/response mechanism. */ export default class Transport { /** * Creates new instance. * * @param {ITransportOptions} options - Optional parameters for configuration of the transport backend. */ constructor({ backend } = {}) { this._listeners = new Map(); this._requestID = 0; this._responseHandlers = new Map(); this._unprocessedMessages = new Set(); this.addListener = this.on; if (backend) { this.setBackend(backend); } } /** * Disposes the current transport backend. */ _disposeBackend() { if (this._backend) { this._backend.dispose(); this._backend = null; } } /** * Handles incoming messages from the transport backend. * * @param {ITransportMessage} message - The message. * @returns {void} */ _onMessageReceived(message) { if (message.type === MessageType.RESPONSE) { const handler = this._responseHandlers.get(message.id); if (handler) { handler(message); this._responseHandlers.delete(message.id); } } else if (message.type === MessageType.REQUEST) { this.emit('request', message.data, (result, error, transfer) => { this._backend.send({ type: MessageType.RESPONSE, error, id: message.id, result }, transfer); }); } 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. * @param {...any} args - Arguments to pass to the listeners. * @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 === null || listenersForEvent === void 0 ? void 0 : 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 {TransportListener} 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 {TransportListener} 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. * @param {Array<any>} [transfer] - An optional array of transferable objects (e.g., ArrayBuffer, MessagePort) * to transfer ownership of to the remote side, rather than cloning them. * @returns {void} */ sendEvent(event = {}, transfer) { if (this._backend) { this._backend.send({ type: MessageType.EVENT, data: event }, transfer); } } /** * Sending request. * * @param {Object} request - The request to be sent. * @returns {Promise<any>} A promise that resolves with the response result or rejects with an error. */ 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!')); } }); try { this._backend.send({ type: MessageType.REQUEST, data: request, id }); } catch (error) { this._responseHandlers.delete(id); reject(error); } }); } /** * Changes the current backend transport. * * @param {ITransportBackend} 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)); } }