Skip to content

Commit

Permalink
feat(refactor): Abstract state bootstrapping & subscriptions from Api (
Browse files Browse the repository at this point in the history
  • Loading branch information
rossbulat authored Jul 28, 2024
1 parent 6097a12 commit 8accc8b
Show file tree
Hide file tree
Showing 15 changed files with 855 additions and 394 deletions.
56 changes: 47 additions & 9 deletions src/contexts/Api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ import BigNumber from 'bignumber.js';
import { SyncController } from 'controllers/SyncController';
import { ApiController } from 'controllers/ApiController';
import type { ApiStatus, ConnectionType } from 'model/Api/types';
import { StakingConstants } from 'model/Query/StakingConstants';
import { Era } from 'model/Query/Era';
import { NetworkMeta } from 'model/Query/NetworkMeta';
import { SubscriptionsController } from 'controllers/SubscriptionsController';
import { BlockNumber } from 'model/Subscribe/BlockNumber';
import { NetworkMetrics } from 'model/Subscribe/NetworkMetrics';
import { ActiveEra } from 'model/Subscribe/ActiveEra';
import { PoolsConfig } from 'model/Subscribe/PoolsConfig';

export const APIContext = createContext<APIContextInterface>(defaultApiContext);

Expand Down Expand Up @@ -147,16 +155,25 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
// Bootstrap app-wide chain state.
const bootstrapNetworkConfig = async () => {
const apiInstance = ApiController.get(network);
const api = apiInstance.api;

// 1. Fetch network data for bootstrapping app state:

// Get general network constants for staking UI.
const newConsts = await new StakingConstants().fetch(api, network);

// Get active and previous era.
const { activeEra: newActiveEra, previousEra } = await new Era().fetch(api);

// Get network meta data related to staking and pools.
const {
consts: newConsts,
networkMetrics: newNetworkMetrics,
activeEra: newActiveEra,
poolsConfig: newPoolsConfig,
stakingMetrics: newStakingMetrics,
} = await apiInstance.bootstrapNetworkConfig();
} = await new NetworkMeta().fetch(api, newActiveEra, previousEra);

// 2. Populate all config state:

// Populate all config state.
setConsts(newConsts);
setStateWithRef(newNetworkMetrics, setNetworkMetrics, networkMetricsRef);
const { index, start } = newActiveEra;
Expand All @@ -174,11 +191,32 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
// Set `initialization` syncing to complete.
SyncController.dispatch('initialization', 'complete');

// Initialise subscriptions.
apiInstance.subscribeBlockNumber();
apiInstance.subscribeNetworkMetrics();
apiInstance.subscribePoolsConfig();
apiInstance.subscribeActiveEra();
// 3. Initialise subscriptions:

// Initialise block number subscription.
SubscriptionsController.set(
network,
'blockNumber',
new BlockNumber(network)
);

// Initialise network metrics subscription.
SubscriptionsController.set(
network,
'networkMetrics',
new NetworkMetrics(network)
);

// Initialise pool config subscription.
SubscriptionsController.set(
network,
'poolsConfig',
new PoolsConfig(network)
);

// Initialise active era subscription. Also handles (re)subscribing to subscriptions that depend
// on active era.
SubscriptionsController.set(network, 'activeEra', new ActiveEra(network));
};

// Handle Api disconnection.
Expand Down
7 changes: 5 additions & 2 deletions src/controllers/ApiController/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export class ApiController {
type: ConnectionType,
rpcEndpoint: string
) {
// NOTE: This method should only be called to connect to a new instance. We therefore assume we
// want to disconnect from all other existing instances.
// NOTE: This method should only be called to connect to a new network. We therefore assume we
// want to disconnect from all other existing instances for the previous network.
await Promise.all(
Object.entries(this.#instances).map(async ([key, instance]) => {
// Cancel pending Sc loading before destroying instance.
Expand All @@ -46,7 +46,10 @@ export class ApiController {
})
);

// TODO: Add People chain instance.
this.instances[network] = new Api(network);

// TODO: Initialize People chain instance.
await this.instances[network].initialize(type, rpcEndpoint);
}

Expand Down
76 changes: 76 additions & 0 deletions src/controllers/SubscriptionsController/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import type { NetworkName } from 'types';
import type { ChainSubscriptions, Subscription } from './types';

// A class to manage subscriptions.

export class SubscriptionsController {
// ------------------------------------------------------
// Class members.
// ------------------------------------------------------

// Subscription objects, keyed by an network.
static #subs: Partial<Record<NetworkName, ChainSubscriptions>> = {};

// ------------------------------------------------------
// Getters.
// ------------------------------------------------------

static get subs() {
return this.#subs;
}

// Gets all subscriptions for a network.
static getAll(network: NetworkName): ChainSubscriptions | undefined {
return this.#subs[network];
}

// Get a subscription by network and subscriptionId.
static get(
network: NetworkName,
subscriptionId: string
): Subscription | undefined {
return this.#subs[network]?.[subscriptionId] || undefined;
}

// ------------------------------------------------------
// Setter.
// ------------------------------------------------------

// Sets a new subscription for a network.
static set(
network: NetworkName,
subscriptionId: string,
subscription: Subscription
): void {
// Ignore if there is already a subscription for this network and subscriptionId.
if (this.#subs?.[network]?.[subscriptionId]) {
return;
}

// Create a new subscriptions record for the network if one doesn't exist.
if (!this.#subs[network]) {
this.#subs[network] = {};
}

// NOTE: We know for certain that `this.#subs[network]` is defined here.
this.#subs[network]![subscriptionId] = subscription;
}

// ------------------------------------------------------
// Unsubscribe.
// ------------------------------------------------------

// Unsubscribe from a subscription and remove it from class state.
static remove(network: NetworkName, subscriptionId: string): void {
if (this.#subs[network]) {
try {
delete this.#subs[network]![subscriptionId];
} catch (e) {
// Silently fail if the subscription doesn't exist.
}
}
}
}
17 changes: 17 additions & 0 deletions src/controllers/SubscriptionsController/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import type { AnyJson } from '@w3ux/types';

// Define all possible subscription classes.
// TODO: Add subscription classes here.
export type Subscription = AnyJson;

// the record of subscriptions, keyed by tabId.
export type ChainSubscriptions = Record<string, Subscription>;

// Abstract class that ensures all subscription classes have an unsubscribe method.
export abstract class Unsubscribable {
// Unsubscribe from unsubs present in this class.
abstract unsubscribe: () => void;
}
Loading

0 comments on commit 8accc8b

Please sign in to comment.