UNPKG

8.3 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.default = void 0;
9
10require("core-js/modules/web.dom.iterable");
11
12var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
13
14require("./polyfill");
15
16var _eventemitter = _interopRequireDefault(require("eventemitter3"));
17
18var _assert = _interopRequireDefault(require("@polkadot/util/assert"));
19
20var _null = _interopRequireDefault(require("@polkadot/util/is/null"));
21
22var _undefined = _interopRequireDefault(require("@polkadot/util/is/undefined"));
23
24var _logger = _interopRequireDefault(require("@polkadot/util/logger"));
25
26var _json = _interopRequireDefault(require("../coder/json"));
27
28// Copyright 2017-2018 @polkadot/api-provider authors & contributors
29// This software may be modified and distributed under the terms
30// of the ISC license. See the LICENSE file for details.
31
32/**
33 * The WebSocket Provider allows sending requests using WebSocket. Unlike the [[HttpProvider]],
34 * it does support subscriptions and allows listening to events such as new blocks or balance changes.
35 *
36 * @example
37 * import createApi from '@polkadot/api';
38 * import WsProvider from '@polkadot/api-provider/ws';
39 * const provider = new WsProvider('ws://127.0.0.1:9944');
40 * const api = createApi(provider);
41 *
42 * @see [[HttpProvider]]
43 */
44class WsProvider extends _eventemitter.default.EventEmitter {
45 /**
46 * @param {string} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944`
47 * @param {boolean = true} autoConnect Whether to connect automatically or not.
48 */
49 constructor(endpoint, autoConnect = true) {
50 super();
51 this.autoConnect = void 0;
52 this.coder = void 0;
53 this.endpoint = void 0;
54 this.handlers = void 0;
55 this._isConnected = void 0;
56 this.l = void 0;
57 this.queued = void 0;
58 this.subscriptions = void 0;
59 this.websocket = void 0;
60
61 this.onSocketClose = () => {
62 this.l.debug(() => ['disconnected from', this.endpoint]);
63 this._isConnected = false;
64 this.emit('disconnected');
65
66 if (this.autoConnect) {
67 setTimeout(() => {
68 this.connect();
69 }, 1000);
70 }
71 };
72
73 this.onSocketError = error => {
74 this.l.error(error);
75 };
76
77 this.onSocketMessage = message => {
78 this.l.debug(() => ['received', message.data]);
79 const response = JSON.parse(message.data);
80 return (0, _undefined.default)(response.method) ? this.onSocketMessageResult(response) : this.onSocketMessageSubscribe(response);
81 };
82
83 this.onSocketMessageResult = response => {
84 this.l.debug(() => ['handling: response =', response, 'id =', response.id]);
85 const handler = this.handlers[response.id];
86
87 if (!handler) {
88 this.l.error(`Unable to find handler for id=${response.id}`);
89 return;
90 }
91
92 try {
93 const method = handler.method,
94 params = handler.params,
95 subscription = handler.subscription;
96 const result = this.coder.decodeResponse(response);
97
98 if (subscription) {
99 this.subscriptions[`${subscription.type}::${result}`] = (0, _objectSpread2.default)({}, subscription, {
100 method,
101 params
102 });
103 }
104
105 handler.callback(null, result);
106 } catch (error) {
107 handler.callback(error, undefined);
108 }
109
110 delete this.handlers[response.id];
111 };
112
113 this.onSocketMessageSubscribe = response => {
114 const subscription = `${response.method}::${response.params.subscription}`;
115 this.l.debug(() => ['handling: response =', response, 'subscription =', subscription]);
116 const handler = this.subscriptions[subscription];
117
118 if (!handler) {
119 this.l.error(`Unable to find handler for subscription=${subscription}`);
120 return;
121 }
122
123 try {
124 const result = this.coder.decodeResponse(response);
125 handler.callback(null, result);
126 } catch (error) {
127 handler.callback(error, undefined);
128 }
129 };
130
131 this.onSocketOpen = () => {
132 (0, _assert.default)(!(0, _null.default)(this.websocket), 'WebSocket cannot be null in onOpen');
133 this.l.debug(() => ['connected to', this.endpoint]);
134 this._isConnected = true;
135 this.emit('connected');
136 Object.keys(this.queued).forEach(id => {
137 try {
138 // @ts-ignore checked above
139 this.websocket.send(this.queued[id]);
140 delete this.queued[id];
141 } catch (error) {
142 this.l.error(error);
143 }
144 });
145 return true;
146 };
147
148 (0, _assert.default)(/^(wss|ws):\/\//.test(endpoint), `Endpoint should start with 'ws://', received '${endpoint}'`);
149 this.autoConnect = autoConnect;
150 this.coder = (0, _json.default)();
151 this.endpoint = endpoint;
152 this._isConnected = false;
153 this.handlers = {};
154 this.l = (0, _logger.default)('api-ws');
155 this.queued = {};
156 this.subscriptions = {};
157 this.websocket = null;
158
159 if (autoConnect) {
160 this.connect();
161 }
162 }
163 /**
164 * The [[WsProvider]] connects automatically by default. if you decided otherwise, you may
165 * connect manually using this method.
166 */
167
168
169 connect() {
170 try {
171 this.websocket = new WebSocket(this.endpoint);
172 this.websocket.onclose = this.onSocketClose;
173 this.websocket.onerror = this.onSocketError;
174 this.websocket.onmessage = this.onSocketMessage;
175 this.websocket.onopen = this.onSocketOpen;
176 } catch (error) {
177 this.l.error(error);
178 }
179 }
180 /**
181 * Whether the node is connected or not.
182 * @return {boolean} true if connected
183 */
184
185
186 isConnected() {
187 return this._isConnected;
188 }
189 /**
190 * Listens on events after having subscribed using the [[subscribe]] function.
191 * @param {ProviderInterface$Emitted} type Event
192 * @param {ProviderInterface$EmitCb} sub Callback
193 * @return {this} [description]
194 */
195
196
197 on(type, sub) {
198 return super.on(type, sub);
199 }
200
201 async send(method, params, subscription) {
202 return new Promise((resolve, reject) => {
203 try {
204 const json = this.coder.encodeJson(method, params);
205 const id = this.coder.getId();
206
207 const callback = (error, result) => {
208 if (error) {
209 reject(error);
210 } else {
211 resolve(result);
212 }
213 };
214
215 this.l.debug(() => ['calling', method, params, json, !!subscription]);
216 this.handlers[id] = {
217 callback,
218 method,
219 params,
220 subscription
221 };
222
223 if (this.isConnected() && !(0, _null.default)(this.websocket)) {
224 this.websocket.send(json);
225 } else {
226 this.queued[id] = json;
227 }
228 } catch (error) {
229 reject(error);
230 }
231 });
232 }
233 /**
234 * Allows subscribing to a specific event.
235 * @param {string} type Subscription type
236 * @param {string} method Subscription method
237 * @param {Array<any>} params Parameters
238 * @param {ProviderInterface$Callback} callback Callback
239 * @return {Promise<number>} Promise resolving to the dd of the subscription you can use with [[unsubscribe]].
240 *
241 * @example
242 * const provider = new WsProvider('ws://127.0.0.1:9944');
243 * const api = createApi(provider);
244 *
245 * api.state.storage([[storage.balances.freeBalance, <Address>]], (_, values) => {
246 * console.log(values)
247 * }).then((subscriptionId) => {
248 * console.log('balance changes subscription id: ', subscriptionId)
249 * })
250 */
251
252
253 async subscribe(type, method, params, callback) {
254 const id = await this.send(method, params, {
255 callback,
256 type
257 });
258 return id;
259 }
260 /**
261 * Allows unsubscribing to subscriptions made with [[subscribe]].
262 */
263
264
265 async unsubscribe(type, method, id) {
266 const subscription = `${type}::${id}`;
267 (0, _assert.default)(!(0, _undefined.default)(this.subscriptions[subscription]), `Unable to find active subscription=${subscription}`);
268 delete this.subscriptions[subscription];
269 const result = await this.send(method, [id]);
270 return result;
271 }
272
273}
274
275exports.default = WsProvider;
\No newline at end of file