@jitsi/js-utils
Version:
Utilities for Jitsi JS projects
209 lines (208 loc) • 7.1 kB
JavaScript
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));
}
}