UNPKG

nope-js-browser

Version:

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

414 lines (413 loc) 16.5 kB
/** * @author Martin Karkowski * @email m.karkowski@zema.de */ import { getEmitterPath, getMethodPath, getPropertyPath, isEmitterPathCorrect, isMethodPathCorrect, isPropertyPathCorrect, } from "../helpers/dispatcherPathes"; import { deepClone } from "../helpers/objectMethods"; import { getNopeLogger } from "../logger/getLogger"; /** * Base Implementation of a Module. * * The Module is used to share information and data. Although it implements the * the Basic behavior to fullfill a given traget. * * @export * @class BaseModule * @implements {INopeModule} */ export class NopeBaseModule { /** * Return the Class Identifier. */ get type() { return Object.getPrototypeOf(this).constructor.name; } /** * Public getter for the functions * * @readonly * @memberof BaseModule */ get methods() { const ret = {}; for (const [name, funcs] of this._registeredMethods.entries()) { ret[name] = funcs.options; } return ret; } /** * Public get to receive a Description of the Properties * * @readonly * @memberof BaseModule */ get properties() { const ret = {}; for (const [name, funcs] of this._registeredProperties.entries()) { ret[name] = funcs.options; } return ret; } /** * Public get to receive a Description of the Properties * * @readonly * @memberof BaseModule */ get events() { const ret = {}; for (const [name, funcs] of this._registeredEvents.entries()) { ret[name] = funcs.options; } return ret; } /** * Creates an instance of BaseModule. * @memberof BaseModule */ constructor(_core) { this._core = _core; this.description = null; this.author = null; this.version = null; this.identifier = null; this._registeredMethods = new Map(); this._registeredProperties = new Map(); this._registeredEvents = new Map(); this.uiLinks = []; this._logger = getNopeLogger("BaseModule"); } /** * Helper Function to register an Observable (a Property.) * * @template T Type of the Property * @template S Setter Type of the Property * @template G Getter Type of the Property * @param {string} name Name, which should be used to register the element. The Name will ALLWAYS (automatically) be assembled using the modules identifier an then the name. * @param {INopeObservable<T, S, G>} observable The Observable representing the Property * @param {IEventOptions<K>} options The Options used to define the registration. * @return {Promise<void>} * @memberof NopeBaseModule */ async registerProperty(name, observable, options) { // Unregister the Function await this.unregisterProperty(name); // Adapt the Topics if (typeof options.topic === "string" && !isPropertyPathCorrect(this.identifier, options.topic)) { options.topic = getPropertyPath(this.identifier, options.topic); } else if (typeof options.topic === "object") { if (options.topic.subscribe && !isPropertyPathCorrect(this.identifier, options.topic.subscribe)) { options.topic.subscribe = getPropertyPath(this.identifier, options.topic.subscribe); } if (options.topic.publish && !isPropertyPathCorrect(this.identifier, options.topic.publish)) { options.topic.publish = getPropertyPath(this.identifier, options.topic.publish); } } else { throw Error("Topic must be provided in the options"); } const _observable = await this._core.dataDistributor.register(observable, options); // Register the new Property. this._registeredProperties.set(name, { observable: _observable, options, }); } /** * Helper Function to register an Event(Emitter) (a Property.) * * @template T Type of the Event(Emitter) * @template S Setter Type of the Event(Emitter) * @template G Getter Type of the Event(Emitter) * @param {string} name Name, which should be used to register the element. The Name will ALLWAYS (automatically) be assembled using the modules identifier an then the name. * @param {INopeObservable<T, S, G>} emitter The Event(Emitter) representing the Property * @param {IEventOptions<K>} options The Options used to define the registration. * @return {Promise<void>} * @memberof NopeBaseModule */ async registerEvent(name, emitter, options) { // Unregister the Function await this.unregisterEvent(name); // Adapt the Topics if (typeof options.topic === "string" && !isEmitterPathCorrect(this.identifier, options.topic)) { options.topic = getEmitterPath(this.identifier, options.topic); } else if (typeof options.topic === "object") { if (options.topic.subscribe && !isEmitterPathCorrect(this.identifier, options.topic.subscribe)) { options.topic.subscribe = getEmitterPath(this.identifier, options.topic.subscribe); } if (options.topic.publish && !isEmitterPathCorrect(this.identifier, options.topic.publish)) { options.topic.publish = getEmitterPath(this.identifier, options.topic.publish); } } const _emitter = await this._core.eventDistributor.register(emitter, options); // Register the new Property. this._registeredEvents.set(name, { emitter: _emitter, options, }); } /** * Function used to register a Method. This Method will be available in the shared network. * * @param {string} name Name of the Method, which is used during registration at the dispatcher * @param {(...args: any[]) => Promise<any>} method The function itself. It must be async. * @param {IServiceOptions} options The Options, used for registering. * @return {Promise<void>} * @memberof NopeBaseModule */ async registerMethod(name, method, options) { // Unregister the Function await this.unregisterFunction(name); // Adapt the Method ID if (options.id) { if (!isMethodPathCorrect(this.identifier, options.id)) { options.id = getMethodPath(this.identifier, options.id); } } else { options.id = getMethodPath(this.identifier, name); } const _method = await this._core.rpcManager.registerService(method, options); // Register the new Function. this._registeredMethods.set(name, { method: _method, options, }); } /** * Unregister a Function * * @param {string} name Name of the function used during registering. * @return {Promise<void>} * @memberof NopeBaseModule */ async unregisterFunction(name) { // Test if the Method is already registerd, // If so => unregister it first. if (this._registeredMethods.has(name)) { this._core.rpcManager.unregisterService(this._registeredMethods.get(name).method); } } /** * Helper Function to unregister an Eventbased Property * * @param {string} name Name of the Property, that has been used to register. * @return {Promise<void>} * @memberof NopeBaseModule */ async unregisterEvent(name) { // Test if the Property is already registerd, // If so => unregister it first. if (this._registeredEvents.has(name)) { this._core.eventDistributor.unregister(this._registeredEvents.get(name).emitter); } } /** * Helper Function to unregister an Observable (a Property.) * * @param {string} name Name of the Property, that has been used to register. * @return {Promise<void>} * @memberof NopeBaseModule */ async unregisterProperty(name) { // Test if the Property is already registerd, // If so => unregister it first. if (this._registeredProperties.has(name)) { this._core.dataDistributor.unregister(this._registeredProperties.get(name).observable); } } /** * Function to return all available Methods. * * @memberof NopeBaseModule */ async listMethods() { return Array.from(this._registeredMethods.values()); } /** * Function used to list all available Properties. * @memberof NopeBaseModule */ async listProperties() { return Array.from(this._registeredProperties.values()); } /** * Function used to list all available Properties. * * @return {Promise<Array<{ observable: INopeObservable<any>, options: IPropertyOptions }>>} * @memberof NopeBaseModule */ async listEvents() { return Array.from(this._registeredEvents.values()); } /** * An init Function. Used to initialize the Element. * * @return {Promise<void>} * @memberof NopeBaseModule */ async init(...args) { // In this base Implementation, check if every requried property is set // correctly. If not => raise an error. if (this.type === null) { throw Error("Please Provide a Name for the Module before initializing"); } if (this.description === null) { throw Error("Please Provide a Description for the Module before initializing"); } if (this.author === null) { throw Error("Please Provide an Author for the Module before initializing"); } if (this.version === null) { throw Error("Please Provide a Version for the Module before initializing"); } if (this.identifier === null) { throw Error("Please Provide an Identifier for the Module before initializing"); } if (this._markedElements) { const _this = this; for (const entry of deepClone(this._markedElements)) { switch (entry.type) { case "method": await this.registerMethod(entry.accessor, (...args) => { return _this[entry.accessor](...args); }, entry.options); break; case "prop": await this.registerProperty(entry.accessor, _this[entry.accessor], entry.options); break; case "event": await this.registerEvent(entry.accessor, _this[entry.accessor], entry.options); break; } } } } /** * Function, which is used to unregister the element. * * @memberof NopeBaseModule */ async dispose() { // Unregister all Methods and Functions for (const name of this._registeredMethods.keys()) { await this.unregisterFunction(name); } // Remove all known Functions this._registeredMethods.clear(); // Unregister all Properties. for (const name of this._registeredProperties.keys()) { await this.unregisterProperty(name); } // Remove all known Properties. this._registeredProperties.clear(); // Unregister all Properties. for (const name of this._registeredEvents.keys()) { await this.unregisterEvent(name); } // Remove all known Properties. this._registeredEvents.clear(); } /** * Helper Function to extract the used identifiert of Property * * @param {(((...args) => Promise<any>) | INopeObservable<any>)} prop_event_or_func The Property or the Function to receive the Name. * @return {*} {string} * @memberof NopeBaseModule */ getIdentifierOf(prop_event_or_func, type = null) { // To Extract the name of the Property or the Function, we will iterate over // the registered properties and the regiestered functions. If the prop or the // function matches ==> return the name otherwise we throw an error. for (const [name, item] of this._registeredProperties.entries()) { const { observable, options } = item; if (observable == prop_event_or_func) { const _subTopic = typeof options.topic === "string" ? options.topic : options.topic.subscribe || null; const _pubTopic = typeof options.topic === "string" ? options.topic : options.topic.publish || null; switch (type) { case "topicToPublish": if (_pubTopic === null) { throw Error("No topic for publishing is defined."); } return _pubTopic; case "topicToSubscribe": if (_subTopic === null) { throw Error("No topic for subscribing is defined."); } return _subTopic; default: if (typeof options.topic === "string") { return options.topic; } throw Error("Prop uses different name for subscribing and publishing. Please specify using the 'type' identier to select"); } } } for (const [name, item] of this._registeredEvents.entries()) { const { emitter, options } = item; if (emitter == prop_event_or_func) { const _subTopic = typeof options.topic === "string" ? options.topic : options.topic.subscribe || null; const _pubTopic = typeof options.topic === "string" ? options.topic : options.topic.publish || null; switch (type) { case "topicToPublish": if (_pubTopic === null) { throw Error("No topic for publishing is defined."); } return _pubTopic; case "topicToSubscribe": if (_subTopic === null) { throw Error("No topic for subscribing is defined."); } return _subTopic; default: if (typeof options.topic === "string") { return options.topic; } throw Error("Prop uses different name for subscribing and publishing. Please specify using the 'type' identier to select"); } } } for (const [name, item] of this._registeredMethods.entries()) { const { method: func, options } = item; if (func == prop_event_or_func) { return options.id; } } throw Error("Element not found or registered"); } /** * Helper function to extract an description of the Module. * * @return {INopeModuleDescription} a parsed description * @memberof NopeBaseModule */ toDescription() { const ret = { author: this.author, description: this.description, methods: this.methods, events: this.events, identifier: this.identifier, properties: this.properties, type: this.type, version: this.version, uiLinks: this.uiLinks, }; return ret; } }