fluence-js/src/internal/AquaHandler.ts

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;
}
}