Skip to content

Commit

Permalink
Generalize polling abstraction (#3636)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
adonesky1 authored Dec 12, 2023
1 parent 1003274 commit 42d0b32
Show file tree
Hide file tree
Showing 16 changed files with 844 additions and 600 deletions.
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/AccountTrackerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
NetworkController,
NetworkState,
} from '@metamask/network-controller';
import { PollingControllerV1 } from '@metamask/polling-controller';
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
import type { PreferencesState } from '@metamask/preferences-controller';
import { assert } from '@metamask/utils';
import { Mutex } from 'async-mutex';
Expand Down Expand Up @@ -61,7 +61,7 @@ export interface AccountTrackerState extends BaseState {
/**
* Controller that tracks the network balances for all user accounts.
*/
export class AccountTrackerController extends PollingControllerV1<
export class AccountTrackerController extends StaticIntervalPollingControllerV1<
AccountTrackerConfig,
AccountTrackerState
> {
Expand Down
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/CurrencyRateController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
NetworkClientId,
NetworkControllerGetNetworkClientByIdAction,
} from '@metamask/network-controller';
import { PollingController } from '@metamask/polling-controller';
import { StaticIntervalPollingController } from '@metamask/polling-controller';
import { Mutex } from 'async-mutex';

import { fetchExchangeRate as defaultFetchExchangeRate } from './crypto-compare';
Expand Down Expand Up @@ -82,7 +82,7 @@ const defaultState = {
* Controller that passively polls on a set interval for an exchange rate from the current network
* asset to the user's preferred currency.
*/
export class CurrencyRateController extends PollingController<
export class CurrencyRateController extends StaticIntervalPollingController<
typeof name,
CurrencyRateState,
CurrencyRateMessenger
Expand Down
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/NftDetectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
NetworkState,
NetworkClient,
} from '@metamask/network-controller';
import { PollingControllerV1 } from '@metamask/polling-controller';
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
import type { PreferencesState } from '@metamask/preferences-controller';
import type { Hex } from '@metamask/utils';

Expand Down Expand Up @@ -147,7 +147,7 @@ export interface NftDetectionConfig extends BaseConfig {
/**
* Controller that passively polls on a set interval for NFT auto detection
*/
export class NftDetectionController extends PollingControllerV1<
export class NftDetectionController extends StaticIntervalPollingControllerV1<
NftDetectionConfig,
BaseState
> {
Expand Down
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/TokenDetectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
NetworkController,
NetworkState,
} from '@metamask/network-controller';
import { PollingControllerV1 } from '@metamask/polling-controller';
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
import type { PreferencesState } from '@metamask/preferences-controller';
import type { Hex } from '@metamask/utils';

Expand Down Expand Up @@ -44,7 +44,7 @@ export interface TokenDetectionConfig extends BaseConfig {
/**
* Controller that passively polls on a set interval for Tokens auto detection
*/
export class TokenDetectionController extends PollingControllerV1<
export class TokenDetectionController extends StaticIntervalPollingControllerV1<
TokenDetectionConfig,
BaseState
> {
Expand Down
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/TokenListController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
NetworkState,
NetworkControllerGetNetworkClientByIdAction,
} from '@metamask/network-controller';
import { PollingController } from '@metamask/polling-controller';
import { StaticIntervalPollingController } from '@metamask/polling-controller';
import type { Hex } from '@metamask/utils';
import { Mutex } from 'async-mutex';

Expand Down Expand Up @@ -91,7 +91,7 @@ const defaultState: TokenListState = {
/**
* Controller that passively polls on a set interval for the list of tokens from metaswaps api
*/
export class TokenListController extends PollingController<
export class TokenListController extends StaticIntervalPollingController<
typeof name,
TokenListState,
TokenListMessenger
Expand Down
4 changes: 2 additions & 2 deletions packages/assets-controllers/src/TokenRatesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
NetworkController,
NetworkState,
} from '@metamask/network-controller';
import { PollingControllerV1 } from '@metamask/polling-controller';
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
import type { PreferencesState } from '@metamask/preferences-controller';
import type { Hex } from '@metamask/utils';
import { isDeepStrictEqual } from 'util';
Expand Down Expand Up @@ -136,7 +136,7 @@ async function getCurrencyConversionRate({
* Controller that passively polls on a set interval for token-to-fiat exchange rates
* for tokens stored in the TokensController
*/
export class TokenRatesController extends PollingControllerV1<
export class TokenRatesController extends StaticIntervalPollingControllerV1<
TokenRatesConfig,
TokenRatesState
> {
Expand Down
4 changes: 2 additions & 2 deletions packages/gas-fee-controller/src/GasFeeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
NetworkState,
ProviderProxy,
} from '@metamask/network-controller';
import { PollingController } from '@metamask/polling-controller';
import { StaticIntervalPollingController } from '@metamask/polling-controller';
import type { Hex } from '@metamask/utils';
import { v1 as random } from 'uuid';

Expand Down Expand Up @@ -253,7 +253,7 @@ const defaultState: GasFeeState = {
/**
* Controller that retrieves gas fee estimate data and polls for updated data on a set interval
*/
export class GasFeeController extends PollingController<
export class GasFeeController extends StaticIntervalPollingController<
typeof name,
GasFeeState,
GasFeeMessenger
Expand Down
138 changes: 138 additions & 0 deletions packages/polling-controller/src/AbstractPollingController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import type { NetworkClientId } from '@metamask/network-controller';
import type { Json } from '@metamask/utils';
import stringify from 'fast-json-stable-stringify';
import { v4 as random } from 'uuid';

export type IPollingController = {
startPollingByNetworkClientId(
networkClientId: NetworkClientId,
options: Json,
): string;

stopAllPolling(): void;

stopPollingByPollingToken(pollingToken: string): void;

onPollingCompleteByNetworkClientId(
networkClientId: NetworkClientId,
callback: (networkClientId: NetworkClientId) => void,
options: Json,
): void;

_executePoll(networkClientId: NetworkClientId, options: Json): Promise<void>;
_startPollingByNetworkClientId(
networkClientId: NetworkClientId,
options: Json,
): void;
_stopPollingByPollingTokenSetId(key: PollingTokenSetId): void;
};

export const getKey = (
networkClientId: NetworkClientId,
options: Json,
): PollingTokenSetId => `${networkClientId}:${stringify(options)}`;

export type PollingTokenSetId = `${NetworkClientId}:${string}`;

type Constructor = new (...args: any[]) => object;

/**
* AbstractPollingControllerBaseMixin
*
* @param Base - The base class to mix onto.
* @returns The composed class.
*/
export function AbstractPollingControllerBaseMixin<TBase extends Constructor>(
Base: TBase,
) {
abstract class AbstractPollingControllerBase
extends Base
implements IPollingController
{
readonly #pollingTokenSets: Map<PollingTokenSetId, Set<string>> = new Map();

#callbacks: Map<
PollingTokenSetId,
Set<(PollingTokenSetId: PollingTokenSetId) => void>
> = new Map();

abstract _executePoll(
networkClientId: NetworkClientId,
options: Json,
): Promise<void>;

abstract _startPollingByNetworkClientId(
networkClientId: NetworkClientId,
options: Json,
): void;

abstract _stopPollingByPollingTokenSetId(key: PollingTokenSetId): void;

startPollingByNetworkClientId(
networkClientId: NetworkClientId,
options: Json = {},
): string {
const pollToken = random();
const key = getKey(networkClientId, options);
const pollingTokenSet =
this.#pollingTokenSets.get(key) ?? new Set<string>();
pollingTokenSet.add(pollToken);
this.#pollingTokenSets.set(key, pollingTokenSet);

if (pollingTokenSet.size === 1) {
this._startPollingByNetworkClientId(networkClientId, options);
}

return pollToken;
}

stopAllPolling() {
this.#pollingTokenSets.forEach((tokenSet, _key) => {
tokenSet.forEach((token) => {
this.stopPollingByPollingToken(token);
});
});
}

stopPollingByPollingToken(pollingToken: string) {
if (!pollingToken) {
throw new Error('pollingToken required');
}

let keyToDelete: PollingTokenSetId | null = null;
for (const [key, tokenSet] of this.#pollingTokenSets) {
if (tokenSet.delete(pollingToken)) {
if (tokenSet.size === 0) {
keyToDelete = key;
}
break;
}
}

if (keyToDelete) {
this._stopPollingByPollingTokenSetId(keyToDelete);
this.#pollingTokenSets.delete(keyToDelete);
const callbacks = this.#callbacks.get(keyToDelete);
if (callbacks) {
for (const callback of callbacks) {
// eslint-disable-next-line n/callback-return
callback(keyToDelete);
}
callbacks.clear();
}
}
}

onPollingCompleteByNetworkClientId(
networkClientId: NetworkClientId,
callback: (networkClientId: NetworkClientId) => void,
options: Json = {},
) {
const key = getKey(networkClientId, options);
const callbacks = this.#callbacks.get(key) ?? new Set<typeof callback>();
callbacks.add(callback);
this.#callbacks.set(key, callbacks);
}
}
return AbstractPollingControllerBase;
}
Loading

0 comments on commit 42d0b32

Please sign in to comment.