socket_provider.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. "use strict";
  2. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  3. if (k2 === undefined) k2 = k;
  4. var desc = Object.getOwnPropertyDescriptor(m, k);
  5. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  6. desc = { enumerable: true, get: function() { return m[k]; } };
  7. }
  8. Object.defineProperty(o, k2, desc);
  9. }) : (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. o[k2] = m[k];
  12. }));
  13. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  14. Object.defineProperty(o, "default", { enumerable: true, value: v });
  15. }) : function(o, v) {
  16. o["default"] = v;
  17. });
  18. var __importStar = (this && this.__importStar) || function (mod) {
  19. if (mod && mod.__esModule) return mod;
  20. var result = {};
  21. if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
  22. __setModuleDefault(result, mod);
  23. return result;
  24. };
  25. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  26. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  27. return new (P || (P = Promise))(function (resolve, reject) {
  28. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  29. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  30. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  31. step((generator = generator.apply(thisArg, _arguments || [])).next());
  32. });
  33. };
  34. Object.defineProperty(exports, "__esModule", { value: true });
  35. exports.SocketProvider = void 0;
  36. const web3_errors_1 = require("web3-errors");
  37. const web3_eip1193_provider_js_1 = require("./web3_eip1193_provider.js");
  38. const chunk_response_parser_js_1 = require("./chunk_response_parser.js");
  39. const validation_js_1 = require("./validation.js");
  40. const web3_deferred_promise_js_1 = require("./web3_deferred_promise.js");
  41. const jsonRpc = __importStar(require("./json_rpc.js"));
  42. const DEFAULT_RECONNECTION_OPTIONS = {
  43. autoReconnect: true,
  44. delay: 5000,
  45. maxAttempts: 5,
  46. };
  47. const NORMAL_CLOSE_CODE = 1000; // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close
  48. class SocketProvider extends web3_eip1193_provider_js_1.Eip1193Provider {
  49. /**
  50. * This is an abstract class for implementing a socket provider (e.g. WebSocket, IPC). It extends the EIP-1193 provider {@link EIP1193Provider}.
  51. * @param socketPath - The path to the socket (e.g. /ipc/path or ws://localhost:8546)
  52. * @param socketOptions - The options for the socket connection. Its type is supposed to be specified in the inherited classes.
  53. * @param reconnectOptions - The options for the socket reconnection {@link ReconnectOptions}
  54. */
  55. constructor(socketPath, socketOptions, reconnectOptions) {
  56. super();
  57. this._connectionStatus = 'connecting';
  58. // Message handlers. Due to bounding of `this` and removing the listeners we have to keep it's reference.
  59. this._onMessageHandler = this._onMessage.bind(this);
  60. this._onOpenHandler = this._onConnect.bind(this);
  61. this._onCloseHandler = this._onCloseEvent.bind(this);
  62. this._onErrorHandler = this._onError.bind(this);
  63. if (!this._validateProviderPath(socketPath))
  64. throw new web3_errors_1.InvalidClientError(socketPath);
  65. this._socketPath = socketPath;
  66. this._socketOptions = socketOptions;
  67. this._reconnectOptions = Object.assign(Object.assign({}, DEFAULT_RECONNECTION_OPTIONS), (reconnectOptions !== null && reconnectOptions !== void 0 ? reconnectOptions : {}));
  68. this._pendingRequestsQueue = new Map();
  69. this._sentRequestsQueue = new Map();
  70. this._init();
  71. this.connect();
  72. this.chunkResponseParser = new chunk_response_parser_js_1.ChunkResponseParser(this._eventEmitter, this._reconnectOptions.autoReconnect);
  73. this.chunkResponseParser.onError(() => {
  74. this._clearQueues();
  75. });
  76. this.isReconnecting = false;
  77. }
  78. get SocketConnection() {
  79. return this._socketConnection;
  80. }
  81. _init() {
  82. this._reconnectAttempts = 0;
  83. }
  84. /**
  85. * Try to establish a connection to the socket
  86. */
  87. connect() {
  88. try {
  89. this._openSocketConnection();
  90. this._connectionStatus = 'connecting';
  91. this._addSocketListeners();
  92. }
  93. catch (e) {
  94. if (!this.isReconnecting) {
  95. this._connectionStatus = 'disconnected';
  96. if (e && e.message) {
  97. throw new web3_errors_1.ConnectionError(`Error while connecting to ${this._socketPath}. Reason: ${e.message}`);
  98. }
  99. else {
  100. throw new web3_errors_1.InvalidClientError(this._socketPath);
  101. }
  102. }
  103. else {
  104. setImmediate(() => {
  105. this._reconnect();
  106. });
  107. }
  108. }
  109. }
  110. // eslint-disable-next-line class-methods-use-this
  111. _validateProviderPath(path) {
  112. return !!path;
  113. }
  114. /**
  115. *
  116. * @returns `true` if the socket supports subscriptions
  117. */
  118. // eslint-disable-next-line class-methods-use-this
  119. supportsSubscriptions() {
  120. return true;
  121. }
  122. on(type, listener) {
  123. this._eventEmitter.on(type, listener);
  124. }
  125. once(type, listener) {
  126. this._eventEmitter.once(type, listener);
  127. }
  128. removeListener(type, listener) {
  129. this._eventEmitter.removeListener(type, listener);
  130. }
  131. _onDisconnect(code, data) {
  132. this._connectionStatus = 'disconnected';
  133. super._onDisconnect(code, data);
  134. }
  135. /**
  136. * Disconnects the socket
  137. * @param code - The code to be sent to the server
  138. * @param data - The data to be sent to the server
  139. */
  140. disconnect(code, data) {
  141. const disconnectCode = code !== null && code !== void 0 ? code : NORMAL_CLOSE_CODE;
  142. this._removeSocketListeners();
  143. if (this.getStatus() !== 'disconnected') {
  144. this._closeSocketConnection(disconnectCode, data);
  145. }
  146. this._onDisconnect(disconnectCode, data);
  147. }
  148. /**
  149. * Removes all listeners for the specified event type.
  150. * @param type - The event type to remove the listeners for
  151. */
  152. removeAllListeners(type) {
  153. this._eventEmitter.removeAllListeners(type);
  154. }
  155. _onError(event) {
  156. // do not emit error while trying to reconnect
  157. if (this.isReconnecting) {
  158. this._reconnect();
  159. }
  160. else {
  161. this._eventEmitter.emit('error', event);
  162. }
  163. }
  164. /**
  165. * Resets the socket, removing all listeners and pending requests
  166. */
  167. reset() {
  168. this._sentRequestsQueue.clear();
  169. this._pendingRequestsQueue.clear();
  170. this._init();
  171. this._removeSocketListeners();
  172. this._addSocketListeners();
  173. }
  174. _reconnect() {
  175. if (this.isReconnecting) {
  176. return;
  177. }
  178. this.isReconnecting = true;
  179. if (this._sentRequestsQueue.size > 0) {
  180. this._sentRequestsQueue.forEach((request, key) => {
  181. request.deferredPromise.reject(new web3_errors_1.PendingRequestsOnReconnectingError());
  182. this._sentRequestsQueue.delete(key);
  183. });
  184. }
  185. if (this._reconnectAttempts < this._reconnectOptions.maxAttempts) {
  186. this._reconnectAttempts += 1;
  187. setTimeout(() => {
  188. this._removeSocketListeners();
  189. this.connect();
  190. this.isReconnecting = false;
  191. }, this._reconnectOptions.delay);
  192. }
  193. else {
  194. this.isReconnecting = false;
  195. this._clearQueues();
  196. this._removeSocketListeners();
  197. this._eventEmitter.emit('error', new web3_errors_1.MaxAttemptsReachedOnReconnectingError(this._reconnectOptions.maxAttempts));
  198. }
  199. }
  200. /**
  201. * Creates a request object to be sent to the server
  202. */
  203. request(request) {
  204. return __awaiter(this, void 0, void 0, function* () {
  205. if ((0, validation_js_1.isNullish)(this._socketConnection)) {
  206. throw new Error('Connection is undefined');
  207. }
  208. // if socket disconnected - open connection
  209. if (this.getStatus() === 'disconnected') {
  210. this.connect();
  211. }
  212. const requestId = jsonRpc.isBatchRequest(request)
  213. ? request[0].id
  214. : request.id;
  215. if (!requestId) {
  216. throw new web3_errors_1.Web3WSProviderError('Request Id not defined');
  217. }
  218. if (this._sentRequestsQueue.has(requestId)) {
  219. throw new web3_errors_1.RequestAlreadySentError(requestId);
  220. }
  221. const deferredPromise = new web3_deferred_promise_js_1.Web3DeferredPromise();
  222. deferredPromise.catch(error => {
  223. this._eventEmitter.emit('error', error);
  224. });
  225. const reqItem = {
  226. payload: request,
  227. deferredPromise,
  228. };
  229. if (this.getStatus() === 'connecting') {
  230. this._pendingRequestsQueue.set(requestId, reqItem);
  231. return reqItem.deferredPromise;
  232. }
  233. this._sentRequestsQueue.set(requestId, reqItem);
  234. try {
  235. this._sendToSocket(reqItem.payload);
  236. }
  237. catch (error) {
  238. this._sentRequestsQueue.delete(requestId);
  239. this._eventEmitter.emit('error', error);
  240. }
  241. return deferredPromise;
  242. });
  243. }
  244. _onConnect() {
  245. this._connectionStatus = 'connected';
  246. this._reconnectAttempts = 0;
  247. super._onConnect();
  248. this._sendPendingRequests();
  249. }
  250. _sendPendingRequests() {
  251. for (const [id, value] of this._pendingRequestsQueue.entries()) {
  252. this._sendToSocket(value.payload);
  253. this._pendingRequestsQueue.delete(id);
  254. this._sentRequestsQueue.set(id, value);
  255. }
  256. }
  257. _onMessage(event) {
  258. const responses = this._parseResponses(event);
  259. if (responses.length === 0) {
  260. // no responses means lost connection, autoreconnect if possible
  261. if (this._reconnectOptions.autoReconnect) {
  262. this._reconnect();
  263. }
  264. return;
  265. }
  266. for (const response of responses) {
  267. if (jsonRpc.isResponseWithNotification(response) &&
  268. response.method.endsWith('_subscription')) {
  269. this._eventEmitter.emit('message', response);
  270. return;
  271. }
  272. const requestId = jsonRpc.isBatchResponse(response)
  273. ? response[0].id
  274. : response.id;
  275. const requestItem = this._sentRequestsQueue.get(requestId);
  276. if (!requestItem) {
  277. return;
  278. }
  279. if (jsonRpc.isBatchResponse(response) ||
  280. jsonRpc.isResponseWithResult(response) ||
  281. jsonRpc.isResponseWithError(response)) {
  282. this._eventEmitter.emit('message', response);
  283. requestItem.deferredPromise.resolve(response);
  284. }
  285. this._sentRequestsQueue.delete(requestId);
  286. }
  287. }
  288. _clearQueues(event) {
  289. if (this._pendingRequestsQueue.size > 0) {
  290. this._pendingRequestsQueue.forEach((request, key) => {
  291. request.deferredPromise.reject(new web3_errors_1.ConnectionNotOpenError(event));
  292. this._pendingRequestsQueue.delete(key);
  293. });
  294. }
  295. if (this._sentRequestsQueue.size > 0) {
  296. this._sentRequestsQueue.forEach((request, key) => {
  297. request.deferredPromise.reject(new web3_errors_1.ConnectionNotOpenError(event));
  298. this._sentRequestsQueue.delete(key);
  299. });
  300. }
  301. this._removeSocketListeners();
  302. }
  303. }
  304. exports.SocketProvider = SocketProvider;
  305. //# sourceMappingURL=socket_provider.js.map