2021-12-04 07:25:26 +00:00
|
|
|
/**
|
|
|
|
* @author Martin Karkowski
|
|
|
|
* @email m.karkowski@zema.de
|
2022-01-07 17:12:08 +00:00
|
|
|
* @create date 2020-01-03 11:52:00
|
|
|
|
* @modify date 2022-01-05 17:51:09
|
2021-12-04 07:25:26 +00:00
|
|
|
* @desc [description]
|
|
|
|
*/
|
|
|
|
|
|
|
|
import * as Logger from "js-logger";
|
|
|
|
import { ILogger } from "js-logger";
|
2022-01-07 17:12:08 +00:00
|
|
|
import { NopeEventEmitter } from "../../eventEmitter/index";
|
|
|
|
import { isAsyncFunction } from "../../helpers/async";
|
|
|
|
import { generateId } from "../../helpers/idMethods";
|
|
|
|
import { MapBasedMergeData } from "../../helpers/mergedData";
|
|
|
|
import { SPLITCHAR } from "../../helpers/objectMethods";
|
|
|
|
import { defineNopeLogger } from "../../logger/getLogger";
|
|
|
|
import { NopePromise } from "../../promise/nopePromise";
|
2021-12-04 07:25:26 +00:00
|
|
|
import {
|
|
|
|
IAvailableServicesMsg,
|
|
|
|
ICallOptions,
|
|
|
|
ICommunicationBridge,
|
2022-01-03 15:46:36 +00:00
|
|
|
IMapBasedMergeData,
|
|
|
|
INopeDispatcherOptions,
|
2021-12-04 07:25:26 +00:00
|
|
|
INopeEventEmitter,
|
|
|
|
INopeObservable,
|
|
|
|
INopePromise,
|
|
|
|
INopeRpcManager,
|
|
|
|
IRequestTaskMsg,
|
|
|
|
IResponseTaskMsg,
|
2022-01-03 15:46:36 +00:00
|
|
|
ITaskCancelationMsg,
|
|
|
|
ValidSelectorFunction,
|
2022-01-07 17:12:08 +00:00
|
|
|
} from "../../types/nope/index";
|
|
|
|
import {
|
|
|
|
INopeConnectivityManager,
|
|
|
|
NopeConnectivityManager,
|
|
|
|
} from "../ConnectivityManager/index";
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A Dispatcher to perform a function on a Remote
|
|
|
|
* Dispatcher. Therefore a Task is created and forwarded
|
|
|
|
* to the remote.
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @class nopeDispatcher
|
|
|
|
*/
|
2022-01-07 17:12:08 +00:00
|
|
|
export class NopeRpcManager<T = {}> implements INopeRpcManager {
|
2021-12-04 07:25:26 +00:00
|
|
|
protected _logger: ILogger;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Element to store the registered Functions
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2022-01-03 15:27:05 +00:00
|
|
|
protected _registeredServices: Map<
|
2021-12-04 07:25:26 +00:00
|
|
|
string,
|
|
|
|
{
|
2021-12-23 10:49:15 +00:00
|
|
|
options: T;
|
2021-12-04 07:25:26 +00:00
|
|
|
func: (...args) => Promise<any>;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
protected _communicatorCallbacks: Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
registeredId: string;
|
|
|
|
type: "request" | "response";
|
|
|
|
cb: (data) => any;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Mapping of the Services a dispatcher is hosting.
|
|
|
|
* Key = Dispatcher-ID
|
|
|
|
* Value = Available Services
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @type {Map<
|
|
|
|
* string,
|
|
|
|
* IAvailableServicesMsg
|
|
|
|
* >}
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _mappingOfDispatchersAndServices: Map<
|
|
|
|
string,
|
|
|
|
IAvailableServicesMsg
|
|
|
|
>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Proxy for accessing the Methods. This proxy provides additional
|
|
|
|
* options, which can be used to detail the calls.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
|
|
|
public methodInterfaceWithOptions: {
|
2022-01-03 18:13:51 +00:00
|
|
|
[index: string]: <T>(
|
|
|
|
options: Partial<ICallOptions>,
|
|
|
|
...args
|
|
|
|
) => INopePromise<T>;
|
2021-12-04 07:25:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Proxy for accessing the Methods. This proxy provides additional
|
|
|
|
* options, which can be used to detail the calls.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
|
|
|
public methodInterface: { [index: string]: <T>(...args) => INopePromise<T> };
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Element showing the available services.
|
2021-12-23 10:48:06 +00:00
|
|
|
* Its more or less a map, that maps the
|
|
|
|
* services with their dispatchers.
|
2021-12-04 07:25:26 +00:00
|
|
|
*
|
|
|
|
* T = services name.
|
|
|
|
* K = dispatcher - ids
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {IMapBasedMergeData<string>}
|
|
|
|
* @memberof INopeRpcManager
|
|
|
|
*/
|
2021-12-23 10:49:15 +00:00
|
|
|
public readonly services: IMapBasedMergeData<
|
|
|
|
string,
|
|
|
|
string,
|
|
|
|
IAvailableServicesMsg
|
|
|
|
>;
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An event Emitter, which will be called when a task is getting
|
|
|
|
* canceled.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {INopeEventEmitter<ITaskCancelationMsg>}
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
|
|
|
public readonly onCancelTask: INopeEventEmitter<ITaskCancelationMsg>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Element to store the running tasks.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _runningInternalRequestedTasks: Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
resolve: (value: any) => void;
|
|
|
|
reject: (error: any) => void;
|
|
|
|
clear: () => void;
|
|
|
|
serviceName: string;
|
|
|
|
timeout?: any;
|
2021-12-23 10:49:15 +00:00
|
|
|
target: string;
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
2021-12-23 10:48:06 +00:00
|
|
|
/**
|
|
|
|
* List, with external tasks, that are running.
|
2022-01-07 17:12:08 +00:00
|
|
|
* key = task-id
|
|
|
|
* value = id of the requester
|
2021-12-23 10:48:06 +00:00
|
|
|
*/
|
2022-01-07 17:12:08 +00:00
|
|
|
protected _runningExternalRequestedTasks: Map<string, string>;
|
2021-12-04 07:25:26 +00:00
|
|
|
|
2021-12-23 10:48:06 +00:00
|
|
|
/**
|
|
|
|
* Flag to show an inital warning
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
protected __warned: boolean;
|
|
|
|
|
2022-01-04 11:40:40 +00:00
|
|
|
/**
|
|
|
|
* The used Communication interface
|
|
|
|
*
|
|
|
|
*/
|
2022-01-07 17:12:08 +00:00
|
|
|
protected readonly _communicator: ICommunicationBridge;
|
2022-01-04 11:40:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag to indicate, that the system is ready.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {INopeObservable<boolean>}
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
|
|
|
public readonly ready: INopeObservable<boolean>;
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
/**
|
|
|
|
* Creates an instance of nopeDispatcher.
|
|
|
|
* @param {nopeRpcDispatcherOptions} options The Options, used by the Dispatcher.
|
|
|
|
* @param {() => INopeObservable<IExternalEventMsg>} _generateObservable A Helper, to generate Observables.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
constructor(
|
|
|
|
public options: INopeDispatcherOptions,
|
2022-01-03 15:27:05 +00:00
|
|
|
protected _generateObservable: <T>() => INopeObservable<T>,
|
|
|
|
protected _defaultSelector: ValidSelectorFunction,
|
2022-01-07 17:12:08 +00:00
|
|
|
protected readonly _id: string = null,
|
|
|
|
protected _connectivityManager: INopeConnectivityManager = null
|
2021-12-04 07:25:26 +00:00
|
|
|
) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._communicator = options.communicator;
|
2021-12-04 07:25:26 +00:00
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
if (_id == null) {
|
|
|
|
this._id = generateId();
|
2022-01-03 15:27:05 +00:00
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
if (_connectivityManager == null) {
|
|
|
|
// Creating a new Status-Manager.
|
|
|
|
this._connectivityManager = new NopeConnectivityManager(
|
|
|
|
options,
|
|
|
|
_generateObservable,
|
|
|
|
this._id
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._logger = defineNopeLogger(options.logger, `core.rpc-manager`);
|
2022-01-03 18:13:51 +00:00
|
|
|
|
|
|
|
// Flag to show if the system is ready or not.
|
|
|
|
this.ready = this._generateObservable();
|
|
|
|
this.ready.setContent(false);
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
this.__warned = false;
|
|
|
|
|
|
|
|
// Define A Proxy for accessing methods easier.
|
|
|
|
const _this = this;
|
|
|
|
const _handlerWithOptions = {
|
|
|
|
get(target, name) {
|
|
|
|
return (options: ICallOptions, ...args) =>
|
|
|
|
_this.performCall(name, args, options);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
// Define the Proxy without the Options
|
|
|
|
const _handlerWithoutOptions = {
|
|
|
|
get(target, name) {
|
|
|
|
return (...args) => _this.performCall(name, args);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
this.methodInterfaceWithOptions = new Proxy({}, _handlerWithOptions);
|
|
|
|
this.methodInterface = new Proxy({}, _handlerWithoutOptions);
|
|
|
|
|
|
|
|
this.services = new MapBasedMergeData(
|
|
|
|
this._mappingOfDispatchersAndServices,
|
|
|
|
"services"
|
|
|
|
);
|
|
|
|
|
|
|
|
this.onCancelTask = new NopeEventEmitter();
|
|
|
|
|
|
|
|
if (this._logger) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.info("manager created id=", this._id);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
2022-01-07 17:12:08 +00:00
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
this.reset();
|
|
|
|
|
|
|
|
this._init().catch((error) => {
|
|
|
|
if (_this._logger) {
|
|
|
|
_this._logger.error("Failed to intialize the Dispatcher", error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function, which will be called, if an dispatcher matches
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @param {IAvailableServicesMsg} msg
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
|
|
|
public updateDispatcher(msg: IAvailableServicesMsg): void {
|
|
|
|
this._mappingOfDispatchersAndServices.set(msg.dispatcher, msg);
|
|
|
|
this.services.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-01-04 11:40:40 +00:00
|
|
|
* Internal Method to handle the rpcs requests.
|
2021-12-04 07:25:26 +00:00
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {IRequestTaskMsg} data The provided data of the request
|
2022-01-04 11:40:40 +00:00
|
|
|
* @param {(...args) => Promise<any>} [_function]
|
|
|
|
* @return {*} {Promise<void>}
|
|
|
|
* @memberof NopeRpcManager
|
2021-12-04 07:25:26 +00:00
|
|
|
*/
|
|
|
|
protected async _handleExternalRequest(
|
|
|
|
data: IRequestTaskMsg,
|
|
|
|
_function?: (...args) => Promise<any>
|
|
|
|
): Promise<void> {
|
|
|
|
try {
|
|
|
|
// Try to get the function if not provided:
|
|
|
|
if (typeof _function !== "function") {
|
2022-01-03 15:27:05 +00:00
|
|
|
_function = this._registeredServices.get(data.functionId)?.func;
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
// If there is a Logger:
|
|
|
|
this._logger.debug(
|
2022-01-07 17:12:08 +00:00
|
|
|
`Dispatcher "${this._id}" received request: "${data.functionId}" -> task: "${data.taskId}"`
|
2021-12-04 07:25:26 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
if (typeof _function === "function") {
|
|
|
|
// Now we check, if we have to perform test, whether
|
|
|
|
// we are allowed to execute the task:
|
2022-01-07 17:12:08 +00:00
|
|
|
if (data.target && data.target !== this._id) {
|
2022-01-03 15:46:36 +00:00
|
|
|
return;
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Callbacks
|
|
|
|
const cbs: Array<(reason) => void> = [];
|
|
|
|
|
|
|
|
const observer = _this.onCancelTask.subscribe((cancelEvent) => {
|
|
|
|
if (cancelEvent.taskId == data.taskId) {
|
|
|
|
// Call Every Callback.
|
|
|
|
cbs.map((cb) => cb(cancelEvent.reason));
|
|
|
|
|
|
|
|
// Although we are allowed to Cancel the Subscription
|
|
|
|
observer.unsubscribe();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Only if the Function is present extract the arguments etc.
|
|
|
|
const args = [];
|
|
|
|
|
|
|
|
// First extract the basic arguments
|
|
|
|
data.params.map((item) => (args[item.idx] = item.data));
|
|
|
|
|
|
|
|
// Perform the Task it self.
|
|
|
|
const _resultPromise = _function(...args);
|
|
|
|
|
|
|
|
if (
|
|
|
|
typeof (_resultPromise as INopePromise<any>)?.cancel === "function"
|
|
|
|
) {
|
|
|
|
// Push the Callback to the Result.
|
|
|
|
cbs.push((reason) =>
|
|
|
|
(_resultPromise as INopePromise<any>).cancel(reason)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
// Store, who has requested the task.
|
|
|
|
_this._runningExternalRequestedTasks.set(data.taskId, data.requestedBy);
|
|
|
|
|
|
|
|
let _result: any = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Wait for the Result to finish.
|
|
|
|
_result = await _resultPromise;
|
|
|
|
// Unsubscribe from Task-Cancelation
|
|
|
|
observer.unsubscribe();
|
|
|
|
} catch (error) {
|
|
|
|
// Unsubscribe from Task-Cancelation
|
|
|
|
observer.unsubscribe();
|
|
|
|
|
|
|
|
// Now throw the Error again.
|
|
|
|
throw error;
|
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
// Define the Result message
|
|
|
|
const result: IResponseTaskMsg = {
|
|
|
|
result: typeof _result !== "undefined" ? _result : null,
|
|
|
|
taskId: data.taskId,
|
|
|
|
type: "response",
|
|
|
|
};
|
|
|
|
|
|
|
|
// Use the communicator to publish the result.
|
2022-01-07 17:12:08 +00:00
|
|
|
await this._communicator.emitRpcResponse(data.resultSink, result);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
if (this._logger) {
|
|
|
|
// If there is a Logger:
|
|
|
|
this._logger.error(
|
2022-01-07 17:12:08 +00:00
|
|
|
`Dispatcher "${this._id}" failed with request: "${data.taskId}"`
|
2021-12-04 07:25:26 +00:00
|
|
|
);
|
|
|
|
this._logger.error(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
// An Error occourd => Forward the Error.
|
|
|
|
const result: IResponseTaskMsg = {
|
|
|
|
error: {
|
|
|
|
error,
|
|
|
|
msg: error.toString(),
|
|
|
|
},
|
|
|
|
taskId: data.taskId,
|
|
|
|
type: "response",
|
|
|
|
};
|
|
|
|
|
|
|
|
// Send the Error via the communicator to the remote.
|
2022-01-07 17:12:08 +00:00
|
|
|
await this._communicator.emitRpcResponse(data.resultSink, result);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Function to handle responses. In Generale,
|
|
|
|
* the dispatcher checks if there is an open task with
|
|
|
|
* the provided id. If so => finish the promise.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {IResponseTaskMsg} data The Data provided to handle the Response.
|
|
|
|
* @return {boolean} Returns a boolean, indicating whether a corresponding task was found or not.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _handleExternalResponse(data: IResponseTaskMsg): boolean {
|
|
|
|
try {
|
|
|
|
// Extract the Task
|
|
|
|
const task = this._runningInternalRequestedTasks.get(data.taskId);
|
|
|
|
|
|
|
|
// Delete the Task:
|
|
|
|
this._runningInternalRequestedTasks.delete(data.taskId);
|
|
|
|
|
|
|
|
// Based on the Result of the Remote => proceed.
|
|
|
|
// Either throw an error or forward the result
|
|
|
|
if (task && data.error) {
|
|
|
|
if (this._logger) {
|
|
|
|
this._logger.error(`Failed with task ${data.taskId}`);
|
|
|
|
this._logger.error(`Reason: ${data.error.msg}`);
|
|
|
|
this._logger.error(data.error);
|
|
|
|
}
|
|
|
|
|
|
|
|
task.reject(data.error);
|
|
|
|
|
|
|
|
// Clearout the Timer
|
|
|
|
if (task.timeout) {
|
|
|
|
clearTimeout(task.timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (task) {
|
|
|
|
task.resolve(data.result);
|
|
|
|
|
|
|
|
// Clearout the Timer
|
|
|
|
if (task.timeout) {
|
|
|
|
clearTimeout(task.timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
this._logger.error("Error during handling an external response");
|
|
|
|
this._logger.error(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function used to update the Available Services.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _sendAvailableServices(): void {
|
|
|
|
// Define the Message
|
|
|
|
const message: IAvailableServicesMsg = {
|
2022-01-07 17:12:08 +00:00
|
|
|
dispatcher: this._id,
|
2022-01-03 15:27:05 +00:00
|
|
|
services: Array.from(this._registeredServices.keys()),
|
2021-12-04 07:25:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
this._logger.debug("sending available services");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the Message.
|
2022-01-07 17:12:08 +00:00
|
|
|
this._communicator.emitNewServicesAvailable(message);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Function, used to initialize the Dispatcher.
|
|
|
|
* It subscribes to the "Messages" of the communicator.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected async _init(): Promise<void> {
|
|
|
|
const _this = this;
|
2022-01-03 18:13:51 +00:00
|
|
|
this.ready.setContent(false);
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
// Wait until the Element is connected.
|
2022-01-07 17:12:08 +00:00
|
|
|
await this._communicator.connected.waitFor();
|
|
|
|
await this._connectivityManager.ready.waitFor();
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
// Iterate over the Defined Functions and create Subscriptions
|
2022-01-03 15:27:05 +00:00
|
|
|
for (const [id, item] of this._registeredServices.entries()) {
|
2021-12-04 07:25:26 +00:00
|
|
|
// Subscribe the Function
|
|
|
|
this._listenForRequest(id, item.func);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe to the availableServices of Remotes.
|
|
|
|
// If there is a new Service => udpate the External Services
|
2022-01-07 17:12:08 +00:00
|
|
|
await this._communicator.onNewServicesAvailable((data) => {
|
2021-12-04 07:25:26 +00:00
|
|
|
try {
|
|
|
|
_this.updateDispatcher(data);
|
|
|
|
} catch (e) {
|
|
|
|
this._logger.error("Error during handling an onNewServicesAvailable");
|
|
|
|
this._logger.error(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
// We will use our status-manager to listen to changes.
|
|
|
|
this._connectivityManager.dispatchers.onChange.subscribe((changes) => {
|
|
|
|
if (changes.added.length) {
|
|
|
|
// If there are dispatchers online,
|
|
|
|
// We will emit our available services.
|
|
|
|
_this._sendAvailableServices();
|
|
|
|
}
|
|
|
|
if (changes.removed.length) {
|
|
|
|
// Remove the dispatchers.
|
|
|
|
changes.removed.map((item) => _this.removeDispatcher(item.id));
|
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
});
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
// We will listen on Cancelations.
|
|
|
|
await this._communicator.onTaskCancelation((event) => {
|
|
|
|
if (event.dispatcher !== _this._id) {
|
2021-12-04 07:25:26 +00:00
|
|
|
_this.onCancelTask.emit(event);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
// Now we listen to unregisteredServices
|
|
|
|
this._communicator.onUnregisterRpc((msg) => {
|
2022-01-03 15:27:05 +00:00
|
|
|
if (_this._registeredServices.has(msg.identifier)) {
|
|
|
|
_this._unregisterService(msg.identifier);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (this._logger) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.info(this._id, "initialized");
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
2022-01-03 18:13:51 +00:00
|
|
|
|
|
|
|
this.ready.setContent(true);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
/**
|
|
|
|
* Helper to remove a dispatcher.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @param {string} dispatcher
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
2021-12-04 07:25:26 +00:00
|
|
|
public removeDispatcher(dispatcher: string): void {
|
|
|
|
// Delete the Generators of the Instances.
|
|
|
|
this._mappingOfDispatchersAndServices.delete(dispatcher);
|
|
|
|
this.services.update();
|
|
|
|
|
|
|
|
// Now we need to cancel every Task of the dispatcher,
|
|
|
|
// which isnt present any more.
|
|
|
|
this.cancelRunningTasksOfDispatcher(
|
|
|
|
dispatcher,
|
|
|
|
new Error(
|
|
|
|
"Dispatcher has been removed! Tasks cannot be executed any more."
|
|
|
|
)
|
|
|
|
);
|
2022-01-07 17:12:08 +00:00
|
|
|
|
|
|
|
// Stop executing the requested Tasks.
|
|
|
|
this.cancelRequestedTasksOfDispatcher(
|
|
|
|
dispatcher,
|
|
|
|
new Error(
|
|
|
|
"Dispatcher has been removed! Tasks cannot be executed any more."
|
|
|
|
)
|
|
|
|
);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to cancel an indivual Task.
|
|
|
|
*
|
|
|
|
* @param {string} taskId The Id of the Task. Which should be canceled.
|
|
|
|
* @param {Error} reason The Reason, why the Task should be canceled (In general shoudl be something meaning full)
|
|
|
|
* @return {*} Flag, that indicates, whether cancelation was sucessfull or not.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public async cancelTask(
|
|
|
|
taskId: string,
|
|
|
|
reason: Error,
|
|
|
|
quite = false
|
|
|
|
): Promise<boolean> {
|
|
|
|
if (this._runningInternalRequestedTasks.has(taskId)) {
|
|
|
|
const task = this._runningInternalRequestedTasks.get(taskId);
|
|
|
|
|
|
|
|
// Delete the task
|
|
|
|
this._runningInternalRequestedTasks.delete(taskId);
|
|
|
|
|
|
|
|
// Propagate the Cancellation (internally):
|
|
|
|
task.reject(reason);
|
|
|
|
|
|
|
|
// Propagate the Cancellation externally.
|
|
|
|
// Therefore use the desired Mode.
|
2022-01-07 17:12:08 +00:00
|
|
|
await this._communicator.emitTaskCancelation({
|
|
|
|
dispatcher: this._id,
|
2021-12-04 07:25:26 +00:00
|
|
|
reason,
|
|
|
|
taskId,
|
|
|
|
quite,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Indicate a successful cancelation.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Task hasnt been found => Cancel the Task.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Helper Function, used to close all tasks with a specific service.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {string} serviceName The Name of the Service.
|
|
|
|
* @param {Error} reason The provided Reason, why cancelation is reuqired.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2021-12-23 10:48:06 +00:00
|
|
|
public async cancelRunningTasksOfService(serviceName: string, reason: Error) {
|
2021-12-04 07:25:26 +00:00
|
|
|
// Provide a List containing all Tasks, that has to be canceled
|
2021-12-23 10:48:06 +00:00
|
|
|
const _tasksToCancel: string[] = [];
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
// Filter all Tasks that shoud be canceled.
|
|
|
|
for (const [id, task] of this._runningInternalRequestedTasks.entries()) {
|
|
|
|
// Therefore compare the reuqired Service by the Task
|
|
|
|
if (task.serviceName === serviceName) {
|
|
|
|
// if the service matches, put it to our list.
|
2021-12-23 10:48:06 +00:00
|
|
|
_tasksToCancel.push(id);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:49:15 +00:00
|
|
|
const promises: Promise<any>[] = [];
|
2021-12-23 10:48:06 +00:00
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
if (_tasksToCancel.length > 0) {
|
|
|
|
// First remove all Tasks.
|
|
|
|
// Then cancel them to avoid side effects
|
2021-12-23 10:49:15 +00:00
|
|
|
for (const id of _tasksToCancel) {
|
|
|
|
promises.push(this.cancelTask(id, reason));
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
2021-12-23 10:48:06 +00:00
|
|
|
}
|
2021-12-04 07:25:26 +00:00
|
|
|
|
2021-12-23 10:49:15 +00:00
|
|
|
await Promise.all(promises);
|
2021-12-23 10:48:06 +00:00
|
|
|
}
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
/**
|
|
|
|
* Helper to cancel all Tasks which have been requested by a Dispatcher.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @param {string} dispatcher
|
|
|
|
* @param {Error} reason
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
|
|
|
public async cancelRequestedTasksOfDispatcher(
|
|
|
|
dispatcher: string,
|
|
|
|
reason: Error
|
|
|
|
) {
|
|
|
|
const toCancel = new Set<string>();
|
|
|
|
|
|
|
|
for (const [
|
|
|
|
taskId,
|
|
|
|
requestedBy,
|
|
|
|
] of this._runningExternalRequestedTasks.entries()) {
|
|
|
|
if (requestedBy == dispatcher) {
|
|
|
|
toCancel.add(taskId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const promises: Promise<any>[] = [];
|
|
|
|
for (const taskId of toCancel) {
|
|
|
|
promises.push(this.cancelTask(taskId, reason));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (promises.length) {
|
|
|
|
await Promise.all(promises);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:48:06 +00:00
|
|
|
/**
|
|
|
|
* Cancels all Tasks of the given Dispatcher
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @param {string} dispatcher
|
|
|
|
* @param {Error} reason
|
|
|
|
* @memberof NopeRpcManager
|
|
|
|
*/
|
2021-12-23 10:49:15 +00:00
|
|
|
public async cancelRunningTasksOfDispatcher(
|
2021-12-23 10:48:06 +00:00
|
|
|
dispatcher: string,
|
|
|
|
reason: Error
|
|
|
|
): Promise<void> {
|
|
|
|
// Provide a List containing all Tasks, that has to be canceled
|
|
|
|
const _tasksToCancel: string[] = [];
|
|
|
|
|
|
|
|
// Filter all Tasks that shoud be canceled.
|
|
|
|
for (const [id, task] of this._runningInternalRequestedTasks.entries()) {
|
|
|
|
// Therefore compare the reuqired Service by the Task
|
|
|
|
if (task.target === dispatcher) {
|
|
|
|
// if the service matches, put it to our list.
|
|
|
|
_tasksToCancel.push(id);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:49:15 +00:00
|
|
|
const promises: Promise<any>[] = [];
|
2021-12-23 10:48:06 +00:00
|
|
|
|
|
|
|
if (_tasksToCancel.length > 0) {
|
|
|
|
// First remove all Tasks.
|
|
|
|
// Then cancel them to avoid side effects
|
2021-12-23 10:49:15 +00:00
|
|
|
for (const id of _tasksToCancel) {
|
|
|
|
promises.push(this.cancelTask(id, reason));
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-23 10:48:06 +00:00
|
|
|
|
2021-12-23 10:49:15 +00:00
|
|
|
await Promise.all(promises);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to test if a specific Service exists.
|
|
|
|
*
|
|
|
|
* @param {string} id The Id of the Serivce
|
|
|
|
* @return {boolean} The result of the Test. True if either local or remotly a service is known.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public serviceExists(id: string): boolean {
|
|
|
|
return this.services.amountOf.has(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to adapt a Request name.
|
|
|
|
* Only used internally
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {string} id the original ID
|
|
|
|
* @return {string} the adapted ID.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _getServiceName(id: string, type: "request" | "response"): string {
|
|
|
|
return id.startsWith(`${type}/`) ? id : `${type}/${id}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Helper Function to subscribe to a Function element.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {string} id the Id of the function
|
|
|
|
* @param {(...args) => Promise<any>} _cb The Callback of the Function
|
|
|
|
* @return {void} the adapted ID.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _listenForRequest(
|
|
|
|
id: string,
|
|
|
|
_cb: (...args) => Promise<any>
|
|
|
|
): void {
|
|
|
|
const _req = this._getServiceName(id, "request");
|
|
|
|
if (!this._communicatorCallbacks.has(_req)) {
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Define a Function.
|
|
|
|
const cb = (data: IRequestTaskMsg) => {
|
|
|
|
if (data.type === "requestOfTask") {
|
|
|
|
_this._handleExternalRequest(data, _cb);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add the Callback.
|
|
|
|
this._communicatorCallbacks.set(_req, {
|
|
|
|
registeredId: _req,
|
|
|
|
type: "request",
|
|
|
|
cb,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register Functions.
|
2022-01-07 17:12:08 +00:00
|
|
|
this._communicator.onRpcRequest(_req, cb);
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
if (this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
// If there is a Logger:
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(`Dispatcher "${this._id}" listening on: "${_req}"`);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function, used to subscribe to results.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {string} id
|
|
|
|
* @param {boolean} deleteAfterCalling
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2022-01-03 15:46:36 +00:00
|
|
|
protected _listenForResult(
|
|
|
|
id: string,
|
|
|
|
deleteAfterCalling: boolean = false
|
|
|
|
): void {
|
2021-12-04 07:25:26 +00:00
|
|
|
const _res = this._getServiceName(id, "response");
|
|
|
|
if (!this._communicatorCallbacks.has(_res)) {
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Define a Function.
|
|
|
|
const cb = (data: IResponseTaskMsg) => {
|
|
|
|
if (data.type === "response") {
|
|
|
|
if (_this._handleExternalResponse(data) && deleteAfterCalling) {
|
|
|
|
_this._removeRpcSubscription(_res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add the Callback.
|
|
|
|
this._communicatorCallbacks.set(_res, {
|
|
|
|
registeredId: _res,
|
|
|
|
type: "response",
|
|
|
|
cb,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register Functions.
|
2022-01-07 17:12:08 +00:00
|
|
|
this._communicator.onRpcResponse(_res, cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to unregister a Function from the Dispatcher
|
|
|
|
* @param {(((...args) => void) | string | number)} func The Function to unregister
|
|
|
|
* @return {*} {boolean} Flag, whether the element was removed (only if found) or not.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _unregisterService(func: ((...args) => void) | string): boolean {
|
|
|
|
const _id =
|
|
|
|
typeof func === "string" ? func : ((func as any).id as string) || "0";
|
|
|
|
|
|
|
|
this._removeRpcSubscription(_id);
|
|
|
|
|
|
|
|
// Publish the Available Services.
|
|
|
|
this._sendAvailableServices();
|
|
|
|
|
|
|
|
if (this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
// If there is a Logger:
|
|
|
|
this._logger.debug(`Dispatcher "${this._id}" unregistered: "${_id}"`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._registeredServices.delete(_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected _removeRpcSubscription(_id: string): void {
|
|
|
|
// Try to unregister the Callback from the communcator:
|
|
|
|
if (this._communicatorCallbacks.has(_id)) {
|
|
|
|
const _callbacks = this._communicatorCallbacks.get(_id);
|
|
|
|
|
|
|
|
switch (_callbacks.type) {
|
|
|
|
case "request":
|
|
|
|
// Unregister the RPC-Request-Listener
|
|
|
|
this._communicator.offRpcRequest(
|
|
|
|
_callbacks.registeredId,
|
|
|
|
_callbacks.cb
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case "response":
|
|
|
|
// Unregister the RPC-Response-Listener
|
|
|
|
this._communicator.offRpcResponse(
|
|
|
|
_callbacks.registeredId,
|
|
|
|
_callbacks.cb
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the Callback
|
|
|
|
this._communicatorCallbacks.delete(_id);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
public adaptServiceId(name: string) {
|
|
|
|
if (name.startsWith(`nope${SPLITCHAR}service${SPLITCHAR}`)) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
return `nope${SPLITCHAR}service${SPLITCHAR}${name}`;
|
|
|
|
}
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
/**
|
|
|
|
* Function to register a Function in the Dispatcher
|
|
|
|
*
|
|
|
|
* @param {(...args) => Promise<any>} func The function which should be called if a request is mapped to the Function.
|
|
|
|
* @param {{
|
|
|
|
* // Flag to enable unregistering the function after calling.
|
|
|
|
* deleteAfterCalling?: boolean,
|
|
|
|
* // Instead of generating a uuid an id could be provided
|
|
|
|
* id?: string;
|
|
|
|
* }} [options={}] Options to enhance the registered ID and enabling unregistering the Element after calling it.
|
|
|
|
* @return {*} {(...args) => Promise<any>} The registered Function
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2022-01-03 15:27:05 +00:00
|
|
|
public registerService(
|
2021-12-04 07:25:26 +00:00
|
|
|
func: (...args) => Promise<any>,
|
|
|
|
options: {
|
|
|
|
/** Instead of generating a uuid an id could be provided */
|
|
|
|
id?: string;
|
2022-01-07 17:12:08 +00:00
|
|
|
// We dont want to add a prefix
|
|
|
|
addNopeServiceIdPrefix?: boolean;
|
2021-12-23 10:49:15 +00:00
|
|
|
} & Partial<T> = {}
|
2021-12-04 07:25:26 +00:00
|
|
|
): (...args) => Promise<any> {
|
|
|
|
const _this = this;
|
|
|
|
// Define / Use the ID of the Function.
|
2022-01-07 17:12:08 +00:00
|
|
|
let _id = options.id || generateId();
|
|
|
|
_id = options.addNopeServiceIdPrefix ? this.adaptServiceId(_id) : _id;
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
let _func = func;
|
|
|
|
|
|
|
|
if (!this.__warned && !isAsyncFunction(func)) {
|
|
|
|
this._logger.warn(
|
|
|
|
"!!! You have provided synchronous functions. They may break NoPE. Use them with care !!!"
|
|
|
|
);
|
|
|
|
this.__warned = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define a ID for the Function
|
2022-01-03 15:27:05 +00:00
|
|
|
(_func as any).id = _id;
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
// Define the callback.
|
2022-01-03 15:27:05 +00:00
|
|
|
(_func as any).unregister = () => _this._unregisterService(_id);
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
// Reister the Function
|
2022-01-03 15:27:05 +00:00
|
|
|
this._registeredServices.set((_func as any).id, {
|
2021-12-23 10:48:06 +00:00
|
|
|
options: options as T,
|
2021-12-04 07:25:26 +00:00
|
|
|
func: _func,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register the Callback:
|
|
|
|
this._listenForRequest(_id, _func);
|
|
|
|
|
2021-12-23 10:48:06 +00:00
|
|
|
// Publish the Available Services.
|
|
|
|
this._sendAvailableServices();
|
2021-12-04 07:25:26 +00:00
|
|
|
|
2021-12-23 10:48:06 +00:00
|
|
|
if (this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
// If there is a Logger:
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(`Dispatcher "${this._id}" registered: "${_id}"`);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
// Return the Function.
|
|
|
|
return _func;
|
|
|
|
}
|
|
|
|
|
2022-01-03 15:46:36 +00:00
|
|
|
public async unregisterService(
|
|
|
|
func: string | ((...args: any[]) => any)
|
|
|
|
): Promise<void> {
|
|
|
|
this._unregisterService(func);
|
2022-01-03 15:27:05 +00:00
|
|
|
}
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
/**
|
|
|
|
* Function which is used to perform a call on the remote.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @template T
|
|
|
|
* @param {string} serviceName serviceName The Name / ID of the Function
|
|
|
|
* @param {any[]} params
|
|
|
|
* @param {(Partial<ICallOptions> & {
|
2022-01-03 15:27:05 +00:00
|
|
|
* selector?: ValidSelectorFunction;
|
2021-12-04 07:25:26 +00:00
|
|
|
* quite?: boolean;
|
|
|
|
* })} [options={}] Options for the Call. You can assign a different selector.
|
|
|
|
* @return {*} {INopePromise<T>} The result of the call
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public performCall<T>(
|
|
|
|
serviceName: string,
|
|
|
|
params: any[],
|
|
|
|
options: Partial<ICallOptions> & {
|
2022-01-03 15:27:05 +00:00
|
|
|
selector?: ValidSelectorFunction;
|
2021-12-04 07:25:26 +00:00
|
|
|
} = {}
|
|
|
|
): INopePromise<T> {
|
|
|
|
// Get a Call Id
|
|
|
|
const _taskId = generateId();
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
const _options = {
|
|
|
|
deletableCallbacks: [],
|
|
|
|
paramsHasNoCallback: false,
|
|
|
|
dynamicCallback: false,
|
|
|
|
resultSink: this._getServiceName(serviceName, "response"),
|
|
|
|
...options,
|
|
|
|
} as ICallOptions;
|
|
|
|
|
2022-01-03 15:27:05 +00:00
|
|
|
this._listenForResult(serviceName);
|
2021-12-04 07:25:26 +00:00
|
|
|
|
|
|
|
const clear = () => {
|
|
|
|
// Remove the task:
|
|
|
|
if (_this._runningInternalRequestedTasks.has(_taskId)) {
|
|
|
|
const task = _this._runningInternalRequestedTasks.get(_taskId);
|
|
|
|
|
|
|
|
// Remove the Timeout.
|
|
|
|
if (task.timeout) {
|
|
|
|
clearTimeout(task.timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the Task itself
|
|
|
|
_this._runningInternalRequestedTasks.delete(_taskId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
_this._logger.debug(`Clearing Callbacks from ${_taskId}`);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (_this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
_this._logger.debug(
|
2022-01-07 17:12:08 +00:00
|
|
|
`Dispatcher "${this._id}" requesting externally Function "${serviceName}" with task: "${_taskId}"`
|
2021-12-04 07:25:26 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define a Callback-Function, which will expect the Task.
|
|
|
|
const ret = new NopePromise<T>(async (resolve, reject) => {
|
|
|
|
try {
|
|
|
|
const requestedTask: any = {
|
|
|
|
resolve,
|
|
|
|
reject,
|
|
|
|
clear,
|
|
|
|
serviceName,
|
|
|
|
timeout: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Register the Handlers,
|
|
|
|
_this._runningInternalRequestedTasks.set(_taskId, requestedTask);
|
|
|
|
|
|
|
|
// Define a Task-Request
|
|
|
|
const taskRequest: IRequestTaskMsg = {
|
|
|
|
functionId: serviceName,
|
|
|
|
params: [],
|
|
|
|
taskId: _taskId,
|
|
|
|
type: "requestOfTask",
|
|
|
|
resultSink: _options.resultSink,
|
2022-01-07 17:12:08 +00:00
|
|
|
requestedBy: _this._id,
|
2021-12-04 07:25:26 +00:00
|
|
|
};
|
|
|
|
|
2022-01-03 15:27:05 +00:00
|
|
|
for (const [idx, contentOfParameter] of params.entries()) {
|
|
|
|
taskRequest.params.push({
|
|
|
|
idx,
|
|
|
|
data: contentOfParameter,
|
|
|
|
});
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
2022-01-03 15:27:05 +00:00
|
|
|
if (!_this.serviceExists(serviceName)) {
|
2021-12-04 07:25:26 +00:00
|
|
|
// Create an Error:
|
|
|
|
const error = new Error(
|
|
|
|
`No Service Provider known for "${serviceName}"`
|
|
|
|
);
|
|
|
|
|
2022-01-03 15:27:05 +00:00
|
|
|
if (_this._logger) {
|
2021-12-04 07:25:26 +00:00
|
|
|
_this._logger.error(
|
|
|
|
`No Service Provider known for "${serviceName}"`
|
|
|
|
);
|
|
|
|
_this._logger.error(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2022-01-03 15:27:05 +00:00
|
|
|
_this.options.forceUsingSelectors ||
|
|
|
|
this.services.amountOf.get(serviceName) > 1
|
2021-12-04 07:25:26 +00:00
|
|
|
) {
|
|
|
|
if (typeof options?.selector === "function") {
|
2022-01-03 15:27:05 +00:00
|
|
|
const dispatcherToUse = await options.selector({
|
2022-01-03 15:46:36 +00:00
|
|
|
rpcManager: this,
|
2022-01-04 11:40:40 +00:00
|
|
|
serviceName,
|
2022-01-03 15:46:36 +00:00
|
|
|
});
|
2022-01-03 15:27:05 +00:00
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
// Assign the Selector:
|
2022-01-03 15:27:05 +00:00
|
|
|
taskRequest.target = dispatcherToUse;
|
2021-12-04 07:25:26 +00:00
|
|
|
} else {
|
2022-01-03 15:27:05 +00:00
|
|
|
const dispatcherToUse = await this._defaultSelector({
|
2022-01-03 15:46:36 +00:00
|
|
|
rpcManager: this,
|
2022-01-04 11:40:40 +00:00
|
|
|
serviceName,
|
2022-01-03 15:46:36 +00:00
|
|
|
});
|
2022-01-03 15:27:05 +00:00
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
// Assign the Selector:
|
2022-01-03 15:27:05 +00:00
|
|
|
taskRequest.target = dispatcherToUse;
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the Message to the specific element:
|
2022-01-07 17:12:08 +00:00
|
|
|
await _this._communicator.emitRpcRequest(
|
2021-12-04 07:25:26 +00:00
|
|
|
_this._getServiceName(taskRequest.functionId, "request"),
|
|
|
|
taskRequest
|
|
|
|
);
|
|
|
|
|
|
|
|
if (_this._logger?.enabledFor((Logger as any).DEBUG)) {
|
|
|
|
_this._logger.debug(
|
2022-01-03 15:46:36 +00:00
|
|
|
`Dispatcher "${
|
2022-01-07 17:12:08 +00:00
|
|
|
this._id
|
2021-12-04 07:25:26 +00:00
|
|
|
}" putting task "${_taskId}" on: "${_this._getServiceName(
|
|
|
|
taskRequest.functionId,
|
|
|
|
"request"
|
|
|
|
)}"`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is a timeout =>
|
|
|
|
if (options.timeout > 0) {
|
|
|
|
requestedTask.timeout = setTimeout(() => {
|
|
|
|
_this.cancelTask(
|
|
|
|
_taskId,
|
|
|
|
new Error(
|
|
|
|
`TIMEOUT. The Service allowed execution time of ${options.timeout.toString()}[ms] has been excided`
|
|
|
|
),
|
2022-01-03 15:27:05 +00:00
|
|
|
false
|
2021-12-04 07:25:26 +00:00
|
|
|
);
|
|
|
|
}, options.timeout);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Clear all Elements of the Function:
|
|
|
|
clear();
|
|
|
|
|
|
|
|
// Throw the error.
|
|
|
|
reject(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ret.taskId = _taskId;
|
|
|
|
ret.cancel = (reason) => {
|
|
|
|
_this.cancelTask(_taskId, reason);
|
|
|
|
};
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to clear all pending tasks
|
|
|
|
*
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public clearTasks(): void {
|
|
|
|
if (this._runningInternalRequestedTasks) {
|
|
|
|
this._runningInternalRequestedTasks.clear();
|
|
|
|
} else this._runningInternalRequestedTasks = new Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to unregister all Functions of the Dispatcher.
|
|
|
|
*
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public unregisterAll(): void {
|
2022-01-03 15:27:05 +00:00
|
|
|
if (this._registeredServices) {
|
|
|
|
for (const id of this._registeredServices.keys()) {
|
|
|
|
this._unregisterService(id);
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
2022-01-03 15:27:05 +00:00
|
|
|
this._registeredServices.clear();
|
2021-12-04 07:25:26 +00:00
|
|
|
} else {
|
2022-01-03 15:27:05 +00:00
|
|
|
this._registeredServices = new Map();
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reset the Callbacks.
|
|
|
|
this._communicatorCallbacks = new Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to reset the Dispatcher.
|
|
|
|
*
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public reset(): void {
|
2022-01-07 17:12:08 +00:00
|
|
|
this.clearTasks();
|
|
|
|
this.unregisterAll();
|
|
|
|
|
2021-12-04 07:25:26 +00:00
|
|
|
this._mappingOfDispatchersAndServices = new Map();
|
2022-01-03 18:13:51 +00:00
|
|
|
this.services.update(this._mappingOfDispatchersAndServices);
|
2021-12-04 07:25:26 +00:00
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
this._runningExternalRequestedTasks = new Map();
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|
2022-01-07 17:12:08 +00:00
|
|
|
|
|
|
|
public async dispose(): Promise<void> {}
|
2021-12-04 07:25:26 +00:00
|
|
|
}
|