Skip to content

Commit 7251544

Browse files
authored
Add init method to ConnectivityController and make getStatus async (#7679)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR introduces an `init()` method to the `ConnectivityController` that asynchronously fetches the initial connectivity status from the adapter. The constructor now initializes with a default state (online) instead of synchronously calling `getStatus()`, enabling better async initialization patterns. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces asynchronous initialization and updates adapter contract. > > - **New:** `init()` fetches initial status via adapter and updates state; controller now starts with default `online` state > - **Changed (BREAKING):** `ConnectivityAdapter.getStatus()` now returns `Promise<ConnectivityStatus>` > - Subscribes to adapter changes using `onConnectivityChange` → `setConnectivityStatus` > - Tests updated to reflect async `getStatus` and new initialization flow; changelog updated > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 742e048. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent df4dc7f commit 7251544

File tree

4 files changed

+132
-65
lines changed

4 files changed

+132
-65
lines changed

packages/connectivity-controller/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Add `init` method to asynchronously fetch and set the initial connectivity status from the adapter ([#7679](https://github.com/MetaMask/core/pull/7679))
13+
- The controller now initializes with a default state (online) and requires calling `init()` to fetch the actual status
1214
- Add `setConnectivityStatus` method to manually set connectivity status ([#7676](https://github.com/MetaMask/core/pull/7676))
1315
- The method is exposed as a messenger action `ConnectivityController:setConnectivityStatus`
1416

17+
### Changed
18+
19+
- **BREAKING:** `ConnectivityAdapter.getStatus()` must now return a `Promise<ConnectivityStatus>` (async) ([#7679](https://github.com/MetaMask/core/pull/7679))
20+
- Adapter implementations must update their `getStatus()` method to return a Promise
21+
- This change enables asynchronous initialization of the controller via the `init()` method
22+
1523
## [0.1.0]
1624

1725
### Added

packages/connectivity-controller/src/ConnectivityController.test.ts

Lines changed: 105 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import type { ConnectivityAdapter, ConnectivityStatus } from './types';
1313

1414
describe('ConnectivityController', () => {
1515
describe('constructor', () => {
16-
it('uses service initial state when online', async () => {
16+
it('initializes with default state (online)', async () => {
1717
const mockAdapter: ConnectivityAdapter = {
18-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Online),
18+
getStatus: jest.fn().mockResolvedValue(CONNECTIVITY_STATUSES.Online),
1919
onConnectivityChange: jest.fn(),
2020
destroy: jest.fn(),
2121
};
@@ -26,23 +26,24 @@ describe('ConnectivityController', () => {
2626
expect(controller.state.connectivityStatus).toBe(
2727
CONNECTIVITY_STATUSES.Online,
2828
);
29-
expect(mockAdapter.getStatus).toHaveBeenCalled();
29+
expect(mockAdapter.getStatus).not.toHaveBeenCalled();
3030
},
3131
);
3232
});
3333

34-
it('uses service initial state when offline', async () => {
34+
it('subscribes to connectivity changes from adapter', async () => {
3535
const mockAdapter: ConnectivityAdapter = {
36-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Offline),
36+
getStatus: jest.fn().mockResolvedValue(CONNECTIVITY_STATUSES.Online),
3737
onConnectivityChange: jest.fn(),
3838
destroy: jest.fn(),
3939
};
4040

4141
await withController(
4242
{ options: { connectivityAdapter: mockAdapter } },
43-
({ controller }) => {
44-
expect(controller.state.connectivityStatus).toBe(
45-
CONNECTIVITY_STATUSES.Offline,
43+
() => {
44+
expect(mockAdapter.onConnectivityChange).toHaveBeenCalledTimes(1);
45+
expect(mockAdapter.onConnectivityChange).toHaveBeenCalledWith(
46+
expect.any(Function),
4647
);
4748
},
4849
);
@@ -111,13 +112,67 @@ describe('ConnectivityController', () => {
111112
});
112113
});
113114

115+
describe('init', () => {
116+
it('fetches initial status from adapter and updates state', async () => {
117+
const mockAdapter: ConnectivityAdapter = {
118+
getStatus: jest.fn().mockResolvedValue(CONNECTIVITY_STATUSES.Offline),
119+
onConnectivityChange: jest.fn(),
120+
destroy: jest.fn(),
121+
};
122+
123+
await withController(
124+
{ options: { connectivityAdapter: mockAdapter } },
125+
async ({ controller }) => {
126+
expect(controller.state.connectivityStatus).toBe(
127+
CONNECTIVITY_STATUSES.Online,
128+
);
129+
130+
await controller.init();
131+
132+
expect(mockAdapter.getStatus).toHaveBeenCalledTimes(1);
133+
expect(controller.state.connectivityStatus).toBe(
134+
CONNECTIVITY_STATUSES.Offline,
135+
);
136+
},
137+
);
138+
});
139+
140+
it('can be called multiple times to refresh status', async () => {
141+
const mockAdapter: ConnectivityAdapter = {
142+
getStatus: jest
143+
.fn()
144+
.mockResolvedValueOnce(CONNECTIVITY_STATUSES.Online)
145+
.mockResolvedValueOnce(CONNECTIVITY_STATUSES.Offline),
146+
onConnectivityChange: jest.fn(),
147+
destroy: jest.fn(),
148+
};
149+
150+
await withController(
151+
{ options: { connectivityAdapter: mockAdapter } },
152+
async ({ controller }) => {
153+
await controller.init();
154+
expect(controller.state.connectivityStatus).toBe(
155+
CONNECTIVITY_STATUSES.Online,
156+
);
157+
158+
await controller.init();
159+
expect(controller.state.connectivityStatus).toBe(
160+
CONNECTIVITY_STATUSES.Offline,
161+
);
162+
163+
expect(mockAdapter.getStatus).toHaveBeenCalledTimes(2);
164+
},
165+
);
166+
});
167+
});
168+
114169
describe('when connectivity changes via the adapter', () => {
115170
it('updates state when service reports offline', async () => {
116171
let onConnectivityChangeCallback: (
117172
connectivityStatus: ConnectivityStatus,
118173
) => void;
119174
const mockAdapter: ConnectivityAdapter = {
120-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Online),
175+
getStatus: jest.fn().mockResolvedValue(CONNECTIVITY_STATUSES.Online),
121176
onConnectivityChange(
122177
callback: (connectivityStatus: ConnectivityStatus) => void,
123178
) {
@@ -145,7 +200,7 @@ describe('ConnectivityController', () => {
145200
connectivityStatus: ConnectivityStatus,
146201
) => void;
147202
const mockAdapter: ConnectivityAdapter = {
148-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Offline),
203+
getStatus: jest.fn().mockResolvedValue(CONNECTIVITY_STATUSES.Offline),
149204
onConnectivityChange(
150205
callback: (connectivityStatus: ConnectivityStatus) => void,
151206
) {
@@ -157,7 +212,7 @@ describe('ConnectivityController', () => {
157212
{ options: { connectivityAdapter: mockAdapter } },
158213
({ controller }) => {
159214
expect(controller.state.connectivityStatus).toBe(
160-
CONNECTIVITY_STATUSES.Offline,
215+
CONNECTIVITY_STATUSES.Online,
161216
);
162217
// Simulate service reporting online
163218
onConnectivityChangeCallback(CONNECTIVITY_STATUSES.Online);
@@ -202,52 +257,51 @@ describe('ConnectivityController', () => {
202257
});
203258

204259
it('can change status from offline to online via direct call', async () => {
205-
const mockAdapter: ConnectivityAdapter = {
206-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Offline),
207-
onConnectivityChange: jest.fn(),
208-
destroy: jest.fn(),
209-
};
210-
211-
await withController(
212-
{ options: { connectivityAdapter: mockAdapter } },
213-
({ controller }) => {
214-
expect(controller.state.connectivityStatus).toBe(
215-
CONNECTIVITY_STATUSES.Offline,
216-
);
260+
await withController(({ controller }) => {
261+
// Start with default state (online)
262+
expect(controller.state.connectivityStatus).toBe(
263+
CONNECTIVITY_STATUSES.Online,
264+
);
217265

218-
controller.setConnectivityStatus(CONNECTIVITY_STATUSES.Online);
266+
// Change to offline
267+
controller.setConnectivityStatus(CONNECTIVITY_STATUSES.Offline);
268+
expect(controller.state.connectivityStatus).toBe(
269+
CONNECTIVITY_STATUSES.Offline,
270+
);
219271

220-
expect(controller.state.connectivityStatus).toBe(
221-
CONNECTIVITY_STATUSES.Online,
222-
);
223-
},
224-
);
272+
// Change back to online
273+
controller.setConnectivityStatus(CONNECTIVITY_STATUSES.Online);
274+
expect(controller.state.connectivityStatus).toBe(
275+
CONNECTIVITY_STATUSES.Online,
276+
);
277+
});
225278
});
226279

227280
it('can change status from offline to online via messenger action', async () => {
228-
const mockAdapter: ConnectivityAdapter = {
229-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Offline),
230-
onConnectivityChange: jest.fn(),
231-
destroy: jest.fn(),
232-
};
233-
234-
await withController(
235-
{ options: { connectivityAdapter: mockAdapter } },
236-
({ rootMessenger, controller }) => {
237-
expect(controller.state.connectivityStatus).toBe(
238-
CONNECTIVITY_STATUSES.Offline,
239-
);
281+
await withController(({ rootMessenger, controller }) => {
282+
// Start with default state (online)
283+
expect(controller.state.connectivityStatus).toBe(
284+
CONNECTIVITY_STATUSES.Online,
285+
);
240286

241-
rootMessenger.call(
242-
'ConnectivityController:setConnectivityStatus',
243-
CONNECTIVITY_STATUSES.Online,
244-
);
287+
// Change to offline
288+
rootMessenger.call(
289+
'ConnectivityController:setConnectivityStatus',
290+
CONNECTIVITY_STATUSES.Offline,
291+
);
292+
expect(controller.state.connectivityStatus).toBe(
293+
CONNECTIVITY_STATUSES.Offline,
294+
);
245295

246-
expect(controller.state.connectivityStatus).toBe(
247-
CONNECTIVITY_STATUSES.Online,
248-
);
249-
},
250-
);
296+
// Change back to online
297+
rootMessenger.call(
298+
'ConnectivityController:setConnectivityStatus',
299+
CONNECTIVITY_STATUSES.Online,
300+
);
301+
expect(controller.state.connectivityStatus).toBe(
302+
CONNECTIVITY_STATUSES.Online,
303+
);
304+
});
251305
});
252306
});
253307
});
@@ -325,7 +379,7 @@ async function withController<ReturnValue>(
325379
const rootMessenger = getRootMessenger();
326380
const controllerMessenger = getMessenger(rootMessenger);
327381
const defaultAdapter: ConnectivityAdapter = {
328-
getStatus: jest.fn().mockReturnValue(CONNECTIVITY_STATUSES.Online),
382+
getStatus: jest.fn().mockResolvedValue(CONNECTIVITY_STATUSES.Online),
329383
onConnectivityChange: jest.fn(),
330384
destroy: jest.fn(),
331385
};

packages/connectivity-controller/src/ConnectivityController.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ export class ConnectivityController extends BaseController<
140140
ConnectivityControllerState,
141141
ConnectivityControllerMessenger
142142
> {
143+
readonly #connectivityAdapter: ConnectivityAdapter;
144+
143145
/**
144146
* Constructs a new {@link ConnectivityController}.
145147
*
@@ -151,30 +153,33 @@ export class ConnectivityController extends BaseController<
151153
messenger,
152154
connectivityAdapter,
153155
}: ConnectivityControllerOptions) {
154-
const initialStatus = connectivityAdapter.getStatus();
155-
156156
super({
157157
messenger,
158158
metadata: connectivityControllerMetadata,
159159
name: controllerName,
160-
state: {
161-
...getDefaultConnectivityControllerState(),
162-
connectivityStatus: initialStatus,
163-
},
160+
state: getDefaultConnectivityControllerState(),
164161
});
165162

166-
connectivityAdapter.onConnectivityChange((status) => {
167-
this.update((draftState) => {
168-
draftState.connectivityStatus = status;
169-
});
170-
});
163+
this.#connectivityAdapter = connectivityAdapter;
164+
165+
this.#connectivityAdapter.onConnectivityChange(
166+
this.setConnectivityStatus.bind(this),
167+
);
171168

172169
this.messenger.registerMethodActionHandlers(
173170
this,
174171
MESSENGER_EXPOSED_METHODS,
175172
);
176173
}
177174

175+
/**
176+
* Initializes the controller by fetching the initial connectivity status.
177+
*/
178+
async init(): Promise<void> {
179+
const initialStatus = await this.#connectivityAdapter.getStatus();
180+
this.setConnectivityStatus(initialStatus);
181+
}
182+
178183
/**
179184
* Sets the connectivity status.
180185
*

packages/connectivity-controller/src/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ export type ConnectivityStatus =
1717
*/
1818
export type ConnectivityAdapter = {
1919
/**
20-
* Returns the current connectivity status.
20+
* Returns a promise that resolves to the current connectivity status.
2121
*
22-
* @returns 'online' if the device is online, 'offline' otherwise.
22+
* @returns A promise that resolves to 'online' if the device is online, 'offline' otherwise.
2323
*/
24-
getStatus(): ConnectivityStatus;
24+
getStatus(): Promise<ConnectivityStatus>;
2525

2626
/**
2727
* Registers a callback to be called when connectivity status changes.

0 commit comments

Comments
 (0)