nope-js-browser
Version:
NoPE Runtime for the Browser. For nodejs please use nope-js-node
203 lines (202 loc) • 7.98 kB
JavaScript
/**
* @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,
};
}),
};
}
}