@metamask-previews/network-connection-banner-controller 0.1.0-preview-7507a11
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 +14 -0
- package/LICENSE +20 -0
- package/README.md +18 -0
- package/dist/NetworkConnectionBannerController-method-action-types.cjs +7 -0
- package/dist/NetworkConnectionBannerController-method-action-types.cjs.map +1 -0
- package/dist/NetworkConnectionBannerController-method-action-types.d.cts +32 -0
- package/dist/NetworkConnectionBannerController-method-action-types.d.cts.map +1 -0
- package/dist/NetworkConnectionBannerController-method-action-types.d.mts +32 -0
- package/dist/NetworkConnectionBannerController-method-action-types.d.mts.map +1 -0
- package/dist/NetworkConnectionBannerController-method-action-types.mjs +6 -0
- package/dist/NetworkConnectionBannerController-method-action-types.mjs.map +1 -0
- package/dist/NetworkConnectionBannerController.cjs +288 -0
- package/dist/NetworkConnectionBannerController.cjs.map +1 -0
- package/dist/NetworkConnectionBannerController.d.cts +146 -0
- package/dist/NetworkConnectionBannerController.d.cts.map +1 -0
- package/dist/NetworkConnectionBannerController.d.mts +146 -0
- package/dist/NetworkConnectionBannerController.d.mts.map +1 -0
- package/dist/NetworkConnectionBannerController.mjs +283 -0
- package/dist/NetworkConnectionBannerController.mjs.map +1 -0
- package/dist/index.cjs +10 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/dist/url-utils.cjs +55 -0
- package/dist/url-utils.cjs.map +1 -0
- package/dist/url-utils.d.cts +24 -0
- package/dist/url-utils.d.cts.map +1 -0
- package/dist/url-utils.d.mts +24 -0
- package/dist/url-utils.d.mts.map +1 -0
- package/dist/url-utils.mjs +54 -0
- package/dist/url-utils.mjs.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +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,2BAA2B,EAC5B,0CAA0C;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,uDAAuD,EACvD,+BAA+B,EAC/B,oCAAoC,EACpC,YAAY,EACb,qCAAqC;AAEtC,OAAO,KAAK,EACV,yCAAyC,EACzC,gCAAgC,EACjC,gDAAgD;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAG3C,OAAO,KAAK,EAAE,8CAA8C,EAAE,oEAAgE;AAG9H;;;;GAIG;AACH,eAAO,MAAM,cAAc,sCAAsC,CAAC;AAElE;;;;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,2BAA2B,CAAC,mBAAmB,EAAE,YAAY,CAAC,GAC9D,2BAA2B,CACzB,6BAA6B,EAC7B,gCAAgC,CACjC,GACD,2BAA2B,CACzB,wBAAwB,EACxB,2BAA2B,CAC5B,CAAC;AAEN;;;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;;IAKC;;;;;OAKG;gBACS,EAAE,SAAS,EAAE,EAAE,wCAAwC;IAyBnE;;;;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;CAwN7E"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { ControllerGetStateAction, ControllerStateChangedEvent } from "@metamask/base-controller";
|
|
2
|
+
import { BaseController } from "@metamask/base-controller";
|
|
3
|
+
import type { ConnectivityControllerGetStateAction, ConnectivityControllerState } from "@metamask/connectivity-controller";
|
|
4
|
+
import type { Messenger } from "@metamask/messenger";
|
|
5
|
+
import type { NetworkControllerGetNetworkConfigurationByChainIdAction, NetworkControllerGetStateAction, NetworkControllerUpdateNetworkAction, NetworkState } from "@metamask/network-controller";
|
|
6
|
+
import type { NetworkEnablementControllerGetStateAction, NetworkEnablementControllerState } from "@metamask/network-enablement-controller";
|
|
7
|
+
import type { Hex } from "@metamask/utils";
|
|
8
|
+
import type { NetworkConnectionBannerControllerMethodActions } from "./NetworkConnectionBannerController-method-action-types.mjs";
|
|
9
|
+
/**
|
|
10
|
+
* The name of the {@link NetworkConnectionBannerController}, used to namespace
|
|
11
|
+
* the controller's actions and events and to namespace the controller's state
|
|
12
|
+
* data when composed with other controllers.
|
|
13
|
+
*/
|
|
14
|
+
export declare const controllerName = "NetworkConnectionBannerController";
|
|
15
|
+
/**
|
|
16
|
+
* Status the banner can be in. `available` means no banner is shown; the
|
|
17
|
+
* `degraded` and `unavailable` values mirror the two-tier escalation that the
|
|
18
|
+
* UI renders.
|
|
19
|
+
*/
|
|
20
|
+
export type NetworkConnectionBannerStatus = 'available' | 'degraded' | 'unavailable';
|
|
21
|
+
/**
|
|
22
|
+
* Details of the failing network the banner should describe. Populated when
|
|
23
|
+
* {@link NetworkConnectionBannerControllerState.status} is `degraded` or
|
|
24
|
+
* `unavailable`, `null` otherwise.
|
|
25
|
+
*
|
|
26
|
+
* `infuraNetworkClientId` is the networkClientId of an Infura endpoint on the
|
|
27
|
+
* same chain that the user can switch to. `null` when the failing endpoint is
|
|
28
|
+
* already Infura, or when no Infura alternative exists.
|
|
29
|
+
*/
|
|
30
|
+
export type NetworkConnectionBannerFailedNetwork = {
|
|
31
|
+
chainId: Hex;
|
|
32
|
+
networkClientId: string;
|
|
33
|
+
networkName: string;
|
|
34
|
+
rpcUrl: string;
|
|
35
|
+
isInfuraEndpoint: boolean;
|
|
36
|
+
infuraNetworkClientId: string | null;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* State for the {@link NetworkConnectionBannerController}.
|
|
40
|
+
*/
|
|
41
|
+
export type NetworkConnectionBannerControllerState = {
|
|
42
|
+
status: NetworkConnectionBannerStatus;
|
|
43
|
+
network: NetworkConnectionBannerFailedNetwork | null;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Constructs the default {@link NetworkConnectionBannerController} state.
|
|
47
|
+
*
|
|
48
|
+
* @returns The default state.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getDefaultNetworkConnectionBannerControllerState(): NetworkConnectionBannerControllerState;
|
|
51
|
+
/**
|
|
52
|
+
* Retrieves the state of the {@link NetworkConnectionBannerController}.
|
|
53
|
+
*/
|
|
54
|
+
export type NetworkConnectionBannerControllerGetStateAction = ControllerGetStateAction<typeof controllerName, NetworkConnectionBannerControllerState>;
|
|
55
|
+
/**
|
|
56
|
+
* Actions that {@link NetworkConnectionBannerControllerMessenger} exposes to
|
|
57
|
+
* other consumers.
|
|
58
|
+
*/
|
|
59
|
+
export type NetworkConnectionBannerControllerActions = NetworkConnectionBannerControllerGetStateAction | NetworkConnectionBannerControllerMethodActions;
|
|
60
|
+
/**
|
|
61
|
+
* Actions from other messengers that
|
|
62
|
+
* {@link NetworkConnectionBannerControllerMessenger} calls.
|
|
63
|
+
*/
|
|
64
|
+
type AllowedActions = NetworkControllerGetStateAction | NetworkControllerGetNetworkConfigurationByChainIdAction | NetworkControllerUpdateNetworkAction | NetworkEnablementControllerGetStateAction | ConnectivityControllerGetStateAction;
|
|
65
|
+
/**
|
|
66
|
+
* Published when the state of {@link NetworkConnectionBannerController}
|
|
67
|
+
* changes.
|
|
68
|
+
*/
|
|
69
|
+
export type NetworkConnectionBannerControllerStateChangedEvent = ControllerStateChangedEvent<typeof controllerName, NetworkConnectionBannerControllerState>;
|
|
70
|
+
/**
|
|
71
|
+
* Events that {@link NetworkConnectionBannerControllerMessenger} exposes to
|
|
72
|
+
* other consumers.
|
|
73
|
+
*/
|
|
74
|
+
export type NetworkConnectionBannerControllerEvents = NetworkConnectionBannerControllerStateChangedEvent;
|
|
75
|
+
/**
|
|
76
|
+
* Events from other messengers that
|
|
77
|
+
* {@link NetworkConnectionBannerControllerMessenger} subscribes to.
|
|
78
|
+
*/
|
|
79
|
+
type AllowedEvents = ControllerStateChangedEvent<'NetworkController', NetworkState> | ControllerStateChangedEvent<'NetworkEnablementController', NetworkEnablementControllerState> | ControllerStateChangedEvent<'ConnectivityController', ConnectivityControllerState>;
|
|
80
|
+
/**
|
|
81
|
+
* The messenger restricted to actions and events accessed by
|
|
82
|
+
* {@link NetworkConnectionBannerController}.
|
|
83
|
+
*/
|
|
84
|
+
export type NetworkConnectionBannerControllerMessenger = Messenger<typeof controllerName, NetworkConnectionBannerControllerActions | AllowedActions, NetworkConnectionBannerControllerEvents | AllowedEvents>;
|
|
85
|
+
/**
|
|
86
|
+
* Options for constructing the {@link NetworkConnectionBannerController}.
|
|
87
|
+
*/
|
|
88
|
+
export type NetworkConnectionBannerControllerOptions = {
|
|
89
|
+
/**
|
|
90
|
+
* The messenger for inter-controller communication.
|
|
91
|
+
*/
|
|
92
|
+
messenger: NetworkConnectionBannerControllerMessenger;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* NetworkConnectionBannerController decides whether the network connection
|
|
96
|
+
* banner should be shown for the user, and which failing network it should
|
|
97
|
+
* describe. It encapsulates the rule, the 5s / 30s timer escalation, and the
|
|
98
|
+
* eTLD+1 grouping that was previously duplicated across MetaMask clients.
|
|
99
|
+
*
|
|
100
|
+
* The banner shows when:
|
|
101
|
+
*
|
|
102
|
+
* - The first failing network's default RPC is a custom (non-Infura) endpoint
|
|
103
|
+
* — users always want to be informed about errors with RPCs they've chosen.
|
|
104
|
+
* - Failed RPCs span more than one registrable domain (likely client-side).
|
|
105
|
+
* - Every enabled EVM network is failing (escape hatch for single-network
|
|
106
|
+
* setups so they still get a signal).
|
|
107
|
+
*
|
|
108
|
+
* A wide single-provider outage (e.g. every `*.infura.io` network goes down at
|
|
109
|
+
* once) collapses to one domain and is suppressed, except in the all-down
|
|
110
|
+
* single-network case. When a custom failure is present, it's surfaced first
|
|
111
|
+
* so the "Switch to MetaMask default RPC" CTA targets the network the user
|
|
112
|
+
* can act on.
|
|
113
|
+
*
|
|
114
|
+
* Clients only need to render the banner from the controller's state and wire
|
|
115
|
+
* click handlers to {@link dismissBanner} or {@link switchToDefaultInfuraRpc}.
|
|
116
|
+
*/
|
|
117
|
+
export declare class NetworkConnectionBannerController extends BaseController<typeof controllerName, NetworkConnectionBannerControllerState, NetworkConnectionBannerControllerMessenger> {
|
|
118
|
+
#private;
|
|
119
|
+
/**
|
|
120
|
+
* Constructs a new {@link NetworkConnectionBannerController}.
|
|
121
|
+
*
|
|
122
|
+
* @param args - The arguments to this controller.
|
|
123
|
+
* @param args.messenger - The messenger suited for this controller.
|
|
124
|
+
*/
|
|
125
|
+
constructor({ messenger }: NetworkConnectionBannerControllerOptions);
|
|
126
|
+
/**
|
|
127
|
+
* Clears the banner state regardless of the current rule outcome. The next
|
|
128
|
+
* subscription-driven evaluation will re-show the banner if the conditions
|
|
129
|
+
* still hold.
|
|
130
|
+
*/
|
|
131
|
+
dismissBanner(): void;
|
|
132
|
+
/**
|
|
133
|
+
* Switches the chain's default RPC endpoint to its first Infura endpoint,
|
|
134
|
+
* causing the banner to clear once the network becomes available again.
|
|
135
|
+
*
|
|
136
|
+
* @param args - The arguments to this action.
|
|
137
|
+
* @param args.chainId - The chain whose default RPC should be switched.
|
|
138
|
+
* @throws If the chain configuration cannot be found, or if it has no
|
|
139
|
+
* Infura endpoint to switch to, or if the default is already Infura.
|
|
140
|
+
*/
|
|
141
|
+
switchToDefaultInfuraRpc({ chainId }: {
|
|
142
|
+
chainId: Hex;
|
|
143
|
+
}): Promise<void>;
|
|
144
|
+
}
|
|
145
|
+
export {};
|
|
146
|
+
//# sourceMappingURL=NetworkConnectionBannerController.d.mts.map
|
|
@@ -0,0 +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,2BAA2B,EAC5B,0CAA0C;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,uDAAuD,EACvD,+BAA+B,EAC/B,oCAAoC,EACpC,YAAY,EACb,qCAAqC;AAEtC,OAAO,KAAK,EACV,yCAAyC,EACzC,gCAAgC,EACjC,gDAAgD;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAG3C,OAAO,KAAK,EAAE,8CAA8C,EAAE,oEAAgE;AAG9H;;;;GAIG;AACH,eAAO,MAAM,cAAc,sCAAsC,CAAC;AAElE;;;;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,2BAA2B,CAAC,mBAAmB,EAAE,YAAY,CAAC,GAC9D,2BAA2B,CACzB,6BAA6B,EAC7B,gCAAgC,CACjC,GACD,2BAA2B,CACzB,wBAAwB,EACxB,2BAA2B,CAC5B,CAAC;AAEN;;;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;;IAKC;;;;;OAKG;gBACS,EAAE,SAAS,EAAE,EAAE,wCAAwC;IAyBnE;;;;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;CAwN7E"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
9
|
+
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");
|
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
|
+
};
|
|
12
|
+
var _NetworkConnectionBannerController_instances, _NetworkConnectionBannerController_degradedTimer, _NetworkConnectionBannerController_unavailableTimer, _NetworkConnectionBannerController_evaluate, _NetworkConnectionBannerController_resetBanner, _NetworkConnectionBannerController_clearTimers, _NetworkConnectionBannerController_findFailedNetworkForBanner;
|
|
13
|
+
import { BaseController } from "@metamask/base-controller";
|
|
14
|
+
import { CONNECTIVITY_STATUSES } from "@metamask/connectivity-controller";
|
|
15
|
+
import { NetworkStatus } from "@metamask/network-controller";
|
|
16
|
+
import { KnownCaipNamespace } from "@metamask/utils";
|
|
17
|
+
import { getDomain } from "./url-utils.mjs";
|
|
18
|
+
/**
|
|
19
|
+
* The name of the {@link NetworkConnectionBannerController}, used to namespace
|
|
20
|
+
* the controller's actions and events and to namespace the controller's state
|
|
21
|
+
* data when composed with other controllers.
|
|
22
|
+
*/
|
|
23
|
+
export const controllerName = 'NetworkConnectionBannerController';
|
|
24
|
+
const networkConnectionBannerControllerMetadata = {
|
|
25
|
+
status: {
|
|
26
|
+
persist: false,
|
|
27
|
+
includeInDebugSnapshot: true,
|
|
28
|
+
includeInStateLogs: true,
|
|
29
|
+
usedInUi: true,
|
|
30
|
+
},
|
|
31
|
+
network: {
|
|
32
|
+
persist: false,
|
|
33
|
+
includeInDebugSnapshot: true,
|
|
34
|
+
includeInStateLogs: true,
|
|
35
|
+
usedInUi: true,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Constructs the default {@link NetworkConnectionBannerController} state.
|
|
40
|
+
*
|
|
41
|
+
* @returns The default state.
|
|
42
|
+
*/
|
|
43
|
+
export function getDefaultNetworkConnectionBannerControllerState() {
|
|
44
|
+
return {
|
|
45
|
+
status: 'available',
|
|
46
|
+
network: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const DEGRADED_BANNER_TIMEOUT_MS = 5000;
|
|
50
|
+
const UNAVAILABLE_BANNER_TIMEOUT_MS = 30000;
|
|
51
|
+
const MESSENGER_EXPOSED_METHODS = [
|
|
52
|
+
'dismissBanner',
|
|
53
|
+
'switchToDefaultInfuraRpc',
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* NetworkConnectionBannerController decides whether the network connection
|
|
57
|
+
* banner should be shown for the user, and which failing network it should
|
|
58
|
+
* describe. It encapsulates the rule, the 5s / 30s timer escalation, and the
|
|
59
|
+
* eTLD+1 grouping that was previously duplicated across MetaMask clients.
|
|
60
|
+
*
|
|
61
|
+
* The banner shows when:
|
|
62
|
+
*
|
|
63
|
+
* - The first failing network's default RPC is a custom (non-Infura) endpoint
|
|
64
|
+
* — users always want to be informed about errors with RPCs they've chosen.
|
|
65
|
+
* - Failed RPCs span more than one registrable domain (likely client-side).
|
|
66
|
+
* - Every enabled EVM network is failing (escape hatch for single-network
|
|
67
|
+
* setups so they still get a signal).
|
|
68
|
+
*
|
|
69
|
+
* A wide single-provider outage (e.g. every `*.infura.io` network goes down at
|
|
70
|
+
* once) collapses to one domain and is suppressed, except in the all-down
|
|
71
|
+
* single-network case. When a custom failure is present, it's surfaced first
|
|
72
|
+
* so the "Switch to MetaMask default RPC" CTA targets the network the user
|
|
73
|
+
* can act on.
|
|
74
|
+
*
|
|
75
|
+
* Clients only need to render the banner from the controller's state and wire
|
|
76
|
+
* click handlers to {@link dismissBanner} or {@link switchToDefaultInfuraRpc}.
|
|
77
|
+
*/
|
|
78
|
+
export class NetworkConnectionBannerController extends BaseController {
|
|
79
|
+
/**
|
|
80
|
+
* Constructs a new {@link NetworkConnectionBannerController}.
|
|
81
|
+
*
|
|
82
|
+
* @param args - The arguments to this controller.
|
|
83
|
+
* @param args.messenger - The messenger suited for this controller.
|
|
84
|
+
*/
|
|
85
|
+
constructor({ messenger }) {
|
|
86
|
+
super({
|
|
87
|
+
messenger,
|
|
88
|
+
metadata: networkConnectionBannerControllerMetadata,
|
|
89
|
+
name: controllerName,
|
|
90
|
+
state: getDefaultNetworkConnectionBannerControllerState(),
|
|
91
|
+
});
|
|
92
|
+
_NetworkConnectionBannerController_instances.add(this);
|
|
93
|
+
_NetworkConnectionBannerController_degradedTimer.set(this, void 0);
|
|
94
|
+
_NetworkConnectionBannerController_unavailableTimer.set(this, void 0);
|
|
95
|
+
const onStateChange = () => __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_evaluate).call(this);
|
|
96
|
+
this.messenger.subscribe('NetworkController:stateChanged', onStateChange);
|
|
97
|
+
this.messenger.subscribe('NetworkEnablementController:stateChanged', onStateChange);
|
|
98
|
+
this.messenger.subscribe('ConnectivityController:stateChanged', onStateChange);
|
|
99
|
+
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Clears the banner state regardless of the current rule outcome. The next
|
|
103
|
+
* subscription-driven evaluation will re-show the banner if the conditions
|
|
104
|
+
* still hold.
|
|
105
|
+
*/
|
|
106
|
+
dismissBanner() {
|
|
107
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_resetBanner).call(this);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Switches the chain's default RPC endpoint to its first Infura endpoint,
|
|
111
|
+
* causing the banner to clear once the network becomes available again.
|
|
112
|
+
*
|
|
113
|
+
* @param args - The arguments to this action.
|
|
114
|
+
* @param args.chainId - The chain whose default RPC should be switched.
|
|
115
|
+
* @throws If the chain configuration cannot be found, or if it has no
|
|
116
|
+
* Infura endpoint to switch to, or if the default is already Infura.
|
|
117
|
+
*/
|
|
118
|
+
async switchToDefaultInfuraRpc({ chainId }) {
|
|
119
|
+
const networkConfiguration = this.messenger.call('NetworkController:getNetworkConfigurationByChainId', chainId);
|
|
120
|
+
if (!networkConfiguration) {
|
|
121
|
+
throw new Error(`No network configuration found for chain ID "${chainId}".`);
|
|
122
|
+
}
|
|
123
|
+
const infuraEndpointIndex = networkConfiguration.rpcEndpoints.findIndex((endpoint) => isInfuraEndpoint(endpoint.url));
|
|
124
|
+
if (infuraEndpointIndex === -1) {
|
|
125
|
+
throw new Error(`No Infura endpoint available for chain ID "${chainId}".`);
|
|
126
|
+
}
|
|
127
|
+
if (infuraEndpointIndex === networkConfiguration.defaultRpcEndpointIndex) {
|
|
128
|
+
// The default is already Infura; nothing to do.
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await this.messenger.call('NetworkController:updateNetwork', chainId, {
|
|
132
|
+
...networkConfiguration,
|
|
133
|
+
defaultRpcEndpointIndex: infuraEndpointIndex,
|
|
134
|
+
}, { replacementSelectedRpcEndpointIndex: infuraEndpointIndex });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
_NetworkConnectionBannerController_degradedTimer = new WeakMap(), _NetworkConnectionBannerController_unavailableTimer = new WeakMap(), _NetworkConnectionBannerController_instances = new WeakSet(), _NetworkConnectionBannerController_evaluate = function _NetworkConnectionBannerController_evaluate() {
|
|
138
|
+
const { connectivityStatus } = this.messenger.call('ConnectivityController:getState');
|
|
139
|
+
if (connectivityStatus === CONNECTIVITY_STATUSES.Offline) {
|
|
140
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_resetBanner).call(this);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const failed = __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_findFailedNetworkForBanner).call(this);
|
|
144
|
+
if (!failed) {
|
|
145
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_resetBanner).call(this);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// If the banner is currently showing for a different network or status,
|
|
149
|
+
// restart the escalation timeline for the new one. Otherwise let the
|
|
150
|
+
// existing timers continue.
|
|
151
|
+
if (this.state.status !== 'available' &&
|
|
152
|
+
this.state.network?.networkClientId === failed.networkClientId) {
|
|
153
|
+
this.update((draft) => {
|
|
154
|
+
draft.network = failed;
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_clearTimers).call(this);
|
|
159
|
+
this.update((draft) => {
|
|
160
|
+
draft.status = 'available';
|
|
161
|
+
draft.network = null;
|
|
162
|
+
});
|
|
163
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_degradedTimer, setTimeout(() => {
|
|
164
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_degradedTimer, undefined, "f");
|
|
165
|
+
const stillFailed = __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_findFailedNetworkForBanner).call(this);
|
|
166
|
+
if (!stillFailed) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.update((draft) => {
|
|
170
|
+
draft.status = 'degraded';
|
|
171
|
+
draft.network = stillFailed;
|
|
172
|
+
});
|
|
173
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_unavailableTimer, setTimeout(() => {
|
|
174
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_unavailableTimer, undefined, "f");
|
|
175
|
+
const stillFailedAtEscalation = __classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_findFailedNetworkForBanner).call(this);
|
|
176
|
+
if (!stillFailedAtEscalation) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.update((draft) => {
|
|
180
|
+
draft.status = 'unavailable';
|
|
181
|
+
draft.network = stillFailedAtEscalation;
|
|
182
|
+
});
|
|
183
|
+
}, UNAVAILABLE_BANNER_TIMEOUT_MS - DEGRADED_BANNER_TIMEOUT_MS), "f");
|
|
184
|
+
}, DEGRADED_BANNER_TIMEOUT_MS), "f");
|
|
185
|
+
}, _NetworkConnectionBannerController_resetBanner = function _NetworkConnectionBannerController_resetBanner() {
|
|
186
|
+
__classPrivateFieldGet(this, _NetworkConnectionBannerController_instances, "m", _NetworkConnectionBannerController_clearTimers).call(this);
|
|
187
|
+
if (this.state.status !== 'available' || this.state.network !== null) {
|
|
188
|
+
this.update((draft) => {
|
|
189
|
+
draft.status = 'available';
|
|
190
|
+
draft.network = null;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}, _NetworkConnectionBannerController_clearTimers = function _NetworkConnectionBannerController_clearTimers() {
|
|
194
|
+
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_degradedTimer, "f") !== undefined) {
|
|
195
|
+
clearTimeout(__classPrivateFieldGet(this, _NetworkConnectionBannerController_degradedTimer, "f"));
|
|
196
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_degradedTimer, undefined, "f");
|
|
197
|
+
}
|
|
198
|
+
if (__classPrivateFieldGet(this, _NetworkConnectionBannerController_unavailableTimer, "f") !== undefined) {
|
|
199
|
+
clearTimeout(__classPrivateFieldGet(this, _NetworkConnectionBannerController_unavailableTimer, "f"));
|
|
200
|
+
__classPrivateFieldSet(this, _NetworkConnectionBannerController_unavailableTimer, undefined, "f");
|
|
201
|
+
}
|
|
202
|
+
}, _NetworkConnectionBannerController_findFailedNetworkForBanner = function _NetworkConnectionBannerController_findFailedNetworkForBanner() {
|
|
203
|
+
const { enabledNetworkMap } = this.messenger.call('NetworkEnablementController:getState');
|
|
204
|
+
const enabledEvmChainIds = Object.entries(enabledNetworkMap[KnownCaipNamespace.Eip155] ?? {})
|
|
205
|
+
.filter(([, enabled]) => enabled)
|
|
206
|
+
.map(([chainId]) => chainId);
|
|
207
|
+
const { networkConfigurationsByChainId, networksMetadata } = this.messenger.call('NetworkController:getState');
|
|
208
|
+
const failedNetworks = [];
|
|
209
|
+
let totalEnabled = 0;
|
|
210
|
+
for (const chainId of enabledEvmChainIds) {
|
|
211
|
+
const networkConfiguration = networkConfigurationsByChainId[chainId];
|
|
212
|
+
if (!networkConfiguration) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const { rpcEndpoints, defaultRpcEndpointIndex, name } = networkConfiguration;
|
|
216
|
+
const defaultRpcEndpoint = rpcEndpoints[defaultRpcEndpointIndex];
|
|
217
|
+
if (!defaultRpcEndpoint) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
totalEnabled += 1;
|
|
221
|
+
const metadata = networksMetadata[defaultRpcEndpoint.networkClientId];
|
|
222
|
+
if (metadata === undefined ||
|
|
223
|
+
metadata.status === NetworkStatus.Available) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const endpointIsInfura = isInfuraEndpoint(defaultRpcEndpoint.url);
|
|
227
|
+
// For custom endpoints (non-Infura), find an Infura endpoint on this
|
|
228
|
+
// chain that we could offer to switch to.
|
|
229
|
+
let infuraNetworkClientId = null;
|
|
230
|
+
if (!endpointIsInfura) {
|
|
231
|
+
const otherInfura = rpcEndpoints.find((endpoint, index) => index !== defaultRpcEndpointIndex && isInfuraEndpoint(endpoint.url));
|
|
232
|
+
infuraNetworkClientId = otherInfura?.networkClientId ?? null;
|
|
233
|
+
}
|
|
234
|
+
failedNetworks.push({
|
|
235
|
+
chainId,
|
|
236
|
+
networkClientId: defaultRpcEndpoint.networkClientId,
|
|
237
|
+
networkName: name,
|
|
238
|
+
rpcUrl: defaultRpcEndpoint.url,
|
|
239
|
+
isInfuraEndpoint: endpointIsInfura,
|
|
240
|
+
infuraNetworkClientId,
|
|
241
|
+
domain: getDomain(defaultRpcEndpoint.url),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (failedNetworks.length === 0) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const firstCustomFailed = failedNetworks.find((entry) => !entry.isInfuraEndpoint);
|
|
248
|
+
const distinctDomains = new Set(failedNetworks
|
|
249
|
+
.map((entry) => entry.domain)
|
|
250
|
+
.filter((domain) => domain !== null)).size;
|
|
251
|
+
const areAllEnabledNetworksFailed = failedNetworks.length === totalEnabled;
|
|
252
|
+
if (!firstCustomFailed &&
|
|
253
|
+
distinctDomains <= 1 &&
|
|
254
|
+
!areAllEnabledNetworksFailed) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
const selected = firstCustomFailed ?? failedNetworks[0];
|
|
258
|
+
return {
|
|
259
|
+
chainId: selected.chainId,
|
|
260
|
+
networkClientId: selected.networkClientId,
|
|
261
|
+
networkName: selected.networkName,
|
|
262
|
+
rpcUrl: selected.rpcUrl,
|
|
263
|
+
isInfuraEndpoint: selected.isInfuraEndpoint,
|
|
264
|
+
infuraNetworkClientId: selected.infuraNetworkClientId,
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Checks if an RPC URL is hosted on the Infura service. Detection is by
|
|
269
|
+
* hostname suffix rather than the exact MetaMask-key URL pattern, so any
|
|
270
|
+
* `*.infura.io` URL counts. That's the right shape for grouping by provider.
|
|
271
|
+
*
|
|
272
|
+
* @param url - The RPC URL to check.
|
|
273
|
+
* @returns True if the URL's host is on `infura.io`.
|
|
274
|
+
*/
|
|
275
|
+
function isInfuraEndpoint(url) {
|
|
276
|
+
try {
|
|
277
|
+
return new URL(url).hostname.toLowerCase().endsWith('.infura.io');
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=NetworkConnectionBannerController.mjs.map
|
|
@@ -0,0 +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 ConnectivityControllerState,\n} from '@metamask/connectivity-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkConfigurationByChainIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerUpdateNetworkAction,\n NetworkState,\n} from '@metamask/network-controller';\nimport { NetworkStatus } from '@metamask/network-controller';\nimport type {\n NetworkEnablementControllerGetStateAction,\n NetworkEnablementControllerState,\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 */\nexport const 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 | ControllerStateChangedEvent<'NetworkController', NetworkState>\n | ControllerStateChangedEvent<\n 'NetworkEnablementController',\n NetworkEnablementControllerState\n >\n | ControllerStateChangedEvent<\n 'ConnectivityController',\n ConnectivityControllerState\n >;\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 /**\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 this.messenger.subscribe('NetworkController:stateChanged', onStateChange);\n this.messenger.subscribe(\n 'NetworkEnablementController:stateChanged',\n onStateChange,\n );\n this.messenger.subscribe(\n 'ConnectivityController:stateChanged',\n onStateChange,\n );\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 the banner is currently showing for a different network or status,\n // restart the escalation timeline for the new one. Otherwise let the\n // existing timers continue.\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 this.#clearTimers();\n this.update((draft) => {\n draft.status = 'available';\n draft.network = null;\n });\n\n this.#degradedTimer = setTimeout(() => {\n this.#degradedTimer = 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 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"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLocalhostOrIPAddress = exports.getDomain = exports.getDefaultNetworkConnectionBannerControllerState = exports.NetworkConnectionBannerController = void 0;
|
|
4
|
+
var NetworkConnectionBannerController_1 = require("./NetworkConnectionBannerController.cjs");
|
|
5
|
+
Object.defineProperty(exports, "NetworkConnectionBannerController", { enumerable: true, get: function () { return NetworkConnectionBannerController_1.NetworkConnectionBannerController; } });
|
|
6
|
+
Object.defineProperty(exports, "getDefaultNetworkConnectionBannerControllerState", { enumerable: true, get: function () { return NetworkConnectionBannerController_1.getDefaultNetworkConnectionBannerControllerState; } });
|
|
7
|
+
var url_utils_1 = require("./url-utils.cjs");
|
|
8
|
+
Object.defineProperty(exports, "getDomain", { enumerable: true, get: function () { return url_utils_1.getDomain; } });
|
|
9
|
+
Object.defineProperty(exports, "isLocalhostOrIPAddress", { enumerable: true, get: function () { return url_utils_1.isLocalhostOrIPAddress; } });
|
|
10
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAeA,6FAG6C;AAF3C,sJAAA,iCAAiC,OAAA;AACjC,qKAAA,gDAAgD,OAAA;AAElD,6CAAgE;AAAvD,sGAAA,SAAS,OAAA;AAAE,mHAAA,sBAAsB,OAAA","sourcesContent":["export type {\n NetworkConnectionBannerControllerState,\n NetworkConnectionBannerControllerGetStateAction,\n NetworkConnectionBannerControllerActions,\n NetworkConnectionBannerControllerStateChangedEvent,\n NetworkConnectionBannerControllerEvents,\n NetworkConnectionBannerControllerMessenger,\n NetworkConnectionBannerControllerOptions,\n NetworkConnectionBannerFailedNetwork,\n NetworkConnectionBannerStatus,\n} from './NetworkConnectionBannerController';\nexport type {\n NetworkConnectionBannerControllerDismissBannerAction,\n NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction,\n} from './NetworkConnectionBannerController-method-action-types';\nexport {\n NetworkConnectionBannerController,\n getDefaultNetworkConnectionBannerControllerState,\n} from './NetworkConnectionBannerController';\nexport { getDomain, isLocalhostOrIPAddress } from './url-utils';\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { NetworkConnectionBannerControllerState, NetworkConnectionBannerControllerGetStateAction, NetworkConnectionBannerControllerActions, NetworkConnectionBannerControllerStateChangedEvent, NetworkConnectionBannerControllerEvents, NetworkConnectionBannerControllerMessenger, NetworkConnectionBannerControllerOptions, NetworkConnectionBannerFailedNetwork, NetworkConnectionBannerStatus, } from "./NetworkConnectionBannerController.cjs";
|
|
2
|
+
export type { NetworkConnectionBannerControllerDismissBannerAction, NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction, } from "./NetworkConnectionBannerController-method-action-types.cjs";
|
|
3
|
+
export { NetworkConnectionBannerController, getDefaultNetworkConnectionBannerControllerState, } from "./NetworkConnectionBannerController.cjs";
|
|
4
|
+
export { getDomain, isLocalhostOrIPAddress } from "./url-utils.cjs";
|
|
5
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,sCAAsC,EACtC,+CAA+C,EAC/C,wCAAwC,EACxC,kDAAkD,EAClD,uCAAuC,EACvC,0CAA0C,EAC1C,wCAAwC,EACxC,oCAAoC,EACpC,6BAA6B,GAC9B,gDAA4C;AAC7C,YAAY,EACV,oDAAoD,EACpD,+DAA+D,GAChE,oEAAgE;AACjE,OAAO,EACL,iCAAiC,EACjC,gDAAgD,GACjD,gDAA4C;AAC7C,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,wBAAoB"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { NetworkConnectionBannerControllerState, NetworkConnectionBannerControllerGetStateAction, NetworkConnectionBannerControllerActions, NetworkConnectionBannerControllerStateChangedEvent, NetworkConnectionBannerControllerEvents, NetworkConnectionBannerControllerMessenger, NetworkConnectionBannerControllerOptions, NetworkConnectionBannerFailedNetwork, NetworkConnectionBannerStatus, } from "./NetworkConnectionBannerController.mjs";
|
|
2
|
+
export type { NetworkConnectionBannerControllerDismissBannerAction, NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction, } from "./NetworkConnectionBannerController-method-action-types.mjs";
|
|
3
|
+
export { NetworkConnectionBannerController, getDefaultNetworkConnectionBannerControllerState, } from "./NetworkConnectionBannerController.mjs";
|
|
4
|
+
export { getDomain, isLocalhostOrIPAddress } from "./url-utils.mjs";
|
|
5
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,sCAAsC,EACtC,+CAA+C,EAC/C,wCAAwC,EACxC,kDAAkD,EAClD,uCAAuC,EACvC,0CAA0C,EAC1C,wCAAwC,EACxC,oCAAoC,EACpC,6BAA6B,GAC9B,gDAA4C;AAC7C,YAAY,EACV,oDAAoD,EACpD,+DAA+D,GAChE,oEAAgE;AACjE,OAAO,EACL,iCAAiC,EACjC,gDAAgD,GACjD,gDAA4C;AAC7C,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,wBAAoB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,iCAAiC,EACjC,gDAAgD,EACjD,gDAA4C;AAC7C,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,wBAAoB","sourcesContent":["export type {\n NetworkConnectionBannerControllerState,\n NetworkConnectionBannerControllerGetStateAction,\n NetworkConnectionBannerControllerActions,\n NetworkConnectionBannerControllerStateChangedEvent,\n NetworkConnectionBannerControllerEvents,\n NetworkConnectionBannerControllerMessenger,\n NetworkConnectionBannerControllerOptions,\n NetworkConnectionBannerFailedNetwork,\n NetworkConnectionBannerStatus,\n} from './NetworkConnectionBannerController';\nexport type {\n NetworkConnectionBannerControllerDismissBannerAction,\n NetworkConnectionBannerControllerSwitchToDefaultInfuraRpcAction,\n} from './NetworkConnectionBannerController-method-action-types';\nexport {\n NetworkConnectionBannerController,\n getDefaultNetworkConnectionBannerControllerState,\n} from './NetworkConnectionBannerController';\nexport { getDomain, isLocalhostOrIPAddress } from './url-utils';\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getDomain = exports.isLocalhostOrIPAddress = void 0;
|
|
7
|
+
const ip_regex_1 = __importDefault(require("ip-regex/index.js"));
|
|
8
|
+
const psl_1 = require("psl");
|
|
9
|
+
/**
|
|
10
|
+
* Check if a hostname is localhost or an IP address (v4 or v6). Public RPC
|
|
11
|
+
* providers use domain names, not raw IP addresses, so a `true` result means
|
|
12
|
+
* the host should not be reduced to an eTLD+1.
|
|
13
|
+
*
|
|
14
|
+
* @param hostname - The hostname to check.
|
|
15
|
+
* @returns True if the hostname is localhost or an IP address.
|
|
16
|
+
*/
|
|
17
|
+
function isLocalhostOrIPAddress(hostname) {
|
|
18
|
+
const lowerHostname = hostname.toLowerCase();
|
|
19
|
+
if (lowerHostname === 'localhost') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
// Remove brackets from IPv6 addresses for testing (e.g., [::1] -> ::1)
|
|
23
|
+
const hostnameWithoutBrackets = lowerHostname.replace(/^\[|\]$/gu, '');
|
|
24
|
+
return (0, ip_regex_1.default)({ exact: true }).test(hostnameWithoutBrackets);
|
|
25
|
+
}
|
|
26
|
+
exports.isLocalhostOrIPAddress = isLocalhostOrIPAddress;
|
|
27
|
+
/**
|
|
28
|
+
* Registrable domain (eTLD+1) for a URL, computed via the Public Suffix List
|
|
29
|
+
* so multi-part suffixes like ".co.uk" resolve correctly. Used to group RPC
|
|
30
|
+
* endpoints by provider so a single provider's wide outage (e.g. *.infura.io)
|
|
31
|
+
* is treated as one failure rather than many.
|
|
32
|
+
*
|
|
33
|
+
* Localhost, IP literals, and single-label hosts are returned verbatim rather
|
|
34
|
+
* than reduced to a domain (psl returns null or garbage for those, and callers
|
|
35
|
+
* grouping by domain still need to distinguish them).
|
|
36
|
+
*
|
|
37
|
+
* @param urlString - The URL to extract a domain from.
|
|
38
|
+
* @returns The domain, or null if the URL is invalid.
|
|
39
|
+
*/
|
|
40
|
+
function getDomain(urlString) {
|
|
41
|
+
let url;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(urlString);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const { hostname } = url;
|
|
49
|
+
if (!hostname.includes('.') || isLocalhostOrIPAddress(hostname)) {
|
|
50
|
+
return hostname;
|
|
51
|
+
}
|
|
52
|
+
return (0, psl_1.get)(hostname) ?? hostname;
|
|
53
|
+
}
|
|
54
|
+
exports.getDomain = getDomain;
|
|
55
|
+
//# sourceMappingURL=url-utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-utils.cjs","sourceRoot":"","sources":["../src/url-utils.ts"],"names":[],"mappings":";;;;;;AAAA,iEAA+B;AAC/B,6BAAoC;AAEpC;;;;;;;GAOG;AACH,SAAgB,sBAAsB,CAAC,QAAgB;IACrD,MAAM,aAAa,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IAE7C,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uEAAuE;IACvE,MAAM,uBAAuB,GAAG,aAAa,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAEvE,OAAO,IAAA,kBAAO,EAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;AAChE,CAAC;AAXD,wDAWC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,SAAS,CAAC,SAAiB;IACzC,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;IAEzB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,sBAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,IAAA,SAAM,EAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC;AACtC,CAAC;AAfD,8BAeC","sourcesContent":["import ipRegex from 'ip-regex';\nimport { get as pslGet } from 'psl';\n\n/**\n * Check if a hostname is localhost or an IP address (v4 or v6). Public RPC\n * providers use domain names, not raw IP addresses, so a `true` result means\n * the host should not be reduced to an eTLD+1.\n *\n * @param hostname - The hostname to check.\n * @returns True if the hostname is localhost or an IP address.\n */\nexport function isLocalhostOrIPAddress(hostname: string): boolean {\n const lowerHostname = hostname.toLowerCase();\n\n if (lowerHostname === 'localhost') {\n return true;\n }\n\n // Remove brackets from IPv6 addresses for testing (e.g., [::1] -> ::1)\n const hostnameWithoutBrackets = lowerHostname.replace(/^\\[|\\]$/gu, '');\n\n return ipRegex({ exact: true }).test(hostnameWithoutBrackets);\n}\n\n/**\n * Registrable domain (eTLD+1) for a URL, computed via the Public Suffix List\n * so multi-part suffixes like \".co.uk\" resolve correctly. Used to group RPC\n * endpoints by provider so a single provider's wide outage (e.g. *.infura.io)\n * is treated as one failure rather than many.\n *\n * Localhost, IP literals, and single-label hosts are returned verbatim rather\n * than reduced to a domain (psl returns null or garbage for those, and callers\n * grouping by domain still need to distinguish them).\n *\n * @param urlString - The URL to extract a domain from.\n * @returns The domain, or null if the URL is invalid.\n */\nexport function getDomain(urlString: string): string | null {\n let url: URL;\n try {\n url = new URL(urlString);\n } catch {\n return null;\n }\n\n const { hostname } = url;\n\n if (!hostname.includes('.') || isLocalhostOrIPAddress(hostname)) {\n return hostname;\n }\n\n return pslGet(hostname) ?? hostname;\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a hostname is localhost or an IP address (v4 or v6). Public RPC
|
|
3
|
+
* providers use domain names, not raw IP addresses, so a `true` result means
|
|
4
|
+
* the host should not be reduced to an eTLD+1.
|
|
5
|
+
*
|
|
6
|
+
* @param hostname - The hostname to check.
|
|
7
|
+
* @returns True if the hostname is localhost or an IP address.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isLocalhostOrIPAddress(hostname: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Registrable domain (eTLD+1) for a URL, computed via the Public Suffix List
|
|
12
|
+
* so multi-part suffixes like ".co.uk" resolve correctly. Used to group RPC
|
|
13
|
+
* endpoints by provider so a single provider's wide outage (e.g. *.infura.io)
|
|
14
|
+
* is treated as one failure rather than many.
|
|
15
|
+
*
|
|
16
|
+
* Localhost, IP literals, and single-label hosts are returned verbatim rather
|
|
17
|
+
* than reduced to a domain (psl returns null or garbage for those, and callers
|
|
18
|
+
* grouping by domain still need to distinguish them).
|
|
19
|
+
*
|
|
20
|
+
* @param urlString - The URL to extract a domain from.
|
|
21
|
+
* @returns The domain, or null if the URL is invalid.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getDomain(urlString: string): string | null;
|
|
24
|
+
//# sourceMappingURL=url-utils.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-utils.d.cts","sourceRoot":"","sources":["../src/url-utils.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAe1D"}
|