/* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. web3.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ import { ClientRequestArgs } from 'http'; import WebSocket, { ClientOptions, CloseEvent } from 'isomorphic-ws'; import { EthExecutionAPI, Web3APIMethod, Web3APIPayload, Web3APISpec, Web3ProviderStatus, } from 'web3-types'; import { isNullish, ReconnectOptions, SocketProvider } from 'web3-utils'; import { ConnectionNotOpenError } from 'web3-errors'; export { ClientRequestArgs } from 'http'; export { ClientOptions } from 'isomorphic-ws'; /** * Use WebSocketProvider to connect to a Node using a WebSocket connection, i.e. over the `ws` or `wss` protocol. * * @example * ```ts * const provider = new WebSocketProvider( * `ws://localhost:8545`, * { * headers: { * // to provide the API key if the Node requires the key to be inside the `headers` for example: * 'x-api-key': '', * }, * }, * { * delay: 500, * autoReconnect: true, * maxAttempts: 10, * }, * ); * ``` * * The second and the third parameters are both optional. And you can for example, the second parameter could be an empty object or undefined. * * @example * ```ts * const provider = new WebSocketProvider( * `ws://localhost:8545`, * {}, * { * delay: 500, * autoReconnect: true, * maxAttempts: 10, * }, * ); * ``` */ export default class WebSocketProvider< API extends Web3APISpec = EthExecutionAPI, > extends SocketProvider { protected readonly _socketOptions?: ClientOptions | ClientRequestArgs; protected _socketConnection?: WebSocket; // eslint-disable-next-line class-methods-use-this protected _validateProviderPath(providerUrl: string): boolean { return typeof providerUrl === 'string' ? /^ws(s)?:\/\//i.test(providerUrl) : false; } /** * This is a class used for Web Socket connections. It extends the abstract class SocketProvider {@link SocketProvider} that extends the EIP-1193 provider {@link EIP1193Provider}. * @param socketPath - The path to the Web Socket. * @param socketOptions - The options for the Web Socket client. * @param reconnectOptions - The options for the socket reconnection {@link ReconnectOptions} */ // this constructor is to specify the type for `socketOptions` for a better intellisense. // eslint-disable-next-line no-useless-constructor public constructor( socketPath: string, socketOptions?: ClientOptions | ClientRequestArgs, reconnectOptions?: Partial, ) { super(socketPath, socketOptions, reconnectOptions); } public getStatus(): Web3ProviderStatus { if (this._socketConnection && !isNullish(this._socketConnection)) { switch (this._socketConnection.readyState) { case this._socketConnection.CONNECTING: { return 'connecting'; } case this._socketConnection.OPEN: { return 'connected'; } default: { return 'disconnected'; } } } return 'disconnected'; } protected _openSocketConnection() { this._socketConnection = new WebSocket( this._socketPath, undefined, this._socketOptions && Object.keys(this._socketOptions).length === 0 ? undefined : this._socketOptions, ); } protected _closeSocketConnection(code?: number, data?: string) { this._socketConnection?.close(code, data); } protected _sendToSocket>( payload: Web3APIPayload, ): void { if (this.getStatus() === 'disconnected') { throw new ConnectionNotOpenError(); } this._socketConnection?.send(JSON.stringify(payload)); } protected _parseResponses(event: WebSocket.MessageEvent) { return this.chunkResponseParser.parseResponse(event.data as string); } protected _addSocketListeners(): void { this._socketConnection?.addEventListener('open', this._onOpenHandler); this._socketConnection?.addEventListener('message', this._onMessageHandler); this._socketConnection?.addEventListener('close', e => this._onCloseHandler(e)); this._socketConnection?.addEventListener('error', this._onErrorHandler); } protected _removeSocketListeners(): void { this._socketConnection?.removeEventListener('message', this._onMessageHandler); this._socketConnection?.removeEventListener('open', this._onOpenHandler); this._socketConnection?.removeEventListener('close', this._onCloseHandler); // note: we intentionally keep the error event listener to be able to emit it in case an error happens when closing the connection } protected _onCloseEvent(event: CloseEvent): void { if ( this._reconnectOptions.autoReconnect && (![1000, 1001].includes(event.code) || !event.wasClean) ) { this._reconnect(); return; } this._clearQueues(event); this._removeSocketListeners(); this._onDisconnect(event.code, event.reason); // disconnect was successful and can safely remove error listener this._socketConnection?.removeEventListener('error', this._onErrorHandler); } } export { WebSocketProvider };