export type TId = number | string import {ArgumentTypes, FunctionPropertyNames, RetType} from './types' import {isPromise} from './isPromise' import {createError, IErrorResponse, IErrorWithData} from './error' import {getValidatorsForMethod, getValidatorsForInstance} from './ensure' import {Validate} from './ensure' export const ERROR_PARSE = { code: -32700, message: 'Parse error', } export const ERROR_INVALID_REQUEST = { code: -32600, message: 'Invalid request', } export const ERROR_METHOD_NOT_FOUND = { code: -32601, message: 'Method not found', } export const ERROR_INVALID_PARAMS = { code: -32602, message: 'Invalid params', } export const ERROR_INTERNAL_ERROR = { code: -32603, message: 'Internal error', } export const ERROR_SERVER = { code: -32000, message: 'Server error', } export function pick>(t: T, keys: K[]) : Pick { return keys.reduce((obj, key) => { // tslint:disable-next-line const fn = t[key] as unknown as Function obj[key] = fn.bind(t) return obj }, {} as Pick) } export function getAllMethods(obj: T): Array> { const props = new Set() do { const l = Object.getOwnPropertyNames(obj) .filter((p, i, arr) => { return typeof (obj as any)[p] === 'function' && p.startsWith('_') === false && p !== 'constructor' }) .forEach(p => props.add(p)) obj = Object.getPrototypeOf(obj) } while (Object.getPrototypeOf(obj)) return Array.from(props) as unknown as Array> } export interface IRequest { jsonrpc: '2.0' id: TId | null method: M params: A } export interface ISuccessResponse { jsonrpc: '2.0' id: TId result: T error: null } export type IResponse = ISuccessResponse | IErrorResponse export function createSuccessResponse(id: number | string, result: T) : ISuccessResponse { return { jsonrpc: '2.0', id, result, error: null, } } async function validateServiceContext< T, M extends FunctionPropertyNames, Context >( id: string | number | null, service: T, method: M, context: Context, ) { async function doValidate(validate: Validate) { const success = await validate(context) if (!success) { throw createError(ERROR_INVALID_REQUEST, { id, data: null, statusCode: 400, }) } } for (const validate of getValidatorsForInstance(service)) { await doValidate(validate) } for (const validate of getValidatorsForMethod(service, method)) { await doValidate(validate) } } export const createRpcService = >( service: T, methods?: M[], ) => { const rpcService = methods ? pick(service, methods) : pick(service, getAllMethods(service)) return { async invoke( req: IRequest>, context: Context, ): Promise> | null> { const {id, method, params} = req if ( req.jsonrpc !== '2.0' || typeof method !== 'string' || !Array.isArray(params) ) { throw createError(ERROR_INVALID_REQUEST, { id, data: null, statusCode: 400, }) } const isNotification = req.id === null || req.id === undefined if ( !rpcService.hasOwnProperty(method) || typeof rpcService[method] !== 'function' ) { throw createError(ERROR_METHOD_NOT_FOUND, { id, data: null, statusCode: 404, }) } await validateServiceContext(id, service, method, context) let retValue = (rpcService[method] as any)(context, ...params) if (!isPromise(retValue) && isNotification) { return null } retValue = await retValue return createSuccessResponse(req.id as any, retValue) }, } }