diff --git a/src/Frontend.ts b/src/Frontend.ts index 1796f0b..72a9150 100644 --- a/src/Frontend.ts +++ b/src/Frontend.ts @@ -4,7 +4,7 @@ import bsock = require('bsock'); import * as T from './Types'; import * as I from './Interfaces'; -import { stripAfterEquals } from './Utils'; +import { stripAfterEquals, appendComma } from './Utils'; @@ -127,10 +127,9 @@ export class RPCSocket implements I.Socket{ private callGenerator(fnName: string, fnArgs:string[], sesame?:string): T.AnyFunction{ const headerArgs = fnArgs.join(",") const argParams = fnArgs.map(stripAfterEquals).join(",") - if(!sesame) - return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' ) - else - return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", "'+sesame+'", '+argParams+')} )()' ) + sesame = appendComma(sesame) + + return eval(`async (${headerArgs}) => { return await this.socket.call("${fnName}", ${sesame} ${argParams})}`) } /** @@ -140,25 +139,19 @@ export class RPCSocket implements I.Socket{ */ private frontEndHookGenerator(fnName: string, fnArgs:string[], sesame?:string): T.HookFunction{ fnArgs.pop() - const headerArgs = fnArgs.join(",") + let headerArgs = fnArgs.join(",") const argParams = fnArgs.map(stripAfterEquals).join(",") + sesame = appendComma(sesame) + headerArgs = appendComma(headerArgs) - if(!sesame){ - return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => { - const r = await this.socket.call("`+fnName+`", `+argParams+`) - if(r && r.result === 'Success'){ - this.socket.hook(r.uuid, callback) - } - return r - } )()` ) - }else{ - return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => { - const r = await this.socket.call("`+fnName+`", "`+sesame+`", `+argParams+`) - if(r && r.result === 'Success'){ - this.socket.hook(r.uuid, callback) - } - return r - } )()` ) - } + return eval( ` + async (${headerArgs} callback) => { + const r = await this.socket.call("${fnName}", ${sesame} ${argParams}) + if(r && r.result === 'Success'){ + this.socket.hook(r.uuid, callback) + this.socket.on('error', e => this.socket.unhook(r.uuid)) + } + return r + }`) } -} \ No newline at end of file +} diff --git a/src/Utils.ts b/src/Utils.ts index 83b82c5..cfd6749 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -10,7 +10,7 @@ import { SubscriptionResponse } from "./Types"; * @param owner The owning RPC group's name * @throws Error on RPC without name property */ -export const rpcToRpcinfo = (rpc : T.RPC, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => { +export const rpcToRpcinfo = (socket: I.Socket, rpc : T.RPC, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => { switch (typeof rpc){ case "object": if(rpc['call']){ @@ -19,7 +19,7 @@ export const rpcToRpcinfo = (rpc : T.RPC, 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 makeError(rpc.name)}:rpc['call'], // check & remove sesame + call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); socket.destroy()}:rpc['call'], // check & remove sesame } }else{ const generator = hookGenerator(>rpc, errorHandler, sesame) @@ -37,7 +37,7 @@ RPC did not provide a name. \nUse 'funtion name(..){ .. }' syntax instead. \n \n<------------OFFENDING RPC: -\n`+rpc.toString()+` +\n${rpc.toString()} \n>------------OFFENDING RPC`) return { owner : owner, @@ -61,7 +61,7 @@ export function rpcHooker(socket: I.Socket, exporter:I.RPCExporter const RPCs = [...exporter.exportRPCs()] - return RPCs.map(rpc => rpcToRpcinfo(rpc, owner, errorHandler, sesame)) + return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame)) .map(info => { const suffix = makeUnique?"-"+uuidv4().substr(0,4):"" const ret:any = info @@ -111,41 +111,33 @@ export function stripAfterEquals(str:string):string{ * @returns A {@link HookFunction} */ const hookGenerator = (rpc:T.HookRPC, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => { - const argsArr = extractArgs(rpc.hook) + let argsArr = extractArgs(rpc.hook) argsArr.pop() //remove 'callback' from the end - const argsStr = argsArr.join(',') + let callArgs = argsArr.join(',') - if(sesameFn){ - const args = ['sesame', ...argsArr].join(',') - 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, rpc.name, [`+args+`]) - } - }`) - } - const args = argsArr.join(',') - return eval(`(socket) => async (`+args+`) => { + const args = sesameFn?(['sesame', ...argsArr].join(',')) + :callArgs + + callArgs = appendComma(callArgs) + + //note rpc.hook is the associated RPC, not a socket.hook + return eval(` + (socket) => async (${args}) => { try{ - const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (...cbargs) => { + if(sesameFn && !sesameFn(sesame)) return + const res = await rpc.hook(${callArgs} (...cbargs) => { if(rpc.onCallback) rpc.onCallback.apply({}, cbargs) socket.call.apply(socket, [res.uuid, ...cbargs]) }) return res }catch(e){ - errorHandler(socket)(e, rpc.name, [`+args+`]) + errorHandler(socket)(e, ${rpc.name}, [${args}]) } }`) } const makeError = (callName: string) => { - return new Error("Unhandled Promise rejection: Call not found: "+callName+". ; Zone: ; Task: Promise.then ; Value: Error: Call not found: "+callName) + return new Error("Call not found: "+callName+". ; Zone: ; Task: Promise.then ; Value: Error: Call not found: "+callName) } /** @@ -179,3 +171,8 @@ export function makeSesameFunction (sesame : T.SesameFunction | string) : T.Sesa return testSesame === sesame } } + + +export function appendComma(s?:string):string{ + return s?`'${s}',`:"" +} \ No newline at end of file diff --git a/test/Test.ts b/test/Test.ts index 62a5276..7f019cf 100644 --- a/test/Test.ts +++ b/test/Test.ts @@ -48,13 +48,12 @@ function makeServer(){ describe('RPCServer', () => { let server: RPCServer<{ topic: string }, any> - before((done) => { + before(() => { server = makeServer() - done() }) - after(async() => { - await server.destroy() + after(() => { + server.destroy() }) it('should be able to use all kinds of RPC definitions', (done) => { @@ -239,7 +238,7 @@ describe('Sesame should unlock the socket', () => { let server: RPCServer let cb = (...args) => {} - before(async() => { + before((done) => { server = new RPCServer(21004, [{ name: "test", exportRPCs: () => [ @@ -261,7 +260,10 @@ describe('Sesame should unlock the socket', () => { sesame: (_sesame) => _sesame === 'sesame!' }) const sock = new RPCSocket(21004, "localhost") - client = await sock.connect('sesame!') + sock.connect('sesame!').then(cli => { + client = cli + done() + }) }) after(() => { @@ -282,13 +284,14 @@ describe('Sesame should unlock the socket', () => { it('should not work without sesame', (done) => { const sock = new RPCSocket(21004, "localhost") - sock.connect( /* no sesame */).then(async (c) => { - c.test.checkCandy().then(d => { - done() + sock.connect( /* no sesame */).then(async (cli) => { + cli.test.checkCandy().then(d => { + done(d) }).catch(e => { //console.log("EXPECTED CLIENT EXCEPTION", String(e)); done() }).finally(() => { + cli.destroy() sock.destroy() }) }) @@ -296,14 +299,15 @@ describe('Sesame should unlock the socket', () => { it('should fail with wrong sesame', (done) => { const sock = new RPCSocket(21004, "localhost") - sock.connect('abasd').then(async (c) => { - c.test.checkCandy().then(d => { + sock.connect('abasd').then(async (cli) => { + cli.test.checkCandy().then(d => { done("should not be able to get candy") }).catch(e => { //console.log("EXPECTED CLIENT EXCEPTION", String(e)); done() }).finally(() => { sock.destroy() + cli.destroy() }) }) }) @@ -323,8 +327,8 @@ describe('Sesame should unlock the socket', () => { it('callback should not work without sesame', (done) => { const sock = new RPCSocket(21004, "localhost") - sock.connect( /* no sesame */).then(async (c) => { - c.test.subscribe((c) => { + sock.connect( /* no sesame */).then(async (cli) => { + cli.test.subscribe((c) => { console.log("CALLBACK TRIGGERED UNEXPECTED"); if(c === candy) @@ -333,16 +337,17 @@ describe('Sesame should unlock the socket', () => { await client.test.checkCandy() if(d == null){ done() + }else done('unexpected valid response '+(d) ) - c.destroy() + }).finally(() => { + sock.destroy() }) }) }) }) - describe('Error handling', ()=>{ let createUser = async( user: {a:any,b:any}) => { @@ -374,6 +379,7 @@ describe('Error handling', ()=>{ done() }) .finally(() => { + cli.destroy() sock.destroy() server.destroy() }) @@ -407,6 +413,7 @@ describe('Error handling', ()=>{ done(e) }) .finally(() => { + cli.destroy() sock.destroy() server.destroy() }) @@ -414,6 +421,7 @@ describe('Error handling', ()=>{ }) }) + describe("Errorhandler functionality", ()=>{ let createUser = async( user: {a:any,b:any}) => { throw new Error("BAD BAD BAD") @@ -447,6 +455,7 @@ describe("Errorhandler functionality", ()=>{ done(e) }) .finally(() => { + cli.destroy() sock.destroy() server.destroy() }) @@ -484,6 +493,7 @@ describe("Errorhandler functionality", ()=>{ done(e) }) .finally(() => { + cli.destroy() sock.destroy() server.destroy() })