diff --git a/packages/jsonrpc/src/express.test.ts b/packages/jsonrpc/src/express.test.ts index 69d2db2..2759682 100644 --- a/packages/jsonrpc/src/express.test.ts +++ b/packages/jsonrpc/src/express.test.ts @@ -6,6 +6,7 @@ import {createClient} from './supertest' import {ensure} from './ensure' import {jsonrpc} from './express' import {noopLogger} from './test-utils' +import {Contextual} from './types' describe('jsonrpc', () => { @@ -20,15 +21,15 @@ describe('jsonrpc', () => { asyncError(message: string): Promise httpError(statusCode: number, message: string): Promise - addWithContext(a: number, b: number): (ctx: IContext) => number + addWithContext(a: number, b: number): number addWithContext2(a: number, b: number): Promise } const ensureLoggedIn = ensure(c => !!c.userId) - class Service implements IService { + class Service implements Contextual { constructor(readonly time: number) {} - add(a: number, b: number) { + add(context: IContext, a: number, b: number) { return a + b } multiply(...numbers: number[]) { @@ -39,13 +40,13 @@ describe('jsonrpc', () => { setTimeout(resolve, this.time) }) } - syncError(message: string) { + syncError(context: IContext, message: string) { throw new Error(message) } - async asyncError(message: string) { + async asyncError(context: IContext, message: string) { throw new Error(message) } - async httpError(statusCode: number, message: string) { + async httpError(context: IContext, statusCode: number, message: string) { const err: any = new Error(message) err.statusCode = statusCode err.errors = [{ @@ -53,12 +54,12 @@ describe('jsonrpc', () => { }] throw err } - addWithContext = (a: number, b: number) => (ctx: IContext): number => { + addWithContext = (ctx: IContext, a: number, b: number) => { return a + b + ctx.userId } @ensureLoggedIn - addWithContext2(a: number, b: number, ctx?: IContext) { + addWithContext2(ctx: IContext, a: number, b: number) { return Promise.resolve(a + b + ctx!.userId) } } diff --git a/packages/jsonrpc/src/jsonrpc.ts b/packages/jsonrpc/src/jsonrpc.ts index 4e12505..7c80c7f 100644 --- a/packages/jsonrpc/src/jsonrpc.ts +++ b/packages/jsonrpc/src/jsonrpc.ts @@ -157,15 +157,7 @@ export const createRpcService = >( await validateServiceContext(id, service, method, context) - // FIXME TODO if user specified too many parameters in the request, - // they might override the context argument! this is dangerous as it - // could allow them to set any userId they would like. We should compare - // method arguments length before invoking this function. - let retValue = (rpcService[method] as any)(...params, context) - - if (typeof retValue === 'function') { - retValue = retValue(context) - } + let retValue = (rpcService[method] as any)(context, ...params) if (!isPromise(retValue) && isNotification) { return null diff --git a/packages/jsonrpc/src/local.test.ts b/packages/jsonrpc/src/local.test.ts index 52c2677..31629ad 100644 --- a/packages/jsonrpc/src/local.test.ts +++ b/packages/jsonrpc/src/local.test.ts @@ -1,11 +1,12 @@ import {createLocalClient} from './local' import {keys} from 'ts-transformer-keys' +import {Contextual, ReverseContextual, TAsyncified} from './types' describe('local', () => { interface IService { add(a: number, b: number): number - addWithContext(a: number, b: number): (context: IContext) => number + addWithContext(a: number, b: number): number } const IServiceKeys = keys() @@ -13,19 +14,18 @@ describe('local', () => { userId: 1000 } - class Service implements IService { - add(a: number, b: number) { + class Service implements Contextual { + add(cx: IContext, a: number, b: number) { return a + b } - addWithContext = (a: number, b: number) => (context: IContext) => { - return a + b + context.userId + addWithContext = (cx: IContext, a: number, b: number) => { + return a + b + cx.userId } } const service = new Service() - const proxy = createLocalClient(service, { - userId: 1000, - }) + + const proxy = createLocalClient(service, () => ({userId: 1000})) describe('add', () => { it('should add two numbers', async () => { diff --git a/packages/jsonrpc/src/local.ts b/packages/jsonrpc/src/local.ts index 2b199dd..2647fe8 100644 --- a/packages/jsonrpc/src/local.ts +++ b/packages/jsonrpc/src/local.ts @@ -1,21 +1,24 @@ -import {TAsyncified} from './types' +import {TAsyncified, Contextual, ReverseContextual} from './types' +import {Request} from 'express' +import {TGetContext} from './express' /** * Creates a local client for a specific service instance. The actual service * will be invoked as if it would be remotely. This helps keep the API similar * on the client- and server-side. */ -export function createLocalClient(service: T, context: any) { +export function createLocalClient( + service: T, + getContext: () => Context, +): TAsyncified> { const proxy = new Proxy({}, { get(obj, prop) { + const context = getContext() return async function makeRequest(...args: any[]) { - const result = (service as any)[prop](...args) - if (typeof result === 'function') { - return result(context) - } + const result = (service as any)[prop](context, ...args) return result } }, }) - return proxy as TAsyncified + return proxy as any } diff --git a/packages/jsonrpc/src/redux.test.ts b/packages/jsonrpc/src/redux.test.ts index 7078542..b788de5 100644 --- a/packages/jsonrpc/src/redux.test.ts +++ b/packages/jsonrpc/src/redux.test.ts @@ -6,7 +6,7 @@ import bodyParser from 'body-parser' import express from 'express' import {AddressInfo} from 'net' import {Server} from 'http' -import {TPendingActions, TAllActions} from './types' +import {Contextual, TPendingActions, TAllActions} from './types' import {combineReducers} from 'redux' import {createActions, createReducer} from './redux' import {createRemoteClient} from './remote' @@ -21,9 +21,8 @@ describe('createActions', () => { add(a: number, b: number): number addAsync(a: number, b: number): Promise addStringsAsync(a: string, b: string): Promise - addWithContext(a: number, b: number): (ctx: IContext) => number - addAsyncWithContext(a: number, b: number): (ctx: IContext) => - Promise + addWithContext(a: number, b: number): number + addAsyncWithContext(a: number, b: number): Promise throwError(bool: boolean): boolean } @@ -31,21 +30,21 @@ describe('createActions', () => { userId: number } - class Service implements IService { - add(a: number, b: number) { + class Service implements Contextual { + add(cx: IContext, a: number, b: number) { return a + b } - addAsync(a: number, b: number) { + addAsync(cx: IContext, a: number, b: number) { return new Promise(resolve => resolve(a + b)) } - addStringsAsync(a: string, b: string) { + addStringsAsync(cx: IContext, a: string, b: string) { return new Promise(resolve => resolve(a + b)) } - addWithContext = (a: number, b: number) => (ctx: IContext) => - a + b + ctx.userId - addAsyncWithContext = (a: number, b: number) => (ctx: IContext) => - new Promise(resolve => resolve(a + b + ctx.userId)) - throwError(bool: boolean) { + addWithContext = (cx: IContext, a: number, b: number) => + a + b + cx.userId + addAsyncWithContext = (cx: IContext, a: number, b: number) => + new Promise(resolve => resolve(a + b + cx.userId)) + throwError(cx: IContext, bool: boolean) { if (bool) { throw new Error('test') } diff --git a/packages/jsonrpc/src/remote.test.ts b/packages/jsonrpc/src/remote.test.ts index d1d3e2f..cafe46a 100644 --- a/packages/jsonrpc/src/remote.test.ts +++ b/packages/jsonrpc/src/remote.test.ts @@ -10,6 +10,7 @@ import {createRemoteClient} from './remote' import {jsonrpc} from './express' import {keys} from 'ts-transformer-keys' import {noopLogger} from './test-utils' +import {Contextual} from './types' describe('remote', () => { @@ -20,11 +21,11 @@ describe('remote', () => { } const IServiceKeys = keys() - class Service implements IService { - add(a: number, b: number) { + class Service implements Contextual { + add(ctx: {}, a: number, b: number) { return a + b } - async fetchItem(obj1: {a: number}, obj2: {b: number}) + async fetchItem(ctx: {}, obj1: {a: number}, obj2: {b: number}) : Promise<{a: number, b: number}> { return Promise.resolve({...obj1, ...obj2}) } diff --git a/packages/jsonrpc/src/types.ts b/packages/jsonrpc/src/types.ts index 8497030..309890d 100644 --- a/packages/jsonrpc/src/types.ts +++ b/packages/jsonrpc/src/types.ts @@ -3,11 +3,10 @@ import {IPendingAction, IResolvedAction, IRejectedAction} from '@rondo.dev/clien export type ArgumentTypes = T extends (...args: infer U) => infer R ? U : never export type RetType = T extends (...args: any[]) => infer R ? R : never -type UnwrapHOC = T extends (...args: any[]) => infer R ? R : T type UnwrapPromise = T extends Promise ? V : T type RetProm = T extends Promise ? T : Promise type PromisifyReturnType = (...a: ArgumentTypes) => - RetProm>> + RetProm> /** * Helps implement a service from a service definiton that has a context as a @@ -15,16 +14,15 @@ type PromisifyReturnType = (...a: ArgumentTypes) => */ export type Contextual = { [K in keyof T]: - T[K] extends () => infer R - ? (cx: Cx) => R : - T[K] extends (a: infer A) => infer R - ? (a: A, cx: Cx) => R : - T[K] extends (a: infer A, b: infer B) => infer R - ? (a: A, b: B, cx: Cx) => R : - T[K] extends (a: infer A, b: infer B, c: infer C) => infer R - ? (a: A, b: B, c: C, cx: Cx) => R : - T[K] extends (a: infer A, b: infer B, c: infer C, d: infer D) => infer R - ? (a: A, b: B, c: C, d: D, cx: Cx) => R : + T[K] extends (...args: infer A) => infer R + ? (cx: Cx, ...args: A) => R : + never +} + +export type ReverseContextual = { + [K in keyof T]: + T[K] extends (cx: any, ...args: infer A) => infer R + ? (...args: A) => R : never } @@ -46,7 +44,7 @@ export interface IReduxed { export type TReduxed = { [K in keyof T]: (...a: ArgumentTypes) => - IRPCPendingAction>>, ActionType, K> + IRPCPendingAction>, ActionType, K> } export type TReduxHandlers = {