opendb_test_rpc
Version:
general purpose library for OpenDB blockchain
287 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 {
// native websocket
ws;
idAwaiter = {};
onOpenHandlers = [];
onAnyMessageHandlers = [];
onNotification = [];
onRequest = [];
onSuccessResponse = [];
onErrorResponse = [];
onErrorHandlers = [];
onCloseHandlers = [];
config = {
responseTimeout: 10000,
};
// 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.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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnBjLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NsaWVudC9ycGMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLE1BQU0sQ0FBQTtBQUN6QixPQUFPLFNBQVMsTUFBTSxlQUFlLENBQUE7QUFXckMsTUFBTSxDQUFOLElBQVksV0FFWDtBQUZELFdBQVksV0FBVztJQUNyQixrQ0FBbUIsQ0FBQTtBQUNyQixDQUFDLEVBRlcsV0FBVyxLQUFYLFdBQVcsUUFFdEI7QUFtREQsTUFBTSxPQUFPLGtCQUFrQjtJQUM3QixtQkFBbUI7SUFDWixFQUFFLENBQVc7SUFFWixTQUFTLEdBR2IsRUFBRSxDQUFBO0lBRUUsY0FBYyxHQUF1QixFQUFFLENBQUE7SUFDdkMsb0JBQW9CLEdBQThCLEVBQUUsQ0FBQTtJQUVwRCxjQUFjLEdBQTJCLEVBQUUsQ0FBQTtJQUMzQyxTQUFTLEdBQXNCLEVBQUUsQ0FBQTtJQUNqQyxpQkFBaUIsR0FBOEIsRUFBRSxDQUFBO0lBQ2pELGVBQWUsR0FBNEIsRUFBRSxDQUFBO0lBRTdDLGVBQWUsR0FBdUIsRUFBRSxDQUFBO0lBQ3hDLGVBQWUsR0FBNEIsRUFBRSxDQUFBO0lBRTdDLE1BQU0sR0FBRztRQUNmLGVBQWUsRUFBRSxLQUFLO0tBQ3ZCLENBQUE7SUFFRCxjQUFjO0lBQ2Q7Ozs7T0FJRztJQUNIO1FBQ0Usc0VBQXNFO1FBRXRFLElBQUksQ0FBQyxFQUFFLEdBQUcsU0FBb0IsQ0FBQTtJQUNoQyxDQUFDO0lBRUQsU0FBUztJQUNUOzs7OztPQUtHO0lBQ0ksS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFXLEVBQUUsU0FBNkI7UUFDN0QsSUFBSSxDQUFDLEVBQUUsR0FBRyxJQUFJLFNBQVMsQ0FBQyxHQUFHLEVBQUUsU0FBUyxDQUFDLENBQUE7UUFDdkMsTUFBTSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUE7SUFDckIsQ0FBQztJQUVELFNBQVM7SUFDRixN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