opendb_test_rpc
Version:
general purpose library for OpenDB blockchain
285 lines • 17.8 kB
JavaScript
import { v1 } from 'uuid';
import WebSocket from 'isomorphic-ws';
export var RpcVersions;
(function (RpcVersions) {
RpcVersions["RPC_VERSION"] = "2.0";
})(RpcVersions || (RpcVersions = {}));
export class RpcWebSocketClient {
// constructor
/**
* Does not start WebSocket connection!
* You need to call connect() method first.
* @memberof RpcWebSocketClient
*/
constructor() {
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
this.idAwaiter = {};
this.onOpenHandlers = [];
this.onAnyMessageHandlers = [];
this.onNotification = [];
this.onRequest = [];
this.onSuccessResponse = [];
this.onErrorResponse = [];
this.onErrorHandlers = [];
this.onCloseHandlers = [];
this.config = {
responseTimeout: 10000,
};
this.ws = undefined;
}
// public
/**
* Starts WebSocket connection. Returns Promise when connection is established.
* @param {string} url
* @param {(string | string[])} [protocols]
* @memberof RpcWebSocketClient
*/
async connect(url, protocols) {
this.ws = new WebSocket(url, protocols);
await this.listen();
}
// events
onOpen(fn) {
this.onOpenHandlers.push(fn);
}
/**
* Native onMessage event. DO NOT USE THIS unless you really have to or for debugging purposes.
* Proper RPC events are onRequest, onNotification, onSuccessResponse and onErrorResponse (or just awaiting response).
* @param {RpcMessageEventFunction} fn
* @memberof RpcWebSocketClient
*/
onAnyMessage(fn) {
this.onAnyMessageHandlers.push(fn);
}
onError(fn) {
this.onErrorHandlers.push(fn);
}
onClose(fn) {
this.onCloseHandlers.push(fn);
}
/**
* Appends onmessage listener on native websocket with RPC handlers.
* If onmessage function was already there, it will call it on beggining.
* Useful if you want to use RPC WebSocket Client on already established WebSocket along with function changeSocket().
* @memberof RpcWebSocketClient
*/
listenMessages() {
let previousOnMessage;
if (this.ws.onmessage) {
previousOnMessage = this.ws.onmessage.bind(this.ws);
}
this.ws.onmessage = (e) => {
if (previousOnMessage) {
previousOnMessage(e);
}
for (const handler of this.onAnyMessageHandlers) {
handler(e);
}
const data = JSON.parse(e.data);
if (this.isNotification(data)) {
// notification
for (const handler of this.onNotification) {
handler(data);
}
}
else if (this.isRequest(data)) {
// request
for (const handler of this.onRequest) {
handler(data);
}
// responses
}
else if (this.isSuccessResponse(data)) {
// success
for (const handler of this.onSuccessResponse) {
handler(data);
}
// resolve awaiting function
this.idAwaiter[data.id](data.result);
}
else if (this.isErrorResponse(data)) {
// error
for (const handler of this.onErrorResponse) {
handler(data);
}
// resolve awaiting function
this.idAwaiter[data.id](data.error);
}
};
}
// communication
/**
* Creates and sends RPC request. Resolves when appropirate response is returned from server or after config.responseTimeout.
* @param {string} method
* @param {*} [params]
* @returns
* @memberof RpcWebSocketClient
*/
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
call(method, params) {
return new Promise((resolve, reject) => {
const data = this.buildRequest(method, params);
// give limited time for response
let timeout;
if (this.config.responseTimeout) {
timeout = setTimeout(() => {
// stop waiting for response
delete this.idAwaiter[data.id];
reject(`Awaiting response to: ${method} with id: ${data.id} timed out.`);
}, this.config.responseTimeout);
}
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
// expect response
this.idAwaiter[data.id] = (responseData) => {
// stop timeout
clearInterval(timeout);
// stop waiting for response
delete this.idAwaiter[data.id];
if (this.isRpcError(responseData)) {
reject(responseData);
return;
}
resolve(responseData);
};
this.ws.send(JSON.stringify(data));
});
}
// -------TODO: Unused Function Notify
/**
* Creates and sends RPC Notification.
* @param {string} method
* @param {*} [params]
* @memberof RpcWebSocketClient
*/
// public notify(method: string, params?: any) {
// this.ws.send(JSON.stringify(this.buildNotification(method, params)))
// }
// setup
/**
* You can provide custom id generation function to replace default uuid/v1.
* @param {() => string} idFn
* @memberof RpcWebSocketClient
*/
customId(idFn) {
this.idFn = idFn;
}
/**
* Allows modifying configuration.
* @param {RpcWebSocketConfig} options
* @memberof RpcWebSocketClient
*/
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
configure(options) {
Object.assign(this.config, options);
}
/**
* Allows you to change used native WebSocket client to another one.
* If you have already-connected WebSocket, use this with listenMessages().
* @param {WebSocket} ws
* @memberof RpcWebSocketClient
*/
changeSocket(ws) {
this.ws = ws;
}
// private
// events
listen() {
return new Promise((resolve, reject) => {
this.ws.onopen = (e) => {
for (const handler of this.onOpenHandlers) {
handler(e);
}
resolve(void 0);
};
// listen for messages
this.listenMessages();
// called before onclose
this.ws.onerror = (e) => {
for (const handler of this.onErrorHandlers) {
handler(e);
}
};
this.ws.onclose = (e) => {
for (const handler of this.onCloseHandlers) {
handler(e);
}
reject();
};
});
}
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
// request
buildRequest(method, params) {
const data = this.buildRequestBase(method, params);
data.jsonrpc = RpcVersions.RPC_VERSION;
return data;
}
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
// private buildRequestBase(method: string, params?: unknown): IRpcRequest {
// const data: IRpcRequest = {} as unknown
// data.id = this.idFn()
// data.method = method
// if (params) {
// data.params = params
// }
// return data
// }
buildRequestBase(method, params) {
const data = {
id: this.idFn(),
jsonrpc: RpcVersions.RPC_VERSION,
method: method,
};
if (params) {
data.params = params;
}
return data;
}
// -------TODO: Unused Function Notify
// notification
// private buildNotification(method: string, params?: any): IRpcNotification {
// const data = this.buildNotificationBase(method, params)
// data.jsonrpc = RpcVersions.RPC_VERSION
// return data
// }
// private buildNotificationBase(
// method: string,
// params?: any
// ): IRpcNotification {
// const data: IRpcNotification = {} as any
// data.method = method
// if (params) {
// data.params = params
// }
// return data
// }
idFn() {
return v1();
}
// tests
isNotification(data) {
return !data.id; // eslint-disable-line @typescript-eslint/no-explicit-any
}
isRequest(data) {
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
return data.method !== undefined;
// return (data as any).method
}
isSuccessResponse(data) {
// TSES_lint : error Do not access Object.prototype method 'hasOwnProperty' from target object
// return data.hasOwnProperty(`result`)
return Object.prototype.hasOwnProperty.call(data, 'result');
}
isErrorResponse(data) {
// TSES_lint : error Do not access Object.prototype method 'hasOwnProperty' from target object
// return data.hasOwnProperty(`error`)
return Object.prototype.hasOwnProperty.call(data, 'error');
}
// TSES-lint: (any - unknown) Unexpected any. Specify a different type
isRpcError(data) {
// return typeof (data as any).code !== 'undefined'
return typeof data.code !== 'undefined';
}
}
export default RpcWebSocketClient;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnBjLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NsaWVudC9ycGMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLE1BQU0sQ0FBQTtBQUN6QixPQUFPLFNBQVMsTUFBTSxlQUFlLENBQUE7QUFXckMsTUFBTSxDQUFOLElBQVksV0FFWDtBQUZELFdBQVksV0FBVztJQUNyQixrQ0FBbUIsQ0FBQTtBQUNyQixDQUFDLEVBRlcsV0FBVyxLQUFYLFdBQVcsUUFFdEI7QUFtREQsTUFBTSxPQUFPLGtCQUFrQjtJQXdCN0IsY0FBYztJQUNkOzs7O09BSUc7SUFDSDtRQUNFLHNFQUFzRTtRQTNCaEUsY0FBUyxHQUdiLEVBQUUsQ0FBQTtRQUVFLG1CQUFjLEdBQXVCLEVBQUUsQ0FBQTtRQUN2Qyx5QkFBb0IsR0FBOEIsRUFBRSxDQUFBO1FBRXBELG1CQUFjLEdBQTJCLEVBQUUsQ0FBQTtRQUMzQyxjQUFTLEdBQXNCLEVBQUUsQ0FBQTtRQUNqQyxzQkFBaUIsR0FBOEIsRUFBRSxDQUFBO1FBQ2pELG9CQUFlLEdBQTRCLEVBQUUsQ0FBQTtRQUU3QyxvQkFBZSxHQUF1QixFQUFFLENBQUE7UUFDeEMsb0JBQWUsR0FBNEIsRUFBRSxDQUFBO1FBRTdDLFdBQU0sR0FBRztZQUNmLGVBQWUsRUFBRSxLQUFLO1NBQ3ZCLENBQUE7UUFXQyxJQUFJLENBQUMsRUFBRSxHQUFHLFNBQW9CLENBQUE7SUFDaEMsQ0FBQztJQUVELFNBQVM7SUFDVDs7Ozs7T0FLRztJQUNJLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBVyxFQUFFLFNBQTZCO1FBQzdELElBQUksQ0FBQyxFQUFFLEdBQUcsSUFBSSxTQUFTLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxDQUFBO1FBQ3ZDLE1BQU0sSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFBO0lBQ3JCLENBQUM7SUFFRCxTQUFTO0lBQ0YsTUFBTSxDQUFDLEVBQW9CO1FBQ2hD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