@metamask-previews/network-connection-banner-controller 0.1.0-preview-51b287c55 → 0.1.0-preview-fe651cdb3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NetworkConnectionBannerController.cjs +11 -8
- package/dist/NetworkConnectionBannerController.cjs.map +1 -1
- package/dist/NetworkConnectionBannerController.d.cts +2 -2
- package/dist/NetworkConnectionBannerController.d.cts.map +1 -1
- package/dist/NetworkConnectionBannerController.d.mts +2 -2
- package/dist/NetworkConnectionBannerController.d.mts.map +1 -1
- package/dist/NetworkConnectionBannerController.mjs +11 -8
- package/dist/NetworkConnectionBannerController.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -67,8 +67,8 @@ const MESSENGER_EXPOSED_METHODS = [
|
|
|
67
67
|
* - The first failing network's default RPC is a custom (non-Infura) endpoint
|
|
68
68
|
* — users always want to be informed about errors with RPCs they've chosen.
|
|
69
69
|
* - Failed RPCs span more than one registrable domain (likely client-side).
|
|
70
|
-
* - Every enabled EVM network
|
|
71
|
-
* setups so they still get a signal).
|
|
70
|
+
* - Every enabled EVM network with known connectivity status is failing
|
|
71
|
+
* (escape hatch for single-network setups so they still get a signal).
|
|
72
72
|
*
|
|
73
73
|
* A wide single-provider outage (e.g. every `*.infura.io` network goes down at
|
|
74
74
|
* once) collapses to one domain and is suppressed, except in the all-down
|
|
@@ -106,6 +106,7 @@ class NetworkConnectionBannerController extends base_controller_1.BaseController
|
|
|
106
106
|
this.messenger.subscribe('ConnectivityController:stateChange', onStateChange);
|
|
107
107
|
/* eslint-enable no-restricted-syntax */
|
|
108
108
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
109
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_evaluate).call(this);
|
|
109
110
|
}
|
|
110
111
|
/**
|
|
111
112
|
* Clears the banner state regardless of the current rule outcome. The next
|
|
@@ -219,7 +220,7 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
219
220
|
.map(([chainId]) => chainId);
|
|
220
221
|
const { networkConfigurationsByChainId, networksMetadata } = this.messenger.call('NetworkController:getState');
|
|
221
222
|
const failedNetworks = [];
|
|
222
|
-
let
|
|
223
|
+
let totalNetworksWithMetadata = 0;
|
|
223
224
|
for (const chainId of enabledEvmChainIds) {
|
|
224
225
|
const networkConfiguration = networkConfigurationsByChainId[chainId];
|
|
225
226
|
if (!networkConfiguration) {
|
|
@@ -230,10 +231,12 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
230
231
|
if (!defaultRpcEndpoint) {
|
|
231
232
|
continue;
|
|
232
233
|
}
|
|
233
|
-
totalEnabled += 1;
|
|
234
234
|
const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];
|
|
235
|
-
if (metadata === undefined
|
|
236
|
-
|
|
235
|
+
if (metadata === undefined) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
totalNetworksWithMetadata += 1;
|
|
239
|
+
if (metadata.status === network_controller_1.NetworkStatus.Available) {
|
|
237
240
|
continue;
|
|
238
241
|
}
|
|
239
242
|
const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);
|
|
@@ -261,10 +264,10 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
261
264
|
const distinctDomains = new Set(failedNetworks
|
|
262
265
|
.map((entry) => entry.domain)
|
|
263
266
|
.filter((domain) => domain !== null)).size;
|
|
264
|
-
const
|
|
267
|
+
const areAllKnownNetworksFailed = failedNetworks.length === totalNetworksWithMetadata;
|
|
265
268
|
if (!firstCustomFailed &&
|
|
266
269
|
distinctDomains <= 1 &&
|
|
267
|
-
!
|
|
270
|
+
!areAllKnownNetworksFailed) {
|
|
268
271
|
return null;
|
|
269
272
|
}
|
|
270
273
|
const selected = firstCustomFailed ?? failedNetworks[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController.cjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller';\nimport type {\n ConnectivityControllerGetStateAction,\n ConnectivityControllerStateChangeEvent,\n} from '@metamask/connectivity-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkConfigurationByChainIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerUpdateNetworkAction,\n NetworkControllerStateChangeEvent,\n} from '@metamask/network-controller';\nimport { NetworkStatus } from '@metamask/network-controller';\nimport type {\n NetworkEnablementControllerGetStateAction,\n NetworkEnablementControllerStateChangeEvent,\n} from '@metamask/network-enablement-controller';\nimport type { Hex } from '@metamask/utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\n\nimport type { NetworkConnectionBannerControllerMethodActions } from './NetworkConnectionBannerController-method-action-types';\nimport { getDomain } from './url-utils';\n\n/**\n * The name of the {@link NetworkConnectionBannerController}, used to namespace\n * the controller's actions and events and to namespace the controller's state\n * data when composed with other controllers.\n */\nconst controllerName = 'NetworkConnectionBannerController';\n\n/**\n * Status the banner can be in. `available` means no banner is shown; the\n * `degraded` and `unavailable` values mirror the two-tier escalation that the\n * UI renders.\n */\nexport type NetworkConnectionBannerStatus =\n | 'available'\n | 'degraded'\n | 'unavailable';\n\n/**\n * Details of the failing network the banner should describe. Populated when\n * {@link NetworkConnectionBannerControllerState.status} is `degraded` or\n * `unavailable`, `null` otherwise.\n *\n * `infuraNetworkClientId` is the networkClientId of an Infura endpoint on the\n * same chain that the user can switch to. `null` when the failing endpoint is\n * already Infura, or when no Infura alternative exists.\n */\nexport type NetworkConnectionBannerFailedNetwork = {\n chainId: Hex;\n networkClientId: string;\n networkName: string;\n rpcUrl: string;\n isInfuraEndpoint: boolean;\n infuraNetworkClientId: string | null;\n};\n\n/**\n * State for the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerState = {\n status: NetworkConnectionBannerStatus;\n network: NetworkConnectionBannerFailedNetwork | null;\n};\n\nconst networkConnectionBannerControllerMetadata = {\n status: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n network: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n} satisfies StateMetadata<NetworkConnectionBannerControllerState>;\n\n/**\n * Constructs the default {@link NetworkConnectionBannerController} state.\n *\n * @returns The default state.\n */\nexport function getDefaultNetworkConnectionBannerControllerState(): NetworkConnectionBannerControllerState {\n return {\n status: 'available',\n network: null,\n };\n}\n\nconst DEGRADED_BANNER_TIMEOUT_MS = 5_000;\nconst UNAVAILABLE_BANNER_TIMEOUT_MS = 30_000;\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'dismissBanner',\n 'switchToDefaultInfuraRpc',\n] as const;\n\n/**\n * Retrieves the state of the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Actions that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerActions =\n | NetworkConnectionBannerControllerGetStateAction\n | NetworkConnectionBannerControllerMethodActions;\n\n/**\n * Actions from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} calls.\n */\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkConfigurationByChainIdAction\n | NetworkControllerUpdateNetworkAction\n | NetworkEnablementControllerGetStateAction\n | ConnectivityControllerGetStateAction;\n\n/**\n * Published when the state of {@link NetworkConnectionBannerController}\n * changes.\n */\nexport type NetworkConnectionBannerControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Events that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerEvents =\n NetworkConnectionBannerControllerStateChangedEvent;\n\n/**\n * Events from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} subscribes to.\n */\ntype AllowedEvents =\n | NetworkControllerStateChangeEvent\n | NetworkEnablementControllerStateChangeEvent\n | ConnectivityControllerStateChangeEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerMessenger = Messenger<\n typeof controllerName,\n NetworkConnectionBannerControllerActions | AllowedActions,\n NetworkConnectionBannerControllerEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerOptions = {\n /**\n * The messenger for inter-controller communication.\n */\n messenger: NetworkConnectionBannerControllerMessenger;\n};\n\n/**\n * NetworkConnectionBannerController decides whether the network connection\n * banner should be shown for the user, and which failing network it should\n * describe. It encapsulates the rule, the 5s / 30s timer escalation, and the\n * eTLD+1 grouping that was previously duplicated across MetaMask clients.\n *\n * The banner shows when:\n *\n * - The first failing network's default RPC is a custom (non-Infura) endpoint\n * — users always want to be informed about errors with RPCs they've chosen.\n * - Failed RPCs span more than one registrable domain (likely client-side).\n * - Every enabled EVM network is failing (escape hatch for single-network\n * setups so they still get a signal).\n *\n * A wide single-provider outage (e.g. every `*.infura.io` network goes down at\n * once) collapses to one domain and is suppressed, except in the all-down\n * single-network case. When a custom failure is present, it's surfaced first\n * so the \"Switch to MetaMask default RPC\" CTA targets the network the user\n * can act on.\n *\n * Clients only need to render the banner from the controller's state and wire\n * click handlers to {@link dismissBanner} or {@link switchToDefaultInfuraRpc}.\n */\nexport class NetworkConnectionBannerController extends BaseController<\n typeof controllerName,\n NetworkConnectionBannerControllerState,\n NetworkConnectionBannerControllerMessenger\n> {\n #degradedTimer: ReturnType<typeof setTimeout> | undefined;\n\n #unavailableTimer: ReturnType<typeof setTimeout> | undefined;\n\n #pendingNetworkClientId: string | undefined;\n\n /**\n * Constructs a new {@link NetworkConnectionBannerController}.\n *\n * @param args - The arguments to this controller.\n * @param args.messenger - The messenger suited for this controller.\n */\n constructor({ messenger }: NetworkConnectionBannerControllerOptions) {\n super({\n messenger,\n metadata: networkConnectionBannerControllerMetadata,\n name: controllerName,\n state: getDefaultNetworkConnectionBannerControllerState(),\n });\n\n const onStateChange = (): void => this.#evaluate();\n // Upstream controllers still expose :stateChange; switch to :stateChanged\n // once those packages migrate their event types.\n /* eslint-disable no-restricted-syntax -- awaiting upstream :stateChanged migration */\n this.messenger.subscribe('NetworkController:stateChange', onStateChange);\n this.messenger.subscribe(\n 'NetworkEnablementController:stateChange',\n onStateChange,\n );\n this.messenger.subscribe(\n 'ConnectivityController:stateChange',\n onStateChange,\n );\n /* eslint-enable no-restricted-syntax */\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Clears the banner state regardless of the current rule outcome. The next\n * subscription-driven evaluation will re-show the banner if the conditions\n * still hold.\n */\n dismissBanner(): void {\n this.#resetBanner();\n }\n\n /**\n * Switches the chain's default RPC endpoint to its first Infura endpoint,\n * causing the banner to clear once the network becomes available again.\n *\n * @param args - The arguments to this action.\n * @param args.chainId - The chain whose default RPC should be switched.\n * @throws If the chain configuration cannot be found, or if it has no\n * Infura endpoint to switch to, or if the default is already Infura.\n */\n async switchToDefaultInfuraRpc({ chainId }: { chainId: Hex }): Promise<void> {\n const networkConfiguration = this.messenger.call(\n 'NetworkController:getNetworkConfigurationByChainId',\n chainId,\n );\n if (!networkConfiguration) {\n throw new Error(\n `No network configuration found for chain ID \"${chainId}\".`,\n );\n }\n\n const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex(\n (endpoint) => isInfuraEndpoint(endpoint.url),\n );\n if (infuraEndpointIndex === -1) {\n throw new Error(\n `No Infura endpoint available for chain ID \"${chainId}\".`,\n );\n }\n if (infuraEndpointIndex === networkConfiguration.defaultRpcEndpointIndex) {\n // The default is already Infura; nothing to do.\n return;\n }\n\n await this.messenger.call(\n 'NetworkController:updateNetwork',\n chainId,\n {\n ...networkConfiguration,\n defaultRpcEndpointIndex: infuraEndpointIndex,\n },\n { replacementSelectedRpcEndpointIndex: infuraEndpointIndex },\n );\n }\n\n #evaluate(): void {\n const { connectivityStatus } = this.messenger.call(\n 'ConnectivityController:getState',\n );\n if (connectivityStatus === CONNECTIVITY_STATUSES.Offline) {\n this.#resetBanner();\n return;\n }\n\n const failed = this.#findFailedNetworkForBanner();\n\n if (!failed) {\n this.#resetBanner();\n return;\n }\n\n if (\n this.state.status !== 'available' &&\n this.state.network?.networkClientId === failed.networkClientId\n ) {\n this.update((draft) => {\n draft.network = failed;\n });\n return;\n }\n\n if (this.#pendingNetworkClientId === failed.networkClientId) {\n return;\n }\n\n this.#clearTimers();\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n\n this.#pendingNetworkClientId = failed.networkClientId;\n this.#degradedTimer = setTimeout(() => {\n this.#degradedTimer = undefined;\n this.#pendingNetworkClientId = undefined;\n const stillFailed = this.#findFailedNetworkForBanner();\n if (!stillFailed) {\n return;\n }\n this.update((draft) => {\n draft.status = 'degraded';\n draft.network = stillFailed;\n });\n this.#unavailableTimer = setTimeout(() => {\n this.#unavailableTimer = undefined;\n const stillFailedAtEscalation = this.#findFailedNetworkForBanner();\n if (!stillFailedAtEscalation) {\n return;\n }\n this.update((draft) => {\n draft.status = 'unavailable';\n draft.network = stillFailedAtEscalation;\n });\n }, UNAVAILABLE_BANNER_TIMEOUT_MS - DEGRADED_BANNER_TIMEOUT_MS);\n }, DEGRADED_BANNER_TIMEOUT_MS);\n }\n\n /**\n * Clears timers and resets banner state to {@link NetworkConnectionBannerStatus|`available`}\n * if it isn't there already.\n */\n #resetBanner(): void {\n this.#clearTimers();\n if (this.state.status !== 'available' || this.state.network !== null) {\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n }\n }\n\n #clearTimers(): void {\n this.#pendingNetworkClientId = undefined;\n if (this.#degradedTimer !== undefined) {\n clearTimeout(this.#degradedTimer);\n this.#degradedTimer = undefined;\n }\n if (this.#unavailableTimer !== undefined) {\n clearTimeout(this.#unavailableTimer);\n this.#unavailableTimer = undefined;\n }\n }\n\n #findFailedNetworkForBanner(): NetworkConnectionBannerFailedNetwork | null {\n const { enabledNetworkMap } = this.messenger.call(\n 'NetworkEnablementController:getState',\n );\n const enabledEvmChainIds = Object.entries(\n enabledNetworkMap[KnownCaipNamespace.Eip155] ?? {},\n )\n .filter(([, enabled]) => enabled)\n .map(([chainId]) => chainId as Hex);\n const { networkConfigurationsByChainId, networksMetadata } =\n this.messenger.call('NetworkController:getState');\n\n type EnrichedFailedNetwork = NetworkConnectionBannerFailedNetwork & {\n domain: string | null;\n };\n const failedNetworks: EnrichedFailedNetwork[] = [];\n let totalEnabled = 0;\n\n for (const chainId of enabledEvmChainIds) {\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n continue;\n }\n\n const { rpcEndpoints, defaultRpcEndpointIndex, name } =\n networkConfiguration;\n const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];\n if (!defaultRpcEndpoint) {\n continue;\n }\n\n totalEnabled += 1;\n\n const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];\n if (\n metadata === undefined ||\n metadata.status === NetworkStatus.Available\n ) {\n continue;\n }\n\n const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);\n\n // For custom endpoints (non-Infura), find an Infura endpoint on this\n // chain that we could offer to switch to.\n let infuraNetworkClientId: string | null = null;\n if (!endpointIsInfura) {\n const otherInfura = rpcEndpoints.find(\n (endpoint, index) =>\n index !== defaultRpcEndpointIndex && isInfuraEndpoint(endpoint.url),\n );\n infuraNetworkClientId = otherInfura?.networkClientId ?? null;\n }\n\n failedNetworks.push({\n chainId,\n networkClientId: defaultRpcEndpoint.networkClientId,\n networkName: name,\n rpcUrl: defaultRpcEndpoint.url,\n isInfuraEndpoint: endpointIsInfura,\n infuraNetworkClientId,\n domain: getDomain(defaultRpcEndpoint.url),\n });\n }\n\n if (failedNetworks.length === 0) {\n return null;\n }\n\n const firstCustomFailed = failedNetworks.find(\n (entry) => !entry.isInfuraEndpoint,\n );\n const distinctDomains = new Set(\n failedNetworks\n .map((entry) => entry.domain)\n .filter((domain): domain is string => domain !== null),\n ).size;\n const areAllEnabledNetworksFailed = failedNetworks.length === totalEnabled;\n\n if (\n !firstCustomFailed &&\n distinctDomains <= 1 &&\n !areAllEnabledNetworksFailed\n ) {\n return null;\n }\n\n const selected = firstCustomFailed ?? failedNetworks[0];\n return {\n chainId: selected.chainId,\n networkClientId: selected.networkClientId,\n networkName: selected.networkName,\n rpcUrl: selected.rpcUrl,\n isInfuraEndpoint: selected.isInfuraEndpoint,\n infuraNetworkClientId: selected.infuraNetworkClientId,\n };\n }\n}\n\n/**\n * Checks if an RPC URL is hosted on the Infura service. Detection is by\n * hostname suffix rather than the exact MetaMask-key URL pattern, so any\n * `*.infura.io` URL counts. That's the right shape for grouping by provider.\n *\n * @param url - The RPC URL to check.\n * @returns True if the URL's host is on `infura.io`.\n */\nfunction isInfuraEndpoint(url: string): boolean {\n try {\n return new URL(url).hostname.toLowerCase().endsWith('.infura.io');\n } catch {\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController.cjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller';\nimport type {\n ConnectivityControllerGetStateAction,\n ConnectivityControllerStateChangeEvent,\n} from '@metamask/connectivity-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkConfigurationByChainIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerUpdateNetworkAction,\n NetworkControllerStateChangeEvent,\n} from '@metamask/network-controller';\nimport { NetworkStatus } from '@metamask/network-controller';\nimport type {\n NetworkEnablementControllerGetStateAction,\n NetworkEnablementControllerStateChangeEvent,\n} from '@metamask/network-enablement-controller';\nimport type { Hex } from '@metamask/utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\n\nimport type { NetworkConnectionBannerControllerMethodActions } from './NetworkConnectionBannerController-method-action-types';\nimport { getDomain } from './url-utils';\n\n/**\n * The name of the {@link NetworkConnectionBannerController}, used to namespace\n * the controller's actions and events and to namespace the controller's state\n * data when composed with other controllers.\n */\nconst controllerName = 'NetworkConnectionBannerController';\n\n/**\n * Status the banner can be in. `available` means no banner is shown; the\n * `degraded` and `unavailable` values mirror the two-tier escalation that the\n * UI renders.\n */\nexport type NetworkConnectionBannerStatus =\n | 'available'\n | 'degraded'\n | 'unavailable';\n\n/**\n * Details of the failing network the banner should describe. Populated when\n * {@link NetworkConnectionBannerControllerState.status} is `degraded` or\n * `unavailable`, `null` otherwise.\n *\n * `infuraNetworkClientId` is the networkClientId of an Infura endpoint on the\n * same chain that the user can switch to. `null` when the failing endpoint is\n * already Infura, or when no Infura alternative exists.\n */\nexport type NetworkConnectionBannerFailedNetwork = {\n chainId: Hex;\n networkClientId: string;\n networkName: string;\n rpcUrl: string;\n isInfuraEndpoint: boolean;\n infuraNetworkClientId: string | null;\n};\n\n/**\n * State for the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerState = {\n status: NetworkConnectionBannerStatus;\n network: NetworkConnectionBannerFailedNetwork | null;\n};\n\nconst networkConnectionBannerControllerMetadata = {\n status: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n network: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n} satisfies StateMetadata<NetworkConnectionBannerControllerState>;\n\n/**\n * Constructs the default {@link NetworkConnectionBannerController} state.\n *\n * @returns The default state.\n */\nexport function getDefaultNetworkConnectionBannerControllerState(): NetworkConnectionBannerControllerState {\n return {\n status: 'available',\n network: null,\n };\n}\n\nconst DEGRADED_BANNER_TIMEOUT_MS = 5_000;\nconst UNAVAILABLE_BANNER_TIMEOUT_MS = 30_000;\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'dismissBanner',\n 'switchToDefaultInfuraRpc',\n] as const;\n\n/**\n * Retrieves the state of the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Actions that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerActions =\n | NetworkConnectionBannerControllerGetStateAction\n | NetworkConnectionBannerControllerMethodActions;\n\n/**\n * Actions from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} calls.\n */\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkConfigurationByChainIdAction\n | NetworkControllerUpdateNetworkAction\n | NetworkEnablementControllerGetStateAction\n | ConnectivityControllerGetStateAction;\n\n/**\n * Published when the state of {@link NetworkConnectionBannerController}\n * changes.\n */\nexport type NetworkConnectionBannerControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Events that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerEvents =\n NetworkConnectionBannerControllerStateChangedEvent;\n\n/**\n * Events from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} subscribes to.\n */\ntype AllowedEvents =\n | NetworkControllerStateChangeEvent\n | NetworkEnablementControllerStateChangeEvent\n | ConnectivityControllerStateChangeEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerMessenger = Messenger<\n typeof controllerName,\n NetworkConnectionBannerControllerActions | AllowedActions,\n NetworkConnectionBannerControllerEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerOptions = {\n /**\n * The messenger for inter-controller communication.\n */\n messenger: NetworkConnectionBannerControllerMessenger;\n};\n\n/**\n * NetworkConnectionBannerController decides whether the network connection\n * banner should be shown for the user, and which failing network it should\n * describe. It encapsulates the rule, the 5s / 30s timer escalation, and the\n * eTLD+1 grouping that was previously duplicated across MetaMask clients.\n *\n * The banner shows when:\n *\n * - The first failing network's default RPC is a custom (non-Infura) endpoint\n * — users always want to be informed about errors with RPCs they've chosen.\n * - Failed RPCs span more than one registrable domain (likely client-side).\n * - Every enabled EVM network with known connectivity status is failing\n * (escape hatch for single-network setups so they still get a signal).\n *\n * A wide single-provider outage (e.g. every `*.infura.io` network goes down at\n * once) collapses to one domain and is suppressed, except in the all-down\n * single-network case. When a custom failure is present, it's surfaced first\n * so the \"Switch to MetaMask default RPC\" CTA targets the network the user\n * can act on.\n *\n * Clients only need to render the banner from the controller's state and wire\n * click handlers to {@link dismissBanner} or {@link switchToDefaultInfuraRpc}.\n */\nexport class NetworkConnectionBannerController extends BaseController<\n typeof controllerName,\n NetworkConnectionBannerControllerState,\n NetworkConnectionBannerControllerMessenger\n> {\n #degradedTimer: ReturnType<typeof setTimeout> | undefined;\n\n #unavailableTimer: ReturnType<typeof setTimeout> | undefined;\n\n #pendingNetworkClientId: string | undefined;\n\n /**\n * Constructs a new {@link NetworkConnectionBannerController}.\n *\n * @param args - The arguments to this controller.\n * @param args.messenger - The messenger suited for this controller.\n */\n constructor({ messenger }: NetworkConnectionBannerControllerOptions) {\n super({\n messenger,\n metadata: networkConnectionBannerControllerMetadata,\n name: controllerName,\n state: getDefaultNetworkConnectionBannerControllerState(),\n });\n\n const onStateChange = (): void => this.#evaluate();\n // Upstream controllers still expose :stateChange; switch to :stateChanged\n // once those packages migrate their event types.\n /* eslint-disable no-restricted-syntax -- awaiting upstream :stateChanged migration */\n this.messenger.subscribe('NetworkController:stateChange', onStateChange);\n this.messenger.subscribe(\n 'NetworkEnablementController:stateChange',\n onStateChange,\n );\n this.messenger.subscribe(\n 'ConnectivityController:stateChange',\n onStateChange,\n );\n /* eslint-enable no-restricted-syntax */\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#evaluate();\n }\n\n /**\n * Clears the banner state regardless of the current rule outcome. The next\n * subscription-driven evaluation will re-show the banner if the conditions\n * still hold.\n */\n dismissBanner(): void {\n this.#resetBanner();\n }\n\n /**\n * Switches the chain's default RPC endpoint to its first Infura endpoint,\n * causing the banner to clear once the network becomes available again.\n *\n * @param args - The arguments to this action.\n * @param args.chainId - The chain whose default RPC should be switched.\n * @throws If the chain configuration cannot be found, or if it has no\n * Infura endpoint to switch to, or if the default is already Infura.\n */\n async switchToDefaultInfuraRpc({ chainId }: { chainId: Hex }): Promise<void> {\n const networkConfiguration = this.messenger.call(\n 'NetworkController:getNetworkConfigurationByChainId',\n chainId,\n );\n if (!networkConfiguration) {\n throw new Error(\n `No network configuration found for chain ID \"${chainId}\".`,\n );\n }\n\n const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex(\n (endpoint) => isInfuraEndpoint(endpoint.url),\n );\n if (infuraEndpointIndex === -1) {\n throw new Error(\n `No Infura endpoint available for chain ID \"${chainId}\".`,\n );\n }\n if (infuraEndpointIndex === networkConfiguration.defaultRpcEndpointIndex) {\n // The default is already Infura; nothing to do.\n return;\n }\n\n await this.messenger.call(\n 'NetworkController:updateNetwork',\n chainId,\n {\n ...networkConfiguration,\n defaultRpcEndpointIndex: infuraEndpointIndex,\n },\n { replacementSelectedRpcEndpointIndex: infuraEndpointIndex },\n );\n }\n\n #evaluate(): void {\n const { connectivityStatus } = this.messenger.call(\n 'ConnectivityController:getState',\n );\n if (connectivityStatus === CONNECTIVITY_STATUSES.Offline) {\n this.#resetBanner();\n return;\n }\n\n const failed = this.#findFailedNetworkForBanner();\n\n if (!failed) {\n this.#resetBanner();\n return;\n }\n\n if (\n this.state.status !== 'available' &&\n this.state.network?.networkClientId === failed.networkClientId\n ) {\n this.update((draft) => {\n draft.network = failed;\n });\n return;\n }\n\n if (this.#pendingNetworkClientId === failed.networkClientId) {\n return;\n }\n\n this.#clearTimers();\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n\n this.#pendingNetworkClientId = failed.networkClientId;\n this.#degradedTimer = setTimeout(() => {\n this.#degradedTimer = undefined;\n this.#pendingNetworkClientId = undefined;\n const stillFailed = this.#findFailedNetworkForBanner();\n if (!stillFailed) {\n return;\n }\n this.update((draft) => {\n draft.status = 'degraded';\n draft.network = stillFailed;\n });\n this.#unavailableTimer = setTimeout(() => {\n this.#unavailableTimer = undefined;\n const stillFailedAtEscalation = this.#findFailedNetworkForBanner();\n if (!stillFailedAtEscalation) {\n return;\n }\n this.update((draft) => {\n draft.status = 'unavailable';\n draft.network = stillFailedAtEscalation;\n });\n }, UNAVAILABLE_BANNER_TIMEOUT_MS - DEGRADED_BANNER_TIMEOUT_MS);\n }, DEGRADED_BANNER_TIMEOUT_MS);\n }\n\n /**\n * Clears timers and resets banner state to {@link NetworkConnectionBannerStatus|`available`}\n * if it isn't there already.\n */\n #resetBanner(): void {\n this.#clearTimers();\n if (this.state.status !== 'available' || this.state.network !== null) {\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n }\n }\n\n #clearTimers(): void {\n this.#pendingNetworkClientId = undefined;\n if (this.#degradedTimer !== undefined) {\n clearTimeout(this.#degradedTimer);\n this.#degradedTimer = undefined;\n }\n if (this.#unavailableTimer !== undefined) {\n clearTimeout(this.#unavailableTimer);\n this.#unavailableTimer = undefined;\n }\n }\n\n #findFailedNetworkForBanner(): NetworkConnectionBannerFailedNetwork | null {\n const { enabledNetworkMap } = this.messenger.call(\n 'NetworkEnablementController:getState',\n );\n const enabledEvmChainIds = Object.entries(\n enabledNetworkMap[KnownCaipNamespace.Eip155] ?? {},\n )\n .filter(([, enabled]) => enabled)\n .map(([chainId]) => chainId as Hex);\n const { networkConfigurationsByChainId, networksMetadata } =\n this.messenger.call('NetworkController:getState');\n\n type EnrichedFailedNetwork = NetworkConnectionBannerFailedNetwork & {\n domain: string | null;\n };\n const failedNetworks: EnrichedFailedNetwork[] = [];\n let totalNetworksWithMetadata = 0;\n\n for (const chainId of enabledEvmChainIds) {\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n continue;\n }\n\n const { rpcEndpoints, defaultRpcEndpointIndex, name } =\n networkConfiguration;\n const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];\n if (!defaultRpcEndpoint) {\n continue;\n }\n\n const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];\n if (metadata === undefined) {\n continue;\n }\n\n totalNetworksWithMetadata += 1;\n\n if (metadata.status === NetworkStatus.Available) {\n continue;\n }\n\n const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);\n\n // For custom endpoints (non-Infura), find an Infura endpoint on this\n // chain that we could offer to switch to.\n let infuraNetworkClientId: string | null = null;\n if (!endpointIsInfura) {\n const otherInfura = rpcEndpoints.find(\n (endpoint, index) =>\n index !== defaultRpcEndpointIndex && isInfuraEndpoint(endpoint.url),\n );\n infuraNetworkClientId = otherInfura?.networkClientId ?? null;\n }\n\n failedNetworks.push({\n chainId,\n networkClientId: defaultRpcEndpoint.networkClientId,\n networkName: name,\n rpcUrl: defaultRpcEndpoint.url,\n isInfuraEndpoint: endpointIsInfura,\n infuraNetworkClientId,\n domain: getDomain(defaultRpcEndpoint.url),\n });\n }\n\n if (failedNetworks.length === 0) {\n return null;\n }\n\n const firstCustomFailed = failedNetworks.find(\n (entry) => !entry.isInfuraEndpoint,\n );\n const distinctDomains = new Set(\n failedNetworks\n .map((entry) => entry.domain)\n .filter((domain): domain is string => domain !== null),\n ).size;\n const areAllKnownNetworksFailed =\n failedNetworks.length === totalNetworksWithMetadata;\n\n if (\n !firstCustomFailed &&\n distinctDomains <= 1 &&\n !areAllKnownNetworksFailed\n ) {\n return null;\n }\n\n const selected = firstCustomFailed ?? failedNetworks[0];\n return {\n chainId: selected.chainId,\n networkClientId: selected.networkClientId,\n networkName: selected.networkName,\n rpcUrl: selected.rpcUrl,\n isInfuraEndpoint: selected.isInfuraEndpoint,\n infuraNetworkClientId: selected.infuraNetworkClientId,\n };\n }\n}\n\n/**\n * Checks if an RPC URL is hosted on the Infura service. Detection is by\n * hostname suffix rather than the exact MetaMask-key URL pattern, so any\n * `*.infura.io` URL counts. That's the right shape for grouping by provider.\n *\n * @param url - The RPC URL to check.\n * @returns True if the URL's host is on `infura.io`.\n */\nfunction isInfuraEndpoint(url: string): boolean {\n try {\n return new URL(url).hostname.toLowerCase().endsWith('.infura.io');\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -102,8 +102,8 @@ export type NetworkConnectionBannerControllerOptions = {
|
|
|
102
102
|
* - The first failing network's default RPC is a custom (non-Infura) endpoint
|
|
103
103
|
* — users always want to be informed about errors with RPCs they've chosen.
|
|
104
104
|
* - Failed RPCs span more than one registrable domain (likely client-side).
|
|
105
|
-
* - Every enabled EVM network
|
|
106
|
-
* setups so they still get a signal).
|
|
105
|
+
* - Every enabled EVM network with known connectivity status is failing
|
|
106
|
+
* (escape hatch for single-network setups so they still get a signal).
|
|
107
107
|
*
|
|
108
108
|
* A wide single-provider outage (e.g. every `*.infura.io` network goes down at
|
|
109
109
|
* once) collapses to one domain and is suppressed, except in the all-down
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController.d.cts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,0CAA0C;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,uDAAuD,EACvD,+BAA+B,EAC/B,oCAAoC,EACpC,iCAAiC,EAClC,qCAAqC;AAEtC,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,gDAAgD;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAG3C,OAAO,KAAK,EAAE,8CAA8C,EAAE,oEAAgE;AAG9H;;;;GAIG;AACH,QAAA,MAAM,cAAc,sCAAsC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,UAAU,GACV,aAAa,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,GAAG,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD,MAAM,EAAE,6BAA6B,CAAC;IACtC,OAAO,EAAE,oCAAoC,GAAG,IAAI,CAAC;CACtD,CAAC;AAiBF;;;;GAIG;AACH,wBAAgB,gDAAgD,IAAI,sCAAsC,CAKzG;AAUD;;GAEG;AACH,MAAM,MAAM,+CAA+C,GACzD,wBAAwB,CACtB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,wCAAwC,GAChD,+CAA+C,GAC/C,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,KAAK,cAAc,GACf,+BAA+B,GAC/B,uDAAuD,GACvD,oCAAoC,GACpC,yCAAyC,GACzC,oCAAoC,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,kDAAkD,GAC5D,2BAA2B,CACzB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,uCAAuC,GACjD,kDAAkD,CAAC;AAErD;;;GAGG;AACH,KAAK,aAAa,GACd,iCAAiC,GACjC,2CAA2C,GAC3C,sCAAsC,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,0CAA0C,GAAG,SAAS,CAChE,OAAO,cAAc,EACrB,wCAAwC,GAAG,cAAc,EACzD,uCAAuC,GAAG,aAAa,CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD;;OAEG;IACH,SAAS,EAAE,0CAA0C,CAAC;CACvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,iCAAkC,SAAQ,cAAc,CACnE,OAAO,cAAc,EACrB,sCAAsC,EACtC,0CAA0C,CAC3C;;IAOC;;;;;OAKG;gBACS,EAAE,SAAS,EAAE,EAAE,wCAAwC;
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController.d.cts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,0CAA0C;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,uDAAuD,EACvD,+BAA+B,EAC/B,oCAAoC,EACpC,iCAAiC,EAClC,qCAAqC;AAEtC,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,gDAAgD;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAG3C,OAAO,KAAK,EAAE,8CAA8C,EAAE,oEAAgE;AAG9H;;;;GAIG;AACH,QAAA,MAAM,cAAc,sCAAsC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,UAAU,GACV,aAAa,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,GAAG,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD,MAAM,EAAE,6BAA6B,CAAC;IACtC,OAAO,EAAE,oCAAoC,GAAG,IAAI,CAAC;CACtD,CAAC;AAiBF;;;;GAIG;AACH,wBAAgB,gDAAgD,IAAI,sCAAsC,CAKzG;AAUD;;GAEG;AACH,MAAM,MAAM,+CAA+C,GACzD,wBAAwB,CACtB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,wCAAwC,GAChD,+CAA+C,GAC/C,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,KAAK,cAAc,GACf,+BAA+B,GAC/B,uDAAuD,GACvD,oCAAoC,GACpC,yCAAyC,GACzC,oCAAoC,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,kDAAkD,GAC5D,2BAA2B,CACzB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,uCAAuC,GACjD,kDAAkD,CAAC;AAErD;;;GAGG;AACH,KAAK,aAAa,GACd,iCAAiC,GACjC,2CAA2C,GAC3C,sCAAsC,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,0CAA0C,GAAG,SAAS,CAChE,OAAO,cAAc,EACrB,wCAAwC,GAAG,cAAc,EACzD,uCAAuC,GAAG,aAAa,CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD;;OAEG;IACH,SAAS,EAAE,0CAA0C,CAAC;CACvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,iCAAkC,SAAQ,cAAc,CACnE,OAAO,cAAc,EACrB,sCAAsC,EACtC,0CAA0C,CAC3C;;IAOC;;;;;OAKG;gBACS,EAAE,SAAS,EAAE,EAAE,wCAAwC;IA+BnE;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAIrB;;;;;;;;OAQG;IACG,wBAAwB,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA8N7E"}
|
|
@@ -102,8 +102,8 @@ export type NetworkConnectionBannerControllerOptions = {
|
|
|
102
102
|
* - The first failing network's default RPC is a custom (non-Infura) endpoint
|
|
103
103
|
* — users always want to be informed about errors with RPCs they've chosen.
|
|
104
104
|
* - Failed RPCs span more than one registrable domain (likely client-side).
|
|
105
|
-
* - Every enabled EVM network
|
|
106
|
-
* setups so they still get a signal).
|
|
105
|
+
* - Every enabled EVM network with known connectivity status is failing
|
|
106
|
+
* (escape hatch for single-network setups so they still get a signal).
|
|
107
107
|
*
|
|
108
108
|
* A wide single-provider outage (e.g. every `*.infura.io` network goes down at
|
|
109
109
|
* once) collapses to one domain and is suppressed, except in the all-down
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController.d.mts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,0CAA0C;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,uDAAuD,EACvD,+BAA+B,EAC/B,oCAAoC,EACpC,iCAAiC,EAClC,qCAAqC;AAEtC,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,gDAAgD;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAG3C,OAAO,KAAK,EAAE,8CAA8C,EAAE,oEAAgE;AAG9H;;;;GAIG;AACH,QAAA,MAAM,cAAc,sCAAsC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,UAAU,GACV,aAAa,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,GAAG,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD,MAAM,EAAE,6BAA6B,CAAC;IACtC,OAAO,EAAE,oCAAoC,GAAG,IAAI,CAAC;CACtD,CAAC;AAiBF;;;;GAIG;AACH,wBAAgB,gDAAgD,IAAI,sCAAsC,CAKzG;AAUD;;GAEG;AACH,MAAM,MAAM,+CAA+C,GACzD,wBAAwB,CACtB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,wCAAwC,GAChD,+CAA+C,GAC/C,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,KAAK,cAAc,GACf,+BAA+B,GAC/B,uDAAuD,GACvD,oCAAoC,GACpC,yCAAyC,GACzC,oCAAoC,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,kDAAkD,GAC5D,2BAA2B,CACzB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,uCAAuC,GACjD,kDAAkD,CAAC;AAErD;;;GAGG;AACH,KAAK,aAAa,GACd,iCAAiC,GACjC,2CAA2C,GAC3C,sCAAsC,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,0CAA0C,GAAG,SAAS,CAChE,OAAO,cAAc,EACrB,wCAAwC,GAAG,cAAc,EACzD,uCAAuC,GAAG,aAAa,CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD;;OAEG;IACH,SAAS,EAAE,0CAA0C,CAAC;CACvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,iCAAkC,SAAQ,cAAc,CACnE,OAAO,cAAc,EACrB,sCAAsC,EACtC,0CAA0C,CAC3C;;IAOC;;;;;OAKG;gBACS,EAAE,SAAS,EAAE,EAAE,wCAAwC;
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController.d.mts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,0CAA0C;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,uDAAuD,EACvD,+BAA+B,EAC/B,oCAAoC,EACpC,iCAAiC,EAClC,qCAAqC;AAEtC,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,gDAAgD;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAG3C,OAAO,KAAK,EAAE,8CAA8C,EAAE,oEAAgE;AAG9H;;;;GAIG;AACH,QAAA,MAAM,cAAc,sCAAsC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,UAAU,GACV,aAAa,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,GAAG,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG;IACnD,MAAM,EAAE,6BAA6B,CAAC;IACtC,OAAO,EAAE,oCAAoC,GAAG,IAAI,CAAC;CACtD,CAAC;AAiBF;;;;GAIG;AACH,wBAAgB,gDAAgD,IAAI,sCAAsC,CAKzG;AAUD;;GAEG;AACH,MAAM,MAAM,+CAA+C,GACzD,wBAAwB,CACtB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,wCAAwC,GAChD,+CAA+C,GAC/C,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,KAAK,cAAc,GACf,+BAA+B,GAC/B,uDAAuD,GACvD,oCAAoC,GACpC,yCAAyC,GACzC,oCAAoC,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,kDAAkD,GAC5D,2BAA2B,CACzB,OAAO,cAAc,EACrB,sCAAsC,CACvC,CAAC;AAEJ;;;GAGG;AACH,MAAM,MAAM,uCAAuC,GACjD,kDAAkD,CAAC;AAErD;;;GAGG;AACH,KAAK,aAAa,GACd,iCAAiC,GACjC,2CAA2C,GAC3C,sCAAsC,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,0CAA0C,GAAG,SAAS,CAChE,OAAO,cAAc,EACrB,wCAAwC,GAAG,cAAc,EACzD,uCAAuC,GAAG,aAAa,CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD;;OAEG;IACH,SAAS,EAAE,0CAA0C,CAAC;CACvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,iCAAkC,SAAQ,cAAc,CACnE,OAAO,cAAc,EACrB,sCAAsC,EACtC,0CAA0C,CAC3C;;IAOC;;;;;OAKG;gBACS,EAAE,SAAS,EAAE,EAAE,wCAAwC;IA+BnE;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAIrB;;;;;;;;OAQG;IACG,wBAAwB,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA8N7E"}
|
|
@@ -63,8 +63,8 @@ const MESSENGER_EXPOSED_METHODS = [
|
|
|
63
63
|
* - The first failing network's default RPC is a custom (non-Infura) endpoint
|
|
64
64
|
* — users always want to be informed about errors with RPCs they've chosen.
|
|
65
65
|
* - Failed RPCs span more than one registrable domain (likely client-side).
|
|
66
|
-
* - Every enabled EVM network
|
|
67
|
-
* setups so they still get a signal).
|
|
66
|
+
* - Every enabled EVM network with known connectivity status is failing
|
|
67
|
+
* (escape hatch for single-network setups so they still get a signal).
|
|
68
68
|
*
|
|
69
69
|
* A wide single-provider outage (e.g. every `*.infura.io` network goes down at
|
|
70
70
|
* once) collapses to one domain and is suppressed, except in the all-down
|
|
@@ -102,6 +102,7 @@ export class NetworkConnectionBannerController extends BaseController {
|
|
|
102
102
|
this.messenger.subscribe('ConnectivityController:stateChange', onStateChange);
|
|
103
103
|
/* eslint-enable no-restricted-syntax */
|
|
104
104
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
105
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_evaluate).call(this);
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
108
|
* Clears the banner state regardless of the current rule outcome. The next
|
|
@@ -214,7 +215,7 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
214
215
|
.map(([chainId]) => chainId);
|
|
215
216
|
const { networkConfigurationsByChainId, networksMetadata } = this.messenger.call('NetworkController:getState');
|
|
216
217
|
const failedNetworks = [];
|
|
217
|
-
let
|
|
218
|
+
let totalNetworksWithMetadata = 0;
|
|
218
219
|
for (const chainId of enabledEvmChainIds) {
|
|
219
220
|
const networkConfiguration = networkConfigurationsByChainId[chainId];
|
|
220
221
|
if (!networkConfiguration) {
|
|
@@ -225,10 +226,12 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
225
226
|
if (!defaultRpcEndpoint) {
|
|
226
227
|
continue;
|
|
227
228
|
}
|
|
228
|
-
totalEnabled += 1;
|
|
229
229
|
const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];
|
|
230
|
-
if (metadata === undefined
|
|
231
|
-
|
|
230
|
+
if (metadata === undefined) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
totalNetworksWithMetadata += 1;
|
|
234
|
+
if (metadata.status === NetworkStatus.Available) {
|
|
232
235
|
continue;
|
|
233
236
|
}
|
|
234
237
|
const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);
|
|
@@ -256,10 +259,10 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
256
259
|
const distinctDomains = new Set(failedNetworks
|
|
257
260
|
.map((entry) => entry.domain)
|
|
258
261
|
.filter((domain) => domain !== null)).size;
|
|
259
|
-
const
|
|
262
|
+
const areAllKnownNetworksFailed = failedNetworks.length === totalNetworksWithMetadata;
|
|
260
263
|
if (!firstCustomFailed &&
|
|
261
264
|
distinctDomains <= 1 &&
|
|
262
|
-
!
|
|
265
|
+
!areAllKnownNetworksFailed) {
|
|
263
266
|
return null;
|
|
264
267
|
}
|
|
265
268
|
const selected = firstCustomFailed ?? failedNetworks[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController.mjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller';\nimport type {\n ConnectivityControllerGetStateAction,\n ConnectivityControllerStateChangeEvent,\n} from '@metamask/connectivity-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkConfigurationByChainIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerUpdateNetworkAction,\n NetworkControllerStateChangeEvent,\n} from '@metamask/network-controller';\nimport { NetworkStatus } from '@metamask/network-controller';\nimport type {\n NetworkEnablementControllerGetStateAction,\n NetworkEnablementControllerStateChangeEvent,\n} from '@metamask/network-enablement-controller';\nimport type { Hex } from '@metamask/utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\n\nimport type { NetworkConnectionBannerControllerMethodActions } from './NetworkConnectionBannerController-method-action-types';\nimport { getDomain } from './url-utils';\n\n/**\n * The name of the {@link NetworkConnectionBannerController}, used to namespace\n * the controller's actions and events and to namespace the controller's state\n * data when composed with other controllers.\n */\nconst controllerName = 'NetworkConnectionBannerController';\n\n/**\n * Status the banner can be in. `available` means no banner is shown; the\n * `degraded` and `unavailable` values mirror the two-tier escalation that the\n * UI renders.\n */\nexport type NetworkConnectionBannerStatus =\n | 'available'\n | 'degraded'\n | 'unavailable';\n\n/**\n * Details of the failing network the banner should describe. Populated when\n * {@link NetworkConnectionBannerControllerState.status} is `degraded` or\n * `unavailable`, `null` otherwise.\n *\n * `infuraNetworkClientId` is the networkClientId of an Infura endpoint on the\n * same chain that the user can switch to. `null` when the failing endpoint is\n * already Infura, or when no Infura alternative exists.\n */\nexport type NetworkConnectionBannerFailedNetwork = {\n chainId: Hex;\n networkClientId: string;\n networkName: string;\n rpcUrl: string;\n isInfuraEndpoint: boolean;\n infuraNetworkClientId: string | null;\n};\n\n/**\n * State for the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerState = {\n status: NetworkConnectionBannerStatus;\n network: NetworkConnectionBannerFailedNetwork | null;\n};\n\nconst networkConnectionBannerControllerMetadata = {\n status: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n network: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n} satisfies StateMetadata<NetworkConnectionBannerControllerState>;\n\n/**\n * Constructs the default {@link NetworkConnectionBannerController} state.\n *\n * @returns The default state.\n */\nexport function getDefaultNetworkConnectionBannerControllerState(): NetworkConnectionBannerControllerState {\n return {\n status: 'available',\n network: null,\n };\n}\n\nconst DEGRADED_BANNER_TIMEOUT_MS = 5_000;\nconst UNAVAILABLE_BANNER_TIMEOUT_MS = 30_000;\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'dismissBanner',\n 'switchToDefaultInfuraRpc',\n] as const;\n\n/**\n * Retrieves the state of the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Actions that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerActions =\n | NetworkConnectionBannerControllerGetStateAction\n | NetworkConnectionBannerControllerMethodActions;\n\n/**\n * Actions from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} calls.\n */\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkConfigurationByChainIdAction\n | NetworkControllerUpdateNetworkAction\n | NetworkEnablementControllerGetStateAction\n | ConnectivityControllerGetStateAction;\n\n/**\n * Published when the state of {@link NetworkConnectionBannerController}\n * changes.\n */\nexport type NetworkConnectionBannerControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Events that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerEvents =\n NetworkConnectionBannerControllerStateChangedEvent;\n\n/**\n * Events from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} subscribes to.\n */\ntype AllowedEvents =\n | NetworkControllerStateChangeEvent\n | NetworkEnablementControllerStateChangeEvent\n | ConnectivityControllerStateChangeEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerMessenger = Messenger<\n typeof controllerName,\n NetworkConnectionBannerControllerActions | AllowedActions,\n NetworkConnectionBannerControllerEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerOptions = {\n /**\n * The messenger for inter-controller communication.\n */\n messenger: NetworkConnectionBannerControllerMessenger;\n};\n\n/**\n * NetworkConnectionBannerController decides whether the network connection\n * banner should be shown for the user, and which failing network it should\n * describe. It encapsulates the rule, the 5s / 30s timer escalation, and the\n * eTLD+1 grouping that was previously duplicated across MetaMask clients.\n *\n * The banner shows when:\n *\n * - The first failing network's default RPC is a custom (non-Infura) endpoint\n * — users always want to be informed about errors with RPCs they've chosen.\n * - Failed RPCs span more than one registrable domain (likely client-side).\n * - Every enabled EVM network is failing (escape hatch for single-network\n * setups so they still get a signal).\n *\n * A wide single-provider outage (e.g. every `*.infura.io` network goes down at\n * once) collapses to one domain and is suppressed, except in the all-down\n * single-network case. When a custom failure is present, it's surfaced first\n * so the \"Switch to MetaMask default RPC\" CTA targets the network the user\n * can act on.\n *\n * Clients only need to render the banner from the controller's state and wire\n * click handlers to {@link dismissBanner} or {@link switchToDefaultInfuraRpc}.\n */\nexport class NetworkConnectionBannerController extends BaseController<\n typeof controllerName,\n NetworkConnectionBannerControllerState,\n NetworkConnectionBannerControllerMessenger\n> {\n #degradedTimer: ReturnType<typeof setTimeout> | undefined;\n\n #unavailableTimer: ReturnType<typeof setTimeout> | undefined;\n\n #pendingNetworkClientId: string | undefined;\n\n /**\n * Constructs a new {@link NetworkConnectionBannerController}.\n *\n * @param args - The arguments to this controller.\n * @param args.messenger - The messenger suited for this controller.\n */\n constructor({ messenger }: NetworkConnectionBannerControllerOptions) {\n super({\n messenger,\n metadata: networkConnectionBannerControllerMetadata,\n name: controllerName,\n state: getDefaultNetworkConnectionBannerControllerState(),\n });\n\n const onStateChange = (): void => this.#evaluate();\n // Upstream controllers still expose :stateChange; switch to :stateChanged\n // once those packages migrate their event types.\n /* eslint-disable no-restricted-syntax -- awaiting upstream :stateChanged migration */\n this.messenger.subscribe('NetworkController:stateChange', onStateChange);\n this.messenger.subscribe(\n 'NetworkEnablementController:stateChange',\n onStateChange,\n );\n this.messenger.subscribe(\n 'ConnectivityController:stateChange',\n onStateChange,\n );\n /* eslint-enable no-restricted-syntax */\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Clears the banner state regardless of the current rule outcome. The next\n * subscription-driven evaluation will re-show the banner if the conditions\n * still hold.\n */\n dismissBanner(): void {\n this.#resetBanner();\n }\n\n /**\n * Switches the chain's default RPC endpoint to its first Infura endpoint,\n * causing the banner to clear once the network becomes available again.\n *\n * @param args - The arguments to this action.\n * @param args.chainId - The chain whose default RPC should be switched.\n * @throws If the chain configuration cannot be found, or if it has no\n * Infura endpoint to switch to, or if the default is already Infura.\n */\n async switchToDefaultInfuraRpc({ chainId }: { chainId: Hex }): Promise<void> {\n const networkConfiguration = this.messenger.call(\n 'NetworkController:getNetworkConfigurationByChainId',\n chainId,\n );\n if (!networkConfiguration) {\n throw new Error(\n `No network configuration found for chain ID \"${chainId}\".`,\n );\n }\n\n const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex(\n (endpoint) => isInfuraEndpoint(endpoint.url),\n );\n if (infuraEndpointIndex === -1) {\n throw new Error(\n `No Infura endpoint available for chain ID \"${chainId}\".`,\n );\n }\n if (infuraEndpointIndex === networkConfiguration.defaultRpcEndpointIndex) {\n // The default is already Infura; nothing to do.\n return;\n }\n\n await this.messenger.call(\n 'NetworkController:updateNetwork',\n chainId,\n {\n ...networkConfiguration,\n defaultRpcEndpointIndex: infuraEndpointIndex,\n },\n { replacementSelectedRpcEndpointIndex: infuraEndpointIndex },\n );\n }\n\n #evaluate(): void {\n const { connectivityStatus } = this.messenger.call(\n 'ConnectivityController:getState',\n );\n if (connectivityStatus === CONNECTIVITY_STATUSES.Offline) {\n this.#resetBanner();\n return;\n }\n\n const failed = this.#findFailedNetworkForBanner();\n\n if (!failed) {\n this.#resetBanner();\n return;\n }\n\n if (\n this.state.status !== 'available' &&\n this.state.network?.networkClientId === failed.networkClientId\n ) {\n this.update((draft) => {\n draft.network = failed;\n });\n return;\n }\n\n if (this.#pendingNetworkClientId === failed.networkClientId) {\n return;\n }\n\n this.#clearTimers();\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n\n this.#pendingNetworkClientId = failed.networkClientId;\n this.#degradedTimer = setTimeout(() => {\n this.#degradedTimer = undefined;\n this.#pendingNetworkClientId = undefined;\n const stillFailed = this.#findFailedNetworkForBanner();\n if (!stillFailed) {\n return;\n }\n this.update((draft) => {\n draft.status = 'degraded';\n draft.network = stillFailed;\n });\n this.#unavailableTimer = setTimeout(() => {\n this.#unavailableTimer = undefined;\n const stillFailedAtEscalation = this.#findFailedNetworkForBanner();\n if (!stillFailedAtEscalation) {\n return;\n }\n this.update((draft) => {\n draft.status = 'unavailable';\n draft.network = stillFailedAtEscalation;\n });\n }, UNAVAILABLE_BANNER_TIMEOUT_MS - DEGRADED_BANNER_TIMEOUT_MS);\n }, DEGRADED_BANNER_TIMEOUT_MS);\n }\n\n /**\n * Clears timers and resets banner state to {@link NetworkConnectionBannerStatus|`available`}\n * if it isn't there already.\n */\n #resetBanner(): void {\n this.#clearTimers();\n if (this.state.status !== 'available' || this.state.network !== null) {\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n }\n }\n\n #clearTimers(): void {\n this.#pendingNetworkClientId = undefined;\n if (this.#degradedTimer !== undefined) {\n clearTimeout(this.#degradedTimer);\n this.#degradedTimer = undefined;\n }\n if (this.#unavailableTimer !== undefined) {\n clearTimeout(this.#unavailableTimer);\n this.#unavailableTimer = undefined;\n }\n }\n\n #findFailedNetworkForBanner(): NetworkConnectionBannerFailedNetwork | null {\n const { enabledNetworkMap } = this.messenger.call(\n 'NetworkEnablementController:getState',\n );\n const enabledEvmChainIds = Object.entries(\n enabledNetworkMap[KnownCaipNamespace.Eip155] ?? {},\n )\n .filter(([, enabled]) => enabled)\n .map(([chainId]) => chainId as Hex);\n const { networkConfigurationsByChainId, networksMetadata } =\n this.messenger.call('NetworkController:getState');\n\n type EnrichedFailedNetwork = NetworkConnectionBannerFailedNetwork & {\n domain: string | null;\n };\n const failedNetworks: EnrichedFailedNetwork[] = [];\n let totalEnabled = 0;\n\n for (const chainId of enabledEvmChainIds) {\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n continue;\n }\n\n const { rpcEndpoints, defaultRpcEndpointIndex, name } =\n networkConfiguration;\n const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];\n if (!defaultRpcEndpoint) {\n continue;\n }\n\n totalEnabled += 1;\n\n const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];\n if (\n metadata === undefined ||\n metadata.status === NetworkStatus.Available\n ) {\n continue;\n }\n\n const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);\n\n // For custom endpoints (non-Infura), find an Infura endpoint on this\n // chain that we could offer to switch to.\n let infuraNetworkClientId: string | null = null;\n if (!endpointIsInfura) {\n const otherInfura = rpcEndpoints.find(\n (endpoint, index) =>\n index !== defaultRpcEndpointIndex && isInfuraEndpoint(endpoint.url),\n );\n infuraNetworkClientId = otherInfura?.networkClientId ?? null;\n }\n\n failedNetworks.push({\n chainId,\n networkClientId: defaultRpcEndpoint.networkClientId,\n networkName: name,\n rpcUrl: defaultRpcEndpoint.url,\n isInfuraEndpoint: endpointIsInfura,\n infuraNetworkClientId,\n domain: getDomain(defaultRpcEndpoint.url),\n });\n }\n\n if (failedNetworks.length === 0) {\n return null;\n }\n\n const firstCustomFailed = failedNetworks.find(\n (entry) => !entry.isInfuraEndpoint,\n );\n const distinctDomains = new Set(\n failedNetworks\n .map((entry) => entry.domain)\n .filter((domain): domain is string => domain !== null),\n ).size;\n const areAllEnabledNetworksFailed = failedNetworks.length === totalEnabled;\n\n if (\n !firstCustomFailed &&\n distinctDomains <= 1 &&\n !areAllEnabledNetworksFailed\n ) {\n return null;\n }\n\n const selected = firstCustomFailed ?? failedNetworks[0];\n return {\n chainId: selected.chainId,\n networkClientId: selected.networkClientId,\n networkName: selected.networkName,\n rpcUrl: selected.rpcUrl,\n isInfuraEndpoint: selected.isInfuraEndpoint,\n infuraNetworkClientId: selected.infuraNetworkClientId,\n };\n }\n}\n\n/**\n * Checks if an RPC URL is hosted on the Infura service. Detection is by\n * hostname suffix rather than the exact MetaMask-key URL pattern, so any\n * `*.infura.io` URL counts. That's the right shape for grouping by provider.\n *\n * @param url - The RPC URL to check.\n * @returns True if the URL's host is on `infura.io`.\n */\nfunction isInfuraEndpoint(url: string): boolean {\n try {\n return new URL(url).hostname.toLowerCase().endsWith('.infura.io');\n } catch {\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController.mjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller';\nimport type {\n ConnectivityControllerGetStateAction,\n ConnectivityControllerStateChangeEvent,\n} from '@metamask/connectivity-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkConfigurationByChainIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerUpdateNetworkAction,\n NetworkControllerStateChangeEvent,\n} from '@metamask/network-controller';\nimport { NetworkStatus } from '@metamask/network-controller';\nimport type {\n NetworkEnablementControllerGetStateAction,\n NetworkEnablementControllerStateChangeEvent,\n} from '@metamask/network-enablement-controller';\nimport type { Hex } from '@metamask/utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\n\nimport type { NetworkConnectionBannerControllerMethodActions } from './NetworkConnectionBannerController-method-action-types';\nimport { getDomain } from './url-utils';\n\n/**\n * The name of the {@link NetworkConnectionBannerController}, used to namespace\n * the controller's actions and events and to namespace the controller's state\n * data when composed with other controllers.\n */\nconst controllerName = 'NetworkConnectionBannerController';\n\n/**\n * Status the banner can be in. `available` means no banner is shown; the\n * `degraded` and `unavailable` values mirror the two-tier escalation that the\n * UI renders.\n */\nexport type NetworkConnectionBannerStatus =\n | 'available'\n | 'degraded'\n | 'unavailable';\n\n/**\n * Details of the failing network the banner should describe. Populated when\n * {@link NetworkConnectionBannerControllerState.status} is `degraded` or\n * `unavailable`, `null` otherwise.\n *\n * `infuraNetworkClientId` is the networkClientId of an Infura endpoint on the\n * same chain that the user can switch to. `null` when the failing endpoint is\n * already Infura, or when no Infura alternative exists.\n */\nexport type NetworkConnectionBannerFailedNetwork = {\n chainId: Hex;\n networkClientId: string;\n networkName: string;\n rpcUrl: string;\n isInfuraEndpoint: boolean;\n infuraNetworkClientId: string | null;\n};\n\n/**\n * State for the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerState = {\n status: NetworkConnectionBannerStatus;\n network: NetworkConnectionBannerFailedNetwork | null;\n};\n\nconst networkConnectionBannerControllerMetadata = {\n status: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n network: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n} satisfies StateMetadata<NetworkConnectionBannerControllerState>;\n\n/**\n * Constructs the default {@link NetworkConnectionBannerController} state.\n *\n * @returns The default state.\n */\nexport function getDefaultNetworkConnectionBannerControllerState(): NetworkConnectionBannerControllerState {\n return {\n status: 'available',\n network: null,\n };\n}\n\nconst DEGRADED_BANNER_TIMEOUT_MS = 5_000;\nconst UNAVAILABLE_BANNER_TIMEOUT_MS = 30_000;\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'dismissBanner',\n 'switchToDefaultInfuraRpc',\n] as const;\n\n/**\n * Retrieves the state of the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Actions that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerActions =\n | NetworkConnectionBannerControllerGetStateAction\n | NetworkConnectionBannerControllerMethodActions;\n\n/**\n * Actions from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} calls.\n */\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkConfigurationByChainIdAction\n | NetworkControllerUpdateNetworkAction\n | NetworkEnablementControllerGetStateAction\n | ConnectivityControllerGetStateAction;\n\n/**\n * Published when the state of {@link NetworkConnectionBannerController}\n * changes.\n */\nexport type NetworkConnectionBannerControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n NetworkConnectionBannerControllerState\n >;\n\n/**\n * Events that {@link NetworkConnectionBannerControllerMessenger} exposes to\n * other consumers.\n */\nexport type NetworkConnectionBannerControllerEvents =\n NetworkConnectionBannerControllerStateChangedEvent;\n\n/**\n * Events from other messengers that\n * {@link NetworkConnectionBannerControllerMessenger} subscribes to.\n */\ntype AllowedEvents =\n | NetworkControllerStateChangeEvent\n | NetworkEnablementControllerStateChangeEvent\n | ConnectivityControllerStateChangeEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerMessenger = Messenger<\n typeof controllerName,\n NetworkConnectionBannerControllerActions | AllowedActions,\n NetworkConnectionBannerControllerEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerOptions = {\n /**\n * The messenger for inter-controller communication.\n */\n messenger: NetworkConnectionBannerControllerMessenger;\n};\n\n/**\n * NetworkConnectionBannerController decides whether the network connection\n * banner should be shown for the user, and which failing network it should\n * describe. It encapsulates the rule, the 5s / 30s timer escalation, and the\n * eTLD+1 grouping that was previously duplicated across MetaMask clients.\n *\n * The banner shows when:\n *\n * - The first failing network's default RPC is a custom (non-Infura) endpoint\n * — users always want to be informed about errors with RPCs they've chosen.\n * - Failed RPCs span more than one registrable domain (likely client-side).\n * - Every enabled EVM network with known connectivity status is failing\n * (escape hatch for single-network setups so they still get a signal).\n *\n * A wide single-provider outage (e.g. every `*.infura.io` network goes down at\n * once) collapses to one domain and is suppressed, except in the all-down\n * single-network case. When a custom failure is present, it's surfaced first\n * so the \"Switch to MetaMask default RPC\" CTA targets the network the user\n * can act on.\n *\n * Clients only need to render the banner from the controller's state and wire\n * click handlers to {@link dismissBanner} or {@link switchToDefaultInfuraRpc}.\n */\nexport class NetworkConnectionBannerController extends BaseController<\n typeof controllerName,\n NetworkConnectionBannerControllerState,\n NetworkConnectionBannerControllerMessenger\n> {\n #degradedTimer: ReturnType<typeof setTimeout> | undefined;\n\n #unavailableTimer: ReturnType<typeof setTimeout> | undefined;\n\n #pendingNetworkClientId: string | undefined;\n\n /**\n * Constructs a new {@link NetworkConnectionBannerController}.\n *\n * @param args - The arguments to this controller.\n * @param args.messenger - The messenger suited for this controller.\n */\n constructor({ messenger }: NetworkConnectionBannerControllerOptions) {\n super({\n messenger,\n metadata: networkConnectionBannerControllerMetadata,\n name: controllerName,\n state: getDefaultNetworkConnectionBannerControllerState(),\n });\n\n const onStateChange = (): void => this.#evaluate();\n // Upstream controllers still expose :stateChange; switch to :stateChanged\n // once those packages migrate their event types.\n /* eslint-disable no-restricted-syntax -- awaiting upstream :stateChanged migration */\n this.messenger.subscribe('NetworkController:stateChange', onStateChange);\n this.messenger.subscribe(\n 'NetworkEnablementController:stateChange',\n onStateChange,\n );\n this.messenger.subscribe(\n 'ConnectivityController:stateChange',\n onStateChange,\n );\n /* eslint-enable no-restricted-syntax */\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#evaluate();\n }\n\n /**\n * Clears the banner state regardless of the current rule outcome. The next\n * subscription-driven evaluation will re-show the banner if the conditions\n * still hold.\n */\n dismissBanner(): void {\n this.#resetBanner();\n }\n\n /**\n * Switches the chain's default RPC endpoint to its first Infura endpoint,\n * causing the banner to clear once the network becomes available again.\n *\n * @param args - The arguments to this action.\n * @param args.chainId - The chain whose default RPC should be switched.\n * @throws If the chain configuration cannot be found, or if it has no\n * Infura endpoint to switch to, or if the default is already Infura.\n */\n async switchToDefaultInfuraRpc({ chainId }: { chainId: Hex }): Promise<void> {\n const networkConfiguration = this.messenger.call(\n 'NetworkController:getNetworkConfigurationByChainId',\n chainId,\n );\n if (!networkConfiguration) {\n throw new Error(\n `No network configuration found for chain ID \"${chainId}\".`,\n );\n }\n\n const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex(\n (endpoint) => isInfuraEndpoint(endpoint.url),\n );\n if (infuraEndpointIndex === -1) {\n throw new Error(\n `No Infura endpoint available for chain ID \"${chainId}\".`,\n );\n }\n if (infuraEndpointIndex === networkConfiguration.defaultRpcEndpointIndex) {\n // The default is already Infura; nothing to do.\n return;\n }\n\n await this.messenger.call(\n 'NetworkController:updateNetwork',\n chainId,\n {\n ...networkConfiguration,\n defaultRpcEndpointIndex: infuraEndpointIndex,\n },\n { replacementSelectedRpcEndpointIndex: infuraEndpointIndex },\n );\n }\n\n #evaluate(): void {\n const { connectivityStatus } = this.messenger.call(\n 'ConnectivityController:getState',\n );\n if (connectivityStatus === CONNECTIVITY_STATUSES.Offline) {\n this.#resetBanner();\n return;\n }\n\n const failed = this.#findFailedNetworkForBanner();\n\n if (!failed) {\n this.#resetBanner();\n return;\n }\n\n if (\n this.state.status !== 'available' &&\n this.state.network?.networkClientId === failed.networkClientId\n ) {\n this.update((draft) => {\n draft.network = failed;\n });\n return;\n }\n\n if (this.#pendingNetworkClientId === failed.networkClientId) {\n return;\n }\n\n this.#clearTimers();\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n\n this.#pendingNetworkClientId = failed.networkClientId;\n this.#degradedTimer = setTimeout(() => {\n this.#degradedTimer = undefined;\n this.#pendingNetworkClientId = undefined;\n const stillFailed = this.#findFailedNetworkForBanner();\n if (!stillFailed) {\n return;\n }\n this.update((draft) => {\n draft.status = 'degraded';\n draft.network = stillFailed;\n });\n this.#unavailableTimer = setTimeout(() => {\n this.#unavailableTimer = undefined;\n const stillFailedAtEscalation = this.#findFailedNetworkForBanner();\n if (!stillFailedAtEscalation) {\n return;\n }\n this.update((draft) => {\n draft.status = 'unavailable';\n draft.network = stillFailedAtEscalation;\n });\n }, UNAVAILABLE_BANNER_TIMEOUT_MS - DEGRADED_BANNER_TIMEOUT_MS);\n }, DEGRADED_BANNER_TIMEOUT_MS);\n }\n\n /**\n * Clears timers and resets banner state to {@link NetworkConnectionBannerStatus|`available`}\n * if it isn't there already.\n */\n #resetBanner(): void {\n this.#clearTimers();\n if (this.state.status !== 'available' || this.state.network !== null) {\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n }\n }\n\n #clearTimers(): void {\n this.#pendingNetworkClientId = undefined;\n if (this.#degradedTimer !== undefined) {\n clearTimeout(this.#degradedTimer);\n this.#degradedTimer = undefined;\n }\n if (this.#unavailableTimer !== undefined) {\n clearTimeout(this.#unavailableTimer);\n this.#unavailableTimer = undefined;\n }\n }\n\n #findFailedNetworkForBanner(): NetworkConnectionBannerFailedNetwork | null {\n const { enabledNetworkMap } = this.messenger.call(\n 'NetworkEnablementController:getState',\n );\n const enabledEvmChainIds = Object.entries(\n enabledNetworkMap[KnownCaipNamespace.Eip155] ?? {},\n )\n .filter(([, enabled]) => enabled)\n .map(([chainId]) => chainId as Hex);\n const { networkConfigurationsByChainId, networksMetadata } =\n this.messenger.call('NetworkController:getState');\n\n type EnrichedFailedNetwork = NetworkConnectionBannerFailedNetwork & {\n domain: string | null;\n };\n const failedNetworks: EnrichedFailedNetwork[] = [];\n let totalNetworksWithMetadata = 0;\n\n for (const chainId of enabledEvmChainIds) {\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n continue;\n }\n\n const { rpcEndpoints, defaultRpcEndpointIndex, name } =\n networkConfiguration;\n const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];\n if (!defaultRpcEndpoint) {\n continue;\n }\n\n const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];\n if (metadata === undefined) {\n continue;\n }\n\n totalNetworksWithMetadata += 1;\n\n if (metadata.status === NetworkStatus.Available) {\n continue;\n }\n\n const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);\n\n // For custom endpoints (non-Infura), find an Infura endpoint on this\n // chain that we could offer to switch to.\n let infuraNetworkClientId: string | null = null;\n if (!endpointIsInfura) {\n const otherInfura = rpcEndpoints.find(\n (endpoint, index) =>\n index !== defaultRpcEndpointIndex && isInfuraEndpoint(endpoint.url),\n );\n infuraNetworkClientId = otherInfura?.networkClientId ?? null;\n }\n\n failedNetworks.push({\n chainId,\n networkClientId: defaultRpcEndpoint.networkClientId,\n networkName: name,\n rpcUrl: defaultRpcEndpoint.url,\n isInfuraEndpoint: endpointIsInfura,\n infuraNetworkClientId,\n domain: getDomain(defaultRpcEndpoint.url),\n });\n }\n\n if (failedNetworks.length === 0) {\n return null;\n }\n\n const firstCustomFailed = failedNetworks.find(\n (entry) => !entry.isInfuraEndpoint,\n );\n const distinctDomains = new Set(\n failedNetworks\n .map((entry) => entry.domain)\n .filter((domain): domain is string => domain !== null),\n ).size;\n const areAllKnownNetworksFailed =\n failedNetworks.length === totalNetworksWithMetadata;\n\n if (\n !firstCustomFailed &&\n distinctDomains <= 1 &&\n !areAllKnownNetworksFailed\n ) {\n return null;\n }\n\n const selected = firstCustomFailed ?? failedNetworks[0];\n return {\n chainId: selected.chainId,\n networkClientId: selected.networkClientId,\n networkName: selected.networkName,\n rpcUrl: selected.rpcUrl,\n isInfuraEndpoint: selected.isInfuraEndpoint,\n infuraNetworkClientId: selected.infuraNetworkClientId,\n };\n }\n}\n\n/**\n * Checks if an RPC URL is hosted on the Infura service. Detection is by\n * hostname suffix rather than the exact MetaMask-key URL pattern, so any\n * `*.infura.io` URL counts. That's the right shape for grouping by provider.\n *\n * @param url - The RPC URL to check.\n * @returns True if the URL's host is on `infura.io`.\n */\nfunction isInfuraEndpoint(url: string): boolean {\n try {\n return new URL(url).hostname.toLowerCase().endsWith('.infura.io');\n } catch {\n return false;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/network-connection-banner-controller",
|
|
3
|
-
"version": "0.1.0-preview-
|
|
3
|
+
"version": "0.1.0-preview-fe651cdb3",
|
|
4
4
|
"description": "Decides when and how to surface the network connection banner based on RPC endpoint health",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@metamask/messenger": "^1.2.0",
|
|
59
59
|
"@metamask/network-controller": "^32.0.0",
|
|
60
60
|
"@metamask/network-enablement-controller": "^5.3.0",
|
|
61
|
-
"@metamask/utils": "^11.
|
|
61
|
+
"@metamask/utils": "^11.11.0",
|
|
62
62
|
"ip-regex": "^4.3.0",
|
|
63
63
|
"psl": "^1.15.0",
|
|
64
64
|
"reselect": "^5.1.1"
|