From 48713fb7bf1716fdc6c891d8da33116f6160a558 Mon Sep 17 00:00:00 2001 From: Shane Date: Tue, 27 May 2025 11:17:09 -0400 Subject: [PATCH 1/2] feat: add context as 'this' to method call --- src/router.ts | 5 +++-- src/transports/http.test.ts | 31 ++++++++++++++++++++++++++++++ src/transports/http.ts | 8 ++++++-- src/transports/https.ts | 8 ++++++-- src/transports/ipc.ts | 8 ++++++-- src/transports/server-transport.ts | 6 ++++-- src/transports/websocket.ts | 8 ++++++-- 7 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/router.ts b/src/router.ts index afc5cb34..6395949e 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 23a96143..c1381fb8 100644 --- a/src/transports/http.test.ts +++ b/src/transports/http.test.ts @@ -142,4 +142,35 @@ 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 = { + getCustom: async function(this: any): Promise { return this.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 aa72d5d2..9fba998a 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 72fedfb8..d31b2fe4 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 b14a66a7..1544ee2f 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 2ed0ebdf..bb5e47c1 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 b0db41bb..fd32e6d2 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)); } From 1dc9df71c793ff6732f84131797b8d1c199e1068 Mon Sep 17 00:00:00 2001 From: Shane Date: Thu, 29 May 2025 14:24:33 -0400 Subject: [PATCH 2/2] fix: dont use this as var name --- src/transports/http.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transports/http.test.ts b/src/transports/http.test.ts index c1381fb8..2f280434 100644 --- a/src/transports/http.test.ts +++ b/src/transports/http.test.ts @@ -157,7 +157,8 @@ describe("http transport", () => { ], } as any; const mapping = { - getCustom: async function(this: any): Promise { return this.req.customProp; } + // 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);