Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit e8d579c

Browse files
Refactor subscription's logic (#6210)
* 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 * add `SimpleProvider` the base of `EIP1193Provider` * update CHANGELOG.md * enable backward compatibility for subscriptions + mark the obsolete as deprecated * add `removeListener` events to `EIP1193Provider` * Fix some events types at `SocketProvider` * add and fix old test cases for multiple subscriptions
1 parent 51a59f9 commit e8d579c

21 files changed

+645
-174
lines changed

packages/web3-core/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ Documentation:
121121

122122
## [Unreleased]
123123

124+
### Added
125+
126+
- Web3Subscription constructor accept a Subscription Manager (as an alternative to accepting Request Manager that is now marked marked as deprecated) (#6210)
127+
128+
### Changed
129+
130+
- Web3Subscription constructor overloading that accept a Request Manager is marked as deprecated (#6210)
131+
124132
### Fixed
125133

126134
- Fixed Batch requests erroring out on one request (#6164)
135+
- Fixed the issue: Subscribing to multiple blockchain events causes every listener to be fired for every registered event (#6210)
136+
- Fixed the issue: Unsubscribe at a Web3Subscription class will still have the id of the subscription at the Web3SubscriptionManager (#6210)
137+
- Fixed the issue: A call to the provider is made for every subscription object (#6210)

packages/web3-core/src/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ export const isLegacySendAsyncProvider = <API extends Web3APISpec>(
5454
export const isSupportedProvider = <API extends Web3APISpec>(
5555
provider: SupportedProviders<API>,
5656
): provider is SupportedProviders<API> =>
57-
Web3BaseProvider.isWeb3Provider(provider) ||
57+
isWeb3Provider(provider) ||
58+
isEIP1193Provider(provider) ||
5859
isLegacyRequestProvider(provider) ||
5960
isLegacySendAsyncProvider(provider) ||
6061
isLegacySendProvider(provider);

packages/web3-core/src/web3_context.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class Web3Context<
9696
public static givenProvider?: SupportedProviders<never>;
9797
public readonly providers = Web3RequestManager.providers;
9898
protected _requestManager: Web3RequestManager<API>;
99-
protected _subscriptionManager?: Web3SubscriptionManager<API, RegisteredSubs>;
99+
protected _subscriptionManager: Web3SubscriptionManager<API, RegisteredSubs>;
100100
protected _accountProvider?: Web3AccountProvider<Web3BaseWalletAccount>;
101101
protected _wallet?: Web3BaseWallet<Web3BaseWalletAccount>;
102102

@@ -146,10 +146,10 @@ export class Web3Context<
146146

147147
if (subscriptionManager) {
148148
this._subscriptionManager = subscriptionManager;
149-
} else if (registeredSubscriptions) {
149+
} else {
150150
this._subscriptionManager = new Web3SubscriptionManager(
151151
this.requestManager,
152-
registeredSubscriptions,
152+
registeredSubscriptions ?? ({} as RegisteredSubs),
153153
);
154154
}
155155

@@ -195,8 +195,7 @@ export class Web3Context<
195195
provider: this.provider,
196196
requestManager: this.requestManager,
197197
subscriptionManager: this.subscriptionManager,
198-
registeredSubscriptions: this.subscriptionManager
199-
?.registeredSubscriptions as RegisteredSubs,
198+
registeredSubscriptions: this.subscriptionManager?.registeredSubscriptions,
200199
providers: this.providers,
201200
wallet: this.wallet,
202201
accountProvider: this.accountProvider,
@@ -231,6 +230,7 @@ export class Web3Context<
231230
this.setConfig(parentContext.config);
232231
this._requestManager = parentContext.requestManager;
233232
this.provider = parentContext.provider;
233+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
234234
this._subscriptionManager = parentContext.subscriptionManager;
235235
this._wallet = parentContext.wallet;
236236
this._accountProvider = parentContext._accountProvider;

packages/web3-core/src/web3_subscription_manager.ts

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,22 @@ You should have received a copy of the GNU Lesser General Public License
1515
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

18-
import { DataFormat, DEFAULT_RETURN_FORMAT, Web3APISpec } from 'web3-types';
18+
import {
19+
DataFormat,
20+
DEFAULT_RETURN_FORMAT,
21+
EIP1193Provider,
22+
JsonRpcNotification,
23+
JsonRpcSubscriptionResult,
24+
JsonRpcSubscriptionResultOld,
25+
Log,
26+
Web3APISpec,
27+
Web3BaseProvider,
28+
} from 'web3-types';
1929
import { ProviderError, SubscriptionError } from 'web3-errors';
2030
import { isNullish } from 'web3-utils';
2131
import { isSupportSubscriptions } from './utils.js';
2232
import { Web3RequestManager, Web3RequestManagerEvent } from './web3_request_manager.js';
33+
// eslint-disable-next-line import/no-cycle
2334
import { Web3SubscriptionConstructor } from './web3_subscriptions.js';
2435

2536
type ShouldUnsubscribeCondition = ({
@@ -31,8 +42,10 @@ type ShouldUnsubscribeCondition = ({
3142
}) => boolean | undefined;
3243

3344
export class Web3SubscriptionManager<
34-
API extends Web3APISpec,
35-
RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor<API> },
45+
API extends Web3APISpec = Web3APISpec,
46+
RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor<API> } = {
47+
[key: string]: Web3SubscriptionConstructor<API>;
48+
},
3649
> {
3750
private readonly _subscriptions: Map<
3851
string,
@@ -41,34 +54,97 @@ export class Web3SubscriptionManager<
4154

4255
/**
4356
*
44-
* @param requestManager
45-
* @param registeredSubscriptions
57+
* @param - requestManager
58+
* @param - registeredSubscriptions
4659
*
4760
* @example
4861
* ```ts
4962
* const requestManager = new Web3RequestManager("ws://localhost:8545");
5063
* const subscriptionManager = new Web3SubscriptionManager(requestManager, {});
5164
* ```
5265
*/
66+
public constructor(
67+
requestManager: Web3RequestManager<API>,
68+
registeredSubscriptions: RegisteredSubs,
69+
);
70+
/**
71+
* @deprecated This constructor overloading should not be used
72+
*/
73+
public constructor(
74+
requestManager: Web3RequestManager<API>,
75+
registeredSubscriptions: RegisteredSubs,
76+
tolerateUnlinkedSubscription: boolean,
77+
);
5378
public constructor(
5479
public readonly requestManager: Web3RequestManager<API>,
5580
public readonly registeredSubscriptions: RegisteredSubs,
81+
private readonly tolerateUnlinkedSubscription: boolean = false,
5682
) {
5783
this.requestManager.on(Web3RequestManagerEvent.BEFORE_PROVIDER_CHANGE, async () => {
5884
await this.unsubscribe();
5985
});
6086

6187
this.requestManager.on(Web3RequestManagerEvent.PROVIDER_CHANGED, () => {
6288
this.clear();
89+
this.listenToProviderEvents();
6390
});
91+
92+
this.listenToProviderEvents();
6493
}
6594

95+
private listenToProviderEvents() {
96+
const providerAsWebProvider = this.requestManager.provider as Web3BaseProvider;
97+
if (
98+
!this.requestManager.provider ||
99+
(typeof providerAsWebProvider?.supportsSubscriptions === 'function' &&
100+
!providerAsWebProvider?.supportsSubscriptions())
101+
) {
102+
return;
103+
}
104+
105+
if (typeof (this.requestManager.provider as EIP1193Provider<API>).on === 'function') {
106+
if (
107+
typeof (this.requestManager.provider as EIP1193Provider<API>).request === 'function'
108+
) {
109+
// Listen to provider messages and data
110+
(this.requestManager.provider as EIP1193Provider<API>).on(
111+
'message',
112+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
113+
(message: any) => this.messageListener(message),
114+
);
115+
} else {
116+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
117+
providerAsWebProvider.on<Log>('data', (data: any) => this.messageListener(data));
118+
}
119+
}
120+
}
121+
122+
protected messageListener(
123+
data?:
124+
| JsonRpcSubscriptionResult
125+
| JsonRpcSubscriptionResultOld<Log>
126+
| JsonRpcNotification<Log>,
127+
) {
128+
if (!data) {
129+
throw new SubscriptionError('Should not call messageListener with no data. Type was');
130+
}
131+
const subscriptionId =
132+
(data as JsonRpcNotification).params?.subscription ||
133+
(data as JsonRpcSubscriptionResultOld).data?.subscription ||
134+
(data as JsonRpcSubscriptionResult).id?.toString(16);
135+
136+
// Process if the received data is related to a subscription
137+
if (subscriptionId) {
138+
const sub = this._subscriptions.get(subscriptionId);
139+
sub?.processSubscriptionData(data);
140+
}
141+
}
66142
/**
67143
* Will create a new subscription
68144
*
69145
* @param name - The subscription you want to subscribe to
70-
* @param args (optional) - Optional additional parameters, depending on the subscription type
71-
* @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) - Specifies how the return data from the call should be formatted.
146+
* @param args - Optional additional parameters, depending on the subscription type
147+
* @param returnFormat- ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) - Specifies how the return data from the call should be formatted.
72148
*
73149
* Will subscribe to a specific topic (note: name)
74150
* @returns The subscription object
@@ -78,19 +154,16 @@ export class Web3SubscriptionManager<
78154
args?: ConstructorParameters<RegisteredSubs[T]>[0],
79155
returnFormat: DataFormat = DEFAULT_RETURN_FORMAT,
80156
): Promise<InstanceType<RegisteredSubs[T]>> {
81-
if (!this.requestManager.provider) {
82-
throw new ProviderError('Provider not available');
83-
}
84-
85157
const Klass: RegisteredSubs[T] = this.registeredSubscriptions[name];
86158
if (!Klass) {
87159
throw new SubscriptionError('Invalid subscription type');
88160
}
89161

162+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
90163
const subscription = new Klass(args ?? undefined, {
91-
requestManager: this.requestManager,
164+
subscriptionManager: this as Web3SubscriptionManager<API, RegisteredSubs>,
92165
returnFormat,
93-
}) as InstanceType<RegisteredSubs[T]>;
166+
} as any) as InstanceType<RegisteredSubs[T]>;
94167

95168
await this.addSubscription(subscription);
96169

@@ -111,6 +184,10 @@ export class Web3SubscriptionManager<
111184
* @param sub - A {@link Web3Subscription} object
112185
*/
113186
public async addSubscription(sub: InstanceType<RegisteredSubs[keyof RegisteredSubs]>) {
187+
if (!this.requestManager.provider) {
188+
throw new ProviderError('Provider not available');
189+
}
190+
114191
if (!this.supportsSubscriptions()) {
115192
throw new SubscriptionError('The current provider does not support subscriptions');
116193
}
@@ -119,34 +196,36 @@ export class Web3SubscriptionManager<
119196
throw new SubscriptionError(`Subscription with id "${sub.id}" already exists`);
120197
}
121198

122-
await sub.subscribe();
199+
await sub.sendSubscriptionRequest();
123200

124201
if (isNullish(sub.id)) {
125202
throw new SubscriptionError('Subscription is not subscribed yet.');
126203
}
127204

128205
this._subscriptions.set(sub.id, sub);
206+
207+
return sub.id;
129208
}
209+
130210
/**
131211
* Will clear a subscription
132212
*
133213
* @param id - The subscription of type {@link Web3Subscription} to remove
134214
*/
135-
136215
public async removeSubscription(sub: InstanceType<RegisteredSubs[keyof RegisteredSubs]>) {
137-
if (isNullish(sub.id)) {
216+
const { id } = sub;
217+
218+
if (isNullish(id)) {
138219
throw new SubscriptionError(
139220
'Subscription is not subscribed yet. Or, had already been unsubscribed but not through the Subscription Manager.',
140221
);
141222
}
142223

143-
if (!this._subscriptions.has(sub.id)) {
144-
throw new SubscriptionError(
145-
`Subscription with id "${sub.id.toString()}" does not exists`,
146-
);
224+
if (!this._subscriptions.has(id) && !this.tolerateUnlinkedSubscription) {
225+
throw new SubscriptionError(`Subscription with id "${id.toString()}" does not exists`);
147226
}
148-
const { id } = sub;
149-
await sub.unsubscribe();
227+
228+
await sub.sendUnsubscribeRequest();
150229
this._subscriptions.delete(id);
151230
return id;
152231
}

0 commit comments

Comments
 (0)