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