import { generateId } from '../helpers/idMethods'; 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 */ export class nopeDispatcher { protected _definedFunctions: Map Promise>; protected _runningTasks: Map void; reject: (error: any) => void; }>; constructor(public communicator: ICommunicationInterface) { this.reset(); this.init(); } protected async _handleExternalRequest(data: requestTask) { try { const _this = this; const _function = _this._definedFunctions.get(data.functionId); if (typeof _function === 'function') { const args = []; data.params.map(item => args[item.idx] = item.data); data.callbacks.map(item => args[item.idx] = async (..._args) => { return await _this.performCall(item.functionId, _args); }); const _result = await _function(...args); const result: responseOfTask = { result: typeof (_result) !== 'undefined' ? _result : null, 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); } if (task) { 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, options: { args?: any[], deleteAfterCalling?: boolean, id?: string; } = {}) { const _this = this; const _id = options.id || generateId(); 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(functionName: string, params: any[], options: { deletableCallbacks: Array; } = { deletableCallbacks: [] }) { // Get a Call Id const _taskId = generateId(); const _registeredIdx: Array = []; const _this = this; // Define a Callback-Function, which will expect the Task. return new Promise((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 void; reject: (error: any) => void; }>(); } public unregisterAll() { if (this._definedFunctions) this._definedFunctions.clear(); else this._definedFunctions = new Map Promise>(); } public reset() { this.clearTasks(); this.unregisterAll(); } }