nope/lib/dispatcher/nopeDispatcher.ts

266 lines
6.3 KiB
TypeScript
Raw Normal View History

2020-09-10 16:21:01 +00:00
import { generateId } from '../helpers/idMethods';
2020-08-21 16:38:21 +00:00
export type CommunicationEvents = 'connection' | 'event' | 'disconnect';
export interface ICommunicationInterface {
on(event: CommunicationEvents, cb: (...args) => void);
send(data): void;
}
type requestTask = {
type: 'request',
taskId: number,
functionId: number | string,
params: {
idx: number,
data: any
}[]
callbacks: {
functionId: string,
idx: number,
deleteAfterCalling: boolean
}[]
}
type responseOfTask = {
type: 'response',
taskId: number,
result?: any,
error?: any
}
/**
* A Dispatcher to perform a function with a remote
*/
2020-08-23 07:18:11 +00:00
export class nopeDispatcher {
2020-08-21 16:38:21 +00:00
protected _definedFunctions: Map<number | string, (...args) => Promise<any>>;
protected _runningTasks: Map<number, {
resolve: (value: any) => void;
reject: (error: any) => void;
}>;
constructor(public communicator: ICommunicationInterface) {
this.reset();
this.init();
}
protected async _handleExternalRequest(data: requestTask) {
try {
const _this = this;
2020-09-10 16:21:01 +00:00
const _function = _this._definedFunctions.get(data.functionId);
2020-08-21 16:38:21 +00:00
if (typeof _function === 'function') {
const args = [];
data.params.map(item => args[item.idx] = item.data);
data.callbacks.map(item => args[item.idx] = async (..._args) => {
2020-09-10 16:21:01 +00:00
return await _this.performCall(item.functionId, _args);
2020-08-21 16:38:21 +00:00
});
2020-09-10 16:21:01 +00:00
const _result = await _function(...args);
2020-08-21 16:38:21 +00:00
const result: responseOfTask = {
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'
}
this.communicator.send(result);
}
} catch (error) {
const result: responseOfTask = {
error,
taskId: data.taskId,
type: 'response'
}
this.communicator.send(result);
}
}
protected _handleExternalResponse(data: responseOfTask) {
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) {
}
}
protected init() {
this.communicator.on('event', (data: requestTask | responseOfTask) => {
switch (data.type) {
case 'request':
this._handleExternalRequest(data);
break;
case 'response':
this._handleExternalResponse(data);
break;
default:
break;
}
});
}
/**
* Function to register a Function
* @param func
* @param options
*/
public registerFunction(func: (...args) => Promise<any>, options: {
args?: any[],
deleteAfterCalling?: boolean,
id?: string;
} = {}) {
const _this = this;
2020-09-10 16:21:01 +00:00
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
_this.unregistFunction(_id);
// Return the Result of the Original Function.
return await func(...args);
}
}
// Define a ID for the Function
_func['id'] = _id;
// Define the callback.
_func['unregister'] = () => _this.unregistFunction(func);
// Reister the Function
this._definedFunctions.set(_func['id'], _func);
// Return the Function.
return _func;
}
/**
* Function to unregister a Function from the Dispatcher
* @param func The Function to unregister
* @returns Flag, whether the element was removed (only if found) or not.
*/
public unregistFunction(func: ((...args) => void) | string | number) {
const _id = typeof func === 'string' ? func : func['id'] || 0;
return this._definedFunctions.delete(_id);
}
public performCall<T>(functionName: string, params: any[], options: {
deletableCallbacks: Array<string | number>;
} = {
deletableCallbacks: []
}) {
// Get a Call Id
2020-09-10 16:21:01 +00:00
const _taskId = generateId();
2020-08-21 16:38:21 +00:00
const _registeredIdx: Array<number | string> = [];
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
});
const _parameters: {
idx: number,
data: any,
}[] = [];
const _callbacks: {
functionId: string,
idx: number,
deleteAfterCalling: boolean
}[] = [];
// Detail the Parameters.
for (const [idx, data] of params.entries()) {
if (typeof data !== "function") {
_parameters.push({
idx,
data
});
} else {
const deleteAfterCalling = options.deletableCallbacks.includes(idx);
const _func = _this.registerFunction(data, {
deleteAfterCalling
});
_registeredIdx.push(_func['id']);
// Register the Callback
_callbacks.push({
functionId: _func['id'],
idx,
deleteAfterCalling
});
}
}
// Perform the call. Therefore create the data package.
const packet: requestTask = {
functionId: functionName,
params: _parameters,
callbacks: _callbacks,
taskId: _taskId,
type: 'request'
}
// Send the Message.
_this.communicator.send(packet)
} catch (e) {
// Delete all Callbacks.
_registeredIdx.map(id => _this.unregistFunction(id));
// Remove the task:
_this._runningTasks.delete(_taskId);
// Throw an error.
reject(e);
}
});
}
public clearTasks() {
if (this._runningTasks)
this._runningTasks.clear();
else
this._runningTasks = new Map<number, {
resolve: (value: any) => void;
reject: (error: any) => void;
}>();
}
public unregisterAll() {
if (this._definedFunctions)
this._definedFunctions.clear();
else
this._definedFunctions = new Map<string, (...args) => Promise<any>>();
}
public reset() {
this.clearTasks();
this.unregisterAll();
}
}