@metamask-previews/network-connection-banner-controller 0.1.0-preview-fe651cdb3 → 0.1.0-preview-3c0e8a808
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/CHANGELOG.md +4 -1
- package/README.md +13 -0
- package/dist/NetworkConnectionBannerController-method-action-types.cjs.map +1 -1
- package/dist/NetworkConnectionBannerController-method-action-types.d.cts +12 -4
- package/dist/NetworkConnectionBannerController-method-action-types.d.cts.map +1 -1
- package/dist/NetworkConnectionBannerController-method-action-types.d.mts +12 -4
- package/dist/NetworkConnectionBannerController-method-action-types.d.mts.map +1 -1
- package/dist/NetworkConnectionBannerController-method-action-types.mjs.map +1 -1
- package/dist/NetworkConnectionBannerController.cjs +149 -93
- package/dist/NetworkConnectionBannerController.cjs.map +1 -1
- package/dist/NetworkConnectionBannerController.d.cts +23 -14
- package/dist/NetworkConnectionBannerController.d.cts.map +1 -1
- package/dist/NetworkConnectionBannerController.d.mts +23 -14
- package/dist/NetworkConnectionBannerController.d.mts.map +1 -1
- package/dist/NetworkConnectionBannerController.mjs +150 -94
- package/dist/NetworkConnectionBannerController.mjs.map +1 -1
- package/dist/index.cjs +1 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +0 -1
- package/dist/index.mjs.map +1 -1
- package/dist/selectors.cjs.map +1 -1
- package/dist/selectors.d.cts +2 -2
- package/dist/selectors.d.cts.map +1 -1
- package/dist/selectors.d.mts +2 -2
- package/dist/selectors.d.mts.map +1 -1
- package/dist/selectors.mjs.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Add `NetworkConnectionBannerController`, which evaluates enabled network RPC
|
|
13
|
+
health after initialization and manages degraded and unavailable banner state,
|
|
14
|
+
dismissal, and switching custom RPC endpoints to an available Infura endpoint
|
|
15
|
+
([#9041](https://github.com/MetaMask/core/pull/9041))
|
|
13
16
|
|
|
14
17
|
[Unreleased]: https://github.com/MetaMask/core/
|
package/README.md
CHANGED
|
@@ -5,6 +5,19 @@ connection banner based on RPC endpoint health. It encapsulates the shared
|
|
|
5
5
|
rule, the 5s/30s timer state machine, and the eTLD+1 grouping previously
|
|
6
6
|
duplicated across MetaMask clients.
|
|
7
7
|
|
|
8
|
+
## Initialization
|
|
9
|
+
|
|
10
|
+
After constructing the controller, call `init()` only after the
|
|
11
|
+
`NetworkController`, `NetworkEnablementController`, and
|
|
12
|
+
`ConnectivityController` have initialized. Until then, upstream state changes
|
|
13
|
+
are ignored and no banner timers run.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
networkConnectionBannerController.init();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`init()` is idempotent and immediately evaluates the latest upstream state.
|
|
20
|
+
|
|
8
21
|
## Installation
|
|
9
22
|
|
|
10
23
|
`yarn add @metamask/network-connection-banner-controller`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.cjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { NetworkConnectionBannerController } from './NetworkConnectionBannerController';\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.cjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { NetworkConnectionBannerController } from './NetworkConnectionBannerController';\n\n/**\n * Starts evaluating network connection state.\n *\n * This method should be called after the upstream network, network\n * enablement, and connectivity controllers have been initialized.\n */\nexport type NetworkConnectionBannerControllerInitAction = {\n type: `NetworkConnectionBannerController:init`;\n handler: NetworkConnectionBannerController['init'];\n};\n\n/**\n * Clears the banner state such that the banner will be hidden.\n */\nexport type NetworkConnectionBannerControllerDismissBannerAction = {\n type: `NetworkConnectionBannerController:dismissBanner`;\n handler: NetworkConnectionBannerController['dismissBanner'];\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 */\nexport type NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction = {\n type: `NetworkConnectionBannerController:switchToDefaultInfuraRpc`;\n handler: NetworkConnectionBannerController['switchToDefaultInfuraRpc'];\n};\n\n/**\n * Union of all NetworkConnectionBannerController action types.\n */\nexport type NetworkConnectionBannerControllerMethodActions =\n | NetworkConnectionBannerControllerInitAction\n | NetworkConnectionBannerControllerDismissBannerAction\n | NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction;\n"]}
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { NetworkConnectionBannerController } from "./NetworkConnectionBannerController.cjs";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Starts evaluating network connection state.
|
|
8
|
+
*
|
|
9
|
+
* This method should be called after the upstream network, network
|
|
10
|
+
* enablement, and connectivity controllers have been initialized.
|
|
11
|
+
*/
|
|
12
|
+
export type NetworkConnectionBannerControllerInitAction = {
|
|
13
|
+
type: `NetworkConnectionBannerController:init`;
|
|
14
|
+
handler: NetworkConnectionBannerController['init'];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Clears the banner state such that the banner will be hidden.
|
|
10
18
|
*/
|
|
11
19
|
export type NetworkConnectionBannerControllerDismissBannerAction = {
|
|
12
20
|
type: `NetworkConnectionBannerController:dismissBanner`;
|
|
@@ -28,5 +36,5 @@ export type NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction = {
|
|
|
28
36
|
/**
|
|
29
37
|
* Union of all NetworkConnectionBannerController action types.
|
|
30
38
|
*/
|
|
31
|
-
export type NetworkConnectionBannerControllerMethodActions = NetworkConnectionBannerControllerDismissBannerAction | NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction;
|
|
39
|
+
export type NetworkConnectionBannerControllerMethodActions = NetworkConnectionBannerControllerInitAction | NetworkConnectionBannerControllerDismissBannerAction | NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction;
|
|
32
40
|
//# sourceMappingURL=NetworkConnectionBannerController-method-action-types.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.d.cts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iCAAiC,EAAE,gDAA4C;AAE7F
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.d.cts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iCAAiC,EAAE,gDAA4C;AAE7F;;;;;GAKG;AACH,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,wCAAwC,CAAC;IAC/C,OAAO,EAAE,iCAAiC,CAAC,MAAM,CAAC,CAAC;CACpD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oDAAoD,GAAG;IACjE,IAAI,EAAE,iDAAiD,CAAC;IACxD,OAAO,EAAE,iCAAiC,CAAC,eAAe,CAAC,CAAC;CAC7D,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,+DAA+D,GAAG;IAC5E,IAAI,EAAE,4DAA4D,CAAC;IACnE,OAAO,EAAE,iCAAiC,CAAC,0BAA0B,CAAC,CAAC;CACxE,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,8CAA8C,GACtD,2CAA2C,GAC3C,oDAAoD,GACpD,+DAA+D,CAAC"}
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { NetworkConnectionBannerController } from "./NetworkConnectionBannerController.mjs";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Starts evaluating network connection state.
|
|
8
|
+
*
|
|
9
|
+
* This method should be called after the upstream network, network
|
|
10
|
+
* enablement, and connectivity controllers have been initialized.
|
|
11
|
+
*/
|
|
12
|
+
export type NetworkConnectionBannerControllerInitAction = {
|
|
13
|
+
type: `NetworkConnectionBannerController:init`;
|
|
14
|
+
handler: NetworkConnectionBannerController['init'];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Clears the banner state such that the banner will be hidden.
|
|
10
18
|
*/
|
|
11
19
|
export type NetworkConnectionBannerControllerDismissBannerAction = {
|
|
12
20
|
type: `NetworkConnectionBannerController:dismissBanner`;
|
|
@@ -28,5 +36,5 @@ export type NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction = {
|
|
|
28
36
|
/**
|
|
29
37
|
* Union of all NetworkConnectionBannerController action types.
|
|
30
38
|
*/
|
|
31
|
-
export type NetworkConnectionBannerControllerMethodActions = NetworkConnectionBannerControllerDismissBannerAction | NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction;
|
|
39
|
+
export type NetworkConnectionBannerControllerMethodActions = NetworkConnectionBannerControllerInitAction | NetworkConnectionBannerControllerDismissBannerAction | NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction;
|
|
32
40
|
//# sourceMappingURL=NetworkConnectionBannerController-method-action-types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.d.mts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iCAAiC,EAAE,gDAA4C;AAE7F
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.d.mts","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iCAAiC,EAAE,gDAA4C;AAE7F;;;;;GAKG;AACH,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,wCAAwC,CAAC;IAC/C,OAAO,EAAE,iCAAiC,CAAC,MAAM,CAAC,CAAC;CACpD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oDAAoD,GAAG;IACjE,IAAI,EAAE,iDAAiD,CAAC;IACxD,OAAO,EAAE,iCAAiC,CAAC,eAAe,CAAC,CAAC;CAC7D,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,+DAA+D,GAAG;IAC5E,IAAI,EAAE,4DAA4D,CAAC;IACnE,OAAO,EAAE,iCAAiC,CAAC,0BAA0B,CAAC,CAAC;CACxE,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,8CAA8C,GACtD,2CAA2C,GAC3C,oDAAoD,GACpD,+DAA+D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.mjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { NetworkConnectionBannerController } from './NetworkConnectionBannerController';\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"NetworkConnectionBannerController-method-action-types.mjs","sourceRoot":"","sources":["../src/NetworkConnectionBannerController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { NetworkConnectionBannerController } from './NetworkConnectionBannerController';\n\n/**\n * Starts evaluating network connection state.\n *\n * This method should be called after the upstream network, network\n * enablement, and connectivity controllers have been initialized.\n */\nexport type NetworkConnectionBannerControllerInitAction = {\n type: `NetworkConnectionBannerController:init`;\n handler: NetworkConnectionBannerController['init'];\n};\n\n/**\n * Clears the banner state such that the banner will be hidden.\n */\nexport type NetworkConnectionBannerControllerDismissBannerAction = {\n type: `NetworkConnectionBannerController:dismissBanner`;\n handler: NetworkConnectionBannerController['dismissBanner'];\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 */\nexport type NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction = {\n type: `NetworkConnectionBannerController:switchToDefaultInfuraRpc`;\n handler: NetworkConnectionBannerController['switchToDefaultInfuraRpc'];\n};\n\n/**\n * Union of all NetworkConnectionBannerController action types.\n */\nexport type NetworkConnectionBannerControllerMethodActions =\n | NetworkConnectionBannerControllerInitAction\n | NetworkConnectionBannerControllerDismissBannerAction\n | NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction;\n"]}
|
|
@@ -10,14 +10,44 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
11
11
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
12
|
};
|
|
13
|
-
var _NetworkConnectionBannerController_instances, _NetworkConnectionBannerController_degradedTimer, _NetworkConnectionBannerController_unavailableTimer, _NetworkConnectionBannerController_pendingNetworkClientId,
|
|
13
|
+
var _NetworkConnectionBannerController_instances, _NetworkConnectionBannerController_degradedTimer, _NetworkConnectionBannerController_unavailableTimer, _NetworkConnectionBannerController_pendingNetworkClientId, _NetworkConnectionBannerController_initialized, _NetworkConnectionBannerController_onUpstreamChange, _NetworkConnectionBannerController_refreshState, _NetworkConnectionBannerController_resetBanner, _NetworkConnectionBannerController_clearTimers, _NetworkConnectionBannerController_findFailedNetwork, _NetworkConnectionBannerController_getEnabledEvmChainIds, _NetworkConnectionBannerController_collectNetworksWithMetadata, _NetworkConnectionBannerController_buildFailedNetwork, _NetworkConnectionBannerController_pickBannerNetwork;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.NetworkConnectionBannerController = exports.getDefaultNetworkConnectionBannerControllerState = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
17
|
const connectivity_controller_1 = require("@metamask/connectivity-controller");
|
|
18
18
|
const network_controller_1 = require("@metamask/network-controller");
|
|
19
|
+
const network_enablement_controller_1 = require("@metamask/network-enablement-controller");
|
|
19
20
|
const utils_1 = require("@metamask/utils");
|
|
21
|
+
const reselect_1 = require("reselect");
|
|
20
22
|
const url_utils_1 = require("./url-utils.cjs");
|
|
23
|
+
/**
|
|
24
|
+
* Selects `networksMetadata` from the `NetworkController` state.
|
|
25
|
+
*
|
|
26
|
+
* @param state - The `NetworkController` state.
|
|
27
|
+
* @returns The networks metadata map keyed by network client id.
|
|
28
|
+
*/
|
|
29
|
+
const selectNetworksMetadata = (state) => state.networksMetadata;
|
|
30
|
+
/**
|
|
31
|
+
* Selects `networkConfigurationsByChainId` from the `NetworkController`
|
|
32
|
+
* state.
|
|
33
|
+
*
|
|
34
|
+
* @param state - The `NetworkController` state.
|
|
35
|
+
* @returns The network configurations keyed by chain id.
|
|
36
|
+
*/
|
|
37
|
+
const selectNetworkConfigurationsByChainId = (state) => state.networkConfigurationsByChainId;
|
|
38
|
+
/**
|
|
39
|
+
* Selects the `NetworkController` state fields that influence the banner
|
|
40
|
+
* rule. Composed with `createSelector` so the return object stays reference
|
|
41
|
+
* stable while unrelated `NetworkController` state (e.g.
|
|
42
|
+
* `selectedNetworkClientId`) changes.
|
|
43
|
+
*
|
|
44
|
+
* @param state - The `NetworkController` state.
|
|
45
|
+
* @returns The relevant network fields.
|
|
46
|
+
*/
|
|
47
|
+
const selectNetworkControllerFields = (0, reselect_1.createSelector)([selectNetworksMetadata, selectNetworkConfigurationsByChainId], (networksMetadata, networkConfigurationsByChainId) => ({
|
|
48
|
+
networksMetadata,
|
|
49
|
+
networkConfigurationsByChainId,
|
|
50
|
+
}));
|
|
21
51
|
/**
|
|
22
52
|
* The name of the {@link NetworkConnectionBannerController}, used to namespace
|
|
23
53
|
* the controller's actions and events and to namespace the controller's state
|
|
@@ -50,9 +80,18 @@ function getDefaultNetworkConnectionBannerControllerState() {
|
|
|
50
80
|
};
|
|
51
81
|
}
|
|
52
82
|
exports.getDefaultNetworkConnectionBannerControllerState = getDefaultNetworkConnectionBannerControllerState;
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
/**
|
|
84
|
+
* How long (in milliseconds) a failing network must remain in a "failed"
|
|
85
|
+
* status ("degraded" or "unavailable") before the degraded banner appears.
|
|
86
|
+
*/
|
|
87
|
+
const DEGRADED_BANNER_TIMEOUT = 5000;
|
|
88
|
+
/**
|
|
89
|
+
* How long (in milliseconds) a failing network must remain in a "failed"
|
|
90
|
+
* status before the banner escalates to "unavailable".
|
|
91
|
+
*/
|
|
92
|
+
const UNAVAILABLE_BANNER_TIMEOUT = 30000;
|
|
55
93
|
const MESSENGER_EXPOSED_METHODS = [
|
|
94
|
+
'init',
|
|
56
95
|
'dismissBanner',
|
|
57
96
|
'switchToDefaultInfuraRpc',
|
|
58
97
|
];
|
|
@@ -97,21 +136,34 @@ class NetworkConnectionBannerController extends base_controller_1.BaseController
|
|
|
97
136
|
_NetworkConnectionBannerController_degradedTimer.set(this, void 0);
|
|
98
137
|
_NetworkConnectionBannerController_unavailableTimer.set(this, void 0);
|
|
99
138
|
_NetworkConnectionBannerController_pendingNetworkClientId.set(this, void 0);
|
|
100
|
-
|
|
139
|
+
_NetworkConnectionBannerController_initialized.set(this, false);
|
|
140
|
+
// Scoped selectors per controller guideline so unrelated upstream
|
|
141
|
+
// `stateChange` events (e.g. a `NetworkController` selected client id
|
|
142
|
+
// update) do not trigger a re-evaluation.
|
|
101
143
|
// Upstream controllers still expose :stateChange; switch to :stateChanged
|
|
102
144
|
// once those packages migrate their event types.
|
|
103
145
|
/* eslint-disable no-restricted-syntax -- awaiting upstream :stateChanged migration */
|
|
104
|
-
this.messenger.subscribe('NetworkController:stateChange',
|
|
105
|
-
this.messenger.subscribe('NetworkEnablementController:stateChange',
|
|
106
|
-
this.messenger.subscribe('ConnectivityController:stateChange',
|
|
146
|
+
this.messenger.subscribe('NetworkController:stateChange', () => __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_onUpstreamChange).call(this), selectNetworkControllerFields);
|
|
147
|
+
this.messenger.subscribe('NetworkEnablementController:stateChange', () => __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_onUpstreamChange).call(this), network_enablement_controller_1.selectEnabledNetworkMap);
|
|
148
|
+
this.messenger.subscribe('ConnectivityController:stateChange', () => __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_onUpstreamChange).call(this), connectivity_controller_1.connectivityControllerSelectors.selectConnectivityStatus);
|
|
107
149
|
/* eslint-enable no-restricted-syntax */
|
|
108
150
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
109
|
-
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_evaluate).call(this);
|
|
110
151
|
}
|
|
111
152
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
153
|
+
* Starts evaluating network connection state.
|
|
154
|
+
*
|
|
155
|
+
* This method should be called after the upstream network, network
|
|
156
|
+
* enablement, and connectivity controllers have been initialized.
|
|
157
|
+
*/
|
|
158
|
+
init() {
|
|
159
|
+
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_initialized, "f")) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_refreshState).call(this);
|
|
163
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_initialized, true, "f");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Clears the banner state such that the banner will be hidden.
|
|
115
167
|
*/
|
|
116
168
|
dismissBanner() {
|
|
117
169
|
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_resetBanner).call(this);
|
|
@@ -130,7 +182,7 @@ class NetworkConnectionBannerController extends base_controller_1.BaseController
|
|
|
130
182
|
if (!networkConfiguration) {
|
|
131
183
|
throw new Error(`No network configuration found for chain ID "${chainId}".`);
|
|
132
184
|
}
|
|
133
|
-
const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex((endpoint) =>
|
|
185
|
+
const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex((endpoint) => getIsInfuraEndpoint(endpoint.url));
|
|
134
186
|
if (infuraEndpointIndex === -1) {
|
|
135
187
|
throw new Error(`No Infura endpoint available for chain ID "${chainId}".`);
|
|
136
188
|
}
|
|
@@ -145,66 +197,66 @@ class NetworkConnectionBannerController extends base_controller_1.BaseController
|
|
|
145
197
|
}
|
|
146
198
|
}
|
|
147
199
|
exports.NetworkConnectionBannerController = NetworkConnectionBannerController;
|
|
148
|
-
_NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnectionBannerController_unavailableTimer = new WeakMap(), _NetworkConnectionBannerController_pendingNetworkClientId = new WeakMap(), _NetworkConnectionBannerController_instances = new WeakSet(),
|
|
200
|
+
_NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnectionBannerController_unavailableTimer = new WeakMap(), _NetworkConnectionBannerController_pendingNetworkClientId = new WeakMap(), _NetworkConnectionBannerController_initialized = new WeakMap(), _NetworkConnectionBannerController_instances = new WeakSet(), _NetworkConnectionBannerController_onUpstreamChange = function _NetworkConnectionBannerController_onUpstreamChange() {
|
|
201
|
+
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_initialized, "f")) {
|
|
202
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_refreshState).call(this);
|
|
203
|
+
}
|
|
204
|
+
}, _NetworkConnectionBannerController_refreshState = function _NetworkConnectionBannerController_refreshState() {
|
|
149
205
|
const { connectivityStatus } = this.messenger.call('ConnectivityController:getState');
|
|
150
206
|
if (connectivityStatus === connectivity_controller_1.CONNECTIVITY_STATUSES.Offline) {
|
|
151
207
|
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_resetBanner).call(this);
|
|
152
208
|
return;
|
|
153
209
|
}
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
210
|
+
const failedNetwork = __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_findFailedNetwork).call(this);
|
|
211
|
+
if (!failedNetwork) {
|
|
156
212
|
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_resetBanner).call(this);
|
|
157
213
|
return;
|
|
158
214
|
}
|
|
159
215
|
if (this.state.status !== 'available' &&
|
|
160
|
-
this.state.network?.networkClientId ===
|
|
161
|
-
this.update((
|
|
162
|
-
|
|
216
|
+
this.state.network?.networkClientId === failedNetwork.networkClientId) {
|
|
217
|
+
this.update((state) => {
|
|
218
|
+
state.network = failedNetwork;
|
|
163
219
|
});
|
|
164
220
|
return;
|
|
165
221
|
}
|
|
166
|
-
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_pendingNetworkClientId, "f") ===
|
|
222
|
+
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_pendingNetworkClientId, "f") === failedNetwork.networkClientId) {
|
|
167
223
|
return;
|
|
168
224
|
}
|
|
169
225
|
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_clearTimers).call(this);
|
|
170
|
-
this.update((
|
|
171
|
-
|
|
172
|
-
|
|
226
|
+
this.update((state) => {
|
|
227
|
+
state.status = 'available';
|
|
228
|
+
state.network = null;
|
|
173
229
|
});
|
|
174
|
-
|
|
230
|
+
// Capture the failing network at schedule time. We trust the messenger
|
|
231
|
+
// contract: if the failure resolves or the target changes during the
|
|
232
|
+
// wait, our upstream subscriptions will have cancelled or replaced this
|
|
233
|
+
// timer via `#clearTimers` before it fires.
|
|
234
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_pendingNetworkClientId, failedNetwork.networkClientId, "f");
|
|
175
235
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_degradedTimer, setTimeout(() => {
|
|
176
236
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_degradedTimer, undefined, "f");
|
|
177
237
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_pendingNetworkClientId, undefined, "f");
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
this.update((draft) => {
|
|
183
|
-
draft.status = 'degraded';
|
|
184
|
-
draft.network = stillFailed;
|
|
238
|
+
this.update((state) => {
|
|
239
|
+
state.status = 'degraded';
|
|
240
|
+
state.network = failedNetwork;
|
|
185
241
|
});
|
|
186
242
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_unavailableTimer, setTimeout(() => {
|
|
187
243
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_unavailableTimer, undefined, "f");
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
this.update((draft) => {
|
|
193
|
-
draft.status = 'unavailable';
|
|
194
|
-
draft.network = stillFailedAtEscalation;
|
|
244
|
+
this.update((state) => {
|
|
245
|
+
state.status = 'unavailable';
|
|
246
|
+
state.network = failedNetwork;
|
|
195
247
|
});
|
|
196
|
-
},
|
|
197
|
-
},
|
|
248
|
+
}, UNAVAILABLE_BANNER_TIMEOUT - DEGRADED_BANNER_TIMEOUT), "f");
|
|
249
|
+
}, DEGRADED_BANNER_TIMEOUT), "f");
|
|
198
250
|
}, _NetworkConnectionBannerController_resetBanner = function _NetworkConnectionBannerController_resetBanner() {
|
|
199
251
|
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_clearTimers).call(this);
|
|
252
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_pendingNetworkClientId, undefined, "f");
|
|
200
253
|
if (this.state.status !== 'available' || this.state.network !== null) {
|
|
201
|
-
this.update((
|
|
202
|
-
|
|
203
|
-
|
|
254
|
+
this.update((state) => {
|
|
255
|
+
state.status = 'available';
|
|
256
|
+
state.network = null;
|
|
204
257
|
});
|
|
205
258
|
}
|
|
206
259
|
}, _NetworkConnectionBannerController_clearTimers = function _NetworkConnectionBannerController_clearTimers() {
|
|
207
|
-
__classPrivateFieldSet(this, _NetworkConnectionBannerController_pendingNetworkClientId, undefined, "f");
|
|
208
260
|
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_degradedTimer, "f") !== undefined) {
|
|
209
261
|
clearTimeout(__classPrivateFieldGet(this, _NetworkConnectionBannerController_degradedTimer, "f"));
|
|
210
262
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_degradedTimer, undefined, "f");
|
|
@@ -213,50 +265,64 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
213
265
|
clearTimeout(__classPrivateFieldGet(this, _NetworkConnectionBannerController_unavailableTimer, "f"));
|
|
214
266
|
__classPrivateFieldSet(this, _NetworkConnectionBannerController_unavailableTimer, undefined, "f");
|
|
215
267
|
}
|
|
216
|
-
},
|
|
268
|
+
}, _NetworkConnectionBannerController_findFailedNetwork = function _NetworkConnectionBannerController_findFailedNetwork() {
|
|
269
|
+
const networksWithMetadata = __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_collectNetworksWithMetadata).call(this);
|
|
270
|
+
const failedNetworks = networksWithMetadata
|
|
271
|
+
.filter(({ metadata }) => metadata.status !== network_controller_1.NetworkStatus.Available)
|
|
272
|
+
.map((network) => __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_buildFailedNetwork).call(this, network));
|
|
273
|
+
return __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_pickBannerNetwork).call(this, failedNetworks, networksWithMetadata.length);
|
|
274
|
+
}, _NetworkConnectionBannerController_getEnabledEvmChainIds = function _NetworkConnectionBannerController_getEnabledEvmChainIds() {
|
|
217
275
|
const { enabledNetworkMap } = this.messenger.call('NetworkEnablementController:getState');
|
|
218
|
-
|
|
276
|
+
return Object.entries(enabledNetworkMap[utils_1.KnownCaipNamespace.Eip155] ?? {})
|
|
219
277
|
.filter(([, enabled]) => enabled)
|
|
220
278
|
.map(([chainId]) => chainId);
|
|
279
|
+
}, _NetworkConnectionBannerController_collectNetworksWithMetadata = function _NetworkConnectionBannerController_collectNetworksWithMetadata() {
|
|
221
280
|
const { networkConfigurationsByChainId, networksMetadata } = this.messenger.call('NetworkController:getState');
|
|
222
|
-
|
|
223
|
-
let totalNetworksWithMetadata = 0;
|
|
224
|
-
for (const chainId of enabledEvmChainIds) {
|
|
281
|
+
return __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_getEnabledEvmChainIds).call(this).flatMap((chainId) => {
|
|
225
282
|
const networkConfiguration = networkConfigurationsByChainId[chainId];
|
|
226
283
|
if (!networkConfiguration) {
|
|
227
|
-
|
|
284
|
+
return [];
|
|
228
285
|
}
|
|
229
286
|
const { rpcEndpoints, defaultRpcEndpointIndex, name } = networkConfiguration;
|
|
230
287
|
const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];
|
|
231
288
|
if (!defaultRpcEndpoint) {
|
|
232
|
-
|
|
289
|
+
return [];
|
|
233
290
|
}
|
|
234
291
|
const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];
|
|
235
|
-
if (metadata
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
totalNetworksWithMetadata += 1;
|
|
239
|
-
if (metadata.status === network_controller_1.NetworkStatus.Available) {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);
|
|
243
|
-
// For custom endpoints (non-Infura), find an Infura endpoint on this
|
|
244
|
-
// chain that we could offer to switch to.
|
|
245
|
-
let infuraNetworkClientId = null;
|
|
246
|
-
if (!endpointIsInfura) {
|
|
247
|
-
const otherInfura = rpcEndpoints.find((endpoint, index) => index !== defaultRpcEndpointIndex && isInfuraEndpoint(endpoint.url));
|
|
248
|
-
infuraNetworkClientId = otherInfura?.networkClientId ?? null;
|
|
292
|
+
if (!metadata) {
|
|
293
|
+
return [];
|
|
249
294
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
chainId,
|
|
298
|
+
name,
|
|
299
|
+
rpcEndpoints,
|
|
300
|
+
defaultRpcEndpointIndex,
|
|
301
|
+
defaultRpcEndpoint,
|
|
302
|
+
metadata,
|
|
303
|
+
},
|
|
304
|
+
];
|
|
305
|
+
});
|
|
306
|
+
}, _NetworkConnectionBannerController_buildFailedNetwork = function _NetworkConnectionBannerController_buildFailedNetwork({ chainId, name, rpcEndpoints, defaultRpcEndpointIndex, defaultRpcEndpoint, }) {
|
|
307
|
+
const isInfuraEndpoint = getIsInfuraEndpoint(defaultRpcEndpoint.url);
|
|
308
|
+
// For custom endpoints (non-Infura), find an Infura endpoint on this
|
|
309
|
+
// chain that we could offer to switch to.
|
|
310
|
+
let switchableInfuraNetworkClientId = null;
|
|
311
|
+
if (!isInfuraEndpoint) {
|
|
312
|
+
const otherInfura = rpcEndpoints.find((endpoint, index) => index !== defaultRpcEndpointIndex &&
|
|
313
|
+
getIsInfuraEndpoint(endpoint.url));
|
|
314
|
+
switchableInfuraNetworkClientId = otherInfura?.networkClientId ?? null;
|
|
259
315
|
}
|
|
316
|
+
return {
|
|
317
|
+
chainId,
|
|
318
|
+
networkClientId: defaultRpcEndpoint.networkClientId,
|
|
319
|
+
name,
|
|
320
|
+
rpcUrl: defaultRpcEndpoint.url,
|
|
321
|
+
isInfuraEndpoint,
|
|
322
|
+
switchableInfuraNetworkClientId,
|
|
323
|
+
domain: (0, url_utils_1.getDomain)(defaultRpcEndpoint.url),
|
|
324
|
+
};
|
|
325
|
+
}, _NetworkConnectionBannerController_pickBannerNetwork = function _NetworkConnectionBannerController_pickBannerNetwork(failedNetworks, totalNetworksWithMetadata) {
|
|
260
326
|
if (failedNetworks.length === 0) {
|
|
261
327
|
return null;
|
|
262
328
|
}
|
|
@@ -270,30 +336,20 @@ _NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnec
|
|
|
270
336
|
!areAllKnownNetworksFailed) {
|
|
271
337
|
return null;
|
|
272
338
|
}
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
chainId: selected.chainId,
|
|
276
|
-
networkClientId: selected.networkClientId,
|
|
277
|
-
networkName: selected.networkName,
|
|
278
|
-
rpcUrl: selected.rpcUrl,
|
|
279
|
-
isInfuraEndpoint: selected.isInfuraEndpoint,
|
|
280
|
-
infuraNetworkClientId: selected.infuraNetworkClientId,
|
|
281
|
-
};
|
|
339
|
+
return firstCustomFailed ?? failedNetworks[0];
|
|
282
340
|
};
|
|
283
341
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
342
|
+
* Whether an RPC URL is a MetaMask Infura endpoint. Matches the
|
|
343
|
+
* `{infuraProjectId}` placeholder form that lives on stored network
|
|
344
|
+
* configurations before the wallet substitutes the real project id at
|
|
345
|
+
* request time. URLs that already carry a substituted key are treated as
|
|
346
|
+
* custom (we do not have the project id in this package).
|
|
287
347
|
*
|
|
288
348
|
* @param url - The RPC URL to check.
|
|
289
|
-
* @returns True if the URL
|
|
349
|
+
* @returns True if the URL is the placeholder form of a MetaMask Infura
|
|
350
|
+
* endpoint.
|
|
290
351
|
*/
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
return new URL(url).hostname.toLowerCase().endsWith('.infura.io');
|
|
294
|
-
}
|
|
295
|
-
catch {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
352
|
+
function getIsInfuraEndpoint(url) {
|
|
353
|
+
return /^https:\/\/[^./]+\.infura\.io\/v3\/\{infuraProjectId\}$/u.test(url);
|
|
298
354
|
}
|
|
299
355
|
//# sourceMappingURL=NetworkConnectionBannerController.cjs.map
|
|
@@ -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 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"]}
|
|
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 {\n CONNECTIVITY_STATUSES,\n connectivityControllerSelectors,\n} from '@metamask/connectivity-controller';\nimport type {\n ConnectivityControllerGetStateAction,\n ConnectivityControllerStateChangeEvent,\n} from '@metamask/connectivity-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkConfiguration,\n NetworkControllerGetNetworkConfigurationByChainIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerUpdateNetworkAction,\n NetworkControllerStateChangeEvent,\n NetworkMetadata,\n NetworkState,\n} from '@metamask/network-controller';\nimport { NetworkStatus } from '@metamask/network-controller';\nimport type {\n NetworkEnablementControllerGetStateAction,\n NetworkEnablementControllerStateChangeEvent,\n} from '@metamask/network-enablement-controller';\nimport { selectEnabledNetworkMap } from '@metamask/network-enablement-controller';\nimport type { Hex } from '@metamask/utils';\nimport { KnownCaipNamespace } from '@metamask/utils';\nimport { createSelector } from 'reselect';\n\nimport type { NetworkConnectionBannerControllerMethodActions } from './NetworkConnectionBannerController-method-action-types';\nimport { getDomain } from './url-utils';\n\n/**\n * Selects `networksMetadata` from the `NetworkController` state.\n *\n * @param state - The `NetworkController` state.\n * @returns The networks metadata map keyed by network client id.\n */\nconst selectNetworksMetadata = (\n state: NetworkState,\n): NetworkState['networksMetadata'] => state.networksMetadata;\n\n/**\n * Selects `networkConfigurationsByChainId` from the `NetworkController`\n * state.\n *\n * @param state - The `NetworkController` state.\n * @returns The network configurations keyed by chain id.\n */\nconst selectNetworkConfigurationsByChainId = (\n state: NetworkState,\n): NetworkState['networkConfigurationsByChainId'] =>\n state.networkConfigurationsByChainId;\n\n/**\n * Selects the `NetworkController` state fields that influence the banner\n * rule. Composed with `createSelector` so the return object stays reference\n * stable while unrelated `NetworkController` state (e.g.\n * `selectedNetworkClientId`) changes.\n *\n * @param state - The `NetworkController` state.\n * @returns The relevant network fields.\n */\nconst selectNetworkControllerFields = createSelector(\n [selectNetworksMetadata, selectNetworkConfigurationsByChainId],\n (networksMetadata, networkConfigurationsByChainId) => ({\n networksMetadata,\n networkConfigurationsByChainId,\n }),\n);\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 a failing network the banner describes.\n */\nexport type FailedNetwork = {\n chainId: Hex;\n networkClientId: string;\n name: string;\n rpcUrl: string;\n isInfuraEndpoint: boolean;\n /**\n * The networkClientId of an Infura endpoint on the same chain that the user\n * can switch to. `null` when the failing endpoint is already Infura or when\n * no Infura alternative exists.\n */\n switchableInfuraNetworkClientId: string | null;\n /**\n * The registrable domain (eTLD+1) of `rpcUrl`, used to group endpoints by\n * provider. `null` when the URL is invalid.\n */\n domain: string | null;\n};\n\n/**\n * State for the {@link NetworkConnectionBannerController}.\n */\nexport type NetworkConnectionBannerControllerState = {\n status: NetworkConnectionBannerStatus;\n network: FailedNetwork | 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\n/**\n * How long (in milliseconds) a failing network must remain in a \"failed\"\n * status (\"degraded\" or \"unavailable\") before the degraded banner appears.\n */\nconst DEGRADED_BANNER_TIMEOUT = 5_000;\n\n/**\n * How long (in milliseconds) a failing network must remain in a \"failed\"\n * status before the banner escalates to \"unavailable\".\n */\nconst UNAVAILABLE_BANNER_TIMEOUT = 30_000;\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'init',\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 #initialized = false;\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 // Scoped selectors per controller guideline so unrelated upstream\n // `stateChange` events (e.g. a `NetworkController` selected client id\n // update) do not trigger a re-evaluation.\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(\n 'NetworkController:stateChange',\n () => this.#onUpstreamChange(),\n selectNetworkControllerFields,\n );\n this.messenger.subscribe(\n 'NetworkEnablementController:stateChange',\n () => this.#onUpstreamChange(),\n selectEnabledNetworkMap,\n );\n this.messenger.subscribe(\n 'ConnectivityController:stateChange',\n () => this.#onUpstreamChange(),\n connectivityControllerSelectors.selectConnectivityStatus,\n );\n /* eslint-enable no-restricted-syntax */\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Starts evaluating network connection state.\n *\n * This method should be called after the upstream network, network\n * enablement, and connectivity controllers have been initialized.\n */\n init(): void {\n if (this.#initialized) {\n return;\n }\n\n this.#refreshState();\n this.#initialized = true;\n }\n\n #onUpstreamChange(): void {\n if (this.#initialized) {\n this.#refreshState();\n }\n }\n\n /**\n * Clears the banner state such that the banner will be hidden.\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) => getIsInfuraEndpoint(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 #refreshState(): 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 failedNetwork = this.#findFailedNetwork();\n\n if (!failedNetwork) {\n this.#resetBanner();\n return;\n }\n\n if (\n this.state.status !== 'available' &&\n this.state.network?.networkClientId === failedNetwork.networkClientId\n ) {\n this.update((state) => {\n state.network = failedNetwork;\n });\n return;\n }\n\n if (this.#pendingNetworkClientId === failedNetwork.networkClientId) {\n return;\n }\n\n this.#clearTimers();\n this.update((state) => {\n state.status = 'available';\n state.network = null;\n });\n\n // Capture the failing network at schedule time. We trust the messenger\n // contract: if the failure resolves or the target changes during the\n // wait, our upstream subscriptions will have cancelled or replaced this\n // timer via `#clearTimers` before it fires.\n this.#pendingNetworkClientId = failedNetwork.networkClientId;\n this.#degradedTimer = setTimeout(() => {\n this.#degradedTimer = undefined;\n this.#pendingNetworkClientId = undefined;\n this.update((state) => {\n state.status = 'degraded';\n state.network = failedNetwork;\n });\n this.#unavailableTimer = setTimeout(() => {\n this.#unavailableTimer = undefined;\n this.update((state) => {\n state.status = 'unavailable';\n state.network = failedNetwork;\n });\n }, UNAVAILABLE_BANNER_TIMEOUT - DEGRADED_BANNER_TIMEOUT);\n }, DEGRADED_BANNER_TIMEOUT);\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 this.#pendingNetworkClientId = undefined;\n if (this.state.status !== 'available' || this.state.network !== null) {\n this.update((state) => {\n state.status = 'available';\n state.network = null;\n });\n }\n }\n\n #clearTimers(): void {\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 #findFailedNetwork(): FailedNetwork | null {\n const networksWithMetadata = this.#collectNetworksWithMetadata();\n const failedNetworks = networksWithMetadata\n .filter(({ metadata }) => metadata.status !== NetworkStatus.Available)\n .map((network) => this.#buildFailedNetwork(network));\n return this.#pickBannerNetwork(failedNetworks, networksWithMetadata.length);\n }\n\n #getEnabledEvmChainIds(): Hex[] {\n const { enabledNetworkMap } = this.messenger.call(\n 'NetworkEnablementController:getState',\n );\n return Object.entries(enabledNetworkMap[KnownCaipNamespace.Eip155] ?? {})\n .filter(([, enabled]) => enabled)\n .map(([chainId]) => chainId as Hex);\n }\n\n #collectNetworksWithMetadata(): NetworkWithMetadata[] {\n const { networkConfigurationsByChainId, networksMetadata } =\n this.messenger.call('NetworkController:getState');\n\n return this.#getEnabledEvmChainIds().flatMap((chainId) => {\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n return [];\n }\n const { rpcEndpoints, defaultRpcEndpointIndex, name } =\n networkConfiguration;\n const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];\n if (!defaultRpcEndpoint) {\n return [];\n }\n const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];\n if (!metadata) {\n return [];\n }\n return [\n {\n chainId,\n name,\n rpcEndpoints,\n defaultRpcEndpointIndex,\n defaultRpcEndpoint,\n metadata,\n },\n ];\n });\n }\n\n #buildFailedNetwork({\n chainId,\n name,\n rpcEndpoints,\n defaultRpcEndpointIndex,\n defaultRpcEndpoint,\n }: NetworkWithMetadata): FailedNetwork {\n const isInfuraEndpoint = getIsInfuraEndpoint(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 switchableInfuraNetworkClientId: string | null = null;\n if (!isInfuraEndpoint) {\n const otherInfura = rpcEndpoints.find(\n (endpoint, index) =>\n index !== defaultRpcEndpointIndex &&\n getIsInfuraEndpoint(endpoint.url),\n );\n switchableInfuraNetworkClientId = otherInfura?.networkClientId ?? null;\n }\n\n return {\n chainId,\n networkClientId: defaultRpcEndpoint.networkClientId,\n name,\n rpcUrl: defaultRpcEndpoint.url,\n isInfuraEndpoint,\n switchableInfuraNetworkClientId,\n domain: getDomain(defaultRpcEndpoint.url),\n };\n }\n\n #pickBannerNetwork(\n failedNetworks: FailedNetwork[],\n totalNetworksWithMetadata: number,\n ): FailedNetwork | null {\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 return firstCustomFailed ?? failedNetworks[0];\n }\n}\n\ntype NetworkWithMetadata = {\n chainId: Hex;\n name: string;\n rpcEndpoints: NetworkConfiguration['rpcEndpoints'];\n defaultRpcEndpointIndex: number;\n defaultRpcEndpoint: NetworkConfiguration['rpcEndpoints'][number];\n metadata: NetworkMetadata;\n};\n\n/**\n * Whether an RPC URL is a MetaMask Infura endpoint. Matches the\n * `{infuraProjectId}` placeholder form that lives on stored network\n * configurations before the wallet substitutes the real project id at\n * request time. URLs that already carry a substituted key are treated as\n * custom (we do not have the project id in this package).\n *\n * @param url - The RPC URL to check.\n * @returns True if the URL is the placeholder form of a MetaMask Infura\n * endpoint.\n */\nfunction getIsInfuraEndpoint(url: string): boolean {\n return /^https:\\/\\/[^./]+\\.infura\\.io\\/v3\\/\\{infuraProjectId\\}$/u.test(url);\n}\n"]}
|