diff --git a/src/router.ts b/src/router.ts index afc5cb3..6395949 100644 --- a/src/router.ts +++ b/src/router.ts @@ -68,7 +68,7 @@ export class Router { this.methodCallValidator = new MethodCallValidator(openrpcDocument); } - public async call(methodName: string, params: any) { + public async call(methodName: string, params: any, context?: any) { const validationErrors = this.methodCallValidator.validate(methodName, params); if (validationErrors instanceof MethodNotFoundError) { @@ -84,7 +84,8 @@ export class Router { const paramsAsArray = params instanceof Array ? params : toArray(methodObject, params); try { - return { result: await this.methods[methodName](...paramsAsArray) }; + const result = await this.methods[methodName].apply(context, paramsAsArray); + return { result }; } catch (e) { if (e instanceof JSONRPCError) { return { error: { code: e.code, message: e.message, data: e.data } }; diff --git a/src/transports/http.test.ts b/src/transports/http.test.ts index 23a9614..2f28043 100644 --- a/src/transports/http.test.ts +++ b/src/transports/http.test.ts @@ -142,4 +142,36 @@ describe("http transport", () => { serverInstance.listen = originalListen; // Do not call stop, since server never started }); + + it("binds req and res as the this context for methods", async () => { + // create an app that attaches customProp to req + const app = connect(); + app.use((req: any, res: any, next: any) => { req.customProp = 'hello'; next(); }); + // set up transport and router with a method that returns this.req.customProp + const transport = new HTTPTransport({ middleware: [], port: 9710, app }); + const minimalDoc = { + openrpc: '1.2.6', + info: { title: 'test', version: '1.0.0' }, + methods: [ + { name: 'getCustom', params: [], result: { name: 'custom', schema: { type: 'string' } } } + ], + } as any; + const mapping = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getCustom: async function(): Promise { return (this as any).req.customProp; } + }; + const router = new Router(minimalDoc, mapping); + transport.addRouter(router); + await transport.start(); + try { + const { result } = await fetch('http://localhost:9710', { + body: JSON.stringify({ id: 'foo', jsonrpc: '2.0', method: 'getCustom', params: [] }), + headers: { 'Content-Type': 'application/json' }, + method: 'post', + }).then((res) => res.json() as Promise); + expect(result).toBe('hello'); + } finally { + await transport.stop(); + } + }); }); diff --git a/src/transports/http.ts b/src/transports/http.ts index aa72d5d..9fba998 100644 --- a/src/transports/http.ts +++ b/src/transports/http.ts @@ -61,11 +61,15 @@ export default class HTTPServerTransport extends ServerTransport { } private async httpRouterHandler(req: any, res: any): Promise { + // bind req and res as the context for method calls + const context = { req, res }; let result = null; if (req.body instanceof Array) { - result = await Promise.all(req.body.map((r: JSONRPCRequest) => super.routerHandler(r))); + result = await Promise.all( + req.body.map((r: JSONRPCRequest) => super.routerHandler(r, context)), + ); } else { - result = await super.routerHandler(req.body); + result = await super.routerHandler(req.body, context); } res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(result)); diff --git a/src/transports/https.ts b/src/transports/https.ts index 72fedfb..d31b2fe 100644 --- a/src/transports/https.ts +++ b/src/transports/https.ts @@ -59,11 +59,15 @@ export default class HTTPSServerTransport extends ServerTransport { } private async httpsRouterHandler(req: any, res: any): Promise { + // bind req and res as the context for method calls + const context = { req, res }; let result = null; if (req.body instanceof Array) { - result = await Promise.all(req.body.map((r: JSONRPCRequest) => super.routerHandler(r))); + result = await Promise.all( + req.body.map((r: JSONRPCRequest) => super.routerHandler(r, context)), + ); } else { - result = await super.routerHandler(req.body); + result = await super.routerHandler(req.body, context); } res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(result)); diff --git a/src/transports/ipc.ts b/src/transports/ipc.ts index b14a66a..1544ee2 100644 --- a/src/transports/ipc.ts +++ b/src/transports/ipc.ts @@ -57,11 +57,15 @@ export default class IPCServerTransport extends ServerTransport { } private async ipcRouterHandler(req: any, respondWith: any) { + // bind req and respondWith as context for method calls + const context = { req, respondWith }; let result = null; if (req instanceof Array) { - result = await Promise.all(req.map((jsonrpcReq: JSONRPCRequest) => super.routerHandler(jsonrpcReq))); + result = await Promise.all( + req.map((jsonrpcReq: JSONRPCRequest) => super.routerHandler(jsonrpcReq, context)), + ); } else { - result = await super.routerHandler(req); + result = await super.routerHandler(req, context); } respondWith(JSON.stringify(result)); } diff --git a/src/transports/server-transport.ts b/src/transports/server-transport.ts index 2ed0ebd..bb5e47c 100644 --- a/src/transports/server-transport.ts +++ b/src/transports/server-transport.ts @@ -41,7 +41,8 @@ export abstract class ServerTransport { throw new Error("Transport missing stop implementation"); } - protected async routerHandler({ id, method, params }: JSONRPCRequest): Promise { + protected async routerHandler(request: JSONRPCRequest, context?: any): Promise { + const { id, method, params } = request; if (this.routers.length === 0) { console.warn("transport method called without a router configured."); // tslint:disable-line throw new Error("No router configured"); @@ -61,9 +62,10 @@ export abstract class ServerTransport { ...Router.methodNotFoundHandler(method) }; } else { + // forward context when invoking the method res = { ...res, - ...await routerForMethod.call(method, params) + ...await (routerForMethod as any).call(method, params, context) }; } diff --git a/src/transports/websocket.ts b/src/transports/websocket.ts index b0db41b..fd32e6d 100644 --- a/src/transports/websocket.ts +++ b/src/transports/websocket.ts @@ -91,11 +91,15 @@ export default class WebSocketServerTransport extends ServerTransport { } private async webSocketRouterHandler(req: any, respondWith: any) { + // bind req and respondWith as context for method calls + const context = { req, respondWith }; let result = null; if (req instanceof Array) { - result = await Promise.all(req.map((r: JSONRPCRequest) => super.routerHandler(r))); + result = await Promise.all( + req.map((r: JSONRPCRequest) => super.routerHandler(r, context)), + ); } else { - result = await super.routerHandler(req); + result = await super.routerHandler(req, context); } respondWith(JSON.stringify(result)); }