Skip to content

Commit 42d0b32

Browse files
authored
Generalize polling abstraction (#3636)
Ready for review: **I will add changelog entries when/if we decide to roll with this approach.** Alternative approach to refactoring the PollingController per this [feedback](#3623 (comment)) on [PollingTracker enhancement work](#3623)
1 parent 1003274 commit 42d0b32

16 files changed

+844
-600
lines changed

packages/assets-controllers/src/AccountTrackerController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
NetworkController,
1212
NetworkState,
1313
} from '@metamask/network-controller';
14-
import { PollingControllerV1 } from '@metamask/polling-controller';
14+
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
1515
import type { PreferencesState } from '@metamask/preferences-controller';
1616
import { assert } from '@metamask/utils';
1717
import { Mutex } from 'async-mutex';
@@ -61,7 +61,7 @@ export interface AccountTrackerState extends BaseState {
6161
/**
6262
* Controller that tracks the network balances for all user accounts.
6363
*/
64-
export class AccountTrackerController extends PollingControllerV1<
64+
export class AccountTrackerController extends StaticIntervalPollingControllerV1<
6565
AccountTrackerConfig,
6666
AccountTrackerState
6767
> {

packages/assets-controllers/src/CurrencyRateController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
NetworkClientId,
1212
NetworkControllerGetNetworkClientByIdAction,
1313
} from '@metamask/network-controller';
14-
import { PollingController } from '@metamask/polling-controller';
14+
import { StaticIntervalPollingController } from '@metamask/polling-controller';
1515
import { Mutex } from 'async-mutex';
1616

1717
import { fetchExchangeRate as defaultFetchExchangeRate } from './crypto-compare';
@@ -82,7 +82,7 @@ const defaultState = {
8282
* Controller that passively polls on a set interval for an exchange rate from the current network
8383
* asset to the user's preferred currency.
8484
*/
85-
export class CurrencyRateController extends PollingController<
85+
export class CurrencyRateController extends StaticIntervalPollingController<
8686
typeof name,
8787
CurrencyRateState,
8888
CurrencyRateMessenger

packages/assets-controllers/src/NftDetectionController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
NetworkState,
1212
NetworkClient,
1313
} from '@metamask/network-controller';
14-
import { PollingControllerV1 } from '@metamask/polling-controller';
14+
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
1515
import type { PreferencesState } from '@metamask/preferences-controller';
1616
import type { Hex } from '@metamask/utils';
1717

@@ -147,7 +147,7 @@ export interface NftDetectionConfig extends BaseConfig {
147147
/**
148148
* Controller that passively polls on a set interval for NFT auto detection
149149
*/
150-
export class NftDetectionController extends PollingControllerV1<
150+
export class NftDetectionController extends StaticIntervalPollingControllerV1<
151151
NftDetectionConfig,
152152
BaseState
153153
> {

packages/assets-controllers/src/TokenDetectionController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
NetworkController,
99
NetworkState,
1010
} from '@metamask/network-controller';
11-
import { PollingControllerV1 } from '@metamask/polling-controller';
11+
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
1212
import type { PreferencesState } from '@metamask/preferences-controller';
1313
import type { Hex } from '@metamask/utils';
1414

@@ -44,7 +44,7 @@ export interface TokenDetectionConfig extends BaseConfig {
4444
/**
4545
* Controller that passively polls on a set interval for Tokens auto detection
4646
*/
47-
export class TokenDetectionController extends PollingControllerV1<
47+
export class TokenDetectionController extends StaticIntervalPollingControllerV1<
4848
TokenDetectionConfig,
4949
BaseState
5050
> {

packages/assets-controllers/src/TokenListController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
NetworkState,
1111
NetworkControllerGetNetworkClientByIdAction,
1212
} from '@metamask/network-controller';
13-
import { PollingController } from '@metamask/polling-controller';
13+
import { StaticIntervalPollingController } from '@metamask/polling-controller';
1414
import type { Hex } from '@metamask/utils';
1515
import { Mutex } from 'async-mutex';
1616

@@ -91,7 +91,7 @@ const defaultState: TokenListState = {
9191
/**
9292
* Controller that passively polls on a set interval for the list of tokens from metaswaps api
9393
*/
94-
export class TokenListController extends PollingController<
94+
export class TokenListController extends StaticIntervalPollingController<
9595
typeof name,
9696
TokenListState,
9797
TokenListMessenger

packages/assets-controllers/src/TokenRatesController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
NetworkController,
1111
NetworkState,
1212
} from '@metamask/network-controller';
13-
import { PollingControllerV1 } from '@metamask/polling-controller';
13+
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
1414
import type { PreferencesState } from '@metamask/preferences-controller';
1515
import type { Hex } from '@metamask/utils';
1616
import { isDeepStrictEqual } from 'util';
@@ -136,7 +136,7 @@ async function getCurrencyConversionRate({
136136
* Controller that passively polls on a set interval for token-to-fiat exchange rates
137137
* for tokens stored in the TokensController
138138
*/
139-
export class TokenRatesController extends PollingControllerV1<
139+
export class TokenRatesController extends StaticIntervalPollingControllerV1<
140140
TokenRatesConfig,
141141
TokenRatesState
142142
> {

packages/gas-fee-controller/src/GasFeeController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
NetworkState,
1919
ProviderProxy,
2020
} from '@metamask/network-controller';
21-
import { PollingController } from '@metamask/polling-controller';
21+
import { StaticIntervalPollingController } from '@metamask/polling-controller';
2222
import type { Hex } from '@metamask/utils';
2323
import { v1 as random } from 'uuid';
2424

@@ -253,7 +253,7 @@ const defaultState: GasFeeState = {
253253
/**
254254
* Controller that retrieves gas fee estimate data and polls for updated data on a set interval
255255
*/
256-
export class GasFeeController extends PollingController<
256+
export class GasFeeController extends StaticIntervalPollingController<
257257
typeof name,
258258
GasFeeState,
259259
GasFeeMessenger
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type { NetworkClientId } from '@metamask/network-controller';
2+
import type { Json } from '@metamask/utils';
3+
import stringify from 'fast-json-stable-stringify';
4+
import { v4 as random } from 'uuid';
5+
6+
export type IPollingController = {
7+
startPollingByNetworkClientId(
8+
networkClientId: NetworkClientId,
9+
options: Json,
10+
): string;
11+
12+
stopAllPolling(): void;
13+
14+
stopPollingByPollingToken(pollingToken: string): void;
15+
16+
onPollingCompleteByNetworkClientId(
17+
networkClientId: NetworkClientId,
18+
callback: (networkClientId: NetworkClientId) => void,
19+
options: Json,
20+
): void;
21+
22+
_executePoll(networkClientId: NetworkClientId, options: Json): Promise<void>;
23+
_startPollingByNetworkClientId(
24+
networkClientId: NetworkClientId,
25+
options: Json,
26+
): void;
27+
_stopPollingByPollingTokenSetId(key: PollingTokenSetId): void;
28+
};
29+
30+
export const getKey = (
31+
networkClientId: NetworkClientId,
32+
options: Json,
33+
): PollingTokenSetId => `${networkClientId}:${stringify(options)}`;
34+
35+
export type PollingTokenSetId = `${NetworkClientId}:${string}`;
36+
37+
type Constructor = new (...args: any[]) => object;
38+
39+
/**
40+
* AbstractPollingControllerBaseMixin
41+
*
42+
* @param Base - The base class to mix onto.
43+
* @returns The composed class.
44+
*/
45+
export function AbstractPollingControllerBaseMixin<TBase extends Constructor>(
46+
Base: TBase,
47+
) {
48+
abstract class AbstractPollingControllerBase
49+
extends Base
50+
implements IPollingController
51+
{
52+
readonly #pollingTokenSets: Map<PollingTokenSetId, Set<string>> = new Map();
53+
54+
#callbacks: Map<
55+
PollingTokenSetId,
56+
Set<(PollingTokenSetId: PollingTokenSetId) => void>
57+
> = new Map();
58+
59+
abstract _executePoll(
60+
networkClientId: NetworkClientId,
61+
options: Json,
62+
): Promise<void>;
63+
64+
abstract _startPollingByNetworkClientId(
65+
networkClientId: NetworkClientId,
66+
options: Json,
67+
): void;
68+
69+
abstract _stopPollingByPollingTokenSetId(key: PollingTokenSetId): void;
70+
71+
startPollingByNetworkClientId(
72+
networkClientId: NetworkClientId,
73+
options: Json = {},
74+
): string {
75+
const pollToken = random();
76+
const key = getKey(networkClientId, options);
77+
const pollingTokenSet =
78+
this.#pollingTokenSets.get(key) ?? new Set<string>();
79+
pollingTokenSet.add(pollToken);
80+
this.#pollingTokenSets.set(key, pollingTokenSet);
81+
82+
if (pollingTokenSet.size === 1) {
83+
this._startPollingByNetworkClientId(networkClientId, options);
84+
}
85+
86+
return pollToken;
87+
}
88+
89+
stopAllPolling() {
90+
this.#pollingTokenSets.forEach((tokenSet, _key) => {
91+
tokenSet.forEach((token) => {
92+
this.stopPollingByPollingToken(token);
93+
});
94+
});
95+
}
96+
97+
stopPollingByPollingToken(pollingToken: string) {
98+
if (!pollingToken) {
99+
throw new Error('pollingToken required');
100+
}
101+
102+
let keyToDelete: PollingTokenSetId | null = null;
103+
for (const [key, tokenSet] of this.#pollingTokenSets) {
104+
if (tokenSet.delete(pollingToken)) {
105+
if (tokenSet.size === 0) {
106+
keyToDelete = key;
107+
}
108+
break;
109+
}
110+
}
111+
112+
if (keyToDelete) {
113+
this._stopPollingByPollingTokenSetId(keyToDelete);
114+
this.#pollingTokenSets.delete(keyToDelete);
115+
const callbacks = this.#callbacks.get(keyToDelete);
116+
if (callbacks) {
117+
for (const callback of callbacks) {
118+
// eslint-disable-next-line n/callback-return
119+
callback(keyToDelete);
120+
}
121+
callbacks.clear();
122+
}
123+
}
124+
}
125+
126+
onPollingCompleteByNetworkClientId(
127+
networkClientId: NetworkClientId,
128+
callback: (networkClientId: NetworkClientId) => void,
129+
options: Json = {},
130+
) {
131+
const key = getKey(networkClientId, options);
132+
const callbacks = this.#callbacks.get(key) ?? new Set<typeof callback>();
133+
callbacks.add(callback);
134+
this.#callbacks.set(key, callbacks);
135+
}
136+
}
137+
return AbstractPollingControllerBase;
138+
}

0 commit comments

Comments
 (0)