2020-09-10 16:21:01 +00:00
|
|
|
import { generateId } from '../helpers/idMethods';
|
2020-09-13 10:35:04 +00:00
|
|
|
import { Logger } from "winston";
|
2020-09-10 16:21:01 +00:00
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* A Layer to communicate.
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @interface ICommunicationInterface
|
|
|
|
*/
|
2020-08-21 16:38:21 +00:00
|
|
|
export interface ICommunicationInterface {
|
2020-09-13 10:35:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Funciton to emit a RPC Request.
|
|
|
|
*
|
|
|
|
* @param {string} name Name of the request
|
|
|
|
* @param {requestTaskMsg} request The Request.
|
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
|
|
|
emitRpcRequest(name: string, request: requestTaskMsg): void;
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Function used to subscribe to updates. If there exist
|
|
|
|
* some state change in the Communication Interface it should
|
|
|
|
* be provided on the registered functions
|
|
|
|
*
|
2020-09-13 10:35:04 +00:00
|
|
|
* @param {responseTaskMsg} result
|
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
|
|
|
emitRpcResult(result: responseTaskMsg): void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function used to subscribe to RPC Results. Each method / function
|
|
|
|
* can provide a unique eventname for the results.
|
|
|
|
*
|
|
|
|
* @param {string} name The Id of the Method.
|
|
|
|
* @param {(result: responseTaskMsg) => void} cb The callback which should be called
|
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
|
|
|
onRpcResult(name: string, cb: (result: responseTaskMsg) => void): void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function used to unsubscribe from Rpc-Results
|
|
|
|
*
|
|
|
|
* @param {string} name The Id of the Method.
|
|
|
|
* @param {(result: responseTaskMsg) => void} cb The callback which should be called
|
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
|
|
|
offRpcResult(name: string, cb: (result: responseTaskMsg) => void): void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to register RPC-Request.
|
|
|
|
*
|
|
|
|
* @param {string} name the name to listen for the request.
|
|
|
|
* @param {(data: requestTaskMsg) => void} cb The callback which should be called, if a request for the method is detected
|
2020-09-11 07:59:23 +00:00
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
2020-09-13 10:35:04 +00:00
|
|
|
onRpcRequest(name: string, cb: (data: requestTaskMsg) => void): void;
|
2020-09-12 00:59:30 +00:00
|
|
|
|
|
|
|
/**
|
2020-09-13 10:35:04 +00:00
|
|
|
* Unregister a listener for a RPC-Request.
|
2020-09-12 00:59:30 +00:00
|
|
|
*
|
2020-09-13 10:35:04 +00:00
|
|
|
* @param {string} name
|
|
|
|
* @param {(data: requestTaskMsg) => void} cb
|
2020-09-12 00:59:30 +00:00
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
2020-09-13 10:35:04 +00:00
|
|
|
offRpcRequest(name: string, cb: (data: requestTaskMsg) => void): void;
|
2020-09-12 00:59:30 +00:00
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
2020-09-13 10:35:04 +00:00
|
|
|
* Function to Emit new Services. They will then be shared to other sytems.
|
2020-09-11 07:59:23 +00:00
|
|
|
*
|
2020-09-13 10:35:04 +00:00
|
|
|
* @param {availableServices} services
|
2020-09-11 07:59:23 +00:00
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
2020-09-13 10:35:04 +00:00
|
|
|
emitNewServicesAvailable(services: availableServices): void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to register a new Callback, which will be called if new Services are available.
|
|
|
|
*
|
|
|
|
* @param {() => void} cb The Callback to Call.
|
|
|
|
* @memberof ICommunicationInterface
|
|
|
|
*/
|
|
|
|
onNewServicesAvailable(cb: (services: availableServices) => void);
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
export type requestOfService = {
|
2020-09-12 07:56:17 +00:00
|
|
|
type: 'requestOfService'
|
2020-09-12 20:23:55 +00:00
|
|
|
taskId: string,
|
2020-09-12 07:56:17 +00:00
|
|
|
functionId: string,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type requestTaskMsg = {
|
|
|
|
type: 'requestOfTask',
|
2020-09-12 20:23:55 +00:00
|
|
|
taskId: string,
|
2020-09-12 00:59:30 +00:00
|
|
|
functionId: string,
|
2020-08-21 16:38:21 +00:00
|
|
|
params: {
|
|
|
|
idx: number,
|
|
|
|
data: any
|
|
|
|
}[]
|
2020-09-12 00:59:30 +00:00
|
|
|
callbacks: ({
|
2020-08-21 16:38:21 +00:00
|
|
|
functionId: string,
|
|
|
|
idx: number,
|
2020-09-12 00:59:30 +00:00
|
|
|
deleteAfterCalling: boolean,
|
|
|
|
} & callOptions)[]
|
|
|
|
}
|
|
|
|
|
|
|
|
export type callOptions = {
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* Array containing indexes of callbacks which could be deleted
|
|
|
|
*
|
|
|
|
* @type {Array<number>}
|
|
|
|
*/
|
2020-09-12 00:59:30 +00:00
|
|
|
deletableCallbacks: Array<number>;
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* Flag, showing that the parameters set has no callback
|
|
|
|
*
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
2020-09-12 07:56:17 +00:00
|
|
|
paramsHasNoCallback?: boolean;
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* Flag to disable error-testing
|
|
|
|
*
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
2020-09-13 10:35:04 +00:00
|
|
|
preventAvailableTest?: boolean;
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
export type responseTaskMsg = {
|
2020-08-21 16:38:21 +00:00
|
|
|
type: 'response',
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* ID of the Task.
|
|
|
|
*
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
taskId: string,
|
|
|
|
/**
|
|
|
|
* Property containing the result. Is
|
|
|
|
* only present, if no error exists.
|
|
|
|
*
|
|
|
|
* @type {*}
|
|
|
|
*/
|
2020-08-21 16:38:21 +00:00
|
|
|
result?: any,
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* Property containing the error, if
|
|
|
|
* it occourd.
|
|
|
|
*
|
|
|
|
* @type {*}
|
|
|
|
*/
|
2020-08-21 16:38:21 +00:00
|
|
|
error?: any
|
|
|
|
}
|
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
export type availableServices = {
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* The Id of the Dispatcher
|
|
|
|
*
|
|
|
|
* @type {string}
|
|
|
|
*/
|
2020-09-12 00:59:30 +00:00
|
|
|
dispatcher: string,
|
2020-09-12 20:23:55 +00:00
|
|
|
/**
|
|
|
|
* The List of registered Service.
|
|
|
|
*
|
|
|
|
* @type {string[]}
|
|
|
|
*/
|
2020-09-12 00:59:30 +00:00
|
|
|
services: string[]
|
|
|
|
}
|
|
|
|
|
|
|
|
export type nopeDispatcherOptions = {
|
2020-09-13 10:35:04 +00:00
|
|
|
communicator: ICommunicationInterface,
|
2020-09-12 00:59:30 +00:00
|
|
|
subscriptionMode?: 'individual' | 'generic',
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
/**
|
2020-09-11 07:59:23 +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
|
2020-08-21 16:38:21 +00:00
|
|
|
*/
|
2020-08-23 07:18:11 +00:00
|
|
|
export class nopeDispatcher {
|
2020-08-21 16:38:21 +00:00
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
public readonly id: string;
|
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
protected _logger: Logger;
|
|
|
|
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Internal Element to store the registered Functions
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-12 00:59:30 +00:00
|
|
|
protected _definedFunctions: Map<string, (...args) => Promise<any>>;
|
2020-09-12 07:56:17 +00:00
|
|
|
protected _remotlyCalledFunctions: Set<string>;
|
2020-09-13 10:35:04 +00:00
|
|
|
protected _communicatorCallbacks: Map<string, {
|
|
|
|
req: (data) => any,
|
|
|
|
res: (data) => any
|
|
|
|
}>;
|
|
|
|
protected _communicator: ICommunicationInterface;
|
|
|
|
protected _mappingOfRemoteDispatchersAndFunctions: Map<string, availableServices>;
|
|
|
|
protected _externalServices: Set<string>;
|
2020-09-12 20:23:55 +00:00
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
public methodInterfaceWithOptions: { [index: string]: (optins: callOptions, ...args) => Promise<any> }
|
|
|
|
public methodInterface: { [index: string]: (...args) => Promise<any> }
|
2020-09-11 07:59:23 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Element to store the running tasks.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-12 20:23:55 +00:00
|
|
|
protected _runningTasks: Map<string, {
|
2020-08-21 16:38:21 +00:00
|
|
|
resolve: (value: any) => void;
|
|
|
|
reject: (error: any) => void;
|
|
|
|
}>;
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Creates an instance of nopeDispatcher.
|
|
|
|
* @param {ICommunicationInterface} communicator The Communication Layer which should be used.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-12 00:59:30 +00:00
|
|
|
constructor(public options: nopeDispatcherOptions) {
|
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
this._communicator = options.communicator;
|
2020-09-12 00:59:30 +00:00
|
|
|
|
|
|
|
this.options.subscriptionMode = this.options.subscriptionMode || 'generic';
|
|
|
|
|
|
|
|
this.id = generateId();
|
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Define A Proxy for accessing methods easier.
|
|
|
|
*/
|
|
|
|
const _this = this;
|
|
|
|
const _handlerWithOptions = {
|
|
|
|
get(target, name) {
|
|
|
|
return async (options: callOptions, ...args) => {
|
|
|
|
return _this.performCall(name, args, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const _handlerWithoutOptions = {
|
|
|
|
get(target, name) {
|
|
|
|
return async (...args) => {
|
|
|
|
return _this.performCall(name, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.methodInterfaceWithOptions = new Proxy({}, _handlerWithOptions);
|
|
|
|
this.methodInterface = new Proxy({}, _handlerWithoutOptions);
|
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
this.reset();
|
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Internal Method to handle some requests.
|
|
|
|
*
|
|
|
|
* @protected
|
2020-09-12 07:56:17 +00:00
|
|
|
* @param {requestTaskMsg} data The provided data of the request
|
2020-09-12 00:59:30 +00:00
|
|
|
* @param {*} [_function=this._definedFunctions.get(data.functionId)] The Function can be provided
|
2020-09-11 07:59:23 +00:00
|
|
|
* @return {Promise<void>}
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-12 07:56:17 +00:00
|
|
|
protected async _handleExternalRequest(data: requestTaskMsg, _function?: (...args) => Promise<any>): Promise<void> {
|
2020-08-21 16:38:21 +00:00
|
|
|
try {
|
2020-09-12 00:59:30 +00:00
|
|
|
|
|
|
|
// Try to get the function if not provided:
|
|
|
|
if (typeof _function !== 'function') {
|
|
|
|
_function = this._definedFunctions.get(data.functionId);
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
const _this = this;
|
2020-09-11 07:59:23 +00:00
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
if (typeof _function === 'function') {
|
2020-09-11 07:59:23 +00:00
|
|
|
// Only if the Function is present extract the arguments etc.
|
2020-08-21 16:38:21 +00:00
|
|
|
const args = [];
|
2020-09-11 07:59:23 +00:00
|
|
|
|
|
|
|
// First extract the basic arguments
|
2020-08-21 16:38:21 +00:00
|
|
|
data.params.map(item => args[item.idx] = item.data);
|
2020-09-11 07:59:23 +00:00
|
|
|
|
|
|
|
// Add the Callbacks. Therefore create a function which will
|
|
|
|
// trigger the remote.
|
2020-09-12 07:56:17 +00:00
|
|
|
data.callbacks.map(options => args[options.idx] = async (..._args) => {
|
|
|
|
return await _this.performCall(options.functionId, _args, options);
|
2020-08-21 16:38:21 +00:00
|
|
|
});
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
// Perform the Task it self.
|
2020-09-10 16:21:01 +00:00
|
|
|
const _result = await _function(...args);
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
// Define the Result message
|
2020-09-12 07:56:17 +00:00
|
|
|
const result: responseTaskMsg = {
|
2020-09-10 16:21:01 +00:00
|
|
|
result: typeof (_result) !== 'undefined' ? _result : null,
|
2020-08-21 16:38:21 +00:00
|
|
|
taskId: data.taskId,
|
|
|
|
type: 'response'
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
// Use the communicator to publish the event.
|
2020-09-13 10:35:04 +00:00
|
|
|
this._communicator.emitRpcResult(result);
|
2020-08-21 16:38:21 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
// An Error occourd => Forward the Error.
|
2020-09-12 07:56:17 +00:00
|
|
|
const result: responseTaskMsg = {
|
2020-08-21 16:38:21 +00:00
|
|
|
error,
|
|
|
|
taskId: data.taskId,
|
|
|
|
type: 'response'
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
// Send the Error via the communicator to the remote.
|
2020-09-13 10:35:04 +00:00
|
|
|
this._communicator.emitRpcResult(result);
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +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
|
2020-09-12 07:56:17 +00:00
|
|
|
* @param {responseTaskMsg} data The Data provided to handle the Response.
|
2020-09-11 07:59:23 +00:00
|
|
|
* @return {void} Nothing
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-12 07:56:17 +00:00
|
|
|
protected _handleExternalResponse(data: responseTaskMsg): void {
|
2020-08-21 16:38:21 +00:00
|
|
|
try {
|
|
|
|
// Extract the Task
|
|
|
|
const task = this._runningTasks.get(data.taskId);
|
|
|
|
|
|
|
|
// Delete the Task:
|
|
|
|
this._runningTasks.delete(data.taskId);
|
|
|
|
|
|
|
|
// Based on the Result of the Remote =>
|
|
|
|
if (task && data.error) {
|
|
|
|
return task.reject(data.error);
|
|
|
|
}
|
2020-08-25 22:11:26 +00:00
|
|
|
if (task) {
|
2020-08-21 16:38:21 +00:00
|
|
|
return task.resolve(data.result);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Internal Function, used to initialize the Dispatcher.
|
|
|
|
* It subscribes to the "Messages" of the communicator.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected init(): void {
|
2020-09-12 00:59:30 +00:00
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Based on the Mode of the Subscription =>
|
|
|
|
// either create indivdual topics for the methods
|
|
|
|
// or use the generice function.
|
|
|
|
switch (this.options.subscriptionMode) {
|
|
|
|
case 'individual':
|
|
|
|
// Iterate over the Defined Functions.
|
|
|
|
for (const [id, cb] of this._definedFunctions.entries()) {
|
|
|
|
// Subscribe the Function
|
|
|
|
this._subscribeToFunction(id, cb);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'generic':
|
|
|
|
// Add a generic Subscription for callbacks:
|
2020-09-13 10:35:04 +00:00
|
|
|
this._communicator.onRpcRequest('request', (data: requestTaskMsg) => {
|
2020-09-12 07:56:17 +00:00
|
|
|
if (data.type === 'requestOfTask') {
|
2020-09-12 00:59:30 +00:00
|
|
|
_this._handleExternalRequest(data);
|
|
|
|
}
|
|
|
|
});
|
2020-09-13 10:35:04 +00:00
|
|
|
|
|
|
|
// Subscribe to Responses
|
|
|
|
this._communicator.onRpcResult('response', (data: responseTaskMsg) => {
|
|
|
|
if (data.type === 'response') {
|
|
|
|
_this._handleExternalResponse(data);
|
|
|
|
}
|
|
|
|
});
|
2020-09-12 00:59:30 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
// Subscribe to the availableServices of Remotes.
|
|
|
|
// If there is a new Service => udpate the External Services
|
|
|
|
this._communicator.onNewServicesAvailable((data: availableServices) => {
|
|
|
|
try {
|
|
|
|
if (data.dispatcher !== this.id){
|
|
|
|
_this._mappingOfRemoteDispatchersAndFunctions.set(data.dispatcher, data);
|
|
|
|
_this._updateExternalServices();
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
2020-09-13 10:35:04 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to update the used Services.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof serviceRegistry
|
|
|
|
*/
|
|
|
|
protected _updateExternalServices() {
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Clear the Services
|
|
|
|
this._externalServices.clear();
|
|
|
|
for (const dispatcherInfo of this._mappingOfRemoteDispatchersAndFunctions.values()) {
|
|
|
|
dispatcherInfo.services.map(service => _this._externalServices.add(service));
|
|
|
|
}
|
2020-09-12 08:09:31 +00:00
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
if (this._logger?.isDebugEnabled()) {
|
|
|
|
// If there is a Logger:
|
|
|
|
this._logger.debug(
|
|
|
|
'new services found: \n' +
|
|
|
|
JSON.stringify(
|
|
|
|
Array.from(this._externalServices),
|
|
|
|
undefined,
|
|
|
|
4
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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){
|
|
|
|
return this._definedFunctions.has(id) || this._externalServices.has(id);
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 08:09:31 +00:00
|
|
|
/**
|
|
|
|
* Function to adapt a Request name.
|
|
|
|
* Only used internally
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @param {string} id the original ID
|
|
|
|
* @return {string} the adapted ID.
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-13 10:35:04 +00:00
|
|
|
protected _getRequestName(id: string, type: 'request' | 'response'): string {
|
|
|
|
return id.startsWith(`${type}/`) ? id : `${type}/${id}`;
|
2020-09-12 00:59:30 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 08:09:31 +00:00
|
|
|
/**
|
|
|
|
* 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 _subscribeToFunction(id: string, cb: (...args) => Promise<any>): void {
|
2020-09-13 10:35:04 +00:00
|
|
|
|
|
|
|
const _req = this._getRequestName(id, 'request');
|
|
|
|
const _res = this._getRequestName(id, 'response')
|
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
if (
|
|
|
|
this.options.subscriptionMode === 'individual' &&
|
2020-09-13 10:35:04 +00:00
|
|
|
!this._communicatorCallbacks.has(_req)
|
2020-09-12 00:59:30 +00:00
|
|
|
) {
|
|
|
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Define a Function.
|
2020-09-13 10:35:04 +00:00
|
|
|
const req = (data: requestTaskMsg) => {
|
2020-09-12 07:56:17 +00:00
|
|
|
if (data.type === 'requestOfTask') {
|
2020-09-12 00:59:30 +00:00
|
|
|
_this._handleExternalRequest(data, cb);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
const res = (data: responseTaskMsg) => {
|
|
|
|
if (data.type === 'response') {
|
|
|
|
_this._handleExternalResponse(data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
// Add the Callback.
|
|
|
|
this._communicatorCallbacks.set(_req, {
|
|
|
|
res,
|
|
|
|
req
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register Functions.
|
|
|
|
this._communicator.onRpcRequest(_req, req);
|
|
|
|
this._communicator.onRpcResult(_res, res);
|
2020-09-12 00:59:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
/**
|
2020-09-11 07:59:23 +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
|
2020-08-21 16:38:21 +00:00
|
|
|
*/
|
|
|
|
public registerFunction(func: (...args) => Promise<any>, options: {
|
2020-09-11 07:59:23 +00:00
|
|
|
/** Flag to enable unregistering the function after calling. */
|
2020-08-21 16:38:21 +00:00
|
|
|
deleteAfterCalling?: boolean,
|
2020-09-11 07:59:23 +00:00
|
|
|
/** Instead of generating a uuid an id could be provided */
|
2020-08-21 16:38:21 +00:00
|
|
|
id?: string;
|
2020-09-12 00:59:30 +00:00
|
|
|
/** Flag to enable / disable sending to registery */
|
|
|
|
preventSendingToRegistery?: boolean;
|
2020-09-11 07:59:23 +00:00
|
|
|
} = {}): (...args) => Promise<any> {
|
2020-08-21 16:38:21 +00:00
|
|
|
|
|
|
|
const _this = this;
|
2020-09-13 10:35:04 +00:00
|
|
|
// Define / Use the ID of the Function.
|
|
|
|
const _id = options.id || generateId();
|
2020-08-21 16:38:21 +00:00
|
|
|
|
|
|
|
let _func = func;
|
|
|
|
|
|
|
|
if (options.deleteAfterCalling) {
|
|
|
|
_func = async (...args) => {
|
|
|
|
// Unregister the Method
|
2020-09-12 07:56:17 +00:00
|
|
|
_this.unregistFunction(_id, {
|
|
|
|
preventSendingToRegistery: options.preventSendingToRegistery
|
|
|
|
});
|
2020-08-21 16:38:21 +00:00
|
|
|
// Return the Result of the Original Function.
|
|
|
|
return await func(...args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define a ID for the Function
|
|
|
|
_func['id'] = _id;
|
|
|
|
|
|
|
|
// Define the callback.
|
2020-09-12 00:59:30 +00:00
|
|
|
_func['unregister'] = () => _this.unregistFunction(_id);
|
2020-08-21 16:38:21 +00:00
|
|
|
|
|
|
|
// Reister the Function
|
|
|
|
this._definedFunctions.set(_func['id'], _func);
|
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
// Register the Callback:
|
|
|
|
this._subscribeToFunction(_id, _func);
|
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
if (!options.preventSendingToRegistery) {
|
2020-09-12 00:59:30 +00:00
|
|
|
// Publish the Available Services.
|
|
|
|
this._sendAvailableServices();
|
2020-09-12 07:56:17 +00:00
|
|
|
}
|
2020-09-12 00:59:30 +00:00
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
// Return the Function.
|
|
|
|
return _func;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to unregister a Function from the Dispatcher
|
2020-09-11 07:59:23 +00:00
|
|
|
* @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
|
2020-08-21 16:38:21 +00:00
|
|
|
*/
|
2020-09-12 07:56:17 +00:00
|
|
|
public unregistFunction(func: ((...args) => void) | string, options: {
|
|
|
|
/** Flag to enable / disable sending to registery */
|
|
|
|
preventSendingToRegistery?: boolean;
|
|
|
|
} = {}): boolean {
|
2020-09-12 00:59:30 +00:00
|
|
|
const _id = typeof func === 'string' ? func : func['id'] as string || '0';
|
|
|
|
|
|
|
|
// Try to unregister the Callback from the communcator:
|
|
|
|
if (this._communicatorCallbacks.has(_id)) {
|
2020-09-13 10:35:04 +00:00
|
|
|
|
|
|
|
const _callbacks = this._communicatorCallbacks.get(_id);
|
|
|
|
|
|
|
|
// Unregister the RPC-Request-Listener
|
|
|
|
this._communicator.offRpcRequest(this._getRequestName(_id, 'request'), _callbacks.req);
|
|
|
|
// Unregister the Result-Listener
|
|
|
|
this._communicator.offRpcRequest(this._getRequestName(_id, 'response'), _callbacks.res);
|
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
// Remove the Callback
|
|
|
|
this._communicatorCallbacks.delete(_id);
|
|
|
|
}
|
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
if (!options.preventSendingToRegistery) {
|
|
|
|
// Publish the Available Services.
|
|
|
|
this._sendAvailableServices();
|
|
|
|
}
|
2020-09-12 00:59:30 +00:00
|
|
|
|
2020-08-21 16:38:21 +00:00
|
|
|
return this._definedFunctions.delete(_id);
|
|
|
|
}
|
|
|
|
|
2020-09-12 00:59:30 +00:00
|
|
|
/**
|
|
|
|
* Function used to update the Available Services.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _sendAvailableServices() {
|
|
|
|
|
|
|
|
// Define the Message
|
|
|
|
const message: availableServices = {
|
|
|
|
dispatcher: this.id,
|
|
|
|
services: Array.from(this._definedFunctions.keys())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the Message.
|
2020-09-13 10:35:04 +00:00
|
|
|
this._communicator.emitNewServicesAvailable(message);
|
2020-09-12 00:59:30 +00:00
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Function which is used to perform a call on the remote.
|
|
|
|
*
|
|
|
|
* @template T
|
|
|
|
* @param {string} functionName The Name / ID of the Function
|
|
|
|
* @param {any[]} params The provided Parameters.
|
|
|
|
* @param {({
|
|
|
|
* deletableCallbacks: Array<number>;
|
|
|
|
* })} [options={
|
2020-09-12 07:56:17 +00:00
|
|
|
* deletableCallbacks: [],
|
|
|
|
* paramsHasNoCallback: false,
|
|
|
|
* preventErrorTest: false
|
|
|
|
* }] You could additiona Optionf for the callback.
|
2020-09-11 07:59:23 +00:00
|
|
|
* @return {*} {Promise<T>} The result of the Operation
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
2020-09-12 07:56:17 +00:00
|
|
|
public performCall<T>(functionName: string, params: any[], options: callOptions = {
|
|
|
|
deletableCallbacks: [],
|
|
|
|
paramsHasNoCallback: false,
|
2020-09-13 10:35:04 +00:00
|
|
|
preventAvailableTest: false
|
2020-09-12 07:56:17 +00:00
|
|
|
}): Promise<T> {
|
2020-08-21 16:38:21 +00:00
|
|
|
// Get a Call Id
|
2020-09-10 16:21:01 +00:00
|
|
|
const _taskId = generateId();
|
2020-09-12 07:56:17 +00:00
|
|
|
const _registeredIdx: Array<string> = [];
|
2020-08-21 16:38:21 +00:00
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Define a Callback-Function, which will expect the Task.
|
|
|
|
return new Promise<T>((resolve, reject) => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Register the Handlers,
|
|
|
|
_this._runningTasks.set(_taskId, {
|
|
|
|
resolve,
|
|
|
|
reject
|
|
|
|
});
|
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
// Define a Task-Request
|
|
|
|
const taskRequest: requestTaskMsg = {
|
2020-09-13 10:35:04 +00:00
|
|
|
functionId: functionName,
|
2020-09-12 07:56:17 +00:00
|
|
|
params,
|
|
|
|
callbacks: [],
|
|
|
|
taskId: _taskId,
|
|
|
|
type: 'requestOfTask'
|
|
|
|
}
|
2020-08-21 16:38:21 +00:00
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
// Test if there is no Callback integrated
|
|
|
|
if (!options.paramsHasNoCallback) {
|
|
|
|
// If so, the parameters has to be detailled:
|
2020-08-21 16:38:21 +00:00
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
// 1. Reset the Params list:
|
|
|
|
taskRequest.params = [];
|
2020-08-21 16:38:21 +00:00
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
// 2. Iterate over all Parameters and
|
|
|
|
// Determin Callbacks. Based on the Parameter-
|
|
|
|
// Type assign it either to packet.params (
|
|
|
|
// for parsable Parameters) and packet.callbacks
|
|
|
|
// (for callback Parameters)
|
|
|
|
for (const [idx, contentOfParameter] of params.entries()) {
|
2020-09-12 00:59:30 +00:00
|
|
|
|
2020-09-12 07:56:17 +00:00
|
|
|
// Test if the parameter is a Function
|
|
|
|
if (typeof contentOfParameter !== "function") {
|
|
|
|
taskRequest.params.push({
|
2020-09-12 00:59:30 +00:00
|
|
|
idx,
|
2020-09-12 07:56:17 +00:00
|
|
|
data: contentOfParameter
|
2020-09-12 00:59:30 +00:00
|
|
|
});
|
|
|
|
} else {
|
2020-09-12 07:56:17 +00:00
|
|
|
// The Parameter is a Callback => store a
|
|
|
|
// Description of the Callback and register
|
|
|
|
// the callback inside of the Dispatcher
|
2020-09-12 00:59:30 +00:00
|
|
|
|
|
|
|
const deleteAfterCalling = options.deletableCallbacks.includes(idx);
|
2020-09-12 07:56:17 +00:00
|
|
|
const _func = _this.registerFunction(contentOfParameter, {
|
2020-09-12 00:59:30 +00:00
|
|
|
deleteAfterCalling,
|
|
|
|
preventSendingToRegistery: true
|
|
|
|
});
|
|
|
|
|
|
|
|
_registeredIdx.push(_func['id']);
|
|
|
|
|
|
|
|
// Register the Callback
|
2020-09-12 07:56:17 +00:00
|
|
|
taskRequest.callbacks.push({
|
2020-09-12 00:59:30 +00:00
|
|
|
functionId: _func['id'],
|
|
|
|
idx,
|
|
|
|
deleteAfterCalling,
|
2020-09-13 10:35:04 +00:00
|
|
|
preventAvailableTest: true,
|
2020-09-12 00:59:30 +00:00
|
|
|
deletableCallbacks: []
|
|
|
|
});
|
|
|
|
}
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
2020-09-12 07:56:17 +00:00
|
|
|
}
|
2020-08-21 16:38:21 +00:00
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
if (!options.preventAvailableTest && !_this.serviceExists(functionName)) {
|
2020-09-12 07:56:17 +00:00
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
// Create an Error:
|
|
|
|
const error = new Error('No Service Provider known for "' + functionName + '"');
|
|
|
|
|
|
|
|
if (this._logger){
|
|
|
|
this._logger.error('No Service Provider known for "' + functionName + '"');
|
|
|
|
this._logger.error(error)
|
|
|
|
}
|
2020-09-12 07:56:17 +00:00
|
|
|
|
2020-09-13 10:35:04 +00:00
|
|
|
throw error;
|
2020-09-12 07:56:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send the Message to the specific element:
|
|
|
|
if (_this.options.subscriptionMode === 'individual') {
|
2020-09-13 10:35:04 +00:00
|
|
|
_this._communicator.emitRpcRequest(this._getRequestName(taskRequest.functionId, 'request'), taskRequest);
|
2020-09-12 07:56:17 +00:00
|
|
|
} else {
|
2020-09-13 10:35:04 +00:00
|
|
|
_this._communicator.emitRpcRequest('request', taskRequest);
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Delete all Callbacks.
|
|
|
|
_registeredIdx.map(id => _this.unregistFunction(id));
|
|
|
|
|
|
|
|
// Remove the task:
|
2020-09-12 00:59:30 +00:00
|
|
|
if (_this._runningTasks.has(_taskId))
|
|
|
|
_this._runningTasks.delete(_taskId);
|
2020-08-21 16:38:21 +00:00
|
|
|
|
|
|
|
// Throw an error.
|
|
|
|
reject(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Function to clear all pending tasks
|
|
|
|
*
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public clearTasks(): void {
|
2020-08-21 16:38:21 +00:00
|
|
|
if (this._runningTasks)
|
|
|
|
this._runningTasks.clear();
|
|
|
|
else
|
2020-09-12 20:23:55 +00:00
|
|
|
this._runningTasks = new Map<string, {
|
2020-08-21 16:38:21 +00:00
|
|
|
resolve: (value: any) => void;
|
|
|
|
reject: (error: any) => void;
|
|
|
|
}>();
|
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Function to unregister all Functions of the Dispatcher.
|
|
|
|
*
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public unregisterAll(): void {
|
2020-09-12 00:59:30 +00:00
|
|
|
if (this._definedFunctions) {
|
|
|
|
for (const id of this._definedFunctions.keys()) {
|
|
|
|
this.unregistFunction(id)
|
|
|
|
}
|
2020-08-21 16:38:21 +00:00
|
|
|
this._definedFunctions.clear();
|
2020-09-12 00:59:30 +00:00
|
|
|
} else {
|
2020-08-21 16:38:21 +00:00
|
|
|
this._definedFunctions = new Map<string, (...args) => Promise<any>>();
|
2020-09-12 00:59:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reset the Callbacks.
|
2020-09-13 10:35:04 +00:00
|
|
|
this._communicatorCallbacks = new Map();
|
2020-08-21 16:38:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-11 07:59:23 +00:00
|
|
|
/**
|
|
|
|
* Function to reset the Dispatcher.
|
|
|
|
*
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
public reset(): void {
|
2020-09-13 10:35:04 +00:00
|
|
|
this._remotlyCalledFunctions = new Set();
|
|
|
|
this._externalServices = new Set();
|
|
|
|
this._mappingOfRemoteDispatchersAndFunctions = new Map();
|
2020-08-21 16:38:21 +00:00
|
|
|
this.clearTasks();
|
|
|
|
this.unregisterAll();
|
|
|
|
}
|
|
|
|
}
|