UNPKG

nope-js-browser

Version:

NoPE Runtime for the Browser. For nodejs please use nope-js-node

203 lines (202 loc) 7.98 kB
/** * @author Martin Karkowski * @email m.karkowski@zema.de */ import { EventEmitter } from "events"; import { generateId } from "../helpers/idMethods"; import { DEBUG, defineNopeLogger, WARN, } from "../logger/index.browser"; import { NopeObservable } from "../observables/nopeObservable"; export class Bridge { /** * Creates an instance of Bridge. * @param {*} [id=generateId()] The ID. (this can be adapted later and is only used to simplify debugging) * @param {string} [loggerName="bridge"] The Name of the Logger. * @param {LoggerLevel} [level="info"] The Level of the Logger. * @memberof Bridge */ constructor(id = generateId(), logger = false) { this.considerConnection = true; this._internalEmitter = new EventEmitter(); this._callbacks = new Map(); this._layers = new Map(); this.id = id; this._logger = defineNopeLogger(logger, `nope.bridge`); this._useInternalEmitter = true; const _this = this; this.connected = new NopeObservable(); this.connected.setContent(false); // Add a custom handler for the connect flag. // the Flag is defined as true, if every socket // is connected. this.connected.getter = () => { for (const data of _this._layers.values()) { if (data.considerConnection && !data.layer.connected.getContent()) { return false; } } return true; }; } async on(eventname, cb) { return this._on(eventname, cb); } async emit(eventname, data) { return this._emit(eventname, null, data); } detailListeners(type, listeners) { } get receivesOwnMessages() { for (const layer of this._layers.values()) { if (!layer.layer.receivesOwnMessages) { return false; } } return true; } async dispose() { // Iterate over the Layers and dispose them. for (const item of this._layers.values()) { await item.layer.dispose(); } } _checkInternalEmitter() { this._useInternalEmitter = true; for (const layer of this._layers.values()) { if (layer.layer.receivesOwnMessages) { this._useInternalEmitter = false; break; } } } /** * Helper Function, which will internally subscribe to the Events of the Layer. * * @protected * @param {ICommunicationInterface} layer The Layer to consinder, on this layer, we will subscribe to the events * @param {keyof ICommunicationInterface} method The method used for subscription * @param {string} event The name of the Event * @param {boolean} forwardData Flag, showing whether data will be forwarded or not. * @memberof BridgeV2 */ _subscribeToCallback(layer, event, forwardData) { const _this = this; // Subscribe to the Event. layer .on(event, (data) => { // Now we are able to iterate over the Methods and forward the content // but only if the Layer forwards the content if (forwardData) { _this._emit(event, layer, data); } else { _this._internalEmitter.emit(event, data); } }) .catch((error) => { if (_this._logger) { _this._logger.error(`failed subscribing to event "${event}"`); _this._logger.error(error); } }); } _on(event, cb) { var _a; // Store the Unspecific callbacks if (!this._callbacks.has(event)) { this._callbacks.set(event, [cb]); // We only are going to subscribe, if there is no log listener. if (((_a = this._logger) === null || _a === void 0 ? void 0 : _a.enabledFor(DEBUG)) && event !== "statusChanged") { this._logger.debug("subscribe to", event); // Rise the max listeners this._internalEmitter.setMaxListeners(this._internalEmitter.getMaxListeners() + 1); // If logging is enable, we subscribe to that. const _this = this; this._internalEmitter.on(event, (data) => { _this._logger.debug("received", event, data); }); } // Iterate over the Layers and on the connected Layers, // subscribe the methods. for (const data of this._layers.values()) { if (data.layer.connected.getContent()) { this._subscribeToCallback(data.layer, event, data.forwardData); } } } else { this._callbacks.get(event).push(cb); } // Rise the max listeners this._internalEmitter.setMaxListeners(this._internalEmitter.getMaxListeners() + 1); // Subscribe this._internalEmitter.on(event, cb); } _emit(event, toExclude = null, dataToSend, force = false) { var _a; if (((_a = this._logger) === null || _a === void 0 ? void 0 : _a.enabledFor(WARN)) && event !== "statusChanged") { this._logger.debug("emitting", event, dataToSend); } if (this._useInternalEmitter || force) { // Emit the Event on the internal Layer. this._internalEmitter.emit(event, dataToSend); } const _this = this; // Iterate over the Layers. for (const data of this._layers.values()) { // If the Layer has been conneced if (data.layer !== toExclude && data.layer.connected.getContent()) { // Only Publish the Data, on which we are forwarding data.layer.emit(event, dataToSend).catch((error) => { if (_this._logger) { _this._logger.error(`failed to emit the event "${event}"`); _this._logger.error(error); } }); } } } async addCommunicationLayer(layer, forwardData = false, considerConnection = false) { if (!this._layers.has(layer.id)) { // Store the Layers: this._layers.set(layer.id, { layer, considerConnection, forwardData, }); // Forward the Events of the Layer // being connected to our aggregated // state const _this = this; layer.connected.subscribe(() => { _this.connected.forcePublish(); }); // Wait until the Layer is connected. await layer.connected.waitFor(); // Register all know unspecific methods for (const [event, cbs] of this._callbacks.entries()) { for (const callback of cbs) { layer.on(event, callback); } } this._checkInternalEmitter(); } } async removeCommunicationLayer(layer) { if (this._layers.has(layer.id)) { this._layers.delete(layer.id); this._checkInternalEmitter(); } } toDescription() { return { connected: this.connected.getContent(), layers: Array.from(this._layers.values()).map((item) => { return { forwardData: item.forwardData, receivesOwnMessages: item.layer.receivesOwnMessages, id: item.layer.id, considerConnection: item.considerConnection, }; }), }; } }