diff --git a/README.md b/README.md index 48d06d72c30..f835529429a 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,7 @@ linkStyle default opacity:0.5 name_controller --> controller_utils; name_controller --> messenger; network_controller --> base_controller; + network_controller --> connectivity_controller; network_controller --> controller_utils; network_controller --> eth_block_tracker; network_controller --> eth_json_rpc_middleware; diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 5817ea03e03..1f6762b8f00 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** NetworkController now requires `ConnectivityController:getState` action handler to be registered on the messenger ([#7627](https://github.com/MetaMask/core/pull/7627)) + - The `NetworkController` now depends on the `ConnectivityController` to prevent retries and suppress events when the user is offline. + - When offline, RPC requests are not retried at the `RpcService` level, preventing unnecessary network calls and circuit breaker failures. + - When offline, `NetworkController:rpcEndpointUnavailable` and `NetworkController:rpcEndpointDegraded` events are suppressed since retries don't occur and circuit breakers don't trigger. + - You must register a `ConnectivityController:getState` action handler on your root messenger that returns an object with a `connectivityStatus` property (`'online'` or `'offline'`). + - You must delegate the `ConnectivityController:getState` action from your root messenger to the `NetworkControllerMessenger` using `rootMessenger.delegate({ messenger: networkControllerMessenger, actions: ['ConnectivityController:getState'] })`. + ## [28.0.0] ### Changed diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 1fc7176dee7..1834430ebda 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -49,6 +49,7 @@ }, "dependencies": { "@metamask/base-controller": "^9.0.0", + "@metamask/connectivity-controller": "^0.0.0", "@metamask/controller-utils": "^11.18.0", "@metamask/eth-block-tracker": "^15.0.0", "@metamask/eth-json-rpc-infura": "^10.3.0", diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 1652fa011a1..87e0603368c 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -3,6 +3,7 @@ import type { ControllerStateChangeEvent, } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; +import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import type { Partialize } from '@metamask/controller-utils'; import { InfuraNetworkType, @@ -729,7 +730,7 @@ export type NetworkControllerActions = /** * All actions that {@link NetworkController} calls internally. */ -type AllowedActions = never; +type AllowedActions = ConnectivityControllerGetStateAction; export type NetworkControllerMessenger = Messenger< typeof controllerName, diff --git a/packages/network-controller/src/create-network-client-tests/rpc-endpoint-events.test.ts b/packages/network-controller/src/create-network-client-tests/rpc-endpoint-events.test.ts index b3352b777c5..69795edbcfe 100644 --- a/packages/network-controller/src/create-network-client-tests/rpc-endpoint-events.test.ts +++ b/packages/network-controller/src/create-network-client-tests/rpc-endpoint-events.test.ts @@ -1,3 +1,4 @@ +import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller'; import { ConstantBackoff, DEFAULT_DEGRADED_THRESHOLD, @@ -263,6 +264,190 @@ describe('createNetworkClient - RPC endpoint events', () => { ); }); + it('does not retry requests when user is offline', async () => { + const failoverEndpointUrl = 'https://failover.endpoint/'; + const request = { + method: 'eth_gasPrice', + params: [], + }; + const expectedError = createResourceUnavailableError(503); + + await withMockedCommunications( + { providerType: networkClientType }, + async (primaryComms) => { + await withMockedCommunications( + { + providerType: 'custom', + customRpcUrl: failoverEndpointUrl, + }, + async () => { + // Mock only one failure - if retries were happening, we'd need more + primaryComms.mockRpcCall({ + request: { + method: 'eth_blockNumber', + params: [], + }, + times: 1, + response: { + httpStatus: 503, + }, + }); + primaryComms.mockRpcCall({ + request: { + method: 'eth_gasPrice', + params: [], + }, + times: 1, + response: { + httpStatus: 503, + }, + }); + + const rootMessenger = buildRootMessenger({ + connectivityStatus: CONNECTIVITY_STATUSES.Offline, + }); + + const rpcEndpointRetriedEventHandler = jest.fn(); + rootMessenger.subscribe( + 'NetworkController:rpcEndpointRetried', + rpcEndpointRetriedEventHandler, + ); + + await withNetworkClient( + { + providerType: networkClientType, + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + isRpcFailoverEnabled: true, + failoverRpcUrls: [failoverEndpointUrl], + messenger: rootMessenger, + getRpcServiceOptions: () => ({ + fetch, + btoa, + policyOptions: { + backoff: new ConstantBackoff(backoffDuration), + }, + }), + }, + async ({ makeRpcCall }) => { + // When offline, errors are not retried, so the request + // should fail immediately without retries + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + + // Verify that retry event was not published + expect( + rpcEndpointRetriedEventHandler, + ).not.toHaveBeenCalled(); + }, + ); + }, + ); + }, + ); + }); + + it('suppresses the NetworkController:rpcEndpointUnavailable event when user is offline', async () => { + const failoverEndpointUrl = 'https://failover.endpoint/'; + const request = { + method: 'eth_gasPrice', + params: [], + }; + const expectedError = createResourceUnavailableError(503); + + await withMockedCommunications( + { providerType: networkClientType }, + async (primaryComms) => { + await withMockedCommunications( + { + providerType: 'custom', + customRpcUrl: failoverEndpointUrl, + }, + async (failoverComms) => { + // The first time a block-cacheable request is made, the + // latest block number is retrieved through the block + // tracker first. + primaryComms.mockRpcCall({ + request: { + method: 'eth_blockNumber', + params: [], + }, + times: DEFAULT_MAX_CONSECUTIVE_FAILURES, + response: { + httpStatus: 503, + }, + }); + failoverComms.mockRpcCall({ + request: { + method: 'eth_blockNumber', + params: [], + }, + times: DEFAULT_MAX_CONSECUTIVE_FAILURES, + response: { + httpStatus: 503, + }, + }); + + const rootMessenger = buildRootMessenger({ + connectivityStatus: CONNECTIVITY_STATUSES.Offline, + }); + + const rpcEndpointUnavailableEventHandler = jest.fn(); + rootMessenger.subscribe( + 'NetworkController:rpcEndpointUnavailable', + rpcEndpointUnavailableEventHandler, + ); + + await withNetworkClient( + { + providerType: networkClientType, + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + isRpcFailoverEnabled: true, + failoverRpcUrls: [failoverEndpointUrl], + messenger: rootMessenger, + getRpcServiceOptions: () => ({ + fetch, + btoa, + policyOptions: { + backoff: new ConstantBackoff(backoffDuration), + }, + }), + }, + async ({ makeRpcCall, clock }) => { + rootMessenger.subscribe( + 'NetworkController:rpcEndpointRetried', + () => { + // Ensure that we advance to the next RPC request + // retry, not the next block tracker request. + clock.tick(backoffDuration); + }, + ); + + // When offline, errors are not retried, so the circuit + // won't break and onServiceBreak won't be called + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + + // Event should be suppressed when offline because retries + // are prevented, so onServiceBreak is never called + expect( + rpcEndpointUnavailableEventHandler, + ).not.toHaveBeenCalled(); + }, + ); + }, + ); + }, + ); + }); + it('does not publish the NetworkController:rpcEndpointChainDegraded event again if the max number of retries is reached in making requests to a failover endpoint', async () => { const failoverEndpointUrl = 'https://failover.endpoint/'; const request = { @@ -1117,6 +1302,156 @@ describe('createNetworkClient - RPC endpoint events', () => { ); }); + it('does not retry requests when user is offline (degraded scenario)', async () => { + const request = { + method: 'eth_gasPrice', + params: [], + }; + const expectedError = createResourceUnavailableError(503); + + await withMockedCommunications( + { providerType: networkClientType }, + async (comms) => { + // Mock only one failure - if retries were happening, we'd need more + comms.mockRpcCall({ + request: { + method: 'eth_blockNumber', + params: [], + }, + times: 1, + response: { + httpStatus: 503, + }, + }); + comms.mockRpcCall({ + request: { + method: 'eth_gasPrice', + params: [], + }, + times: 1, + response: { + httpStatus: 503, + }, + }); + + const rootMessenger = buildRootMessenger({ + connectivityStatus: CONNECTIVITY_STATUSES.Offline, + }); + + const rpcEndpointRetriedEventHandler = jest.fn(); + rootMessenger.subscribe( + 'NetworkController:rpcEndpointRetried', + rpcEndpointRetriedEventHandler, + ); + + await withNetworkClient( + { + providerType: networkClientType, + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + messenger: rootMessenger, + getRpcServiceOptions: () => ({ + fetch, + btoa, + policyOptions: { + backoff: new ConstantBackoff(backoffDuration), + }, + }), + }, + async ({ makeRpcCall }) => { + // When offline, errors are not retried, so the request + // should fail immediately without retries + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + + // Verify that retry event was not published + expect(rpcEndpointRetriedEventHandler).not.toHaveBeenCalled(); + }, + ); + }, + ); + }); + + it('suppresses the NetworkController:rpcEndpointDegraded event when user is offline', async () => { + const request = { + method: 'eth_gasPrice', + params: [], + }; + const expectedError = createResourceUnavailableError(503); + + await withMockedCommunications( + { providerType: networkClientType }, + async (comms) => { + // The first time a block-cacheable request is made, the + // latest block number is retrieved through the block + // tracker first. + comms.mockRpcCall({ + request: { + method: 'eth_blockNumber', + params: [], + }, + times: DEFAULT_MAX_CONSECUTIVE_FAILURES, + response: { + httpStatus: 503, + }, + }); + + const rootMessenger = buildRootMessenger({ + connectivityStatus: CONNECTIVITY_STATUSES.Offline, + }); + + const rpcEndpointDegradedEventHandler = jest.fn(); + rootMessenger.subscribe( + 'NetworkController:rpcEndpointDegraded', + rpcEndpointDegradedEventHandler, + ); + + await withNetworkClient( + { + providerType: networkClientType, + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + messenger: rootMessenger, + getRpcServiceOptions: () => ({ + fetch, + btoa, + policyOptions: { + backoff: new ConstantBackoff(backoffDuration), + }, + }), + }, + async ({ makeRpcCall, clock }) => { + rootMessenger.subscribe( + 'NetworkController:rpcEndpointRetried', + () => { + // Ensure that we advance to the next RPC request + // retry, not the next block tracker request. + clock.tick(backoffDuration); + }, + ); + + // When offline, errors are not retried, so the circuit + // won't accumulate failures and onServiceDegraded won't be called + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + await expect(makeRpcCall(request)).rejects.toThrow( + expectedError, + ); + + // Event should be suppressed when offline because retries + // are prevented, so onServiceDegraded is never called + expect( + rpcEndpointDegradedEventHandler, + ).not.toHaveBeenCalled(); + }, + ); + }, + ); + }); + it('publishes the NetworkController:rpcEndpointDegraded event when the time to complete a request to a primary endpoint is continually too long', async () => { const request = { method: 'eth_gasPrice', diff --git a/packages/network-controller/src/create-network-client.ts b/packages/network-controller/src/create-network-client.ts index e06d853b084..6a0a78c9148 100644 --- a/packages/network-controller/src/create-network-client.ts +++ b/packages/network-controller/src/create-network-client.ts @@ -1,3 +1,4 @@ +import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller'; import type { CockatielFailureReason, InfuraNetworkType, @@ -210,10 +211,19 @@ function createRpcServiceChain({ const availableEndpointUrls: [string, ...string[]] = isRpcFailoverEnabled ? [primaryEndpointUrl, ...(configuration.failoverRpcUrls ?? [])] : [primaryEndpointUrl]; + + const isOffline = (): boolean => { + const connectivityState = messenger.call('ConnectivityController:getState'); + return ( + connectivityState.connectivityStatus === CONNECTIVITY_STATUSES.Offline + ); + }; + const rpcServiceConfigurations = availableEndpointUrls.map((endpointUrl) => ({ ...getRpcServiceOptions(endpointUrl), endpointUrl, logger, + isOffline, })); /** @@ -305,6 +315,7 @@ function createRpcServiceChain({ ...rest }) => { const error = getError(rest); + messenger.publish('NetworkController:rpcEndpointDegraded', { chainId: configuration.chainId, networkClientId: id, diff --git a/packages/network-controller/src/rpc-service/rpc-service.test.ts b/packages/network-controller/src/rpc-service/rpc-service.test.ts index 463c80f93e9..dd3aa5235fb 100644 --- a/packages/network-controller/src/rpc-service/rpc-service.test.ts +++ b/packages/network-controller/src/rpc-service/rpc-service.test.ts @@ -1424,6 +1424,107 @@ function testsForRetriableFetchErrors({ expect(onAvailableListener).toHaveBeenCalledTimes(1); }); + it('does not retry when offline, only makes one fetch call', async () => { + const clock = getClock(); + const mockFetch = jest.fn(() => { + throw producedError; + }); + const service = new RpcService({ + fetch: mockFetch, + btoa, + endpointUrl: 'https://rpc.example.chain', + isOffline: (): boolean => true, + }); + service.onRetry(() => { + clock.next(); + }); + + const jsonRpcRequest = { + id: 1, + jsonrpc: '2.0' as const, + method: 'eth_chainId', + params: [], + }; + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + // When offline, no retries should happen, so only 1 fetch call + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it('does not call onDegraded when offline', async () => { + const clock = getClock(); + const mockFetch = jest.fn(() => { + throw producedError; + }); + const endpointUrl = 'https://rpc.example.chain'; + const onDegradedListener = jest.fn(); + const service = new RpcService({ + fetch: mockFetch, + btoa, + endpointUrl, + isOffline: (): boolean => true, + }); + service.onRetry(() => { + clock.next(); + }); + service.onDegraded(onDegradedListener); + + const jsonRpcRequest = { + id: 1, + jsonrpc: '2.0' as const, + method: 'eth_chainId', + params: [], + }; + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + + // When offline, retries don't happen, so onDegraded should not be called + expect(onDegradedListener).not.toHaveBeenCalled(); + }); + + it('does not call onBreak when offline', async () => { + const clock = getClock(); + const mockFetch = jest.fn(() => { + throw producedError; + }); + const endpointUrl = 'https://rpc.example.chain'; + const onBreakListener = jest.fn(); + const service = new RpcService({ + fetch: mockFetch, + btoa, + endpointUrl, + isOffline: (): boolean => true, + }); + service.onRetry(() => { + clock.next(); + }); + service.onBreak(onBreakListener); + + const jsonRpcRequest = { + id: 1, + jsonrpc: '2.0' as const, + method: 'eth_chainId', + params: [], + }; + // Make multiple requests - even though we'd normally break the circuit, + // when offline, no retries happen so circuit won't break + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + + // When offline, retries don't happen, so circuit won't break and onBreak + // should not be called + expect(onBreakListener).not.toHaveBeenCalled(); + }); + /* eslint-enable jest/require-top-level-describe */ } @@ -1654,5 +1755,87 @@ function testsForRetriableResponses({ ); }); + it('does not retry when offline, only makes one request', async () => { + const clock = getClock(); + const scope = nock('https://rpc.example.chain') + .post('/', { + id: 1, + jsonrpc: '2.0', + method: 'eth_chainId', + params: [], + }) + .times(1) + .reply(httpStatus, responseBody); + const service = new RpcService({ + fetch, + btoa, + endpointUrl: 'https://rpc.example.chain', + isOffline: (): boolean => true, + }); + service.onRetry(() => { + clock.next(); + }); + + const jsonRpcRequest = { + id: 1, + jsonrpc: '2.0' as const, + method: 'eth_chainId', + params: [], + }; + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + // When offline, no retries should happen, so only 1 request + expect(scope.isDone()).toBe(true); + }); + + it('does not call onBreak when offline', async () => { + const clock = getClock(); + const scope = nock('https://rpc.example.chain') + .post('/', { + id: 1, + jsonrpc: '2.0', + method: 'eth_chainId', + params: [], + }) + .times(3) + .reply(httpStatus, responseBody); + const endpointUrl = 'https://rpc.example.chain'; + const onBreakListener = jest.fn(); + const service = new RpcService({ + fetch, + btoa, + endpointUrl, + isOffline: (): boolean => true, + }); + service.onRetry(() => { + clock.next(); + }); + service.onBreak(onBreakListener); + + const jsonRpcRequest = { + id: 1, + jsonrpc: '2.0' as const, + method: 'eth_chainId', + params: [], + }; + // Make multiple requests - even though we'd normally break the circuit, + // when offline, no retries happen so circuit won't break + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + await expect(service.request(jsonRpcRequest)).rejects.toThrow( + expectedError, + ); + + // When offline, retries don't happen, so circuit won't break and onBreak + // should not be called + expect(onBreakListener).not.toHaveBeenCalled(); + expect(scope.isDone()).toBe(true); + }); + /* eslint-enable jest/require-top-level-describe,jest/no-identical-title */ } diff --git a/packages/network-controller/src/rpc-service/rpc-service.ts b/packages/network-controller/src/rpc-service/rpc-service.ts index df09eda0e0a..9b3deae8eba 100644 --- a/packages/network-controller/src/rpc-service/rpc-service.ts +++ b/packages/network-controller/src/rpc-service/rpc-service.ts @@ -57,6 +57,12 @@ export type RpcServiceOptions = { * not accepted, as it is overwritten. See {@link createServicePolicy}. */ policyOptions?: Omit; + /** + * A function that checks if the user is currently offline. If provided and + * returns true, connection errors will not be retried, preventing degraded + * and break callbacks from being triggered. + */ + isOffline?: () => boolean; }; const log = createModuleLogger(projectLogger, 'RpcService'); @@ -277,6 +283,7 @@ export class RpcService implements AbstractRpcService { logger, fetchOptions = {}, policyOptions = {}, + isOffline, } = options; this.#fetch = givenFetch; @@ -294,6 +301,12 @@ export class RpcService implements AbstractRpcService { maxConsecutiveFailures: DEFAULT_MAX_CONSECUTIVE_FAILURES, ...policyOptions, retryFilterPolicy: handleWhen((error) => { + // If user is offline, don't retry any errors + // This prevents degraded/break callbacks from being triggered + if (isOffline?.()) { + return false; + } + return ( // Ignore errors where the request failed to establish isConnectionError(error) || diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index 039bc41c759..c525540fb24 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -1,3 +1,5 @@ +import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller'; +import type { ConnectivityStatus } from '@metamask/connectivity-controller'; import { ChainId, InfuraNetworkType, @@ -84,15 +86,30 @@ export const TESTNET = { * Build a root messenger that includes all events used by the network * controller. * + * @param options - Optional configuration. + * @param options.connectivityStatus - The connectivity status to return by default. + * If not provided, defaults to Online. * @returns The messenger. */ -export function buildRootMessenger(): RootMessenger { +export function buildRootMessenger(options?: { + connectivityStatus?: ConnectivityStatus; +}): RootMessenger { const rootMessenger = new Messenger< MockAnyNamespace, MessengerActions, MessengerEvents >({ namespace: MOCK_ANY_NAMESPACE, captureException: jest.fn() }); + const connectivityStatus = + options?.connectivityStatus ?? CONNECTIVITY_STATUSES.Online; + + rootMessenger.registerActionHandler( + 'ConnectivityController:getState', + () => ({ + connectivityStatus, + }), + ); + return rootMessenger; } @@ -115,6 +132,11 @@ export function buildNetworkControllerMessenger( parent: rootMessenger, }); + rootMessenger.delegate({ + messenger: networkControllerMessenger, + actions: ['ConnectivityController:getState'], + }); + return networkControllerMessenger; } diff --git a/packages/network-controller/tsconfig.build.json b/packages/network-controller/tsconfig.build.json index 3aa1aa62e0f..a81948ea66a 100644 --- a/packages/network-controller/tsconfig.build.json +++ b/packages/network-controller/tsconfig.build.json @@ -8,6 +8,7 @@ "references": [ { "path": "../base-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, + { "path": "../connectivity-controller/tsconfig.build.json" }, { "path": "../eth-block-tracker/tsconfig.build.json" }, { "path": "../eth-json-rpc-middleware/tsconfig.build.json" }, { "path": "../eth-json-rpc-provider/tsconfig.build.json" }, diff --git a/packages/network-controller/tsconfig.json b/packages/network-controller/tsconfig.json index 9f1911d6466..e5a54a5785b 100644 --- a/packages/network-controller/tsconfig.json +++ b/packages/network-controller/tsconfig.json @@ -7,6 +7,7 @@ "references": [ { "path": "../base-controller" }, { "path": "../controller-utils" }, + { "path": "../connectivity-controller" }, { "path": "../eth-block-tracker" }, { "path": "../eth-json-rpc-middleware" }, { "path": "../eth-json-rpc-provider" }, diff --git a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts index 9cb52e729d1..f8f0a9eaeaf 100644 --- a/packages/transaction-controller/src/TransactionControllerIntegration.test.ts +++ b/packages/transaction-controller/src/TransactionControllerIntegration.test.ts @@ -5,6 +5,8 @@ import type { ApprovalControllerEvents, } from '@metamask/approval-controller'; import { ApprovalController } from '@metamask/approval-controller'; +import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller'; +import type { ConnectivityControllerGetStateAction } from '@metamask/connectivity-controller'; import { ApprovalType, BUILT_IN_NETWORKS, @@ -80,6 +82,7 @@ type AllTransactionControllerEvents = type AllActions = | AllTransactionControllerActions | NetworkControllerActions + | ConnectivityControllerGetStateAction | ApprovalControllerActions | AccountsControllerActions | RemoteFeatureFlagControllerGetStateAction; @@ -183,15 +186,26 @@ const setupController = async ( namespace: MOCK_ANY_NAMESPACE, }); + rootMessenger.registerActionHandler( + 'ConnectivityController:getState', + () => ({ + connectivityStatus: CONNECTIVITY_STATUSES.Online, + }), + ); + const networkControllerMessenger = new Messenger< 'NetworkController', - NetworkControllerActions, + NetworkControllerActions | ConnectivityControllerGetStateAction, NetworkControllerEvents, typeof rootMessenger >({ namespace: 'NetworkController', parent: rootMessenger, }); + rootMessenger.delegate({ + messenger: networkControllerMessenger, + actions: ['ConnectivityController:getState'], + }); const networkController = new NetworkController({ messenger: networkControllerMessenger, infuraProjectId, diff --git a/yarn.lock b/yarn.lock index 88b4ce0614c..a23971ddd31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2964,7 +2964,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/connectivity-controller@workspace:packages/connectivity-controller": +"@metamask/connectivity-controller@npm:^0.0.0, @metamask/connectivity-controller@workspace:packages/connectivity-controller": version: 0.0.0-use.local resolution: "@metamask/connectivity-controller@workspace:packages/connectivity-controller" dependencies: @@ -4207,6 +4207,7 @@ __metadata: "@json-rpc-specification/meta-schema": "npm:^1.0.6" "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^9.0.0" + "@metamask/connectivity-controller": "npm:^0.0.0" "@metamask/controller-utils": "npm:^11.18.0" "@metamask/eth-block-tracker": "npm:^15.0.0" "@metamask/eth-json-rpc-infura": "npm:^10.3.0"