From f9ba36d80e4c06fe102e97c8a145c4aec2fca34b Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 09:17:19 +0200 Subject: [PATCH 01/15] refactor subscription's logic - Moved subscribing to provider events from Web3Subscription to Web3SubscriptionManager - subscribe and un subscribe called at Web3Subscription now is the same as calling them on Web3SubscriptionManager - Web3Subscription is lined now to Web3SubscriptionManager instead of directly to Web3RequestManager - update test cases ... --- packages/web3-core/src/web3_context.ts | 10 +- .../src/web3_subscription_manager.ts | 121 +++++++++++++++--- packages/web3-core/src/web3_subscriptions.ts | 93 +++++--------- .../test/unit/web3_subscription.test.ts | 27 ++-- .../unit/web3_subscription_manager.test.ts | 69 ++++++---- .../web3_subscription_old_providers.test.ts | 66 ++++++---- .../web3-eth-accounts/test/config/setup.js | 4 + packages/web3-eth-contract/src/contract.ts | 14 +- .../web3-eth-contract/src/log_subscription.ts | 8 +- packages/web3-eth/src/web3_eth.ts | 10 +- packages/web3-eth/src/web3_subscriptions.ts | 16 +-- packages/web3-eth/test/integration/setup.js | 2 +- .../test/unit/web3_eth_subscription.test.ts | 23 ++-- packages/web3-types/src/web3_base_provider.ts | 14 ++ packages/web3/test/e2e/e2e_utils.ts | 2 +- 15 files changed, 294 insertions(+), 185 deletions(-) diff --git a/packages/web3-core/src/web3_context.ts b/packages/web3-core/src/web3_context.ts index 1b654822af4..0038b59fb65 100644 --- a/packages/web3-core/src/web3_context.ts +++ b/packages/web3-core/src/web3_context.ts @@ -96,7 +96,7 @@ export class Web3Context< public static givenProvider?: SupportedProviders; public readonly providers = Web3RequestManager.providers; protected _requestManager: Web3RequestManager; - protected _subscriptionManager?: Web3SubscriptionManager; + protected _subscriptionManager: Web3SubscriptionManager; protected _accountProvider?: Web3AccountProvider; protected _wallet?: Web3BaseWallet; @@ -146,10 +146,10 @@ export class Web3Context< if (subscriptionManager) { this._subscriptionManager = subscriptionManager; - } else if (registeredSubscriptions) { + } else { this._subscriptionManager = new Web3SubscriptionManager( this.requestManager, - registeredSubscriptions, + registeredSubscriptions ?? ({} as RegisteredSubs), ); } @@ -195,8 +195,7 @@ export class Web3Context< provider: this.provider, requestManager: this.requestManager, subscriptionManager: this.subscriptionManager, - registeredSubscriptions: this.subscriptionManager - ?.registeredSubscriptions as RegisteredSubs, + registeredSubscriptions: this.subscriptionManager?.registeredSubscriptions, providers: this.providers, wallet: this.wallet, accountProvider: this.accountProvider, @@ -231,6 +230,7 @@ export class Web3Context< this.setConfig(parentContext.config); this._requestManager = parentContext.requestManager; this.provider = parentContext.provider; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._subscriptionManager = parentContext.subscriptionManager; this._wallet = parentContext.wallet; this._accountProvider = parentContext._accountProvider; diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index 95012249d7c..5c12e1f77fe 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -15,11 +15,22 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { DataFormat, DEFAULT_RETURN_FORMAT, Web3APISpec } from 'web3-types'; +import { + DataFormat, + DEFAULT_RETURN_FORMAT, + EIP1193Provider, + JsonRpcNotification, + JsonRpcSubscriptionResult, + JsonRpcSubscriptionResultOld, + Log, + Web3APISpec, + Web3BaseProvider, +} from 'web3-types'; import { ProviderError, SubscriptionError } from 'web3-errors'; -import { isNullish } from 'web3-utils'; +import { jsonRpc, isNullish } from 'web3-utils'; import { isSupportSubscriptions } from './utils.js'; import { Web3RequestManager, Web3RequestManagerEvent } from './web3_request_manager.js'; +// eslint-disable-next-line import/no-cycle import { Web3SubscriptionConstructor } from './web3_subscriptions.js'; type ShouldUnsubscribeCondition = ({ @@ -31,8 +42,10 @@ type ShouldUnsubscribeCondition = ({ }) => boolean | undefined; export class Web3SubscriptionManager< - API extends Web3APISpec, - RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor }, + API extends Web3APISpec = Web3APISpec, + RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor } = { + [key: string]: Web3SubscriptionConstructor; + }, > { private readonly _subscriptions: Map< string, @@ -60,9 +73,71 @@ export class Web3SubscriptionManager< this.requestManager.on(Web3RequestManagerEvent.PROVIDER_CHANGED, () => { this.clear(); + this.listenToProviderEvents(); }); + + this.listenToProviderEvents(); + } + + private listenToProviderEvents() { + const providerAsWebProvider = this.requestManager.provider as Web3BaseProvider; + if ( + !this.requestManager.provider || + (typeof providerAsWebProvider?.supportsSubscriptions === 'function' && + !providerAsWebProvider?.supportsSubscriptions()) + ) { + return; + } + + if (typeof (this.requestManager.provider as EIP1193Provider).on === 'function') { + if ( + typeof (this.requestManager.provider as EIP1193Provider).request === 'function' + ) { + // Listen to provider messages and data + (this.requestManager.provider as EIP1193Provider).on( + 'message', + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument + (message: any) => this.messageListener(message), + ); + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument + providerAsWebProvider.on('data', (data: any) => this.messageListener(data)); + } + } } + protected messageListener( + data?: + | JsonRpcSubscriptionResult + | JsonRpcSubscriptionResultOld + | JsonRpcNotification, + ) { + if (!data) { + throw new SubscriptionError('Should not call messageListener with no data. Type was'); + } + const subscriptionId = + (data as JsonRpcNotification).params?.subscription || + (data as JsonRpcSubscriptionResultOld).data?.subscription || + (data as JsonRpcSubscriptionResult).id?.toString(16); + + // Process if the received data is related to a subscription + if (subscriptionId) { + const sub = this._subscriptions.get(subscriptionId); + if (sub) { + // for EIP-1193 provider + if (data?.data) { + sub.processSubscriptionResult(data?.data?.result ?? data?.data); + } else if ( + data && + jsonRpc.isResponseWithNotification( + data as unknown as JsonRpcSubscriptionResult | JsonRpcNotification, + ) + ) { + sub.processSubscriptionResult(data?.params.result); + } + } + } + } /** * Will create a new subscription * @@ -78,19 +153,19 @@ export class Web3SubscriptionManager< args?: ConstructorParameters[0], returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise> { - if (!this.requestManager.provider) { - throw new ProviderError('Provider not available'); - } - const Klass: RegisteredSubs[T] = this.registeredSubscriptions[name]; if (!Klass) { throw new SubscriptionError('Invalid subscription type'); } - const subscription = new Klass(args ?? undefined, { - requestManager: this.requestManager, - returnFormat, - }) as InstanceType; + const subscription = new Klass( + args ?? undefined, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + this as Web3SubscriptionManager, + { + returnFormat, + }, + ) as InstanceType; await this.addSubscription(subscription); @@ -111,6 +186,10 @@ export class Web3SubscriptionManager< * @param sub - A {@link Web3Subscription} object */ public async addSubscription(sub: InstanceType) { + if (!this.requestManager.provider) { + throw new ProviderError('Provider not available'); + } + if (!this.supportsSubscriptions()) { throw new SubscriptionError('The current provider does not support subscriptions'); } @@ -119,34 +198,38 @@ export class Web3SubscriptionManager< throw new SubscriptionError(`Subscription with id "${sub.id}" already exists`); } - await sub.subscribe(); + await sub.sendSubscriptionRequest(); if (isNullish(sub.id)) { throw new SubscriptionError('Subscription is not subscribed yet.'); } this._subscriptions.set(sub.id, sub); + + return sub.id; } + /** * Will clear a subscription * * @param id - The subscription of type {@link Web3Subscription} to remove */ - public async removeSubscription(sub: InstanceType) { - if (isNullish(sub.id)) { + const { id } = sub; + + if (isNullish(id)) { throw new SubscriptionError( 'Subscription is not subscribed yet. Or, had already been unsubscribed but not through the Subscription Manager.', ); } - if (!this._subscriptions.has(sub.id)) { + if (!this._subscriptions.has(id)) { throw new SubscriptionError( - `Subscription with id "${sub.id.toString()}" does not exists`, + `Failed to remove Subscription. Subscription with id "${id.toString()}" does not exists`, ); } - const { id } = sub; - await sub.unsubscribe(); + + await sub.sendUnsubscribeRequest(); this._subscriptions.delete(id); return id; } diff --git a/packages/web3-core/src/web3_subscriptions.ts b/packages/web3-core/src/web3_subscriptions.ts index 5a7644a11a4..38f2ceb6f72 100644 --- a/packages/web3-core/src/web3_subscriptions.ts +++ b/packages/web3-core/src/web3_subscriptions.ts @@ -17,24 +17,18 @@ along with web3.js. If not, see . // eslint-disable-next-line max-classes-per-file import { - HexString, BlockOutput, - Web3BaseProvider, - Web3APISpec, - Web3APIParams, - EthExecutionAPI, - Log, - JsonRpcNotification, - JsonRpcSubscriptionResult, - DataFormat, DEFAULT_RETURN_FORMAT, - JsonRpcSubscriptionResultOld, - EIP1193Provider, + DataFormat, + EthExecutionAPI, + HexString, + Web3APIParams, + Web3APISpec, } from 'web3-types'; -import { jsonRpc } from 'web3-utils'; import { Web3EventEmitter, Web3EventMap } from './web3_event_emitter.js'; -import { Web3RequestManager } from './web3_request_manager.js'; +// eslint-disable-next-line import/no-cycle +import { Web3SubscriptionManager } from './web3_subscription_manager.js'; export abstract class Web3Subscription< EventMap extends Web3EventMap, @@ -42,20 +36,21 @@ export abstract class Web3Subscription< ArgsType = any, API extends Web3APISpec = EthExecutionAPI, > extends Web3EventEmitter { - private readonly _requestManager: Web3RequestManager; + private readonly _subscriptionManager: Web3SubscriptionManager; private readonly _lastBlock?: BlockOutput; private readonly _returnFormat: DataFormat; - private _id?: HexString; - private _messageListener?: (data?: JsonRpcNotification) => void; + protected _id?: HexString; public constructor( public readonly args: ArgsType, - - options: { requestManager: Web3RequestManager; returnFormat?: DataFormat }, + subscriptionManager: Web3SubscriptionManager, + options?: { + returnFormat?: DataFormat; + }, ) { super(); - this._requestManager = options.requestManager; - this._returnFormat = options.returnFormat ?? (DEFAULT_RETURN_FORMAT as DataFormat); + this._subscriptionManager = subscriptionManager; + this._returnFormat = options?.returnFormat ?? (DEFAULT_RETURN_FORMAT as DataFormat); } public get id() { @@ -67,41 +62,17 @@ export abstract class Web3Subscription< } public async subscribe() { - this._id = await this._requestManager.send({ + return this._subscriptionManager.addSubscription(this); + } + + public async sendSubscriptionRequest(): Promise { + this._id = await this._subscriptionManager.requestManager.send({ method: 'eth_subscribe', params: this._buildSubscriptionParams(), }); - - const messageListener = ( - data?: - | JsonRpcSubscriptionResult - | JsonRpcSubscriptionResultOld - | JsonRpcNotification, - ) => { - // for EIP-1193 provider - if (data?.data) { - this._processSubscriptionResult(data?.data?.result ?? data?.data); - return; - } - - if ( - data && - jsonRpc.isResponseWithNotification( - data as unknown as JsonRpcSubscriptionResult | JsonRpcNotification, - ) - ) { - this._processSubscriptionResult(data?.params.result); - } - }; - - if (typeof (this._requestManager.provider as EIP1193Provider).request === 'function') { - (this._requestManager.provider as Web3BaseProvider).on('message', messageListener); - } else { - (this._requestManager.provider as Web3BaseProvider).on('data', messageListener); - } - - this._messageListener = messageListener; + return this._id; } + protected get returnFormat() { return this._returnFormat; } @@ -115,25 +86,24 @@ export abstract class Web3Subscription< return; } - await this._requestManager.send({ + await this._subscriptionManager.removeSubscription(this); + } + + public async sendUnsubscribeRequest() { + await this._subscriptionManager.requestManager.send({ method: 'eth_unsubscribe', params: [this.id] as Web3APIParams, }); - this._id = undefined; - (this._requestManager.provider as Web3BaseProvider).removeListener( - 'message', - this._messageListener as never, - ); } // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars - protected _processSubscriptionResult(_data: unknown) { + public processSubscriptionResult(_data: unknown) { // Do nothing - This should be overridden in subclass. } // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars - protected _processSubscriptionError(_err: Error) { + public processSubscriptionError(_err: Error) { // Do nothing - This should be overridden in subclass. } @@ -152,5 +122,8 @@ export type Web3SubscriptionConstructor< // 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: { requestManager: Web3RequestManager; returnFormat?: DataFormat }, + subscriptionManager: Web3SubscriptionManager, + options: { + returnFormat?: DataFormat; + }, ) => SubscriptionType; diff --git a/packages/web3-core/test/unit/web3_subscription.test.ts b/packages/web3-core/test/unit/web3_subscription.test.ts index 6b1bb911e34..0a4c9e9b081 100644 --- a/packages/web3-core/test/unit/web3_subscription.test.ts +++ b/packages/web3-core/test/unit/web3_subscription.test.ts @@ -15,25 +15,32 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import { Web3SubscriptionManager } from '../../src'; import { ExampleSubscription } from './fixtures/example_subscription'; +const subscriptions = { example: ExampleSubscription as never }; + describe('Web3Subscription', () => { let requestManager: any; + let subscriptionManager: Web3SubscriptionManager; let sub: ExampleSubscription; beforeEach(() => { // const web3 = new Web3('http://localhost:8545'); requestManager = { - send: jest.fn(), + send: jest.fn().mockImplementation(async () => { + return 'sub-id'; + }), on: jest.fn(), provider: { on: jest.fn(), removeListener: jest.fn(), request: jest.fn() }, }; - sub = new ExampleSubscription({ param1: 'value' }, { requestManager }); + subscriptionManager = new Web3SubscriptionManager(requestManager, subscriptions); + + sub = new ExampleSubscription({ param1: 'value' }, subscriptionManager); }); describe('subscribe', () => { it('should invoke request manager for subscription', async () => { - (requestManager.send as jest.Mock).mockResolvedValue('sub-id'); await sub.subscribe(); expect(requestManager.send).toHaveBeenCalledTimes(1); @@ -44,8 +51,6 @@ describe('Web3Subscription', () => { }); it('should set correct subscription id', async () => { - (requestManager.send as jest.Mock).mockResolvedValue('sub-id'); - expect(sub.id).toBeUndefined(); await sub.subscribe(); expect(sub.id).toBe('sub-id'); @@ -64,7 +69,9 @@ describe('Web3Subscription', () => { describe('unsubscribe', () => { beforeEach(() => { + sub = new ExampleSubscription({ param1: 'value' }, subscriptionManager); sub['_id'] = 'sub-id'; + subscriptionManager.subscriptions.set('sub-id', sub); }); it('should invoke request manager to unsubscribe', async () => { @@ -82,15 +89,5 @@ describe('Web3Subscription', () => { await sub.unsubscribe(); expect(sub.id).toBeUndefined(); }); - - it('should remove listener for "message" event', async () => { - await sub.unsubscribe(); - - expect(requestManager.provider.removeListener).toHaveBeenCalledTimes(1); - expect(requestManager.provider.removeListener).toHaveBeenCalledWith( - 'message', - undefined, - ); - }); }); }); diff --git a/packages/web3-core/test/unit/web3_subscription_manager.test.ts b/packages/web3-core/test/unit/web3_subscription_manager.test.ts index cf2d120ddee..1ba58390868 100644 --- a/packages/web3-core/test/unit/web3_subscription_manager.test.ts +++ b/packages/web3-core/test/unit/web3_subscription_manager.test.ts @@ -26,35 +26,54 @@ const subscriptions = { example: ExampleSubscription as never }; describe('Web3SubscriptionManager', () => { let requestManager: any; + let subManager: Web3SubscriptionManager; beforeEach(() => { - requestManager = { send: jest.fn(), on: jest.fn(), provider: jest.fn() }; + requestManager = { + send: jest.fn().mockImplementation(async () => { + return 'sub-id'; + }), + on: jest.fn(), + provider: { on: jest.fn() }, + }; + subManager = new Web3SubscriptionManager(requestManager, subscriptions); (ExampleSubscription as jest.Mock).mockClear(); }); describe('constructor', () => { it('should create subscription manager object', () => { - const subManager = new Web3SubscriptionManager(requestManager, {}); + subManager = new Web3SubscriptionManager(requestManager, {}); expect(subManager).toBeInstanceOf(Web3SubscriptionManager); }); it('should create register events for request manager', () => { - const subManager = new Web3SubscriptionManager(requestManager, {}); - - expect(subManager).toBeDefined(); - expect(requestManager.on).toHaveBeenCalledTimes(2); - expect(requestManager.on).toHaveBeenCalledWith( + const requestMan: any = { + send: jest.fn(), + on: jest.fn(), + provider: { + on: jest + .fn() + .mockImplementation((_: string, callback: (a: string) => unknown) => + callback('something'), + ), + }, + }; + const subscriptionMan = new Web3SubscriptionManager(requestMan, {}); + + expect(subscriptionMan).toBeDefined(); + expect(requestMan.on).toHaveBeenCalledTimes(2); + expect(requestMan.on).toHaveBeenCalledWith( Web3RequestManagerEvent.BEFORE_PROVIDER_CHANGE, expect.any(Function), ); - expect(requestManager.on).toHaveBeenCalledWith( + expect(requestMan.on).toHaveBeenCalledWith( Web3RequestManagerEvent.PROVIDER_CHANGED, expect.any(Function), ); }); it('should register the subscription types', () => { - const subManager = new Web3SubscriptionManager(requestManager, { + subManager = new Web3SubscriptionManager(requestManager, { example: ExampleSubscription as never, }); @@ -63,8 +82,6 @@ describe('Web3SubscriptionManager', () => { }); describe('subscribe', () => { - let subManager: Web3SubscriptionManager; - beforeEach(() => { subManager = new Web3SubscriptionManager(requestManager, subscriptions); @@ -93,33 +110,31 @@ describe('Web3SubscriptionManager', () => { }); it('should return valid subscription type if subscribed', async () => { - jest.spyOn(subManager, 'addSubscription').mockResolvedValue(); + jest.spyOn(subManager, 'addSubscription').mockResolvedValue('123'); const result = await subManager.subscribe('example'); expect(result).toBeInstanceOf(ExampleSubscription); }); it('should initialize subscription with valid args', async () => { - jest.spyOn(subManager, 'addSubscription').mockResolvedValue(); + jest.spyOn(subManager, 'addSubscription').mockResolvedValue('456'); const result = await subManager.subscribe('example', { test1: 'test1' }); expect(result).toBeInstanceOf(ExampleSubscription); expect(ExampleSubscription).toHaveBeenCalledTimes(1); - expect(ExampleSubscription).toHaveBeenCalledWith( - { test1: 'test1' }, - { requestManager, returnFormat: DEFAULT_RETURN_FORMAT }, - ); + expect(ExampleSubscription).toHaveBeenCalledWith({ test1: 'test1' }, subManager, { + returnFormat: DEFAULT_RETURN_FORMAT, + }); }); }); describe('addSubscription', () => { - let subManager: Web3SubscriptionManager; let sub: ExampleSubscription; beforeEach(() => { subManager = new Web3SubscriptionManager(requestManager, subscriptions); jest.spyOn(subManager, 'supportsSubscriptions').mockReturnValue(true); - sub = new ExampleSubscription({ param1: 'param1' }, { requestManager }); + sub = new ExampleSubscription({ param1: 'param1' }, subManager); (sub as any).id = '123'; }); @@ -133,10 +148,15 @@ describe('Web3SubscriptionManager', () => { }); it('should try to subscribe the subscription', async () => { + sub = new ExampleSubscription({ param1: 'param1' }, subManager); + jest.spyOn(sub, 'sendSubscriptionRequest').mockImplementation(async () => { + (sub as any).id = 'value'; + return Promise.resolve(sub.id as string); + }); await subManager.addSubscription(sub); - expect(sub.subscribe).toHaveBeenCalledTimes(1); - expect(sub.subscribe).toHaveBeenCalledWith(); + expect(sub.sendSubscriptionRequest).toHaveBeenCalledTimes(1); + expect(sub.sendSubscriptionRequest).toHaveBeenCalledWith(); }); it('should set the subscription to the map', async () => { @@ -149,13 +169,12 @@ describe('Web3SubscriptionManager', () => { }); describe('removeSubscription', () => { - let subManager: Web3SubscriptionManager; let sub: ExampleSubscription; beforeEach(async () => { subManager = new Web3SubscriptionManager(requestManager, subscriptions); jest.spyOn(subManager, 'supportsSubscriptions').mockReturnValue(true); - sub = new ExampleSubscription({ param1: 'param1' }, { requestManager }); + sub = new ExampleSubscription({ param1: 'param1' }, subManager); (sub as any).id = '123'; await subManager.addSubscription(sub); @@ -180,8 +199,8 @@ describe('Web3SubscriptionManager', () => { it('should try to unsubscribe the subscription', async () => { await subManager.removeSubscription(sub); - expect(sub.unsubscribe).toHaveBeenCalledTimes(1); - expect(sub.unsubscribe).toHaveBeenCalledWith(); + expect(sub.sendUnsubscribeRequest).toHaveBeenCalledTimes(1); + expect(sub.sendUnsubscribeRequest).toHaveBeenCalledWith(); }); it('should remove the subscription to the map', async () => { diff --git a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts index 36ff7b4d32c..0b17d2e01ab 100644 --- a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts +++ b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts @@ -17,10 +17,13 @@ along with web3.js. If not, see . import { ExampleSubscription } from './fixtures/example_subscription'; import { Web3EventEmitter } from '../../src/web3_event_emitter'; +import { Web3SubscriptionManager } from '../../src'; describe('Web3Subscription', () => { let requestManager: any; + let subscriptionManager: Web3SubscriptionManager; let eipRequestManager: any; + let subscriptionManagerWithEipReqMan: Web3SubscriptionManager; let provider: Web3EventEmitter; let eipProvider: Web3EventEmitter; @@ -29,76 +32,91 @@ describe('Web3Subscription', () => { eipProvider = new Web3EventEmitter(); // @ts-expect-error add to test eip providers eipProvider.request = jest.fn(); - requestManager = { send: jest.fn(), on: jest.fn(), provider }; - eipRequestManager = { send: jest.fn(), on: jest.fn(), provider: eipProvider }; + requestManager = { + send: jest.fn().mockImplementation(async () => { + return 'sub-id'; + }), + on: jest.fn(), + provider, + }; + subscriptionManager = new Web3SubscriptionManager(requestManager, {}); + + eipRequestManager = { + send: jest.fn().mockImplementation(async () => { + return 'sub-id'; + }), + on: jest.fn(), + provider: eipProvider, + }; + subscriptionManagerWithEipReqMan = new Web3SubscriptionManager(eipRequestManager, {}); }); describe('providers response for old provider', () => { it('data with result', async () => { + const sub = new ExampleSubscription({ param1: 'param1' }, subscriptionManager); + await sub.subscribe(); const testData = { data: { + subscription: sub.id, result: { some: 1, }, }, }; - const sub = new ExampleSubscription({ param1: 'param1' }, { requestManager }); - await sub.subscribe(); - // @ts-expect-error spy on protected method - const processResult = jest.spyOn(sub, '_processSubscriptionResult'); + const processResult = jest.spyOn(sub, 'processSubscriptionResult'); provider.emit('data', testData); expect(processResult).toHaveBeenCalledWith(testData.data.result); }); it('data without result for old provider', async () => { + const sub = new ExampleSubscription({ param1: 'param1' }, subscriptionManager); + await sub.subscribe(); const testData = { data: { + subscription: sub.id, other: { some: 1, }, }, }; - const sub = new ExampleSubscription({ param1: 'param1' }, { requestManager }); - await sub.subscribe(); - // @ts-expect-error spy on protected method - const processResult = jest.spyOn(sub, '_processSubscriptionResult'); + const processResult = jest.spyOn(sub, 'processSubscriptionResult'); provider.emit('data', testData); expect(processResult).toHaveBeenCalledWith(testData.data); }); it('data with result for eipProvider', async () => { + const sub = new ExampleSubscription( + { param1: 'param1' }, + subscriptionManagerWithEipReqMan, + ); + await sub.subscribe(); const testData = { data: { + subscription: sub.id, result: { some: 1, }, }, }; - const sub = new ExampleSubscription( - { param1: 'param1' }, - { requestManager: eipRequestManager }, - ); - await sub.subscribe(); - // @ts-expect-error spy on protected method - const processResult = jest.spyOn(sub, '_processSubscriptionResult'); + const processResult = jest.spyOn(sub, 'processSubscriptionResult'); eipProvider.emit('message', testData); expect(processResult).toHaveBeenCalledWith(testData.data.result); }); it('data without result for eipProvider', async () => { + const sub = new ExampleSubscription( + { param1: 'param1' }, + subscriptionManagerWithEipReqMan, + ); + await sub.subscribe(); const testData = { data: { + subscription: sub.id, other: { some: 1, }, }, }; - const sub = new ExampleSubscription( - { param1: 'param1' }, - { requestManager: eipRequestManager }, - ); - await sub.subscribe(); - // @ts-expect-error spy on protected method - const processResult = jest.spyOn(sub, '_processSubscriptionResult'); + const processResult = jest.spyOn(sub, 'processSubscriptionResult'); eipProvider.emit('message', testData); expect(processResult).toHaveBeenCalledWith(testData.data); }); diff --git a/packages/web3-eth-accounts/test/config/setup.js b/packages/web3-eth-accounts/test/config/setup.js index 0b6b9109ce0..b3c35155474 100644 --- a/packages/web3-eth-accounts/test/config/setup.js +++ b/packages/web3-eth-accounts/test/config/setup.js @@ -22,3 +22,7 @@ require('jest-extended'); // @todo extend jest to have "toHaveBeenCalledOnceWith" matcher. process.env.NODE_ENV = 'test'; + +const jestTimeout = 10000; + +jest.setTimeout(jestTimeout); diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 7abe1d6c163..3f94447c03b 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -15,7 +15,13 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { Web3Context, Web3EventEmitter, Web3PromiEvent, Web3ConfigEvent } from 'web3-core'; +import { + Web3Context, + Web3EventEmitter, + Web3PromiEvent, + Web3ConfigEvent, + Web3SubscriptionManager, +} from 'web3-core'; import { ContractExecutionError, ContractTransactionDataAndInputError, @@ -1140,7 +1146,11 @@ export class Contract abi, jsonInterface: this._jsonInterface, }, - { requestManager: this.requestManager, returnFormat }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + this.subscriptionManager as Web3SubscriptionManager, + { + returnFormat, + }, ); if (!isNullish(fromBlock)) { // emit past events when fromBlock is defined diff --git a/packages/web3-eth-contract/src/log_subscription.ts b/packages/web3-eth-contract/src/log_subscription.ts index 3ae67b92cd2..3eb82ea4632 100644 --- a/packages/web3-eth-contract/src/log_subscription.ts +++ b/packages/web3-eth-contract/src/log_subscription.ts @@ -16,7 +16,7 @@ along with web3.js. If not, see . */ import { AbiEventFragment, LogsInput, HexString, Topic, DataFormat } from 'web3-types'; -import { Web3RequestManager, Web3Subscription } from 'web3-core'; +import { Web3Subscription, Web3SubscriptionManager } from 'web3-core'; // eslint-disable-next-line import/no-cycle import { decodeEventABI } from './encoding.js'; // eslint-disable-next-line import/no-cycle @@ -112,12 +112,12 @@ export class LogsSubscription extends Web3Subscription< abi: AbiEventFragment & { signature: HexString }; jsonInterface: ContractAbiWithSignature; }, + subscriptionManager: Web3SubscriptionManager, options: { - requestManager: Web3RequestManager; returnFormat?: DataFormat; }, ) { - super(args, options); + super(args, subscriptionManager, options); this.address = args.address; this.topics = args.topics; @@ -132,7 +132,7 @@ export class LogsSubscription extends Web3Subscription< ]; } - protected _processSubscriptionResult(data: LogsInput): void { + public processSubscriptionResult(data: LogsInput): void { const decoded = decodeEventABI(this.abi, data, this.jsonInterface, super.returnFormat); this.emit('data', decoded); } diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts index 0fbf506090b..97673702ccd 100644 --- a/packages/web3-eth/src/web3_eth.ts +++ b/packages/web3-eth/src/web3_eth.ts @@ -1590,11 +1590,7 @@ export class Web3Eth extends Web3Context[0], returnFormat: ReturnType = DEFAULT_RETURN_FORMAT as ReturnType, ): Promise> { - const subscription = (await this.subscriptionManager?.subscribe( - name, - args, - returnFormat, - )) as InstanceType; + const subscription = await this.subscriptionManager?.subscribe(name, args, returnFormat); if ( subscription instanceof LogsSubscription && name === 'logs' && @@ -1606,11 +1602,11 @@ export class Web3Eth extends Web3Context { for (const log of logs) { - subscription._processSubscriptionResult(log as LogsOutput); + subscription.processSubscriptionResult(log as LogsOutput); } }) .catch(e => { - subscription._processSubscriptionError(e as Error); + subscription.processSubscriptionError(e as Error); }); }); } diff --git a/packages/web3-eth/src/web3_subscriptions.ts b/packages/web3-eth/src/web3_subscriptions.ts index 5e312c0c72b..ee14a3ebae5 100644 --- a/packages/web3-eth/src/web3_subscriptions.ts +++ b/packages/web3-eth/src/web3_subscriptions.ts @@ -58,11 +58,11 @@ export class LogsSubscription extends Web3Subscription< return ['logs', this.args] as ['logs', any]; } - public _processSubscriptionResult(data: LogsOutput) { + public processSubscriptionResult(data: LogsOutput) { this.emit('data', format(logSchema, data, super.returnFormat)); } - public _processSubscriptionError(error: Error) { + public processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -87,11 +87,11 @@ export class NewPendingTransactionsSubscription extends Web3Subscription< return ['newPendingTransactions'] as ['newPendingTransactions']; } - protected _processSubscriptionResult(data: string) { + public processSubscriptionResult(data: string) { this.emit('data', format({ format: 'string' }, data, super.returnFormat)); } - protected _processSubscriptionError(error: Error) { + public processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -135,11 +135,11 @@ export class NewHeadsSubscription extends Web3Subscription< return ['newHeads'] as ['newHeads']; } - protected _processSubscriptionResult(data: BlockHeaderOutput) { + public processSubscriptionResult(data: BlockHeaderOutput) { this.emit('data', format(blockHeaderSchema, data, super.returnFormat)); } - protected _processSubscriptionError(error: Error) { + public processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -174,7 +174,7 @@ export class SyncingSubscription extends Web3Subscription< return ['syncing'] as ['syncing']; } - protected _processSubscriptionResult( + public processSubscriptionResult( data: | { syncing: boolean; @@ -197,7 +197,7 @@ export class SyncingSubscription extends Web3Subscription< } } - protected _processSubscriptionError(error: Error) { + public processSubscriptionError(error: Error) { this.emit('error', error); } } diff --git a/packages/web3-eth/test/integration/setup.js b/packages/web3-eth/test/integration/setup.js index 3982381b2ad..19e711c511e 100644 --- a/packages/web3-eth/test/integration/setup.js +++ b/packages/web3-eth/test/integration/setup.js @@ -19,6 +19,6 @@ along with web3.js. If not, see . // eslint-disable-next-line @typescript-eslint/no-require-imports require('../config/setup'); -const jestTimeout = process.env.WEB3_SYSTEM_TEST_PROVIDER.includes('ipc') ? 30000 : 20000; +const jestTimeout = process.env.WEB3_SYSTEM_TEST_PROVIDER.includes('ipc') ? 60000 : 50000; jest.setTimeout(jestTimeout); diff --git a/packages/web3-eth/test/unit/web3_eth_subscription.test.ts b/packages/web3-eth/test/unit/web3_eth_subscription.test.ts index 3633fd79fa6..c7da4f7b57c 100644 --- a/packages/web3-eth/test/unit/web3_eth_subscription.test.ts +++ b/packages/web3-eth/test/unit/web3_eth_subscription.test.ts @@ -14,7 +14,7 @@ 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 { Web3RequestManager, Web3SubscriptionManager } from 'web3-core'; +import { Web3SubscriptionManager } from 'web3-core'; import { Web3BaseProvider } from 'web3-types'; import * as rpcMethodWrappers from '../../src/rpc_method_wrappers'; import { LogsSubscription } from '../../src'; @@ -28,7 +28,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { let web3Eth: Web3Eth; it('should return the subscription data provided by the Subscription Manager', async () => { - const requestManager = { send: jest.fn(), on: jest.fn(), provider: jest.fn() }; + const requestManager = { send: jest.fn(), on: jest.fn(), provider: { on: jest.fn() } }; const subManager = new Web3SubscriptionManager(requestManager as any, undefined as any); const dummyLogs = { logs: { test1: 'test1' } }; @@ -45,16 +45,11 @@ describe('Web3Eth subscribe and clear subscriptions', () => { expect(logs).toStrictEqual(dummyLogs); }); - it('should call `_processSubscriptionResult` when the logs are of type LogsSubscription and the `fromBlock` is provided', async () => { - const requestManager = { send: jest.fn(), on: jest.fn(), provider: jest.fn() }; + it('should call `processSubscriptionResult` when the logs are of type LogsSubscription and the `fromBlock` is provided', async () => { + const requestManager = { send: jest.fn(), on: jest.fn(), provider: { on: jest.fn() } }; const subManager = new Web3SubscriptionManager(requestManager as any, undefined as any); - const dummyLogs = new LogsSubscription( - {}, - { - requestManager: requestManager as unknown as Web3RequestManager, - }, - ); + const dummyLogs = new LogsSubscription({}, subManager); jest.spyOn(subManager, 'subscribe').mockResolvedValueOnce(dummyLogs); jest.spyOn(rpcMethodWrappers, 'getLogs').mockResolvedValueOnce(mockGetLogsRpcResponse); @@ -64,7 +59,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { } as unknown as Web3BaseProvider, subscriptionManager: subManager, }); - jest.spyOn(dummyLogs, '_processSubscriptionResult'); + jest.spyOn(dummyLogs, 'processSubscriptionResult'); const logs = await web3Eth.subscribe('logs', { fromBlock: 0, @@ -72,11 +67,11 @@ describe('Web3Eth subscribe and clear subscriptions', () => { await sleep(100); expect(logs).toStrictEqual(dummyLogs); - expect(dummyLogs._processSubscriptionResult).toHaveBeenCalled(); + expect(dummyLogs.processSubscriptionResult).toHaveBeenCalled(); }); it('should be able to clear subscriptions', async () => { - const requestManager = { send: jest.fn(), on: jest.fn(), provider: jest.fn() }; + const requestManager = { send: jest.fn(), on: jest.fn(), provider: { on: jest.fn() } }; const subManager = new Web3SubscriptionManager(requestManager as any, undefined as any); jest.spyOn(subManager, 'unsubscribe'); @@ -94,7 +89,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { }); it('should be able to clear subscriptions and pass `shouldClearSubscription` when passing `notClearSyncing`', async () => { - const requestManager = { send: jest.fn(), on: jest.fn(), provider: jest.fn() }; + const requestManager = { send: jest.fn(), on: jest.fn(), provider: { on: jest.fn() } }; const subManager = new Web3SubscriptionManager(requestManager as any, undefined as any); jest.spyOn(subManager, 'unsubscribe'); diff --git a/packages/web3-types/src/web3_base_provider.ts b/packages/web3-types/src/web3_base_provider.ts index 2db1b9bee37..03e51bbcb35 100644 --- a/packages/web3-types/src/web3_base_provider.ts +++ b/packages/web3-types/src/web3_base_provider.ts @@ -94,7 +94,21 @@ export interface LegacyRequestProvider { ): void; } +export interface ProviderInfo { + chainId: string; +} + +export type ProviderChainId = string; + +export type ProviderAccounts = string[]; + export interface EIP1193Provider { + on(event: 'connect', listener: (info: ProviderInfo) => void): void; + on(event: 'disconnect', listener: (error: ProviderRpcError) => void): void; + on(event: 'message', listener: (message: ProviderMessage) => void): void; + on(event: 'chainChanged', listener: (chainId: ProviderChainId) => void): void; + on(event: 'accountsChanged', listener: (accounts: ProviderAccounts) => void): void; + request, ResponseType = Web3APIReturnType>( args: Web3APIPayload, ): Promise | unknown>; diff --git a/packages/web3/test/e2e/e2e_utils.ts b/packages/web3/test/e2e/e2e_utils.ts index 40fdc325265..f332ce9e92d 100644 --- a/packages/web3/test/e2e/e2e_utils.ts +++ b/packages/web3/test/e2e/e2e_utils.ts @@ -52,7 +52,7 @@ export const getE2ETestAccountAddress = (): string => { export const getE2ETestContractAddress = () => secrets[getSystemTestBackend().toUpperCase() as 'SEPOLIA' | 'MAINNET'] - .DEPLOYED_TEST_CONTRACT_ADDRESS; + .DEPLOYED_TEST_CONTRACT_ADDRESS as string; export const getAllowedSendTransaction = (): boolean => { if (process.env.ALLOWED_SEND_TRANSACTION !== undefined) { From 5801c4430873835c2a4a79696f2f405806576220 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 13:36:35 +0200 Subject: [PATCH 02/15] add `SimpleProvider` the base of `EIP1193Provider` --- packages/web3-core/src/utils.ts | 3 ++- packages/web3-types/src/web3_base_provider.ts | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/web3-core/src/utils.ts b/packages/web3-core/src/utils.ts index 30eafa16fe2..3107da22a12 100644 --- a/packages/web3-core/src/utils.ts +++ b/packages/web3-core/src/utils.ts @@ -54,7 +54,8 @@ export const isLegacySendAsyncProvider = ( export const isSupportedProvider = ( provider: SupportedProviders, ): provider is SupportedProviders => - Web3BaseProvider.isWeb3Provider(provider) || + isWeb3Provider(provider) || + isEIP1193Provider(provider) || isLegacyRequestProvider(provider) || isLegacySendAsyncProvider(provider) || isLegacySendProvider(provider); diff --git a/packages/web3-types/src/web3_base_provider.ts b/packages/web3-types/src/web3_base_provider.ts index 03e51bbcb35..ab3fe137fce 100644 --- a/packages/web3-types/src/web3_base_provider.ts +++ b/packages/web3-types/src/web3_base_provider.ts @@ -94,6 +94,12 @@ export interface LegacyRequestProvider { ): void; } +export interface SimpleProvider { + request, ResponseType = Web3APIReturnType>( + args: Web3APIPayload, + ): Promise | unknown>; +} + export interface ProviderInfo { chainId: string; } @@ -102,16 +108,12 @@ export type ProviderChainId = string; export type ProviderAccounts = string[]; -export interface EIP1193Provider { +export interface EIP1193Provider extends SimpleProvider { on(event: 'connect', listener: (info: ProviderInfo) => void): void; on(event: 'disconnect', listener: (error: ProviderRpcError) => void): void; on(event: 'message', listener: (message: ProviderMessage) => void): void; on(event: 'chainChanged', listener: (chainId: ProviderChainId) => void): void; on(event: 'accountsChanged', listener: (accounts: ProviderAccounts) => void): void; - - request, ResponseType = Web3APIReturnType>( - args: Web3APIPayload, - ): Promise | unknown>; } // Provider interface compatible with EIP-1193 @@ -258,7 +260,8 @@ export type SupportedProviders = | Web3BaseProvider | LegacyRequestProvider | LegacySendProvider - | LegacySendAsyncProvider; + | LegacySendAsyncProvider + | SimpleProvider; export type Web3BaseProviderConstructor = new ( url: string, From ed9aefb3f2915346057c836da4052966829b11d9 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 15:16:08 +0200 Subject: [PATCH 03/15] ignore lint issue --- packages/web3/test/e2e/e2e_utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3/test/e2e/e2e_utils.ts b/packages/web3/test/e2e/e2e_utils.ts index f332ce9e92d..8001e72fc55 100644 --- a/packages/web3/test/e2e/e2e_utils.ts +++ b/packages/web3/test/e2e/e2e_utils.ts @@ -51,6 +51,7 @@ export const getE2ETestAccountAddress = (): string => { }; export const getE2ETestContractAddress = () => + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion secrets[getSystemTestBackend().toUpperCase() as 'SEPOLIA' | 'MAINNET'] .DEPLOYED_TEST_CONTRACT_ADDRESS as string; From 2ff5a008b5d6aba81405486e6706070073561c88 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 15:25:48 +0200 Subject: [PATCH 04/15] revert a change to a method --- packages/web3-core/src/web3_subscriptions.ts | 2 +- packages/web3-eth/src/web3_eth.ts | 2 +- packages/web3-eth/src/web3_subscriptions.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web3-core/src/web3_subscriptions.ts b/packages/web3-core/src/web3_subscriptions.ts index 38f2ceb6f72..372b67a26cc 100644 --- a/packages/web3-core/src/web3_subscriptions.ts +++ b/packages/web3-core/src/web3_subscriptions.ts @@ -103,7 +103,7 @@ export abstract class Web3Subscription< } // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars - public processSubscriptionError(_err: Error) { + protected _processSubscriptionError(_err: Error) { // Do nothing - This should be overridden in subclass. } diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts index 97673702ccd..bc12eea5421 100644 --- a/packages/web3-eth/src/web3_eth.ts +++ b/packages/web3-eth/src/web3_eth.ts @@ -1606,7 +1606,7 @@ export class Web3Eth extends Web3Context { - subscription.processSubscriptionError(e as Error); + subscription._processSubscriptionError(e as Error); }); }); } diff --git a/packages/web3-eth/src/web3_subscriptions.ts b/packages/web3-eth/src/web3_subscriptions.ts index ee14a3ebae5..9eff781f416 100644 --- a/packages/web3-eth/src/web3_subscriptions.ts +++ b/packages/web3-eth/src/web3_subscriptions.ts @@ -62,7 +62,7 @@ export class LogsSubscription extends Web3Subscription< this.emit('data', format(logSchema, data, super.returnFormat)); } - public processSubscriptionError(error: Error) { + protected _processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -91,7 +91,7 @@ export class NewPendingTransactionsSubscription extends Web3Subscription< this.emit('data', format({ format: 'string' }, data, super.returnFormat)); } - public processSubscriptionError(error: Error) { + protected _processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -139,7 +139,7 @@ export class NewHeadsSubscription extends Web3Subscription< this.emit('data', format(blockHeaderSchema, data, super.returnFormat)); } - public processSubscriptionError(error: Error) { + protected _processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -197,7 +197,7 @@ export class SyncingSubscription extends Web3Subscription< } } - public processSubscriptionError(error: Error) { + protected _processSubscriptionError(error: Error) { this.emit('error', error); } } From 4d533cb97b6711c072448dfa182e46026cef4ce6 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 15:41:18 +0200 Subject: [PATCH 05/15] revert some MR changes --- packages/web3-core/src/web3_subscription_manager.ts | 4 ++-- packages/web3-core/src/web3_subscriptions.ts | 2 +- .../test/unit/web3_subscription_old_providers.test.ts | 8 ++++---- packages/web3-eth-contract/src/log_subscription.ts | 2 +- packages/web3-eth/src/web3_eth.ts | 2 +- packages/web3-eth/src/web3_subscriptions.ts | 10 +++++----- .../web3-eth/test/unit/web3_eth_subscription.test.ts | 6 +++--- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index 5c12e1f77fe..e9bca8800b4 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -126,14 +126,14 @@ export class Web3SubscriptionManager< if (sub) { // for EIP-1193 provider if (data?.data) { - sub.processSubscriptionResult(data?.data?.result ?? data?.data); + sub._processSubscriptionResult(data?.data?.result ?? data?.data); } else if ( data && jsonRpc.isResponseWithNotification( data as unknown as JsonRpcSubscriptionResult | JsonRpcNotification, ) ) { - sub.processSubscriptionResult(data?.params.result); + sub._processSubscriptionResult(data?.params.result); } } } diff --git a/packages/web3-core/src/web3_subscriptions.ts b/packages/web3-core/src/web3_subscriptions.ts index 372b67a26cc..b32763108ba 100644 --- a/packages/web3-core/src/web3_subscriptions.ts +++ b/packages/web3-core/src/web3_subscriptions.ts @@ -98,7 +98,7 @@ export abstract class Web3Subscription< } // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars - public processSubscriptionResult(_data: unknown) { + protected _processSubscriptionResult(_data: unknown) { // Do nothing - This should be overridden in subclass. } diff --git a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts index 0b17d2e01ab..35b29b81814 100644 --- a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts +++ b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts @@ -63,7 +63,7 @@ describe('Web3Subscription', () => { }, }, }; - const processResult = jest.spyOn(sub, 'processSubscriptionResult'); + const processResult = jest.spyOn(sub, '_processSubscriptionResult'); provider.emit('data', testData); expect(processResult).toHaveBeenCalledWith(testData.data.result); }); @@ -79,7 +79,7 @@ describe('Web3Subscription', () => { }, }, }; - const processResult = jest.spyOn(sub, 'processSubscriptionResult'); + const processResult = jest.spyOn(sub, '_processSubscriptionResult'); provider.emit('data', testData); expect(processResult).toHaveBeenCalledWith(testData.data); }); @@ -97,7 +97,7 @@ describe('Web3Subscription', () => { }, }, }; - const processResult = jest.spyOn(sub, 'processSubscriptionResult'); + const processResult = jest.spyOn(sub, '_processSubscriptionResult'); eipProvider.emit('message', testData); expect(processResult).toHaveBeenCalledWith(testData.data.result); }); @@ -116,7 +116,7 @@ describe('Web3Subscription', () => { }, }, }; - const processResult = jest.spyOn(sub, 'processSubscriptionResult'); + const processResult = jest.spyOn(sub, '_processSubscriptionResult'); eipProvider.emit('message', testData); expect(processResult).toHaveBeenCalledWith(testData.data); }); diff --git a/packages/web3-eth-contract/src/log_subscription.ts b/packages/web3-eth-contract/src/log_subscription.ts index 3eb82ea4632..64780741f72 100644 --- a/packages/web3-eth-contract/src/log_subscription.ts +++ b/packages/web3-eth-contract/src/log_subscription.ts @@ -132,7 +132,7 @@ export class LogsSubscription extends Web3Subscription< ]; } - public processSubscriptionResult(data: LogsInput): void { + public _processSubscriptionResult(data: LogsInput): void { const decoded = decodeEventABI(this.abi, data, this.jsonInterface, super.returnFormat); this.emit('data', decoded); } diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts index bc12eea5421..d76b2678ed6 100644 --- a/packages/web3-eth/src/web3_eth.ts +++ b/packages/web3-eth/src/web3_eth.ts @@ -1602,7 +1602,7 @@ export class Web3Eth extends Web3Context { for (const log of logs) { - subscription.processSubscriptionResult(log as LogsOutput); + subscription._processSubscriptionResult(log as LogsOutput); } }) .catch(e => { diff --git a/packages/web3-eth/src/web3_subscriptions.ts b/packages/web3-eth/src/web3_subscriptions.ts index 9eff781f416..5e312c0c72b 100644 --- a/packages/web3-eth/src/web3_subscriptions.ts +++ b/packages/web3-eth/src/web3_subscriptions.ts @@ -58,11 +58,11 @@ export class LogsSubscription extends Web3Subscription< return ['logs', this.args] as ['logs', any]; } - public processSubscriptionResult(data: LogsOutput) { + public _processSubscriptionResult(data: LogsOutput) { this.emit('data', format(logSchema, data, super.returnFormat)); } - protected _processSubscriptionError(error: Error) { + public _processSubscriptionError(error: Error) { this.emit('error', error); } } @@ -87,7 +87,7 @@ export class NewPendingTransactionsSubscription extends Web3Subscription< return ['newPendingTransactions'] as ['newPendingTransactions']; } - public processSubscriptionResult(data: string) { + protected _processSubscriptionResult(data: string) { this.emit('data', format({ format: 'string' }, data, super.returnFormat)); } @@ -135,7 +135,7 @@ export class NewHeadsSubscription extends Web3Subscription< return ['newHeads'] as ['newHeads']; } - public processSubscriptionResult(data: BlockHeaderOutput) { + protected _processSubscriptionResult(data: BlockHeaderOutput) { this.emit('data', format(blockHeaderSchema, data, super.returnFormat)); } @@ -174,7 +174,7 @@ export class SyncingSubscription extends Web3Subscription< return ['syncing'] as ['syncing']; } - public processSubscriptionResult( + protected _processSubscriptionResult( data: | { syncing: boolean; diff --git a/packages/web3-eth/test/unit/web3_eth_subscription.test.ts b/packages/web3-eth/test/unit/web3_eth_subscription.test.ts index c7da4f7b57c..069b8dcdb11 100644 --- a/packages/web3-eth/test/unit/web3_eth_subscription.test.ts +++ b/packages/web3-eth/test/unit/web3_eth_subscription.test.ts @@ -45,7 +45,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { expect(logs).toStrictEqual(dummyLogs); }); - it('should call `processSubscriptionResult` when the logs are of type LogsSubscription and the `fromBlock` is provided', async () => { + it('should call `_processSubscriptionResult` when the logs are of type LogsSubscription and the `fromBlock` is provided', async () => { const requestManager = { send: jest.fn(), on: jest.fn(), provider: { on: jest.fn() } }; const subManager = new Web3SubscriptionManager(requestManager as any, undefined as any); @@ -59,7 +59,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { } as unknown as Web3BaseProvider, subscriptionManager: subManager, }); - jest.spyOn(dummyLogs, 'processSubscriptionResult'); + jest.spyOn(dummyLogs, '_processSubscriptionResult'); const logs = await web3Eth.subscribe('logs', { fromBlock: 0, @@ -67,7 +67,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { await sleep(100); expect(logs).toStrictEqual(dummyLogs); - expect(dummyLogs.processSubscriptionResult).toHaveBeenCalled(); + expect(dummyLogs._processSubscriptionResult).toHaveBeenCalled(); }); it('should be able to clear subscriptions', async () => { From f090d16432e4966ecd604c042c058f085fac2c03 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 16:03:06 +0200 Subject: [PATCH 06/15] fix build error --- .../src/web3_subscription_manager.ts | 16 ++--------- packages/web3-core/src/web3_subscriptions.ts | 27 +++++++++++++++++-- .../web3-eth-contract/src/log_subscription.ts | 2 +- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index e9bca8800b4..70a617b4e87 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -27,7 +27,7 @@ import { Web3BaseProvider, } from 'web3-types'; import { ProviderError, SubscriptionError } from 'web3-errors'; -import { jsonRpc, isNullish } from 'web3-utils'; +import { isNullish } from 'web3-utils'; import { isSupportSubscriptions } from './utils.js'; import { Web3RequestManager, Web3RequestManagerEvent } from './web3_request_manager.js'; // eslint-disable-next-line import/no-cycle @@ -123,19 +123,7 @@ export class Web3SubscriptionManager< // Process if the received data is related to a subscription if (subscriptionId) { const sub = this._subscriptions.get(subscriptionId); - if (sub) { - // for EIP-1193 provider - if (data?.data) { - sub._processSubscriptionResult(data?.data?.result ?? data?.data); - } else if ( - data && - jsonRpc.isResponseWithNotification( - data as unknown as JsonRpcSubscriptionResult | JsonRpcNotification, - ) - ) { - sub._processSubscriptionResult(data?.params.result); - } - } + sub?.processSubscriptionData(data); } } /** diff --git a/packages/web3-core/src/web3_subscriptions.ts b/packages/web3-core/src/web3_subscriptions.ts index b32763108ba..81bc4e394da 100644 --- a/packages/web3-core/src/web3_subscriptions.ts +++ b/packages/web3-core/src/web3_subscriptions.ts @@ -21,14 +21,18 @@ import { DEFAULT_RETURN_FORMAT, DataFormat, EthExecutionAPI, + JsonRpcSubscriptionResult, + JsonRpcSubscriptionResultOld, + JsonRpcNotification, + Log, HexString, Web3APIParams, Web3APISpec, } from 'web3-types'; -import { Web3EventEmitter, Web3EventMap } from './web3_event_emitter.js'; - +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'; export abstract class Web3Subscription< EventMap extends Web3EventMap, @@ -65,6 +69,25 @@ export abstract class Web3Subscription< 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', diff --git a/packages/web3-eth-contract/src/log_subscription.ts b/packages/web3-eth-contract/src/log_subscription.ts index 64780741f72..d2c0d5398b7 100644 --- a/packages/web3-eth-contract/src/log_subscription.ts +++ b/packages/web3-eth-contract/src/log_subscription.ts @@ -132,7 +132,7 @@ export class LogsSubscription extends Web3Subscription< ]; } - public _processSubscriptionResult(data: LogsInput): void { + protected _processSubscriptionResult(data: LogsInput): void { const decoded = decodeEventABI(this.abi, data, this.jsonInterface, super.returnFormat); this.emit('data', decoded); } From 8de66454788d16430f890dddb3587e35e83df478 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 16:09:45 +0200 Subject: [PATCH 07/15] revert some MR changes --- packages/web3-core/src/web3_subscription_manager.ts | 4 +--- .../test/unit/web3_subscription_old_providers.test.ts | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index 70a617b4e87..e489bfa2eeb 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -212,9 +212,7 @@ export class Web3SubscriptionManager< } if (!this._subscriptions.has(id)) { - throw new SubscriptionError( - `Failed to remove Subscription. Subscription with id "${id.toString()}" does not exists`, - ); + throw new SubscriptionError(`Subscription with id "${id.toString()}" does not exists`); } await sub.sendUnsubscribeRequest(); diff --git a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts index 35b29b81814..cf6391887fc 100644 --- a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts +++ b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts @@ -63,6 +63,7 @@ describe('Web3Subscription', () => { }, }, }; + // @ts-expect-error spy on protected method const processResult = jest.spyOn(sub, '_processSubscriptionResult'); provider.emit('data', testData); expect(processResult).toHaveBeenCalledWith(testData.data.result); @@ -79,6 +80,7 @@ describe('Web3Subscription', () => { }, }, }; + // @ts-expect-error spy on protected method const processResult = jest.spyOn(sub, '_processSubscriptionResult'); provider.emit('data', testData); expect(processResult).toHaveBeenCalledWith(testData.data); @@ -97,6 +99,7 @@ describe('Web3Subscription', () => { }, }, }; + // @ts-expect-error spy on protected method const processResult = jest.spyOn(sub, '_processSubscriptionResult'); eipProvider.emit('message', testData); expect(processResult).toHaveBeenCalledWith(testData.data.result); @@ -116,6 +119,7 @@ describe('Web3Subscription', () => { }, }, }; + // @ts-expect-error spy on protected method const processResult = jest.spyOn(sub, '_processSubscriptionResult'); eipProvider.emit('message', testData); expect(processResult).toHaveBeenCalledWith(testData.data); From 08bb7b31000c8b8fba76294506c338f49eb59238 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:04:46 +0200 Subject: [PATCH 08/15] update CHANGELOG.md --- packages/web3-core/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3-core/CHANGELOG.md b/packages/web3-core/CHANGELOG.md index 9e89681e04d..59dfc00589b 100644 --- a/packages/web3-core/CHANGELOG.md +++ b/packages/web3-core/CHANGELOG.md @@ -124,3 +124,4 @@ Documentation: ### Fixed - Fixed Batch requests erroring out on one request (#6164) +- Fixed the issue: Subscribing to multiple blockchain events causes every listener to be fired for every registered event (#6210). From ea722de0388e7d700f37a4db754808c52e4d8c71 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:21:17 +0200 Subject: [PATCH 09/15] enable backward compatibility for subscriptions + mark the obsolete as deprecated --- .../src/web3_subscription_manager.ts | 28 +++++-- packages/web3-core/src/web3_subscriptions.ts | 48 +++++++++-- .../__snapshots__/web3_context.test.ts.snap | 1 + .../test/unit/web3_subscription.test.ts | 83 ++++++++++++++++++- .../unit/web3_subscription_manager.test.ts | 22 +++-- .../web3_subscription_old_providers.test.ts | 8 +- packages/web3-eth-contract/src/contract.ts | 7 +- .../web3-eth-contract/src/log_subscription.ts | 10 ++- 8 files changed, 172 insertions(+), 35 deletions(-) diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index e489bfa2eeb..399343c5aee 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -63,9 +63,22 @@ export class Web3SubscriptionManager< * const subscriptionManager = new Web3SubscriptionManager(requestManager, {}); * ``` */ + public constructor( + requestManager: Web3RequestManager, + registeredSubscriptions: RegisteredSubs, + ); + /** + * @deprecated This constructor overloading should not be used + */ + public constructor( + requestManager: Web3RequestManager, + registeredSubscriptions: RegisteredSubs, + tolerateUnlinkedSubscription: boolean, + ); public constructor( public readonly requestManager: Web3RequestManager, public readonly registeredSubscriptions: RegisteredSubs, + private readonly tolerateUnlinkedSubscription: boolean = false, ) { this.requestManager.on(Web3RequestManagerEvent.BEFORE_PROVIDER_CHANGE, async () => { await this.unsubscribe(); @@ -146,14 +159,11 @@ export class Web3SubscriptionManager< throw new SubscriptionError('Invalid subscription type'); } - const subscription = new Klass( - args ?? undefined, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - this as Web3SubscriptionManager, - { - returnFormat, - }, - ) as InstanceType; + const subscription = new Klass(args ?? undefined, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + subscriptionManager: this as Web3SubscriptionManager, + returnFormat, + }) as InstanceType; await this.addSubscription(subscription); @@ -211,7 +221,7 @@ export class Web3SubscriptionManager< ); } - if (!this._subscriptions.has(id)) { + if (!this._subscriptions.has(id) && !this.tolerateUnlinkedSubscription) { throw new SubscriptionError(`Subscription with id "${id.toString()}" does not exists`); } diff --git a/packages/web3-core/src/web3_subscriptions.ts b/packages/web3-core/src/web3_subscriptions.ts index 81bc4e394da..320cf1f4c36 100644 --- a/packages/web3-core/src/web3_subscriptions.ts +++ b/packages/web3-core/src/web3_subscriptions.ts @@ -30,9 +30,12 @@ import { Web3APISpec, } from 'web3-types'; import { jsonRpc } from 'web3-utils'; +import { SubscriptionError } from 'web3-errors'; + // 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'; export abstract class Web3Subscription< EventMap extends Web3EventMap, @@ -45,15 +48,46 @@ export abstract class Web3Subscription< 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, - subscriptionManager: Web3SubscriptionManager, - options?: { + options: ( + | { subscriptionManager: Web3SubscriptionManager; requestManager: never } + | { requestManager: Web3RequestManager; subscriptionManager: never } + ) & { returnFormat?: DataFormat; }, ) { super(); - this._subscriptionManager = subscriptionManager; + const { requestManager } = options; + const { subscriptionManager } = options; + if (requestManager && subscriptionManager) { + throw new SubscriptionError( + 'Only requestManager or subscriptionManager should be provided at Subscription constructor', + ); + } + if (!requestManager && !subscriptionManager) { + throw new SubscriptionError( + 'Either requestManager or subscriptionManager should be provided at Subscription constructor', + ); + } + 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); } @@ -65,7 +99,7 @@ export abstract class Web3Subscription< return this._lastBlock; } - public async subscribe() { + public async subscribe(): Promise { return this._subscriptionManager.addSubscription(this); } @@ -145,8 +179,10 @@ export type Web3SubscriptionConstructor< // 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, - subscriptionManager: Web3SubscriptionManager, - options: { + options: ( + | { subscriptionManager: Web3SubscriptionManager } + | { requestManager: Web3RequestManager } + ) & { returnFormat?: DataFormat; }, ) => SubscriptionType; diff --git a/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap b/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap index a69eb6a8bee..8053e771302 100644 --- a/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap +++ b/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap @@ -73,6 +73,7 @@ Object { }, "useRpcCallSpecification": undefined, }, + "tolerateUnlinkedSubscription": false, }, "wallet": undefined, } diff --git a/packages/web3-core/test/unit/web3_subscription.test.ts b/packages/web3-core/test/unit/web3_subscription.test.ts index 0a4c9e9b081..7a5efedaa04 100644 --- a/packages/web3-core/test/unit/web3_subscription.test.ts +++ b/packages/web3-core/test/unit/web3_subscription.test.ts @@ -26,7 +26,6 @@ describe('Web3Subscription', () => { let sub: ExampleSubscription; beforeEach(() => { - // const web3 = new Web3('http://localhost:8545'); requestManager = { send: jest.fn().mockImplementation(async () => { return 'sub-id'; @@ -35,11 +34,12 @@ describe('Web3Subscription', () => { provider: { on: jest.fn(), removeListener: jest.fn(), request: jest.fn() }, }; subscriptionManager = new Web3SubscriptionManager(requestManager, subscriptions); - - sub = new ExampleSubscription({ param1: 'value' }, subscriptionManager); }); describe('subscribe', () => { + beforeEach(() => { + sub = new ExampleSubscription({ param1: 'value' }, { subscriptionManager }); + }); it('should invoke request manager for subscription', async () => { await sub.subscribe(); @@ -69,7 +69,7 @@ describe('Web3Subscription', () => { describe('unsubscribe', () => { beforeEach(() => { - sub = new ExampleSubscription({ param1: 'value' }, subscriptionManager); + sub = new ExampleSubscription({ param1: 'value' }, { subscriptionManager }); sub['_id'] = 'sub-id'; subscriptionManager.subscriptions.set('sub-id', sub); }); @@ -91,3 +91,78 @@ describe('Web3Subscription', () => { }); }); }); + +describe('Web3Subscription without subscription manager - (deprecated)', () => { + let requestManager: any; + let sub: ExampleSubscription; + + beforeEach(() => { + requestManager = { + send: jest.fn().mockImplementation(async () => { + return 'sub-id'; + }), + on: jest.fn(), + provider: { on: jest.fn(), removeListener: jest.fn(), request: jest.fn() }, + }; + }); + describe('subscribe', () => { + beforeEach(() => { + // eslint-disable-next-line deprecation/deprecation + sub = new ExampleSubscription({ param1: 'value' }, { requestManager }); + }); + + it('should invoke request manager for subscription', async () => { + (requestManager.send as jest.Mock).mockResolvedValue('sub-id'); + await sub.subscribe(); + + expect(requestManager.send).toHaveBeenCalledTimes(1); + expect(requestManager.send).toHaveBeenCalledWith({ + method: 'eth_subscribe', + params: ['newHeads'], + }); + }); + + it('should set correct subscription id', async () => { + (requestManager.send as jest.Mock).mockResolvedValue('sub-id'); + + expect(sub.id).toBeUndefined(); + await sub.subscribe(); + expect(sub.id).toBe('sub-id'); + }); + + it('should start listening to the "message" event', async () => { + // requestManager.provider.on.mockClear(); + await sub.subscribe(); + + expect(requestManager.provider.on).toHaveBeenCalledTimes(1); + expect(requestManager.provider.on).toHaveBeenCalledWith( + 'message', + expect.any(Function), + ); + }); + }); + + describe('unsubscribe', () => { + beforeEach(() => { + // eslint-disable-next-line deprecation/deprecation + sub = new ExampleSubscription({ param1: 'value' }, { requestManager }); + sub['_id'] = 'sub-id'; + }); + + it('should invoke request manager to unsubscribe', async () => { + await sub.unsubscribe(); + + expect(requestManager.provider.on).toHaveBeenCalledTimes(1); + expect(requestManager.provider.on).toHaveBeenCalledWith( + 'message', + expect.any(Function), + ); + }); + + it('should remove the subscription id', async () => { + expect(sub.id).toBe('sub-id'); + await sub.unsubscribe(); + expect(sub.id).toBeUndefined(); + }); + }); +}); diff --git a/packages/web3-core/test/unit/web3_subscription_manager.test.ts b/packages/web3-core/test/unit/web3_subscription_manager.test.ts index 1ba58390868..403719b292a 100644 --- a/packages/web3-core/test/unit/web3_subscription_manager.test.ts +++ b/packages/web3-core/test/unit/web3_subscription_manager.test.ts @@ -122,9 +122,10 @@ describe('Web3SubscriptionManager', () => { expect(result).toBeInstanceOf(ExampleSubscription); expect(ExampleSubscription).toHaveBeenCalledTimes(1); - expect(ExampleSubscription).toHaveBeenCalledWith({ test1: 'test1' }, subManager, { - returnFormat: DEFAULT_RETURN_FORMAT, - }); + expect(ExampleSubscription).toHaveBeenCalledWith( + { test1: 'test1' }, + { subscriptionManager: subManager, returnFormat: DEFAULT_RETURN_FORMAT }, + ); }); }); @@ -134,7 +135,10 @@ describe('Web3SubscriptionManager', () => { beforeEach(() => { subManager = new Web3SubscriptionManager(requestManager, subscriptions); jest.spyOn(subManager, 'supportsSubscriptions').mockReturnValue(true); - sub = new ExampleSubscription({ param1: 'param1' }, subManager); + sub = new ExampleSubscription( + { param1: 'param1' }, + { subscriptionManager: subManager }, + ); (sub as any).id = '123'; }); @@ -148,7 +152,10 @@ describe('Web3SubscriptionManager', () => { }); it('should try to subscribe the subscription', async () => { - sub = new ExampleSubscription({ param1: 'param1' }, subManager); + sub = new ExampleSubscription( + { param1: 'param1' }, + { subscriptionManager: subManager }, + ); jest.spyOn(sub, 'sendSubscriptionRequest').mockImplementation(async () => { (sub as any).id = 'value'; return Promise.resolve(sub.id as string); @@ -174,7 +181,10 @@ describe('Web3SubscriptionManager', () => { beforeEach(async () => { subManager = new Web3SubscriptionManager(requestManager, subscriptions); jest.spyOn(subManager, 'supportsSubscriptions').mockReturnValue(true); - sub = new ExampleSubscription({ param1: 'param1' }, subManager); + sub = new ExampleSubscription( + { param1: 'param1' }, + { subscriptionManager: subManager }, + ); (sub as any).id = '123'; await subManager.addSubscription(sub); diff --git a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts index cf6391887fc..d82856d020a 100644 --- a/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts +++ b/packages/web3-core/test/unit/web3_subscription_old_providers.test.ts @@ -53,7 +53,7 @@ describe('Web3Subscription', () => { describe('providers response for old provider', () => { it('data with result', async () => { - const sub = new ExampleSubscription({ param1: 'param1' }, subscriptionManager); + const sub = new ExampleSubscription({ param1: 'param1' }, { subscriptionManager }); await sub.subscribe(); const testData = { data: { @@ -70,7 +70,7 @@ describe('Web3Subscription', () => { }); it('data without result for old provider', async () => { - const sub = new ExampleSubscription({ param1: 'param1' }, subscriptionManager); + const sub = new ExampleSubscription({ param1: 'param1' }, { subscriptionManager }); await sub.subscribe(); const testData = { data: { @@ -88,7 +88,7 @@ describe('Web3Subscription', () => { it('data with result for eipProvider', async () => { const sub = new ExampleSubscription( { param1: 'param1' }, - subscriptionManagerWithEipReqMan, + { subscriptionManager: subscriptionManagerWithEipReqMan }, ); await sub.subscribe(); const testData = { @@ -108,7 +108,7 @@ describe('Web3Subscription', () => { it('data without result for eipProvider', async () => { const sub = new ExampleSubscription( { param1: 'param1' }, - subscriptionManagerWithEipReqMan, + { subscriptionManager: subscriptionManagerWithEipReqMan }, ); await sub.subscribe(); const testData = { diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 3f94447c03b..054a2c0a142 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -1146,9 +1146,12 @@ export class Contract abi, jsonInterface: this._jsonInterface, }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - this.subscriptionManager as Web3SubscriptionManager, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + subscriptionManager: this.subscriptionManager as Web3SubscriptionManager< + unknown, + any + >, returnFormat, }, ); diff --git a/packages/web3-eth-contract/src/log_subscription.ts b/packages/web3-eth-contract/src/log_subscription.ts index d2c0d5398b7..2a938fb7080 100644 --- a/packages/web3-eth-contract/src/log_subscription.ts +++ b/packages/web3-eth-contract/src/log_subscription.ts @@ -16,7 +16,7 @@ along with web3.js. If not, see . */ import { AbiEventFragment, LogsInput, HexString, Topic, DataFormat } from 'web3-types'; -import { Web3Subscription, Web3SubscriptionManager } from 'web3-core'; +import { Web3RequestManager, Web3Subscription, Web3SubscriptionManager } from 'web3-core'; // eslint-disable-next-line import/no-cycle import { decodeEventABI } from './encoding.js'; // eslint-disable-next-line import/no-cycle @@ -112,12 +112,14 @@ export class LogsSubscription extends Web3Subscription< abi: AbiEventFragment & { signature: HexString }; jsonInterface: ContractAbiWithSignature; }, - subscriptionManager: Web3SubscriptionManager, - options: { + options: ( + | { subscriptionManager: Web3SubscriptionManager } + | { requestManager: Web3RequestManager } + ) & { returnFormat?: DataFormat; }, ) { - super(args, subscriptionManager, options); + super(args, options); this.address = args.address; this.topics = args.topics; From bc6ab8ae8319bae16382802ad90ae1b4ebf514a4 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 21 Jun 2023 00:29:37 +0200 Subject: [PATCH 10/15] update CHANGELOG.md --- packages/web3-core/CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/web3-core/CHANGELOG.md b/packages/web3-core/CHANGELOG.md index 59dfc00589b..257f4190cdb 100644 --- a/packages/web3-core/CHANGELOG.md +++ b/packages/web3-core/CHANGELOG.md @@ -121,7 +121,17 @@ Documentation: ## [Unreleased] +### Added + +- Web3Subscription constructor accept a Subscription Manager (as an alternative to accepting Request Manager that is now marked marked as deprecated) (#6210) + +### Changed + +- Web3Subscription constructor overloading that accept a Request Manager is marked as deprecated (#6210) + ### Fixed - Fixed Batch requests erroring out on one request (#6164) -- Fixed the issue: Subscribing to multiple blockchain events causes every listener to be fired for every registered event (#6210). +- Fixed the issue: Subscribing to multiple blockchain events causes every listener to be fired for every registered event (#6210) +- Fixed the issue: Unsubscribe at a Web3Subscription class will still have the id of the subscription at the Web3SubscriptionManager (#6210) +- Fixed the issue: A call to the provider is made for every subscription object. This means multiple subscription is made at the provider for each subscription object (#6210) From a6ec45fd89387dac42d820bc3ee8a5131b1017b3 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 21 Jun 2023 00:33:14 +0200 Subject: [PATCH 11/15] update CHANGELOG.md --- packages/web3-core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web3-core/CHANGELOG.md b/packages/web3-core/CHANGELOG.md index 257f4190cdb..edfab2e0d46 100644 --- a/packages/web3-core/CHANGELOG.md +++ b/packages/web3-core/CHANGELOG.md @@ -134,4 +134,4 @@ Documentation: - Fixed Batch requests erroring out on one request (#6164) - Fixed the issue: Subscribing to multiple blockchain events causes every listener to be fired for every registered event (#6210) - Fixed the issue: Unsubscribe at a Web3Subscription class will still have the id of the subscription at the Web3SubscriptionManager (#6210) -- Fixed the issue: A call to the provider is made for every subscription object. This means multiple subscription is made at the provider for each subscription object (#6210) +- Fixed the issue: A call to the provider is made for every subscription object (#6210) From b76d97bef202cc9a41e704e52f7b09f93a5411cf Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:38:44 +0200 Subject: [PATCH 12/15] fix build error --- .../src/web3_subscription_manager.ts | 6 +-- packages/web3-core/src/web3_subscriptions.ts | 39 ++++++++++++------- .../web3-eth-contract/src/log_subscription.ts | 26 ++++++++++++- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index 399343c5aee..3e12ad49620 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -159,11 +159,11 @@ export class Web3SubscriptionManager< throw new SubscriptionError('Invalid subscription type'); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const subscription = new Klass(args ?? undefined, { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - subscriptionManager: this as Web3SubscriptionManager, + subscriptionManager: this as Web3SubscriptionManager, returnFormat, - }) as InstanceType; + } as any) as InstanceType; await this.addSubscription(subscription); diff --git a/packages/web3-core/src/web3_subscriptions.ts b/packages/web3-core/src/web3_subscriptions.ts index 320cf1f4c36..a657920fa3d 100644 --- a/packages/web3-core/src/web3_subscriptions.ts +++ b/packages/web3-core/src/web3_subscriptions.ts @@ -62,15 +62,15 @@ export abstract class Web3Subscription< public constructor( public readonly args: ArgsType, options: ( - | { subscriptionManager: Web3SubscriptionManager; requestManager: never } - | { requestManager: Web3RequestManager; subscriptionManager: never } + | { subscriptionManager: Web3SubscriptionManager } + | { requestManager: Web3RequestManager } ) & { returnFormat?: DataFormat; }, ) { super(); - const { requestManager } = options; - const { subscriptionManager } = options; + const { requestManager } = options as { requestManager: Web3RequestManager }; + const { subscriptionManager } = options as { subscriptionManager: Web3SubscriptionManager }; if (requestManager && subscriptionManager) { throw new SubscriptionError( 'Only requestManager or subscriptionManager should be provided at Subscription constructor', @@ -175,14 +175,23 @@ 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 } - | { requestManager: Web3RequestManager } - ) & { - returnFormat?: DataFormat; - }, -) => SubscriptionType; +> = + | (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); diff --git a/packages/web3-eth-contract/src/log_subscription.ts b/packages/web3-eth-contract/src/log_subscription.ts index 2a938fb7080..7522d7e82f2 100644 --- a/packages/web3-eth-contract/src/log_subscription.ts +++ b/packages/web3-eth-contract/src/log_subscription.ts @@ -104,6 +104,29 @@ export class LogsSubscription extends Web3Subscription< public readonly jsonInterface: ContractAbiWithSignature; + public constructor( + args: { + address?: HexString; + // eslint-disable-next-line @typescript-eslint/ban-types + topics?: (Topic | Topic[] | null)[]; + abi: AbiEventFragment & { signature: HexString }; + jsonInterface: ContractAbiWithSignature; + }, + options: { subscriptionManager: Web3SubscriptionManager; returnFormat?: DataFormat }, + ); + /** + * @deprecated This constructor overloading should not be used + */ + public constructor( + args: { + address?: HexString; + // eslint-disable-next-line @typescript-eslint/ban-types + topics?: (Topic | Topic[] | null)[]; + abi: AbiEventFragment & { signature: HexString }; + jsonInterface: ContractAbiWithSignature; + }, + options: { requestManager: Web3RequestManager; returnFormat?: DataFormat }, + ); public constructor( args: { address?: HexString; @@ -119,7 +142,8 @@ export class LogsSubscription extends Web3Subscription< returnFormat?: DataFormat; }, ) { - super(args, options); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + super(args, options as any); this.address = args.address; this.topics = args.topics; From e83a2c8b216b421c48d7a9d4b561c57d4b8f4a4a Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:08:35 +0200 Subject: [PATCH 13/15] fix lint --- packages/web3-core/src/web3_subscription_manager.ts | 8 ++++---- packages/web3-eth/test/unit/web3_eth_subscription.test.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web3-core/src/web3_subscription_manager.ts b/packages/web3-core/src/web3_subscription_manager.ts index 3e12ad49620..8e76498e3c2 100644 --- a/packages/web3-core/src/web3_subscription_manager.ts +++ b/packages/web3-core/src/web3_subscription_manager.ts @@ -54,8 +54,8 @@ export class Web3SubscriptionManager< /** * - * @param requestManager - * @param registeredSubscriptions + * @param - requestManager + * @param - registeredSubscriptions * * @example * ```ts @@ -143,8 +143,8 @@ export class Web3SubscriptionManager< * Will create a new subscription * * @param name - The subscription you want to subscribe to - * @param args (optional) - Optional additional parameters, depending on the subscription type - * @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) - Specifies how the return data from the call should be formatted. + * @param args - Optional additional parameters, depending on the subscription type + * @param returnFormat- ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) - Specifies how the return data from the call should be formatted. * * Will subscribe to a specific topic (note: name) * @returns The subscription object diff --git a/packages/web3-eth/test/unit/web3_eth_subscription.test.ts b/packages/web3-eth/test/unit/web3_eth_subscription.test.ts index 069b8dcdb11..edbf28d215f 100644 --- a/packages/web3-eth/test/unit/web3_eth_subscription.test.ts +++ b/packages/web3-eth/test/unit/web3_eth_subscription.test.ts @@ -49,7 +49,7 @@ describe('Web3Eth subscribe and clear subscriptions', () => { const requestManager = { send: jest.fn(), on: jest.fn(), provider: { on: jest.fn() } }; const subManager = new Web3SubscriptionManager(requestManager as any, undefined as any); - const dummyLogs = new LogsSubscription({}, subManager); + const dummyLogs = new LogsSubscription({}, { subscriptionManager: subManager }); jest.spyOn(subManager, 'subscribe').mockResolvedValueOnce(dummyLogs); jest.spyOn(rpcMethodWrappers, 'getLogs').mockResolvedValueOnce(mockGetLogsRpcResponse); From 6c599860b93d4417597e8f43551ca44d3b13db55 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:41:31 +0200 Subject: [PATCH 14/15] add `removeListener` events to `EIP1193Provider` + update CHANGELOG + Fix some events types at `SocketProvider` --- packages/web3-types/CHANGELOG.md | 9 ++++++ packages/web3-types/src/web3_base_provider.ts | 13 ++++++++ packages/web3-utils/src/socket_provider.ts | 30 ++++++++++++++----- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/web3-types/CHANGELOG.md b/packages/web3-types/CHANGELOG.md index eae97aa7dde..533fed62170 100644 --- a/packages/web3-types/CHANGELOG.md +++ b/packages/web3-types/CHANGELOG.md @@ -121,6 +121,15 @@ Documentation: ## [Unreleased] +### Added + +- Added the `SimpleProvider` interface which has only `request(args)` method that is compatible with EIP-1193 (#6210) +- Added the `Eip1193EventName` type that contains the possible events names according to EIP-1193 (#6210) + +### Changed + +- The `EIP1193Provider` class has now all the events (for `on` and `removeListener`) according to EIP-1193 (#6210) + ### Fixed - Fixed bug #6185, now web3.js compiles on typescript v5 (#6195) diff --git a/packages/web3-types/src/web3_base_provider.ts b/packages/web3-types/src/web3_base_provider.ts index ab3fe137fce..6fa7ad18fa7 100644 --- a/packages/web3-types/src/web3_base_provider.ts +++ b/packages/web3-types/src/web3_base_provider.ts @@ -108,12 +108,25 @@ export type ProviderChainId = string; export type ProviderAccounts = string[]; +export type Eip1193EventName = + | 'connect' + | 'disconnect' + | 'message' + | 'chainChanged' + | 'accountsChanged'; + export interface EIP1193Provider extends SimpleProvider { on(event: 'connect', listener: (info: ProviderInfo) => void): void; on(event: 'disconnect', listener: (error: ProviderRpcError) => void): void; on(event: 'message', listener: (message: ProviderMessage) => void): void; on(event: 'chainChanged', listener: (chainId: ProviderChainId) => void): void; on(event: 'accountsChanged', listener: (accounts: ProviderAccounts) => void): void; + + removeListener(event: 'connect', listener: (info: ProviderInfo) => void): void; + removeListener(event: 'disconnect', listener: (error: ProviderRpcError) => void): void; + removeListener(event: 'message', listener: (message: ProviderMessage) => void): void; + removeListener(event: 'chainChanged', listener: (chainId: ProviderChainId) => void): void; + removeListener(event: 'accountsChanged', listener: (accounts: ProviderAccounts) => void): void; } // Provider interface compatible with EIP-1193 diff --git a/packages/web3-utils/src/socket_provider.ts b/packages/web3-utils/src/socket_provider.ts index 86017cfd5c7..3e0e4bd4bae 100644 --- a/packages/web3-utils/src/socket_provider.ts +++ b/packages/web3-utils/src/socket_provider.ts @@ -16,6 +16,7 @@ along with web3.js. If not, see . */ import { ConnectionEvent, + Eip1193EventName, EthExecutionAPI, JsonRpcBatchRequest, JsonRpcBatchResponse, @@ -223,8 +224,11 @@ export abstract class SocketProvider< listener: Web3Eip1193ProviderEventCallback | Web3ProviderEventCallback, ): void; public on( - type: string | 'disconnect' | 'connect' | 'chainChanged' | 'accountsChanged', - listener: Web3Eip1193ProviderEventCallback

| Web3ProviderEventCallback, + type: string | Eip1193EventName, + listener: + | Web3Eip1193ProviderEventCallback

+ | Web3ProviderMessageEventCallback + | Web3ProviderEventCallback, ): void { this._eventEmitter.on(type, listener); } @@ -249,15 +253,20 @@ export abstract class SocketProvider< ): void; public once( type: 'message', - listener: Web3Eip1193ProviderEventCallback | Web3ProviderEventCallback, + listener: + | Web3Eip1193ProviderEventCallback + | Web3ProviderMessageEventCallback, ): void; public once( type: string, listener: Web3Eip1193ProviderEventCallback | Web3ProviderEventCallback, ): void; public once( - type: string | 'disconnect' | 'connect' | 'chainChanged' | 'accountsChanged', - listener: Web3Eip1193ProviderEventCallback

| Web3ProviderEventCallback, + type: string | Eip1193EventName, + listener: + | Web3Eip1193ProviderEventCallback

+ | Web3ProviderMessageEventCallback + | Web3ProviderEventCallback, ): void { this._eventEmitter.once(type, listener); } @@ -285,15 +294,20 @@ export abstract class SocketProvider< ): void; public removeListener( type: 'message', - listener: Web3Eip1193ProviderEventCallback | Web3ProviderEventCallback, + listener: + | Web3Eip1193ProviderEventCallback + | Web3ProviderMessageEventCallback, ): void; public removeListener( type: string, listener: Web3Eip1193ProviderEventCallback | Web3ProviderEventCallback, ): void; public removeListener( - type: string | 'disconnect' | 'connect' | 'chainChanged' | 'accountsChanged', - listener: Web3Eip1193ProviderEventCallback

| Web3ProviderEventCallback, + type: string | Eip1193EventName, + listener: + | Web3Eip1193ProviderEventCallback

+ | Web3ProviderMessageEventCallback + | Web3ProviderEventCallback, ): void { this._eventEmitter.removeListener(type, listener); } From 8727ea1ec65113a00104f77b170554d269e4c026 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 22 Jun 2023 15:47:23 +0200 Subject: [PATCH 15/15] add and fix old test cases for multiple subscriptions --- .../integration/subscription_heads.test.ts | 21 +++- .../subscription_on_2_events.test.ts | 114 ++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 packages/web3-eth/test/integration/subscription_on_2_events.test.ts diff --git a/packages/web3-eth/test/integration/subscription_heads.test.ts b/packages/web3-eth/test/integration/subscription_heads.test.ts index b79e943d1dc..44ef600efc1 100644 --- a/packages/web3-eth/test/integration/subscription_heads.test.ts +++ b/packages/web3-eth/test/integration/subscription_heads.test.ts @@ -43,9 +43,9 @@ describeIf(isSocket)('subscription', () => { let times = 0; const pr = new Promise((resolve: Resolve, reject) => { sub.on('data', (data: BlockHeaderOutput) => { - if (data.parentHash) { - times += 1; - } + expect(typeof data.parentHash).toBe('string'); + + times += 1; expect(times).toBeGreaterThanOrEqual(times); if (times >= checkTxCount) { resolve(); @@ -65,12 +65,25 @@ describeIf(isSocket)('subscription', () => { await web3.eth.subscriptionManager?.removeSubscription(sub); await closeOpenConnection(web3.eth); }); - it(`clear`, async () => { + it(`remove at subscriptionManager`, async () => { const web3Eth = new Web3Eth(clientUrl); await waitForOpenConnection(web3Eth); const sub: NewHeadsSubscription = await web3Eth.subscribe('newHeads'); expect(sub.id).toBeDefined(); + const subId = sub.id as string; await web3Eth.subscriptionManager?.removeSubscription(sub); + expect(web3Eth.subscriptionManager.subscriptions.has(subId)).toBe(false); + expect(sub.id).toBeUndefined(); + await closeOpenConnection(web3Eth); + }); + it(`remove at subscribe object`, async () => { + const web3Eth = new Web3Eth(clientUrl); + await waitForOpenConnection(web3Eth); + const sub: NewHeadsSubscription = await web3Eth.subscribe('newHeads'); + expect(sub.id).toBeDefined(); + const subId = sub.id as string; + await sub.unsubscribe(); + expect(web3Eth.subscriptionManager.subscriptions.has(subId)).toBe(false); expect(sub.id).toBeUndefined(); await closeOpenConnection(web3Eth); }); diff --git a/packages/web3-eth/test/integration/subscription_on_2_events.test.ts b/packages/web3-eth/test/integration/subscription_on_2_events.test.ts new file mode 100644 index 00000000000..bfcfb99da92 --- /dev/null +++ b/packages/web3-eth/test/integration/subscription_on_2_events.test.ts @@ -0,0 +1,114 @@ +/* +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 import/no-extraneous-dependencies +import { BlockHeaderOutput, Web3 } from 'web3'; +import { + closeOpenConnection, + describeIf, + getSystemTestProvider, + isSocket, + sendFewSampleTxs, + waitForOpenConnection, +} from '../fixtures/system_test_utils'; +import { Resolve } from './helper'; + +const checkTxCount = 2; +describeIf(isSocket)('subscription on multiple events', () => { + test(`catch the data of pendingTransactions and newHeads`, async () => { + const web3 = new Web3(getSystemTestProvider()); + const web3Eth = web3.eth; + await waitForOpenConnection(web3Eth); + const pendingTransactionsSub = await web3Eth.subscribe('pendingTransactions'); + + let pendingTransactionsCount = 0; + const pendingTransactionsData = new Promise((resolve: Resolve, reject) => { + (() => { + pendingTransactionsSub.on('data', (data: string) => { + expect(typeof data).toBe('string'); + + pendingTransactionsCount += 1; + if (pendingTransactionsCount >= checkTxCount) { + resolve(); + } + }); + pendingTransactionsSub.on('error', error => { + reject(error); + }); + })(); + }); + + const newHeadsSub = await web3.eth.subscribe('newHeads'); + let newHeadsCount = 0; + const newHeadsData = new Promise((resolve: Resolve, reject) => { + newHeadsSub.on('data', (data: BlockHeaderOutput) => { + expect(typeof data.parentHash).toBe('string'); + + newHeadsCount += 1; + if (newHeadsCount >= checkTxCount) { + resolve(); + } + }); + newHeadsSub.on('error', error => { + reject(error); + }); + }); + + await sendFewSampleTxs(2); + + await pendingTransactionsData; + await newHeadsData; + + await closeOpenConnection(web3Eth); + }); + + test(`catch the data of an event even after subscribing off another one`, async () => { + const web3 = new Web3(getSystemTestProvider()); + const web3Eth = web3.eth; + await waitForOpenConnection(web3Eth); + const pendingTransactionsSub = await web3Eth.subscribe('pendingTransactions'); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + pendingTransactionsSub.on('data', () => {}); + pendingTransactionsSub.on('error', error => { + throw error; + }); + + const newHeadsSub = await web3.eth.subscribe('newHeads'); + let times = 0; + const newHeadsData = new Promise((resolve: Resolve, reject) => { + newHeadsSub.on('data', (data: BlockHeaderOutput) => { + expect(typeof data.parentHash).toBe('string'); + + times += 1; + if (times >= checkTxCount) { + resolve(); + } + }); + newHeadsSub.on('error', error => { + reject(error); + }); + }); + + await pendingTransactionsSub.unsubscribe(); + + await sendFewSampleTxs(2); + + await newHeadsData; + + await closeOpenConnection(web3Eth); + }); +});