mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-04-25 09:52:12 +00:00
238 lines
7.9 KiB
TypeScript
238 lines
7.9 KiB
TypeScript
import { ResultCodes, SecurityTetraplet } from './commonTypes';
|
|
|
|
/**
|
|
* Particle context. Contains additional information about particle which triggered `call` air instruction from Aquamarine interpreter
|
|
*/
|
|
interface ParticleContext {
|
|
/**
|
|
* The particle ID
|
|
*/
|
|
particleId: string;
|
|
[x: string]: any;
|
|
}
|
|
|
|
/**
|
|
* Represents the information passed from Aquamarine interpreter when a `call` air instruction is executed on the local peer
|
|
*/
|
|
interface AquaCall {
|
|
/**
|
|
* Service ID as specified in `call` air instruction
|
|
*/
|
|
serviceId: string;
|
|
|
|
/**
|
|
* Function name as specified in `call` air instruction
|
|
*/
|
|
fnName: string;
|
|
|
|
/**
|
|
* Arguments as specified in `call` air instruction
|
|
*/
|
|
args: any[];
|
|
|
|
/**
|
|
* Security Tetraplets recieved from Aquamarine interpreter
|
|
*/
|
|
tetraplets: SecurityTetraplet[][];
|
|
|
|
/**
|
|
* Particle context, @see {@link ParticleContext}
|
|
*/
|
|
particleContext: ParticleContext;
|
|
|
|
[x: string]: any;
|
|
}
|
|
|
|
/**
|
|
* Type for all the possible ovjects that can be return to the Aquamarine interpreter
|
|
*/
|
|
export type AquaResultType = object | boolean | number | string;
|
|
|
|
/**
|
|
* Represents the result of the `call` air instruction to be returned into Aquamarine interpreter
|
|
*/
|
|
interface AquaCallResult {
|
|
/**
|
|
* Return code to be returned to Aquamarine interpreter
|
|
*/
|
|
retCode: ResultCodes;
|
|
|
|
/**
|
|
* Result object to be returned to Aquamarine interpreter
|
|
*/
|
|
result: AquaResultType;
|
|
[x: string]: any;
|
|
}
|
|
|
|
/**
|
|
* Type for the middleware used in AquaCallHandler middleware chain.
|
|
* In a nutshell middelware is a function of request, response and function to trigger the next middleware in chain.
|
|
* Each middleware is free to write additional properties to either request or response object.
|
|
* When the chain finishes the response is passed back to Aquamarine interpreter
|
|
* @param { AquaCall } req - information about the air `call` instruction
|
|
* @param { AquaCallResult } resp - response to be passed to Aquamarine interpreter
|
|
* @param { Function } next - function which invokes next middleware in chain
|
|
*/
|
|
export type Middleware = (req: AquaCall, resp: AquaCallResult, next: Function) => void;
|
|
|
|
/**
|
|
* Convenience middleware factory. Registeres a handler for a pair of 'serviceId/fnName'.
|
|
* The return value of the handler is passed back to Aquamarine
|
|
* @param { string } serviceId - The identifier of service which would be used to make calls from Aquamarine
|
|
* @param { string } fnName - The identifier of function which would be used to make calls from Aquamarine
|
|
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => object } handler - The handler which should handle the call. The result is any object passed back to Aquamarine
|
|
*/
|
|
export const fnHandler = (
|
|
serviceId: string,
|
|
fnName: string,
|
|
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => AquaResultType,
|
|
) => {
|
|
return (req: AquaCall, resp: AquaCallResult, next: Function): void => {
|
|
if (req.fnName === fnName && req.serviceId === serviceId) {
|
|
const res = handler(req.args, req.tetraplets);
|
|
resp.retCode = ResultCodes.success;
|
|
resp.result = res;
|
|
}
|
|
next();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Convenience middleware factory. Registeres a handler for a pair of 'serviceId/fnName'.
|
|
* Similar to @see { @link fnHandler } but instead returns and empty object immediately runs the handler asynchronously
|
|
* @param { string } serviceId - The identifier of service which would be used to make calls from Aquamarine
|
|
* @param { string } fnName - The identifier of function which would be used to make calls from Aquamarine
|
|
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => void } handler - The handler which should handle the call.
|
|
*/
|
|
export const fnAsEventHandler = (
|
|
serviceId: string,
|
|
fnName: string,
|
|
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => void,
|
|
) => {
|
|
return (req: AquaCall, resp: AquaCallResult, next: Function): void => {
|
|
if (req.fnName === fnName && req.serviceId === serviceId) {
|
|
setTimeout(() => {
|
|
handler(req.args, req.tetraplets);
|
|
}, 0);
|
|
|
|
resp.retCode = ResultCodes.success;
|
|
resp.result = {};
|
|
}
|
|
next();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Error catching middleware
|
|
*/
|
|
export const errorHandler: Middleware = (req: AquaCall, resp: AquaCallResult, next: Function): void => {
|
|
try {
|
|
next();
|
|
} catch (e) {
|
|
resp.retCode = ResultCodes.exceptionInHandler;
|
|
resp.result = e.toString();
|
|
}
|
|
};
|
|
|
|
type AquaCallFunction = (req: AquaCall, resp: AquaCallResult) => void;
|
|
|
|
/**
|
|
* Class defines the handling of a `call` air intruction executed by aquamarine on the local peer.
|
|
* All the execution process is defined by the chain of middlewares - architecture popular among backend web frameworks.
|
|
* Each middleware has the form of `(req: AquaCall, resp: AquaCallResult, next: Function) => void;`
|
|
* A handler starts with an empty middleware chain and does nothing.
|
|
* To execute the handler use @see { @link execute } function
|
|
*/
|
|
export class AquaCallHandler {
|
|
private middlewares: Middleware[] = [];
|
|
|
|
/**
|
|
* Appends middleware to the chain of middlewares
|
|
* @param { Middleware } middleware
|
|
*/
|
|
use(middleware: Middleware): AquaCallHandler {
|
|
this.middlewares.push(middleware);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Removes the middleware from the chain of middlewares
|
|
* @param { Middleware } middleware
|
|
*/
|
|
unUse(middleware: Middleware): AquaCallHandler {
|
|
const index = this.middlewares.indexOf(middleware);
|
|
if (index !== -1) {
|
|
this.middlewares.splice(index, 1);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Combine handler with another one. Combintaion is done by copying middleware chain from the argument's handler into current one.
|
|
* Please note, that current handler's middlewares take precedence over the ones from handler to be combined with
|
|
* @param { AquaCallHandler } other - AquaCallHandler to be combined with
|
|
*/
|
|
combineWith(other: AquaCallHandler): AquaCallHandler {
|
|
this.middlewares = [...this.middlewares, ...other.middlewares];
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Convinience method for registring @see { @link fnHandler } middleware
|
|
*/
|
|
on(
|
|
serviceId: string,
|
|
fnName: string,
|
|
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => AquaResultType,
|
|
): Function {
|
|
const mw = fnHandler(serviceId, fnName, handler);
|
|
this.use(mw);
|
|
return () => {
|
|
this.unUse(mw);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convinience method for registring @see { @link fnAsEventHandler } middleware
|
|
*/
|
|
onEvent(
|
|
serviceId: string,
|
|
fnName: string,
|
|
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => void,
|
|
): Function {
|
|
const mw = fnAsEventHandler(serviceId, fnName, handler);
|
|
this.use(mw);
|
|
return () => {
|
|
this.unUse(mw);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Collapses middleware chain into a single function.
|
|
*/
|
|
buildFunction(): AquaCallFunction {
|
|
const result = this.middlewares.reduceRight<AquaCallFunction>(
|
|
(agg, cur) => {
|
|
return (req, resp) => {
|
|
cur(req, resp, () => agg(req, resp));
|
|
};
|
|
},
|
|
(req, res) => {},
|
|
);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Executes the handler with the specified AquaCall request. Return the result response
|
|
*/
|
|
execute(req: AquaCall): AquaCallResult {
|
|
const res: AquaCallResult = {
|
|
retCode: ResultCodes.unkownError,
|
|
result: `The handler did not set any result. Make sure you are calling the right peer and the handler has been registered. Original request data was: serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
|
};
|
|
this.buildFunction()(req, res);
|
|
return res;
|
|
}
|
|
}
|