2020-10-12 16:13:10 +00:00
|
|
|
/**
|
|
|
|
* @author Martin Karkowski
|
|
|
|
* @email m.karkowski@zema.de
|
|
|
|
* @create date 2020-10-12 17:54:08
|
2020-12-04 18:10:33 +00:00
|
|
|
* @modify date 2020-12-02 07:35:09
|
2020-10-12 16:13:10 +00:00
|
|
|
* @desc [description]
|
|
|
|
*/
|
|
|
|
|
2021-10-18 06:00:35 +00:00
|
|
|
import { ILogger } from "js-logger";
|
2020-12-04 18:10:33 +00:00
|
|
|
import {
|
|
|
|
getMethodPath,
|
|
|
|
getPropertyPath,
|
|
|
|
isMethodPathCorrect,
|
2021-12-04 07:25:26 +00:00
|
|
|
isPropertyPathCorrect,
|
2020-12-04 18:10:33 +00:00
|
|
|
} from "../helpers/dispatcherPathes";
|
2020-11-24 14:14:56 +00:00
|
|
|
import { deepClone } from "../helpers/objectMethods";
|
2021-10-18 06:00:35 +00:00
|
|
|
import { getNopeLogger } from "../logger/getLogger";
|
2020-11-06 08:10:30 +00:00
|
|
|
import { INopeDispatcher } from "../types/nope/nopeDispatcher.interface";
|
2020-12-04 18:10:33 +00:00
|
|
|
import {
|
|
|
|
IAuthor,
|
|
|
|
IFunctionOptions,
|
|
|
|
INopeModule,
|
|
|
|
INopeModuleDescription,
|
|
|
|
IPropertyOptions,
|
2021-12-04 07:25:26 +00:00
|
|
|
IVersion,
|
2020-12-04 18:10:33 +00:00
|
|
|
} from "../types/nope/nopeModule.interface";
|
2020-11-06 08:10:30 +00:00
|
|
|
import { INopeObservable } from "../types/nope/nopeObservable.interface";
|
2020-10-12 16:13:10 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base Implementation of a Module.
|
2020-12-04 18:10:33 +00:00
|
|
|
*
|
|
|
|
* The Module is used to share information and data. Although it implements the
|
2020-11-06 08:10:30 +00:00
|
|
|
* the Basic behavior to fullfill a given traget.
|
2020-10-12 16:13:10 +00:00
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @class BaseModule
|
2020-10-13 13:18:25 +00:00
|
|
|
* @implements {INopeModule}
|
2020-10-12 16:13:10 +00:00
|
|
|
*/
|
2020-10-13 13:18:25 +00:00
|
|
|
export class NopeBaseModule implements INopeModule {
|
2020-12-04 18:10:33 +00:00
|
|
|
/**
|
|
|
|
* Return the Class Identifier.
|
|
|
|
*/
|
|
|
|
public get type(): string {
|
|
|
|
return Object.getPrototypeOf(this).constructor.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Description of the Module. This is used to Describe roughly
|
|
|
|
* what the module is capable of
|
|
|
|
* doing.
|
|
|
|
*
|
|
|
|
* @type {string}
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
public description: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Description of the Author. Use to Mail etc.
|
|
|
|
*
|
|
|
|
* @type {IAuthor}
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
public author: IAuthor;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Description of the provided Version of the Module.
|
|
|
|
*
|
|
|
|
* @type {IVersion}
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
public version: IVersion;
|
|
|
|
|
|
|
|
protected _registeredFunctions: Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
func: (...args: any[]) => Promise<any>;
|
|
|
|
options: IFunctionOptions;
|
2020-10-13 13:18:25 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
>;
|
2020-10-13 13:18:25 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
protected _registeredProperties: Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
observable: INopeObservable<any>;
|
|
|
|
options: IPropertyOptions;
|
2020-10-13 13:18:25 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Public getter for the functions
|
|
|
|
*
|
|
|
|
* @readonly
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
public get functions() {
|
|
|
|
const ret: { [index: string]: IFunctionOptions } = {};
|
|
|
|
|
|
|
|
for (const [name, funcs] of this._registeredFunctions.entries()) {
|
|
|
|
ret[name] = funcs.options;
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
2020-11-06 08:10:30 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2020-10-12 16:13:10 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
/**
|
|
|
|
* Public get to receive a Description of the Properties
|
|
|
|
*
|
|
|
|
* @readonly
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
public get properties() {
|
|
|
|
const ret: { [index: string]: IPropertyOptions<any> } = {};
|
2020-10-12 16:13:10 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
for (const [name, funcs] of this._registeredProperties.entries()) {
|
|
|
|
ret[name] = funcs.options;
|
|
|
|
}
|
2020-10-12 16:13:10 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Identifier of the Module.
|
|
|
|
*
|
|
|
|
* @type {string}
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
public identifier: string;
|
|
|
|
|
|
|
|
public _markedElements: Array<{
|
|
|
|
accessor: string;
|
|
|
|
options: IPropertyOptions | IFunctionOptions;
|
|
|
|
type: "method" | "prop";
|
|
|
|
}>;
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
protected _logger: ILogger;
|
2021-10-18 06:00:35 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
/**
|
|
|
|
* Creates an instance of BaseModule.
|
|
|
|
* @memberof BaseModule
|
|
|
|
*/
|
|
|
|
constructor(protected _dispatcher: INopeDispatcher) {
|
|
|
|
this.description = null;
|
|
|
|
this.author = null;
|
|
|
|
this.version = null;
|
|
|
|
this.identifier = null;
|
|
|
|
this._registeredFunctions = new Map();
|
|
|
|
this._registeredProperties = new Map();
|
2021-04-20 18:49:15 +00:00
|
|
|
this.uiLinks = [];
|
2021-10-18 06:00:35 +00:00
|
|
|
this._logger = getNopeLogger("BaseModule");
|
2020-12-04 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
2021-04-20 18:49:15 +00:00
|
|
|
public uiLinks: { name: string; description: string; link: string }[];
|
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
/**
|
|
|
|
* Helper Function to register an Observable (a Property.)
|
|
|
|
*
|
|
|
|
* @template T Type of the Property
|
|
|
|
* @template K Additional Pipe Options
|
|
|
|
* @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 {IPropertyOptions<K>} options The Options used to define the registration.
|
|
|
|
* @return {*} {Promise<void>}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async registerProperty<T, K = any, S = T, G = T>(
|
|
|
|
name: string,
|
|
|
|
observable: INopeObservable<T, S, G>,
|
|
|
|
options: IPropertyOptions<K>
|
|
|
|
): Promise<void> {
|
|
|
|
// Unregister the Function
|
|
|
|
await this.unregisterProperty(name);
|
|
|
|
|
|
|
|
// Adapt the Topics
|
|
|
|
if (
|
|
|
|
typeof options.topic === "string" &&
|
|
|
|
!isPropertyPathCorrect(this.identifier, options.topic as string)
|
|
|
|
) {
|
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const _observable = await this._dispatcher.registerObservable(
|
|
|
|
observable,
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
// Register the new Property.
|
|
|
|
this._registeredProperties.set(name, {
|
|
|
|
observable: _observable,
|
2021-12-04 07:25:26 +00:00
|
|
|
options,
|
2020-12-04 18:10:33 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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>} func The function itself. It must be async.
|
|
|
|
* @param {IFunctionOptions} options The Options, used for registering.
|
|
|
|
* @return {*} {Promise<void>}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async registerMethod(
|
|
|
|
name: string,
|
|
|
|
func: (...args: any[]) => Promise<any>,
|
|
|
|
options: IFunctionOptions
|
|
|
|
): Promise<void> {
|
|
|
|
// 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);
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
const _func = await this._dispatcher.registerFunction(func, options);
|
|
|
|
|
|
|
|
// Register the new Function.
|
|
|
|
this._registeredFunctions.set(name, {
|
|
|
|
func: _func,
|
2021-12-04 07:25:26 +00:00
|
|
|
options,
|
2020-12-04 18:10:33 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregister a Function
|
|
|
|
*
|
|
|
|
* @param {string} name Name of the function used during registering.
|
|
|
|
* @return {*} {Promise<void>}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async unregisterFunction(name: string): Promise<void> {
|
|
|
|
// Test if the Method is already registerd,
|
|
|
|
// If so => unregister it first.
|
|
|
|
if (this._registeredFunctions.has(name)) {
|
|
|
|
this._dispatcher.unregisterFunction(
|
|
|
|
this._registeredFunctions.get(name).func,
|
|
|
|
{
|
2021-12-04 07:25:26 +00:00
|
|
|
preventSendingToRegistery: true,
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
);
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
public async unregisterProperty(name: string): Promise<void> {
|
|
|
|
// Test if the Property is already registerd,
|
|
|
|
// If so => unregister it first.
|
|
|
|
if (this._registeredProperties.has(name)) {
|
|
|
|
this._dispatcher.unregisterObservable(
|
|
|
|
this._registeredProperties.get(name).observable,
|
|
|
|
{
|
2021-12-04 07:25:26 +00:00
|
|
|
preventSendingToRegistery: true,
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
);
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to return all available Methods.
|
|
|
|
*
|
|
|
|
* @return {*} {Promise<{ func: (...args: any[]) => Promise<any>; options: IFunctionOptions; }[]>}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async listFunctions(): Promise<
|
|
|
|
{ func: (...args: any[]) => Promise<any>; options: IFunctionOptions }[]
|
|
|
|
> {
|
|
|
|
return Array.from(this._registeredFunctions.values());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function used to list all available Properties.
|
|
|
|
*
|
|
|
|
* @return {*} {Promise<Array<{ observable: INopeObservable<any>, options: IPropertyOptions }>>}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async listProperties(): Promise<
|
|
|
|
Array<{ observable: INopeObservable<any>; options: IPropertyOptions }>
|
|
|
|
> {
|
|
|
|
return Array.from(this._registeredProperties.values());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An init Function. Used to initialize the Element.
|
|
|
|
*
|
|
|
|
* @return {*} {Promise<void>}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async init(...args): Promise<void> {
|
|
|
|
// 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");
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
if (this.description === null) {
|
|
|
|
throw Error(
|
|
|
|
"Please Provide a Description for the Module before initializing"
|
|
|
|
);
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
if (this.author === null) {
|
|
|
|
throw Error(
|
|
|
|
"Please Provide an Author for the Module before initializing"
|
|
|
|
);
|
|
|
|
}
|
2020-10-13 13:18:25 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
if (this.version === null) {
|
|
|
|
throw Error(
|
|
|
|
"Please Provide a Version for the Module before initializing"
|
|
|
|
);
|
|
|
|
}
|
2020-11-05 17:02:29 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
if (this.identifier === null) {
|
|
|
|
throw Error(
|
|
|
|
"Please Provide an Identifier for the Module before initializing"
|
|
|
|
);
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
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 as IFunctionOptions
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case "prop":
|
|
|
|
await this.registerProperty(
|
|
|
|
entry.accessor,
|
|
|
|
_this[entry.accessor],
|
|
|
|
entry.options as IPropertyOptions
|
|
|
|
);
|
|
|
|
break;
|
2020-10-12 16:13:10 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function, which is used to unregister the element.
|
|
|
|
*
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public async dispose() {
|
|
|
|
// Unregister all Methods and Functions
|
|
|
|
for (const name of this._registeredFunctions.keys()) {
|
|
|
|
await this.unregisterFunction(name);
|
|
|
|
}
|
2020-10-12 16:13:10 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
// Remove all known Functions
|
|
|
|
this._registeredFunctions.clear();
|
2020-10-12 16:13:10 +00:00
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
// Unregister all Properties.
|
|
|
|
for (const name of this._registeredProperties.keys()) {
|
|
|
|
await this.unregisterProperty(name);
|
2020-10-13 13:18:25 +00:00
|
|
|
}
|
2020-12-04 18:10:33 +00:00
|
|
|
|
|
|
|
// Remove all known Properties.
|
|
|
|
this._registeredProperties.clear();
|
|
|
|
}
|
|
|
|
|
2021-05-04 14:51:27 +00:00
|
|
|
/**
|
|
|
|
* Helper Function to extract the used identifiert of Property
|
|
|
|
*
|
|
|
|
* @param {(((...args) => Promise<any>) | INopeObservable<any>)} propOrFunc The Property or the Function to receive the Name.
|
|
|
|
* @return {*} {string}
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public getIdentifierOf(
|
2021-08-17 15:52:46 +00:00
|
|
|
propOrFunc: ((...args) => Promise<any>) | INopeObservable<any>,
|
|
|
|
type: "topicToPublish" | "topicToSubscribe" = null
|
2021-05-04 14:51:27 +00:00
|
|
|
): string {
|
|
|
|
// 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.
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
for (const [
|
|
|
|
name,
|
|
|
|
{ observable, options },
|
|
|
|
] of this._registeredProperties.entries()) {
|
2021-05-04 14:51:27 +00:00
|
|
|
if (observable == propOrFunc) {
|
2021-08-17 15:52:46 +00:00
|
|
|
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;
|
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
throw Error(
|
|
|
|
"Prop uses different name for subscribing and publishing. Please specify using the 'type' identier to select"
|
|
|
|
);
|
2021-08-17 15:52:46 +00:00
|
|
|
}
|
2021-05-04 14:51:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
for (const [
|
|
|
|
name,
|
|
|
|
{ func, options },
|
|
|
|
] of this._registeredFunctions.entries()) {
|
2021-05-04 14:51:27 +00:00
|
|
|
if (func == propOrFunc) {
|
2021-08-17 15:52:46 +00:00
|
|
|
return options.id;
|
2021-05-04 14:51:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw Error("Element not found or registered");
|
|
|
|
}
|
|
|
|
|
2020-12-04 18:10:33 +00:00
|
|
|
/**
|
|
|
|
* Helper function to extract an description of the Module.
|
|
|
|
*
|
|
|
|
* @return {INopeModuleDescription} a parsed description
|
|
|
|
* @memberof NopeBaseModule
|
|
|
|
*/
|
|
|
|
public toDescription(): INopeModuleDescription {
|
|
|
|
const ret: INopeModuleDescription = {
|
|
|
|
author: this.author,
|
|
|
|
description: this.description,
|
|
|
|
functions: this.functions,
|
|
|
|
identifier: this.identifier,
|
|
|
|
properties: this.properties,
|
|
|
|
type: this.type,
|
2021-04-20 18:49:15 +00:00
|
|
|
version: this.version,
|
2021-12-04 07:25:26 +00:00
|
|
|
uiLinks: this.uiLinks,
|
2020-12-04 18:10:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|