parent
e777beb79b
commit
b823f1a03c
@ -0,0 +1,85 @@ |
||||
import { ReqMiddleware, ResMiddleware, MiddlewareType } from './types'; |
||||
import { RPCMethod } from './rpc'; |
||||
|
||||
class BaseProvider { |
||||
middlewares = { |
||||
request: { |
||||
use: (fn: ReqMiddleware, match: string | RPCMethod | RegExp = '*') => { |
||||
this.pushMiddleware(fn, MiddlewareType.REQ, match); |
||||
}, |
||||
}, |
||||
response: { |
||||
use: (fn: ResMiddleware, match: string | RPCMethod | RegExp = '*') => { |
||||
this.pushMiddleware(fn, MiddlewareType.RES, match); |
||||
}, |
||||
}, |
||||
}; |
||||
protected url: string; |
||||
protected reqMiddleware: ReqMiddleware = new Map().set('*', []); |
||||
protected resMiddleware: ResMiddleware = new Map().set('*', []); |
||||
|
||||
constructor( |
||||
url: string, |
||||
reqMiddleware: ReqMiddleware = new Map(), |
||||
resMiddleware: ResMiddleware = new Map(), |
||||
) { |
||||
this.reqMiddleware = reqMiddleware; |
||||
this.resMiddleware = resMiddleware; |
||||
this.url = url; |
||||
} |
||||
|
||||
protected pushMiddleware( |
||||
fn: any, |
||||
type: MiddlewareType, |
||||
match: string | RPCMethod | RegExp, |
||||
) { |
||||
if (type !== MiddlewareType.REQ && type !== MiddlewareType.RES) { |
||||
throw new Error('Please specify the type of middleware being added'); |
||||
} |
||||
if (type === MiddlewareType.REQ) { |
||||
const current = this.reqMiddleware.get(match) || []; |
||||
this.reqMiddleware.set(match, [...current, <ReqMiddleware>fn]); |
||||
} else { |
||||
const current = this.resMiddleware.get(match) || []; |
||||
this.resMiddleware.set(match, [...current, <ResMiddleware>fn]); |
||||
} |
||||
} |
||||
protected getMiddleware( |
||||
method: RPCMethod, |
||||
): [ReqMiddleware[], ResMiddleware[]] { |
||||
const requests: ReqMiddleware[] = []; |
||||
const responses: ResMiddleware[] = []; |
||||
|
||||
for (const [key, transformers] of this.reqMiddleware.entries()) { |
||||
if (typeof key === 'string' && key !== '*' && key === method) { |
||||
requests.push(...transformers); |
||||
} |
||||
|
||||
if (key instanceof RegExp && key.test(method)) { |
||||
requests.push(...transformers); |
||||
} |
||||
|
||||
if (key === '*') { |
||||
requests.push(...transformers); |
||||
} |
||||
} |
||||
|
||||
for (const [key, transformers] of this.resMiddleware.entries()) { |
||||
if (typeof key === 'string' && key !== '*' && key === method) { |
||||
responses.push(...transformers); |
||||
} |
||||
|
||||
if (key instanceof RegExp && key.test(method)) { |
||||
responses.push(...transformers); |
||||
} |
||||
|
||||
if (key === '*') { |
||||
responses.push(...transformers); |
||||
} |
||||
} |
||||
|
||||
return [requests, responses]; |
||||
} |
||||
} |
||||
|
||||
export { BaseProvider }; |
@ -0,0 +1,34 @@ |
||||
import fetch from 'cross-fetch'; |
||||
import { RPCRequest, RPCResponseBody, RPCError, RPCResult } from './types'; |
||||
|
||||
export const fetchRPC = { |
||||
requestHandler: (request: RPCRequest<any[]>, headers: any) => |
||||
fetch(request.url, { |
||||
method: |
||||
request.options && request.options.method |
||||
? request.options.method |
||||
: 'POST', |
||||
cache: 'no-cache', |
||||
mode: 'cors', |
||||
redirect: 'follow', |
||||
referrer: 'no-referrer', |
||||
body: JSON.stringify(request.payload), |
||||
headers: { |
||||
...headers, |
||||
...(request.options && request.options.headers |
||||
? request.options.headers |
||||
: {}), |
||||
}, |
||||
}), |
||||
responseHandler: ( |
||||
response: Response, |
||||
request: RPCRequest<any>, |
||||
handler: any, |
||||
) => |
||||
response |
||||
.json() |
||||
.then((body: RPCResponseBody<RPCResult, RPCError>) => { |
||||
return { ...body, req: request }; |
||||
}) |
||||
.then(handler), |
||||
}; |
@ -0,0 +1,161 @@ |
||||
import { BaseProvider } from './baseProvider'; |
||||
import { fetchRPC } from './defaultFetcher'; |
||||
import { |
||||
composeMiddleware, |
||||
performRPC, |
||||
DEFAULT_TIMEOUT, |
||||
DEFAULT_HEADERS, |
||||
} from './net'; |
||||
|
||||
import { RPCRequestPayload } from './types'; |
||||
|
||||
const defaultOptions = { |
||||
method: 'POST', |
||||
timeout: DEFAULT_TIMEOUT, |
||||
headers: DEFAULT_HEADERS, |
||||
user: null, |
||||
password: null, |
||||
}; |
||||
|
||||
class HttpProvider extends BaseProvider { |
||||
url: string; |
||||
fetcher: any; |
||||
options: any; |
||||
constructor(url: string, options: any, fetcher: any) { |
||||
super(url); |
||||
this.url = url || 'http://localhost:4201'; |
||||
this.fetcher = fetcher || fetchRPC; |
||||
if (options) { |
||||
this.options = { |
||||
method: options.method || defaultOptions.method, |
||||
timeout: options.timeout || defaultOptions.timeout, |
||||
user: options.user || defaultOptions.user, |
||||
password: options.password || defaultOptions.password, |
||||
headers: options.headers || defaultOptions.headers, |
||||
}; |
||||
} else { |
||||
this.options = defaultOptions; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @function send |
||||
* @memberof HttpProvider.prototype |
||||
* @param {Object} payload - payload object |
||||
* @param {Function} callback - callback function |
||||
* @return {any} - RPC Response |
||||
*/ |
||||
send(payload: RPCRequestPayload<object>, callback: any): Promise<any> { |
||||
return this.requestFunc({ payload, callback }); |
||||
} |
||||
|
||||
/** |
||||
* @function sendServer |
||||
* @memberof HttpProvider.prototype |
||||
* @param {String} endpoint - endpoint to server |
||||
* @param {Object} payload - payload object |
||||
* @param {Function} callback - callback function |
||||
* @return {Function} - RPC Response |
||||
*/ |
||||
sendServer( |
||||
endpoint: string, |
||||
payload: RPCRequestPayload<object>, |
||||
callback: any, |
||||
): Promise<any> { |
||||
return this.requestFunc({ endpoint, payload, callback }); |
||||
} |
||||
|
||||
requestFunc({ |
||||
endpoint, |
||||
payload, |
||||
callback, |
||||
}: { |
||||
endpoint?: string; |
||||
payload: RPCRequestPayload<object>; |
||||
callback?: any; |
||||
}): Promise<any> { |
||||
const [tReq, tRes] = this.getMiddleware(payload.method); |
||||
const reqMiddleware = composeMiddleware( |
||||
...tReq, |
||||
(obj: object) => this.optionsHandler(obj), |
||||
(obj: object) => this.endpointHandler(obj, endpoint), |
||||
this.payloadHandler, |
||||
); |
||||
const resMiddleware = composeMiddleware( |
||||
(data: object) => this.callbackHandler(data, callback), |
||||
...tRes, |
||||
); |
||||
|
||||
const req = reqMiddleware(payload); |
||||
|
||||
return performRPC(req, resMiddleware, this.fetcher); |
||||
} |
||||
|
||||
/** |
||||
* @function payloadHandler |
||||
* @memberof HttpProvider.prototype |
||||
* @param {Object} payload - payload object |
||||
* @return {Object} - to payload object |
||||
*/ |
||||
payloadHandler(payload: RPCRequestPayload<object>): object { |
||||
return { payload }; |
||||
} |
||||
|
||||
/** |
||||
* @function endpointHandler |
||||
* @memberof HttpProvider.prototype |
||||
* @param {Object} obj - payload object |
||||
* @param {String} endpoint - add the endpoint to payload object |
||||
* @return {Object} - assign a new object |
||||
*/ |
||||
endpointHandler(obj: object, endpoint?: string): object { |
||||
return { |
||||
...obj, |
||||
url: |
||||
endpoint !== null && endpoint !== undefined |
||||
? `${this.url}${endpoint}` |
||||
: this.url, |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* @function optionsHandler |
||||
* @memberof HttpProvider.prototype |
||||
* @param {Object} obj - options object |
||||
* @return {Object} - assign a new option object |
||||
*/ |
||||
optionsHandler(obj: object) { |
||||
if (this.options.user && this.options.password) { |
||||
const AUTH_TOKEN = `Basic ${Buffer.from( |
||||
`${this.options.user}:${this.options.password}`, |
||||
).toString('base64')}`;
|
||||
this.options.headers.Authorization = AUTH_TOKEN; |
||||
} |
||||
|
||||
return { ...obj, options: this.options }; |
||||
} |
||||
|
||||
/** |
||||
* @function callbackHandler |
||||
* @memberof HttpProvider.prototype |
||||
* @param {Object} data - from server |
||||
* @param {Function} cb - callback function |
||||
* @return {Object|Function} - return object or callback function |
||||
*/ |
||||
callbackHandler(data: any, cb: any): any { |
||||
if (cb) { |
||||
cb(null, data); |
||||
} |
||||
return data; |
||||
} |
||||
|
||||
subscribe() { |
||||
throw new Error('HTTPProvider does not support subscriptions.'); |
||||
} |
||||
|
||||
unsubscribe() { |
||||
throw new Error('HTTPProvider does not support subscriptions.'); |
||||
} |
||||
} |
||||
|
||||
export { HttpProvider }; |
@ -0,0 +1,6 @@ |
||||
export * from './baseProvider'; |
||||
export * from './defaultFetcher'; |
||||
export * from './http'; |
||||
export * from './net'; |
||||
export * from './rpc'; |
||||
export * from './types'; |
@ -0,0 +1,44 @@ |
||||
export const DEFAULT_TIMEOUT: number = 120000; |
||||
|
||||
export const DEFAULT_HEADERS: object = { 'Content-Type': 'application/json' }; |
||||
|
||||
function _fetch(fetchPromise: Promise<any>, timeout: number) { |
||||
let abortFn: () => void; |
||||
|
||||
const abortPromise = new Promise((resolve, reject) => { |
||||
abortFn = () => reject(new Error(`request Timeout in ${timeout} ms`)); |
||||
}); |
||||
const abortablePromise = Promise.race([fetchPromise, abortPromise]); |
||||
|
||||
setTimeout(() => { |
||||
abortFn(); |
||||
}, timeout); |
||||
|
||||
return abortablePromise; |
||||
} |
||||
|
||||
export const performRPC = async (request: any, handler: any, fetcher: any) => { |
||||
try { |
||||
const response = await _fetch( |
||||
fetcher.requestHandler(request, DEFAULT_HEADERS), |
||||
request.options && request.options.timeout |
||||
? request.options.timeout |
||||
: DEFAULT_TIMEOUT, |
||||
); |
||||
return fetcher.responseHandler(response, request, handler); |
||||
} catch (err) { |
||||
throw err; |
||||
} |
||||
}; |
||||
|
||||
export function composeMiddleware(...fns: any[]): any { |
||||
if (fns.length === 0) { |
||||
return (arg: any) => arg; |
||||
} |
||||
|
||||
if (fns.length === 1) { |
||||
return fns[0]; |
||||
} |
||||
|
||||
return fns.reduce((a, b) => (arg: any) => a(b(arg))); |
||||
} |
@ -0,0 +1,42 @@ |
||||
export const enum RPCMethod { |
||||
// account related
|
||||
FetchBalance = 'FetchBalance', |
||||
// block info related
|
||||
GetLatestBlock = 'GetLatestBlock', |
||||
GetBlock = 'GetBlock', |
||||
GetEstimtedGas = 'GetEstimatedGas', |
||||
GetLatestTransactions = 'GetLatestTransactions', |
||||
GetLatestDSBlocks = 'GetLatestDSBlocks', |
||||
// transaction related
|
||||
SendTransaction = 'SendTransaction', |
||||
SendTransactionToShard = 'SendTransactionToShard', |
||||
SendTransactionToBlock = 'SendTransactionToBlock', |
||||
GetTransaction = 'GetTransaction', |
||||
} |
||||
|
||||
export const enum RPCErrorCode { |
||||
// Standard JSON-RPC 2.0 errors
|
||||
// RPC_INVALID_REQUEST is internally mapped to HTTP_BAD_REQUEST (400).
|
||||
// It should not be used for application-layer errors.
|
||||
RPC_INVALID_REQUEST = -32600, |
||||
// RPC_METHOD_NOT_FOUND is internally mapped to HTTP_NOT_FOUND (404).
|
||||
// It should not be used for application-layer errors.
|
||||
RPC_METHOD_NOT_FOUND = -32601, |
||||
RPC_INVALID_PARAMS = -32602, |
||||
// RPC_INTERNAL_ERROR should only be used for genuine errors in bitcoind
|
||||
// (for example datadir corruption).
|
||||
RPC_INTERNAL_ERROR = -32603, |
||||
RPC_PARSE_ERROR = -32700, |
||||
|
||||
// General application defined errors
|
||||
RPC_MISC_ERROR = -1, // std::exception thrown in command handling
|
||||
RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter
|
||||
RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key
|
||||
RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter
|
||||
RPC_DATABASE_ERROR = -20, // Database error
|
||||
RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format
|
||||
RPC_VERIFY_ERROR = -25, // General error during transaction or block submission
|
||||
RPC_VERIFY_REJECTED = -26, // Transaction or block was rejected by network rules
|
||||
RPC_IN_WARMUP = -28, // Client still warming up
|
||||
RPC_METHOD_DEPRECATED = -32, // RPC method is deprecated
|
||||
} |
@ -0,0 +1,55 @@ |
||||
import { RPCMethod, RPCErrorCode } from './rpc'; |
||||
|
||||
export type ReqMiddleware = Map<string | RPCMethod | RegExp, any[]>; |
||||
export type ResMiddleware = Map<string | RPCMethod | RegExp, any[]>; |
||||
|
||||
export const enum MiddlewareType { |
||||
REQ, |
||||
RES, |
||||
} |
||||
|
||||
export interface Middleware { |
||||
request: object; |
||||
response: object; |
||||
} |
||||
|
||||
export interface RPCRequestPayload<T> { |
||||
id: number; |
||||
jsonrpc: string; |
||||
method: RPCMethod; |
||||
params: T; |
||||
} |
||||
|
||||
export interface RPCRequestOptions { |
||||
headers: []; |
||||
method: string; |
||||
} |
||||
|
||||
export interface RPCRequest<T> { |
||||
url: string; |
||||
payload: RPCRequestPayload<T>; |
||||
options: RPCRequestOptions; |
||||
} |
||||
|
||||
export interface RPCResponseBase { |
||||
jsonrpc: string; |
||||
id: string; |
||||
} |
||||
|
||||
export interface RPCResponseBody<R, E> extends RPCResponseBase { |
||||
result: R; |
||||
error: E; |
||||
} |
||||
|
||||
export interface RPCError { |
||||
code: RPCErrorCode; |
||||
message: string; |
||||
data: any; |
||||
} |
||||
|
||||
export interface RPCResult { |
||||
resultString: string; |
||||
resultMap: Map<string, any>; |
||||
resultList: any[]; |
||||
raw: any; |
||||
} |
Loading…
Reference in new issue