UNPKG

web3-utils

Version:

Collection of utility functions used in web3.js.

278 lines 11.1 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { ConnectionError, ConnectionNotOpenError, InvalidClientError, MaxAttemptsReachedOnReconnectingError, PendingRequestsOnReconnectingError, RequestAlreadySentError, Web3WSProviderError, } from 'web3-errors'; import { Eip1193Provider } from './web3_eip1193_provider.js'; import { ChunkResponseParser } from './chunk_response_parser.js'; import { isNullish } from './validation.js'; import { Web3DeferredPromise } from './web3_deferred_promise.js'; import * as jsonRpc from './json_rpc.js'; const DEFAULT_RECONNECTION_OPTIONS = { autoReconnect: true, delay: 5000, maxAttempts: 5, }; const NORMAL_CLOSE_CODE = 1000; // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close export class SocketProvider extends Eip1193Provider { /** * This is an abstract class for implementing a socket provider (e.g. WebSocket, IPC). It extends the EIP-1193 provider {@link EIP1193Provider}. * @param socketPath - The path to the socket (e.g. /ipc/path or ws://localhost:8546) * @param socketOptions - The options for the socket connection. Its type is supposed to be specified in the inherited classes. * @param reconnectOptions - The options for the socket reconnection {@link ReconnectOptions} */ constructor(socketPath, socketOptions, reconnectOptions) { super(); this._connectionStatus = 'connecting'; // Message handlers. Due to bounding of `this` and removing the listeners we have to keep it's reference. this._onMessageHandler = this._onMessage.bind(this); this._onOpenHandler = this._onConnect.bind(this); this._onCloseHandler = this._onCloseEvent.bind(this); this._onErrorHandler = this._onError.bind(this); if (!this._validateProviderPath(socketPath)) throw new InvalidClientError(socketPath); this._socketPath = socketPath; this._socketOptions = socketOptions; this._reconnectOptions = Object.assign(Object.assign({}, DEFAULT_RECONNECTION_OPTIONS), (reconnectOptions !== null && reconnectOptions !== void 0 ? reconnectOptions : {})); this._pendingRequestsQueue = new Map(); this._sentRequestsQueue = new Map(); this._init(); this.connect(); this.chunkResponseParser = new ChunkResponseParser(this._eventEmitter, this._reconnectOptions.autoReconnect); this.chunkResponseParser.onError(() => { this._clearQueues(); }); this.isReconnecting = false; } get SocketConnection() { return this._socketConnection; } _init() { this._reconnectAttempts = 0; } /** * Try to establish a connection to the socket */ connect() { try { this._openSocketConnection(); this._connectionStatus = 'connecting'; this._addSocketListeners(); } catch (e) { if (!this.isReconnecting) { this._connectionStatus = 'disconnected'; if (e && e.message) { throw new ConnectionError(`Error while connecting to ${this._socketPath}. Reason: ${e.message}`); } else { throw new InvalidClientError(this._socketPath); } } else { setImmediate(() => { this._reconnect(); }); } } } // eslint-disable-next-line class-methods-use-this _validateProviderPath(path) { return !!path; } /** * * @returns `true` if the socket supports subscriptions */ // eslint-disable-next-line class-methods-use-this supportsSubscriptions() { return true; } on(type, listener) { this._eventEmitter.on(type, listener); } once(type, listener) { this._eventEmitter.once(type, listener); } removeListener(type, listener) { this._eventEmitter.removeListener(type, listener); } _onDisconnect(code, data) { this._connectionStatus = 'disconnected'; super._onDisconnect(code, data); } /** * Disconnects the socket * @param code - The code to be sent to the server * @param data - The data to be sent to the server */ disconnect(code, data) { const disconnectCode = code !== null && code !== void 0 ? code : NORMAL_CLOSE_CODE; this._removeSocketListeners(); if (this.getStatus() !== 'disconnected') { this._closeSocketConnection(disconnectCode, data); } this._onDisconnect(disconnectCode, data); } /** * Removes all listeners for the specified event type. * @param type - The event type to remove the listeners for */ removeAllListeners(type) { this._eventEmitter.removeAllListeners(type); } _onError(event) { // do not emit error while trying to reconnect if (this.isReconnecting) { this._reconnect(); } else { this._eventEmitter.emit('error', event); } } /** * Resets the socket, removing all listeners and pending requests */ reset() { this._sentRequestsQueue.clear(); this._pendingRequestsQueue.clear(); this._init(); this._removeSocketListeners(); this._addSocketListeners(); } _reconnect() { if (this.isReconnecting) { return; } this.isReconnecting = true; if (this._sentRequestsQueue.size > 0) { this._sentRequestsQueue.forEach((request, key) => { request.deferredPromise.reject(new PendingRequestsOnReconnectingError()); this._sentRequestsQueue.delete(key); }); } if (this._reconnectAttempts < this._reconnectOptions.maxAttempts) { this._reconnectAttempts += 1; setTimeout(() => { this._removeSocketListeners(); this.connect(); this.isReconnecting = false; }, this._reconnectOptions.delay); } else { this.isReconnecting = false; this._clearQueues(); this._removeSocketListeners(); this._eventEmitter.emit('error', new MaxAttemptsReachedOnReconnectingError(this._reconnectOptions.maxAttempts)); } } /** * Creates a request object to be sent to the server */ request(request) { return __awaiter(this, void 0, void 0, function* () { if (isNullish(this._socketConnection)) { throw new Error('Connection is undefined'); } // if socket disconnected - open connection if (this.getStatus() === 'disconnected') { this.connect(); } const requestId = jsonRpc.isBatchRequest(request) ? request[0].id : request.id; if (!requestId) { throw new Web3WSProviderError('Request Id not defined'); } if (this._sentRequestsQueue.has(requestId)) { throw new RequestAlreadySentError(requestId); } const deferredPromise = new Web3DeferredPromise(); deferredPromise.catch(error => { this._eventEmitter.emit('error', error); }); const reqItem = { payload: request, deferredPromise, }; if (this.getStatus() === 'connecting') { this._pendingRequestsQueue.set(requestId, reqItem); return reqItem.deferredPromise; } this._sentRequestsQueue.set(requestId, reqItem); try { this._sendToSocket(reqItem.payload); } catch (error) { this._sentRequestsQueue.delete(requestId); this._eventEmitter.emit('error', error); } return deferredPromise; }); } _onConnect() { this._connectionStatus = 'connected'; this._reconnectAttempts = 0; super._onConnect(); this._sendPendingRequests(); } _sendPendingRequests() { for (const [id, value] of this._pendingRequestsQueue.entries()) { this._sendToSocket(value.payload); this._pendingRequestsQueue.delete(id); this._sentRequestsQueue.set(id, value); } } _onMessage(event) { const responses = this._parseResponses(event); if (responses.length === 0) { // no responses means lost connection, autoreconnect if possible if (this._reconnectOptions.autoReconnect) { this._reconnect(); } return; } for (const response of responses) { if (jsonRpc.isResponseWithNotification(response) && response.method.endsWith('_subscription')) { this._eventEmitter.emit('message', response); return; } const requestId = jsonRpc.isBatchResponse(response) ? response[0].id : response.id; const requestItem = this._sentRequestsQueue.get(requestId); if (!requestItem) { return; } if (jsonRpc.isBatchResponse(response) || jsonRpc.isResponseWithResult(response) || jsonRpc.isResponseWithError(response)) { this._eventEmitter.emit('message', response); requestItem.deferredPromise.resolve(response); } this._sentRequestsQueue.delete(requestId); } } _clearQueues(event) { if (this._pendingRequestsQueue.size > 0) { this._pendingRequestsQueue.forEach((request, key) => { request.deferredPromise.reject(new ConnectionNotOpenError(event)); this._pendingRequestsQueue.delete(key); }); } if (this._sentRequestsQueue.size > 0) { this._sentRequestsQueue.forEach((request, key) => { request.deferredPromise.reject(new ConnectionNotOpenError(event)); this._sentRequestsQueue.delete(key); }); } this._removeSocketListeners(); } } //# sourceMappingURL=socket_provider.js.map