/*
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 .
*/
// eslint-disable-next-line max-classes-per-file
import {
BlockOutput,
DEFAULT_RETURN_FORMAT,
DataFormat,
EthExecutionAPI,
JsonRpcSubscriptionResult,
JsonRpcSubscriptionResultOld,
JsonRpcNotification,
Log,
HexString,
Web3APIParams,
Web3APISpec,
} from 'web3-types';
import { jsonRpc } from 'web3-utils';
// eslint-disable-next-line import/no-cycle
import { Web3SubscriptionManager } from './web3_subscription_manager.js';
import { Web3EventEmitter, Web3EventMap } from './web3_event_emitter.js';
import { Web3RequestManager } from './web3_request_manager.js';
type CommonSubscriptionEvents = {
data: unknown; // Fires on each incoming block header.
error: Error; // Fires when an error in the subscription occurs.
connected: string; // Fires once after the subscription successfully connected. Returns the subscription id.
};
export abstract class Web3Subscription<
EventMap extends Web3EventMap,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ArgsType = any,
API extends Web3APISpec = EthExecutionAPI,
// The following generic type is just to define the type `CombinedEventMap` and use it inside the class
// it combines the user passed `EventMap` with the `CommonSubscriptionEvents`
// However, this type definition could be refactored depending on the closure of
// [Permit type alias declarations inside a class](https://github.com/microsoft/TypeScript/issues/7061)
CombinedEventMap extends CommonSubscriptionEvents = EventMap & CommonSubscriptionEvents,
> extends Web3EventEmitter {
private readonly _subscriptionManager: Web3SubscriptionManager;
private readonly _lastBlock?: BlockOutput;
private readonly _returnFormat: DataFormat;
protected _id?: HexString;
public constructor(
args: ArgsType,
options: { subscriptionManager: Web3SubscriptionManager; returnFormat?: DataFormat },
);
/**
* @deprecated This constructor overloading should not be used
*/
public constructor(
args: ArgsType,
options: { requestManager: Web3RequestManager; returnFormat?: DataFormat },
);
public constructor(
public readonly args: ArgsType,
options: (
| { subscriptionManager: Web3SubscriptionManager }
| { requestManager: Web3RequestManager }
) & {
returnFormat?: DataFormat;
},
) {
super();
const { requestManager } = options as { requestManager: Web3RequestManager };
const { subscriptionManager } = options as { subscriptionManager: Web3SubscriptionManager };
if (requestManager) {
// eslint-disable-next-line deprecation/deprecation
this._subscriptionManager = new Web3SubscriptionManager(requestManager, {}, true);
} else {
this._subscriptionManager = subscriptionManager;
}
this._returnFormat = options?.returnFormat ?? (DEFAULT_RETURN_FORMAT as DataFormat);
}
public get id() {
return this._id;
}
public get lastBlock() {
return this._lastBlock;
}
public async subscribe(): Promise {
return this._subscriptionManager.addSubscription(this);
}
public processSubscriptionData(
data:
| JsonRpcSubscriptionResult
| JsonRpcSubscriptionResultOld
| JsonRpcNotification,
) {
if (data?.data) {
// for EIP-1193 provider
this._processSubscriptionResult(data?.data?.result ?? data?.data);
} else if (
data &&
jsonRpc.isResponseWithNotification(
data as unknown as JsonRpcSubscriptionResult | JsonRpcNotification,
)
) {
this._processSubscriptionResult(data?.params.result);
}
}
public async sendSubscriptionRequest(): Promise {
this._id = await this._subscriptionManager.requestManager.send({
method: 'eth_subscribe',
params: this._buildSubscriptionParams(),
});
this.emit('connected', this._id);
return this._id;
}
protected get returnFormat() {
return this._returnFormat;
}
protected get subscriptionManager() {
return this._subscriptionManager;
}
public async resubscribe() {
await this.unsubscribe();
await this.subscribe();
}
public async unsubscribe() {
if (!this.id) {
return;
}
await this._subscriptionManager.removeSubscription(this);
}
public async sendUnsubscribeRequest() {
await this._subscriptionManager.requestManager.send({
method: 'eth_unsubscribe',
params: [this.id] as Web3APIParams,
});
this._id = undefined;
}
// eslint-disable-next-line class-methods-use-this
protected formatSubscriptionResult(data: CombinedEventMap['data']) {
return data;
}
public _processSubscriptionResult(data: CombinedEventMap['data'] | unknown) {
this.emit('data', this.formatSubscriptionResult(data));
}
public _processSubscriptionError(error: Error) {
this.emit('error', error);
}
// eslint-disable-next-line class-methods-use-this
protected _buildSubscriptionParams(): Web3APIParams {
// This should be overridden in the subclass
throw new Error('Implement in the child class');
}
}
export type Web3SubscriptionConstructor<
API extends Web3APISpec,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
SubscriptionType extends Web3Subscription = Web3Subscription,
> =
| (new (
// We accept any type of arguments here and don't deal with this type internally
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any,
options:
| { subscriptionManager: Web3SubscriptionManager; returnFormat?: DataFormat }
| { requestManager: Web3RequestManager; returnFormat?: DataFormat },
) => SubscriptionType)
| (new (
args: any,
options: {
subscriptionManager: Web3SubscriptionManager;
returnFormat?: DataFormat;
},
) => SubscriptionType)
| (new (
args: any,
options: { requestManager: Web3RequestManager; returnFormat?: DataFormat },
) => SubscriptionType);