UNPKG

@polkadot/api-provider

Version:
291 lines (245 loc) 8.64 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; require("core-js/modules/web.dom.iterable"); var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread")); require("./polyfill"); var _eventemitter = _interopRequireDefault(require("eventemitter3")); var _assert = _interopRequireDefault(require("@polkadot/util/assert")); var _null = _interopRequireDefault(require("@polkadot/util/is/null")); var _undefined = _interopRequireDefault(require("@polkadot/util/is/undefined")); var _logger = _interopRequireDefault(require("@polkadot/util/logger")); var _json = _interopRequireDefault(require("../coder/json")); // Copyright 2017-2018 @polkadot/api-provider authors & contributors // This software may be modified and distributed under the terms // of the ISC license. See the LICENSE file for details. /** * @name WsProvider * @summary The WebSocket Provider allows sending requests using WebSocket to a WebSocket RPC server TCP port. * @description Unlike the [[HttpProvider]], it does support subscriptions and allows * listening to events such as new blocks or balance changes. * @example * <BR> * * ```javascript * import Api from '@polkadot/api'; * import WsProvider from '@polkadot/api-provider/ws'; * * const provider = new WsProvider('ws://127.0.0.1:9944'); * const api = new Api(provider); * ``` * * @see [[HttpProvider]] */ class WsProvider extends _eventemitter.default.EventEmitter { /** * @param {string} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944` * @param {boolean} autoConnect Whether to connect automatically or not. */ constructor(endpoint, autoConnect = true) { super(); this.autoConnect = void 0; this.coder = void 0; this.endpoint = void 0; this.handlers = void 0; this._isConnected = void 0; this.l = void 0; this.queued = void 0; this.subscriptions = void 0; this.websocket = void 0; this.onSocketClose = () => { this.l.debug(() => ['disconnected from', this.endpoint]); this._isConnected = false; this.emit('disconnected'); if (this.autoConnect) { setTimeout(() => { this.connect(); }, 1000); } }; this.onSocketError = error => { this.l.error(error); }; this.onSocketMessage = message => { this.l.debug(() => ['received', message.data]); const response = JSON.parse(message.data); return (0, _undefined.default)(response.method) ? this.onSocketMessageResult(response) : this.onSocketMessageSubscribe(response); }; this.onSocketMessageResult = response => { this.l.debug(() => ['handling: response =', response, 'id =', response.id]); const handler = this.handlers[response.id]; if (!handler) { this.l.error(`Unable to find handler for id=${response.id}`); return; } try { const method = handler.method, params = handler.params, subscription = handler.subscription; const result = this.coder.decodeResponse(response); if (subscription) { this.subscriptions[`${subscription.type}::${result}`] = (0, _objectSpread2.default)({}, subscription, { method, params }); } handler.callback(null, result); } catch (error) { handler.callback(error, undefined); } delete this.handlers[response.id]; }; this.onSocketMessageSubscribe = response => { const subscription = `${response.method}::${response.params.subscription}`; this.l.debug(() => ['handling: response =', response, 'subscription =', subscription]); const handler = this.subscriptions[subscription]; if (!handler) { this.l.error(`Unable to find handler for subscription=${subscription}`); return; } try { const result = this.coder.decodeResponse(response); handler.callback(null, result); } catch (error) { handler.callback(error, undefined); } }; this.onSocketOpen = () => { (0, _assert.default)(!(0, _null.default)(this.websocket), 'WebSocket cannot be null in onOpen'); this.l.debug(() => ['connected to', this.endpoint]); this._isConnected = true; this.emit('connected'); Object.keys(this.queued).forEach(id => { try { // @ts-ignore checked above this.websocket.send(this.queued[id]); delete this.queued[id]; } catch (error) { this.l.error(error); } }); return true; }; (0, _assert.default)(/^(wss|ws):\/\//.test(endpoint), `Endpoint should start with 'ws://', received '${endpoint}'`); this.autoConnect = autoConnect; this.coder = (0, _json.default)(); this.endpoint = endpoint; this._isConnected = false; this.handlers = {}; this.l = (0, _logger.default)('api-ws'); this.queued = {}; this.subscriptions = {}; this.websocket = null; if (autoConnect) { this.connect(); } } /** * @summary Manually connect * @description The [[WsProvider]] connects automatically by default, however if you decided otherwise, you may * connect manually using this method. */ connect() { try { this.websocket = new WebSocket(this.endpoint); this.websocket.onclose = this.onSocketClose; this.websocket.onerror = this.onSocketError; this.websocket.onmessage = this.onSocketMessage; this.websocket.onopen = this.onSocketOpen; } catch (error) { this.l.error(error); } } /** * @summary Whether the node is connected or not. * @return {boolean} true if connected */ isConnected() { return this._isConnected; } /** * @summary Listens on events after having subscribed using the [[subscribe]] function. * @param {ProviderInterface$Emitted} type Event * @param {ProviderInterface$EmitCb} sub Callback * @return {this} [description] */ on(type, sub) { return super.on(type, sub); } /** * @summary Send JSON data using WebSockets to configured HTTP Endpoint or queue. */ async send(method, params, subscription) { return new Promise((resolve, reject) => { try { const json = this.coder.encodeJson(method, params); const id = this.coder.getId(); const callback = (error, result) => { if (error) { reject(error); } else { resolve(result); } }; this.l.debug(() => ['calling', method, params, json, !!subscription]); this.handlers[id] = { callback, method, params, subscription }; if (this.isConnected() && !(0, _null.default)(this.websocket)) { this.websocket.send(json); } else { this.queued[id] = json; } } catch (error) { reject(error); } }); } /** * @name subscribe * @summary Allows subscribing to a specific event. * @param {string} type Subscription type * @param {string} method Subscription method * @param {Array<any>} params Parameters * @param {ProviderInterface$Callback} callback Callback * @return {Promise<number>} Promise resolving to the dd of the subscription you can use with [[unsubscribe]]. * * @example * <BR> * * ```javascript * const provider = new WsProvider('ws://127.0.0.1:9944'); * const api = new Api(provider); * * api.state.storage([[storage.balances.freeBalance, <Address>]], (_, values) => { * console.log(values) * }).then((subscriptionId) => { * console.log('balance changes subscription id: ', subscriptionId) * }) * ``` */ async subscribe(type, method, params, callback) { const id = await this.send(method, params, { callback, type }); return id; } /** * @summary Allows unsubscribing to subscriptions made with [[subscribe]]. */ async unsubscribe(type, method, id) { const subscription = `${type}::${id}`; (0, _assert.default)(!(0, _undefined.default)(this.subscriptions[subscription]), `Unable to find active subscription=${subscription}`); delete this.subscriptions[subscription]; const result = await this.send(method, [id]); return result; } } exports.default = WsProvider;