|  | @@ -10,8 +10,7 @@ import { SubscriptionResponse } from "./Types";
 | 
		
	
		
			
			| 10 | 10 |   * @param owner The owning RPC group's name
 | 
		
	
		
			
			| 11 | 11 |   * @throws Error on RPC without name property
 | 
		
	
		
			
			| 12 | 12 |   */
 | 
		
	
		
			
			| 13 |  | -export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, sesame?:T.SesameFunction):T.RpcInfo => {
 | 
		
	
		
			
			| 14 |  | -    
 | 
		
	
		
			
			|  | 13 | +export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => {
 | 
		
	
		
			
			| 15 | 14 |      switch (typeof rpc){
 | 
		
	
		
			
			| 16 | 15 |          case  "object":
 | 
		
	
		
			
			| 17 | 16 |              if(rpc['call']){
 | 
		
	
	
		
			
			|  | @@ -20,10 +19,10 @@ export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner
 | 
		
	
		
			
			| 20 | 19 |                      argNames: extractArgs(rpc['call']),
 | 
		
	
		
			
			| 21 | 20 |                      type: "Call",
 | 
		
	
		
			
			| 22 | 21 |                      name: rpc.name,
 | 
		
	
		
			
			| 23 |  | -                    call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw new Error('Bad sesame')}:rpc['call'], // check & remove sesame 
 | 
		
	
		
			
			|  | 22 | +                    call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); throw makeError(rpc.name)}:rpc['call'], // check & remove sesame 
 | 
		
	
		
			
			| 24 | 23 |                  }
 | 
		
	
		
			
			| 25 | 24 |              }else{
 | 
		
	
		
			
			| 26 |  | -                const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, sesame)
 | 
		
	
		
			
			|  | 25 | +                const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, sesame)
 | 
		
	
		
			
			| 27 | 26 |                  return {
 | 
		
	
		
			
			| 28 | 27 |                      owner: owner,
 | 
		
	
		
			
			| 29 | 28 |                      argNames: extractArgs(generator(undefined)),
 | 
		
	
	
		
			
			|  | @@ -45,7 +44,7 @@ RPC did not provide a name.
 | 
		
	
		
			
			| 45 | 44 |                  argNames: extractArgs(rpc),
 | 
		
	
		
			
			| 46 | 45 |                  type: "Call",
 | 
		
	
		
			
			| 47 | 46 |                  name: rpc.name,
 | 
		
	
		
			
			| 48 |  | -                call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args)}:rpc, // check & remove sesame 
 | 
		
	
		
			
			|  | 47 | +                call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args); throw makeError(rpc.name)}:rpc, // check & remove sesame 
 | 
		
	
		
			
			| 49 | 48 |              }
 | 
		
	
		
			
			| 50 | 49 |      }
 | 
		
	
		
			
			| 51 | 50 |      throw new Error("Bad socketIORPC type "+ typeof rpc)
 | 
		
	
	
		
			
			|  | @@ -57,62 +56,97 @@ RPC did not provide a name.
 | 
		
	
		
			
			| 57 | 56 |   * @param exporter The exporter
 | 
		
	
		
			
			| 58 | 57 |   * @param makeUnique @default true Attach a suffix to RPC names
 | 
		
	
		
			
			| 59 | 58 |   */
 | 
		
	
		
			
			| 60 |  | -export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
 | 
		
	
		
			
			|  | 59 | +export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
 | 
		
	
		
			
			| 61 | 60 |      const owner = exporter.name
 | 
		
	
		
			
			| 62 | 61 |      const RPCs = [...exporter.exportRPCs()]
 | 
		
	
		
			
			| 63 | 62 |  
 | 
		
	
		
			
			| 64 | 63 |  
 | 
		
	
		
			
			| 65 |  | -    return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, sesame))
 | 
		
	
		
			
			|  | 64 | +    return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, errorHandler, sesame))
 | 
		
	
		
			
			| 66 | 65 |      .map(info => {
 | 
		
	
		
			
			| 67 | 66 |          const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
 | 
		
	
		
			
			| 68 | 67 |          const ret:any = info
 | 
		
	
		
			
			| 69 | 68 |          ret.uniqueName = info.name+suffix
 | 
		
	
		
			
			| 70 | 69 |  
 | 
		
	
		
			
			|  | 70 | +        let rpcFunction
 | 
		
	
		
			
			| 71 | 71 |  
 | 
		
	
		
			
			| 72 |  | -        switch(info.type){
 | 
		
	
		
			
			| 73 |  | -            case "Hook":
 | 
		
	
		
			
			| 74 |  | -                socket.hook(ret.uniqueName, info.generator(socket))
 | 
		
	
		
			
			| 75 |  | -                break;
 | 
		
	
		
			
			| 76 |  | -            case "Call":
 | 
		
	
		
			
			| 77 |  | -                socket.hook(ret.uniqueName, info.call)
 | 
		
	
		
			
			| 78 |  | -                break;
 | 
		
	
		
			
			| 79 |  | -        }
 | 
		
	
		
			
			| 80 |  | -        socket.on('close', () => socket.unhook(info.name))
 | 
		
	
		
			
			|  | 72 | +        if(info.type === 'Hook')
 | 
		
	
		
			
			|  | 73 | +            rpcFunction = info.generator(socket)
 | 
		
	
		
			
			|  | 74 | +        else
 | 
		
	
		
			
			|  | 75 | +            rpcFunction = info.call
 | 
		
	
		
			
			|  | 76 | +
 | 
		
	
		
			
			|  | 77 | +        socket.hook(ret.uniqueName, callGenerator(socket, rpcFunction, errorHandler))
 | 
		
	
		
			
			| 81 | 78 |          return ret
 | 
		
	
		
			
			| 82 | 79 |      })
 | 
		
	
		
			
			| 83 | 80 |  }
 | 
		
	
		
			
			|  | 81 | +
 | 
		
	
		
			
			|  | 82 | +/**
 | 
		
	
		
			
			|  | 83 | + * Decorate an RPC with the error handler
 | 
		
	
		
			
			|  | 84 | + * @param rpcFunction the function to decorate
 | 
		
	
		
			
			|  | 85 | + */
 | 
		
	
		
			
			|  | 86 | +const callGenerator = (socket: I.Socket, rpcFunction : T.AnyFunction, errorHandler: T.ErrorHandler) : T.AnyFunction => {
 | 
		
	
		
			
			|  | 87 | +    const argsArr = extractArgs(rpcFunction)
 | 
		
	
		
			
			|  | 88 | +    const args = argsArr.join(',')
 | 
		
	
		
			
			|  | 89 | +    const argsStr = argsArr.map(stripAfterEquals).join(',')
 | 
		
	
		
			
			|  | 90 | +
 | 
		
	
		
			
			|  | 91 | +    return eval(`async (`+args+`) => {
 | 
		
	
		
			
			|  | 92 | +        try{
 | 
		
	
		
			
			|  | 93 | +            return await rpcFunction(`+argsStr+`)
 | 
		
	
		
			
			|  | 94 | +        }catch(e){
 | 
		
	
		
			
			|  | 95 | +            errorHandler(socket)(e)
 | 
		
	
		
			
			|  | 96 | +        }
 | 
		
	
		
			
			|  | 97 | +    }`)
 | 
		
	
		
			
			|  | 98 | +}
 | 
		
	
		
			
			|  | 99 | +
 | 
		
	
		
			
			|  | 100 | +/**
 | 
		
	
		
			
			|  | 101 | + * Utility function to strip parameters like "a = 3" of their defaults
 | 
		
	
		
			
			|  | 102 | + * @param str The parameter to modify
 | 
		
	
		
			
			|  | 103 | + */
 | 
		
	
		
			
			|  | 104 | +export function stripAfterEquals(str:string):string{
 | 
		
	
		
			
			|  | 105 | +    return str.split("=")[0]
 | 
		
	
		
			
			|  | 106 | +}
 | 
		
	
		
			
			|  | 107 | +
 | 
		
	
		
			
			| 84 | 108 |  /**
 | 
		
	
		
			
			| 85 | 109 |   * Utility function to generate {@link HookFunction} from a RPC for backend
 | 
		
	
		
			
			| 86 | 110 |   * @param rpc The RPC to transform
 | 
		
	
		
			
			| 87 | 111 |   * @returns A {@link HookFunction}
 | 
		
	
		
			
			| 88 | 112 |   */
 | 
		
	
		
			
			| 89 |  | -const hookGenerator = (rpc:T.HookRPC<any, any, any>, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => { 
 | 
		
	
		
			
			|  | 113 | +const hookGenerator = (rpc:T.HookRPC<any, any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => { 
 | 
		
	
		
			
			| 90 | 114 |      const argsArr = extractArgs(rpc.hook)
 | 
		
	
		
			
			| 91 | 115 |      argsArr.pop() //remove 'callback' from the end
 | 
		
	
		
			
			| 92 | 116 |      const argsStr = argsArr.join(',')
 | 
		
	
		
			
			| 93 | 117 |  
 | 
		
	
		
			
			| 94 | 118 |      if(sesameFn){
 | 
		
	
		
			
			| 95 | 119 |          const args = ['sesame', ...argsArr].join(',')
 | 
		
	
		
			
			| 96 |  | -        const f =  eval(`(socket) => async (`+args+`) => {
 | 
		
	
		
			
			| 97 |  | -            if(!sesameFn(sesame)) return
 | 
		
	
		
			
			| 98 |  | -            const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
 | 
		
	
		
			
			| 99 |  | -                if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
 | 
		
	
		
			
			| 100 |  | -                socket.call.apply(socket, [res.uuid, ...cbargs])
 | 
		
	
		
			
			| 101 |  | -            })
 | 
		
	
		
			
			| 102 |  | -            return res
 | 
		
	
		
			
			|  | 120 | +        return eval(`(socket) => async (`+args+`) => {
 | 
		
	
		
			
			|  | 121 | +            try{
 | 
		
	
		
			
			|  | 122 | +                if(!sesameFn(sesame)) return
 | 
		
	
		
			
			|  | 123 | +                const res = await rpc.hook(`+argsStr+(argsStr.length!==0?',':'')+` (...cbargs) => {
 | 
		
	
		
			
			|  | 124 | +                    if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
 | 
		
	
		
			
			|  | 125 | +                    socket.call.apply(socket, [res.uuid, ...cbargs])
 | 
		
	
		
			
			|  | 126 | +                })
 | 
		
	
		
			
			|  | 127 | +                return res
 | 
		
	
		
			
			|  | 128 | +            }catch(e){
 | 
		
	
		
			
			|  | 129 | +                errorHandler(socket, e)
 | 
		
	
		
			
			|  | 130 | +            }
 | 
		
	
		
			
			| 103 | 131 |          }`)
 | 
		
	
		
			
			| 104 |  | -        return f
 | 
		
	
		
			
			| 105 | 132 |      }
 | 
		
	
		
			
			| 106 | 133 |      const args = argsArr.join(',')
 | 
		
	
		
			
			| 107 | 134 |      return eval(`(socket) => async (`+args+`) => {
 | 
		
	
		
			
			| 108 |  | -        const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
 | 
		
	
		
			
			| 109 |  | -            if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
 | 
		
	
		
			
			| 110 |  | -            socket.call.apply(socket, [res.uuid, ...cbargs])
 | 
		
	
		
			
			| 111 |  | -        })
 | 
		
	
		
			
			| 112 |  | -        return res
 | 
		
	
		
			
			|  | 135 | +        try{
 | 
		
	
		
			
			|  | 136 | +            const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => {
 | 
		
	
		
			
			|  | 137 | +                if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
 | 
		
	
		
			
			|  | 138 | +                socket.call.apply(socket, [res.uuid, ...cbargs])
 | 
		
	
		
			
			|  | 139 | +            })
 | 
		
	
		
			
			|  | 140 | +            return res
 | 
		
	
		
			
			|  | 141 | +        }catch(e){
 | 
		
	
		
			
			|  | 142 | +            errorHandler(socket, e)
 | 
		
	
		
			
			|  | 143 | +        }
 | 
		
	
		
			
			| 113 | 144 |      }`)
 | 
		
	
		
			
			| 114 | 145 |  }
 | 
		
	
		
			
			| 115 | 146 |  
 | 
		
	
		
			
			|  | 147 | +const makeError = (callName: string) => {
 | 
		
	
		
			
			|  | 148 | +    return new Error("Unhandled Promise rejection: Call not found: "+callName+". ; Zone: <root> ; Task: Promise.then ; Value: Error: Call not found: "+callName)
 | 
		
	
		
			
			|  | 149 | +}
 | 
		
	
		
			
			| 116 | 150 |  
 | 
		
	
		
			
			| 117 | 151 |  /**
 | 
		
	
		
			
			| 118 | 152 |   * Extract a string list of parameters from a function
 |