2021-03-03 22:01:05 +03:00
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 ;
}
2021-04-20 14:19:08 +03:00
/ * *
* Type for all the possible ovjects that can be return to the Aquamarine interpreter
* /
export type AquaResultType = object | boolean | number | string ;
2021-03-03 22:01:05 +03:00
/ * *
* 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 ;
/ * *
2021-04-20 14:19:08 +03:00
* Result object to be returned to Aquamarine interpreter
2021-03-03 22:01:05 +03:00
* /
2021-04-20 14:19:08 +03:00
result : AquaResultType ;
2021-03-03 22:01:05 +03:00
[ 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 ,
2021-04-20 14:19:08 +03:00
handler : ( args : any [ ] , tetraplets : SecurityTetraplet [ ] [ ] ) = > AquaResultType ,
2021-03-03 22:01:05 +03:00
) = > {
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
* /
2021-04-20 14:19:08 +03:00
on (
serviceId : string ,
fnName : string ,
handler : ( args : any [ ] , tetraplets : SecurityTetraplet [ ] [ ] ) = > AquaResultType ,
) : Function {
2021-03-03 22:01:05 +03:00
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 ,
2021-04-13 17:04:36 +03:00
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 } ' ` ,
2021-03-03 22:01:05 +03:00
} ;
this . buildFunction ( ) ( req , res ) ;
return res ;
}
}