UNPKG

opendb_test_rpc

Version:

general purpose library for OpenDB blockchain

287 lines 17.8 kB
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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