export type Id = number | string import { getValidatorsForInstance, getValidatorsForMethod, Validate } from './ensure' import { createError, ErrorResponse } from './error' import { isPromise } from './isPromise' import { ArgumentTypes, FunctionPropertyNames, RetType } from './types' 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 { Object.getOwnPropertyNames(obj) .filter(p => { 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 Request { jsonrpc: '2.0' id: Id | null method: M params: A } export interface SuccessResponse { jsonrpc: '2.0' id: Id result: T error: null } export type Response = SuccessResponse | ErrorResponse export function createSuccessResponse( id: number | string, result: T, ): SuccessResponse { 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: Request>, 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 ( !Object.prototype.hasOwnProperty.call(rpcService, 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) }, } }