289 lines
6.9 KiB
TypeScript
289 lines
6.9 KiB
TypeScript
|
|
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 CallDispatcher {
|
|
|
|
protected _definedFunctions: Map<number | string, (...args) => Promise<any>>;
|
|
protected _runningTasks: Map<number, {
|
|
resolve: (value: any) => void;
|
|
reject: (error: any) => void;
|
|
}>;
|
|
protected _currentId = 1;
|
|
protected _currentCallId = 1;
|
|
|
|
constructor(public communicator: ICommunicationInterface) {
|
|
this.reset();
|
|
this.init();
|
|
}
|
|
|
|
protected async _handleExternalRequest(data: requestTask) {
|
|
try {
|
|
const _this = this;
|
|
const _function = _this._definedFunctions.get(data.taskId);
|
|
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: responseOfTask = {
|
|
result: await _function(...args),
|
|
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 && data.result) {
|
|
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;
|
|
const _id = options.id || this._currentId++;
|
|
|
|
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
|
|
const _taskId = this._currentCallId++;
|
|
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._currentId = 1;
|
|
this._currentCallId = 1;
|
|
this.clearTasks();
|
|
this.unregisterAll();
|
|
}
|
|
}
|
|
|
|
declare const global;
|
|
declare const window;
|
|
|
|
/**
|
|
* Function to extract a Singleton Dispatcher
|
|
* @param uuid The Unique Id of the Dispatcher
|
|
* @param communicator The provided communicator
|
|
*/
|
|
export function getDispatcher(uuid: string, communicator: ICommunicationInterface){
|
|
|
|
// Get the global variable
|
|
const _global = typeof global !== "undefined" ? global : window;
|
|
const _accessor = '__singletons__'+uuid;
|
|
const _hasDispatcher = _global[_accessor] !== undefined;
|
|
|
|
if (!_hasDispatcher){
|
|
_global[_accessor] = new CallDispatcher(communicator);
|
|
}
|
|
|
|
return _global[_accessor] as CallDispatcher
|
|
} |