Better ErrorHandler and more tests

This commit is contained in:
2020-01-20 00:01:25 +01:00
parent cbefba6625
commit c4e280077b
6 changed files with 162 additions and 84 deletions
+4 -7
View File
@@ -42,18 +42,15 @@ export class RPCServer<
this.errorHandler = (socket:I.Socket) => (error:any) => {
if(conf.errorHandler) conf.errorHandler(socket, error)
socket.destroy();
console.error("Caught websocket error", String(error))
else throw error
}
this.closeHandler = (socket:I.Socket) => {
if(!conf.closeHandler) console.log("Connection on port "+socket.port+" closing")
else conf.closeHandler(socket)
if(conf.closeHandler) conf.closeHandler(socket)
}
this.connectionHandler = (socket:I.Socket) => {
if(!conf.connectionHandler) console.log("New connection on port "+socket.port)
else conf.connectionHandler(socket)
if(conf.connectionHandler) conf.connectionHandler(socket)
}
let badRPC
@@ -86,7 +83,7 @@ export class RPCServer<
protected initRPCs(socket:I.Socket){
socket.hook('info', () => rpcInfos)
const rpcInfos:T.ExtendedRpcInfo[] = [
...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter, this.sesame))
...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter, this.errorHandler, this.sesame))
]
}
+2 -7
View File
@@ -4,14 +4,9 @@ import bsock = require('bsock');
import * as T from './Types';
import * as I from './Interfaces';
import { stripAfterEquals } from './Utils';
/**
* Utility function to strip parameters like "a = 3" of their defaults
* @param str The parameter to modify
*/
function stripAfterEquals(str:string):string{
return str.split("=")[0]
}
/**
* A websocket-on-steroids with built-in RPC capabilities
+2 -1
View File
@@ -8,7 +8,7 @@ export type ConnectionHandler = (socket:I.Socket) => void
export type ErrorHandler = (socket:I.Socket, error:any) => void
export type CloseHandler = (socket:I.Socket) => void
export type SesameFunction = (sesame : string) => boolean
export type ExceptionHandling = 'local' | 'remote'
export type SesameConf = {
sesame?: string | SesameFunction
}
@@ -17,6 +17,7 @@ export type ServerConf = {
errorHandler?: ErrorHandler
closeHandler?: CloseHandler
visibility?: Visibility
exceptionHandling?: ExceptionHandling
} & SesameConf
export type SocketConf = {
+64 -30
View File
@@ -10,8 +10,7 @@ import { SubscriptionResponse } from "./Types";
* @param owner The owning RPC group's name
* @throws Error on RPC without name property
*/
export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, sesame?:T.SesameFunction):T.RpcInfo => {
export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => {
switch (typeof rpc){
case "object":
if(rpc['call']){
@@ -20,10 +19,10 @@ export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner
argNames: extractArgs(rpc['call']),
type: "Call",
name: rpc.name,
call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw new Error('Bad sesame')}:rpc['call'], // check & remove sesame
call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw makeError(rpc.name)}:rpc['call'], // check & remove sesame
}
}else{
const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, sesame)
const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, sesame)
return {
owner: owner,
argNames: extractArgs(generator(undefined)),
@@ -45,7 +44,7 @@ RPC did not provide a name.
argNames: extractArgs(rpc),
type: "Call",
name: rpc.name,
call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args)}:rpc, // check & remove sesame
call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args); throw makeError(rpc.name)}:rpc, // check & remove sesame
}
}
throw new Error("Bad socketIORPC type "+ typeof rpc)
@@ -57,62 +56,97 @@ RPC did not provide a name.
* @param exporter The exporter
* @param makeUnique @default true Attach a suffix to RPC names
*/
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
const owner = exporter.name
const RPCs = [...exporter.exportRPCs()]
return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, sesame))
return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, errorHandler, sesame))
.map(info => {
const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
const ret:any = info
ret.uniqueName = info.name+suffix
let rpcFunction
switch(info.type){
case "Hook":
socket.hook(ret.uniqueName, info.generator(socket))
break;
case "Call":
socket.hook(ret.uniqueName, info.call)
break;
}
socket.on('close', () => socket.unhook(info.name))
if(info.type === 'Hook')
rpcFunction = info.generator(socket)
else
rpcFunction = info.call
socket.hook(ret.uniqueName, callGenerator(socket, rpcFunction, errorHandler))
return ret
})
}
/**
* Decorate an RPC with the error handler
* @param rpcFunction the function to decorate
*/
const callGenerator = (socket: I.Socket, rpcFunction : T.AnyFunction, errorHandler: T.ErrorHandler) : T.AnyFunction => {
const argsArr = extractArgs(rpcFunction)
const args = argsArr.join(',')
const argsStr = argsArr.map(stripAfterEquals).join(',')
return eval(`async (`+args+`) => {
try{
return await rpcFunction(`+argsStr+`)
}catch(e){
errorHandler(socket)(e)
}
}`)
}
/**
* Utility function to strip parameters like "a = 3" of their defaults
* @param str The parameter to modify
*/
export function stripAfterEquals(str:string):string{
return str.split("=")[0]
}
/**
* Utility function to generate {@link HookFunction} from a RPC for backend
* @param rpc The RPC to transform
* @returns A {@link HookFunction}
*/
const hookGenerator = (rpc:T.HookRPC<any, any, any>, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
const hookGenerator = (rpc:T.HookRPC<any, any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
const argsArr = extractArgs(rpc.hook)
argsArr.pop() //remove 'callback' from the end
const argsStr = argsArr.join(',')
if(sesameFn){
const args = ['sesame', ...argsArr].join(',')
const f = eval(`(socket) => async (`+args+`) => {
if(!sesameFn(sesame)) return
const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
return eval(`(socket) => async (`+args+`) => {
try{
if(!sesameFn(sesame)) return
const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
socket.call.apply(socket, [res.uuid, ...cbargs])
})
return res
}catch(e){
errorHandler(socket, e)
}
}`)
}
const args = argsArr.join(',')
return eval(`(socket) => async (`+args+`) => {
try{
const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
socket.call.apply(socket, [res.uuid, ...cbargs])
})
return res
}`)
return f
}
const args = argsArr.join(',')
return eval(`(socket) => async (`+args+`) => {
const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
socket.call.apply(socket, [res.uuid, ...cbargs])
})
return res
}catch(e){
errorHandler(socket, e)
}
}`)
}
const makeError = (callName: string) => {
return new Error("Unhandled Promise rejection: Call not found: "+callName+". ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: "+callName)
}
/**
* Extract a string list of parameters from a function