Skip to content

Commit 9fb9729

Browse files
chore: Notification controller eslint cleanup (#7483)
## Explanation This PR addresses and removes ESLint suppressions from three files within `packages/notification-services-controller` as part of a larger cleanup effort (ASSETS-2100). * **Current state and why it needs to change:** The identified files contained suppressions for `@typescript-eslint/explicit-function-return-type`, `no-restricted-globals`, `require-atomic-updates`, and `id-length`. These suppressions masked potential code quality issues and reduced code clarity. * **Solution:** * **`push-utils.test.ts`**: Explicit return types were added to all functions to satisfy `@typescript-eslint/explicit-function-return-type`. For `no-restricted-globals` related to `self`, targeted `eslint-disable-next-line` comments with explanations were added, as `self` is required for testing service worker functionality. * **`push-utils.ts`**: Explicit return types were added to functions to satisfy `@typescript-eslint/explicit-function-return-type`. The `require-atomic-updates` issue was addressed with a targeted `eslint-disable-next-line` comment and explanation, as the race condition for caching a boolean is acceptable. * **`is-onchain-notification.ts`**: The `id-length` violation was fixed by renaming the parameter `n` to `notification` for improved readability. * All corresponding entries for these files have been removed from `eslint-suppressions.json`. * **Changes whose purpose might not be obvious:** The `eslint-disable-next-line` comments for `no-restricted-globals` and `require-atomic-updates` are intentional and include inline explanations in the code, detailing why these specific rules are being ignored at those points. ## References * Fixes ASSETS-2100 ## 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/contributing.md#updating-changelogs) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them --- <a href="https://cursor.com/background-agent?bcId=bc-6ab48784-58dc-44dd-b632-a564eb71d229"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-6ab48784-58dc-44dd-b632-a564eb71d229"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds explicit return types and stricter typings to web push utils and tests, renames a short param in on-chain notification check, and removes related entries from eslint-suppressions.json. > > - **Notification Services Push (web)**: > - **Typing/ESLint**: Add explicit return types to `getPushAvailability`, `listenToPushNotificationsReceived`, `listenToPushNotificationsClicked`, and `createSubscribeToPushNotifications`; ensure handlers return `Promise<void>`/`void`; type `unsubscribe` functions. > - **Support caching**: Document acceptable race; add targeted `eslint-disable-next-line require-atomic-updates`. > - **Tests (`web/push-utils.test.ts`)**: > - Refactor helper utilities into typed `function`s; add explicit return types; type Jest spies; add targeted `eslint-disable-next-line no-restricted-globals` for `self` usage; simulate `notificationclick` via `self.dispatchEvent`. > - **Shared**: > - Rename `isOnChainRawNotification` param from `n` to `notification` for clarity. > - **Tooling**: > - Remove corresponding entries for these files from `eslint-suppressions.json`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ba5e91a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 2a3a2eb commit 9fb9729

File tree

4 files changed

+80
-60
lines changed

4 files changed

+80
-60
lines changed

eslint-suppressions.json

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,27 +1638,6 @@
16381638
"count": 9
16391639
}
16401640
},
1641-
"packages/notification-services-controller/src/NotificationServicesPushController/web/push-utils.test.ts": {
1642-
"@typescript-eslint/explicit-function-return-type": {
1643-
"count": 10
1644-
},
1645-
"no-restricted-globals": {
1646-
"count": 3
1647-
}
1648-
},
1649-
"packages/notification-services-controller/src/NotificationServicesPushController/web/push-utils.ts": {
1650-
"@typescript-eslint/explicit-function-return-type": {
1651-
"count": 7
1652-
},
1653-
"require-atomic-updates": {
1654-
"count": 1
1655-
}
1656-
},
1657-
"packages/notification-services-controller/src/shared/is-onchain-notification.ts": {
1658-
"id-length": {
1659-
"count": 1
1660-
}
1661-
},
16621641
"packages/phishing-controller/src/BulkTokenScan.test.ts": {
16631642
"@typescript-eslint/explicit-function-return-type": {
16641643
"count": 2

packages/notification-services-controller/src/NotificationServicesPushController/web/push-utils.test.ts

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const firebaseApp: FirebaseAppModule.FirebaseApp = {
3333
options: mockEnv,
3434
};
3535

36-
const arrangeFirebaseAppMocks = () => {
36+
function arrangeFirebaseAppMocks(): {
37+
mockGetApp: jest.SpyInstance;
38+
mockInitializeApp: jest.SpyInstance;
39+
} {
3740
const mockGetApp = jest
3841
.spyOn(FirebaseAppModule, 'getApp')
3942
.mockReturnValue(firebaseApp);
@@ -43,9 +46,14 @@ const arrangeFirebaseAppMocks = () => {
4346
.mockReturnValue(firebaseApp);
4447

4548
return { mockGetApp, mockInitializeApp };
46-
};
47-
48-
const arrangeFirebaseMessagingSWMocks = () => {
49+
}
50+
51+
function arrangeFirebaseMessagingSWMocks(): {
52+
mockIsSupported: jest.SpyInstance;
53+
mockGetMessaging: jest.SpyInstance;
54+
mockOnBackgroundMessage: jest.SpyInstance;
55+
mockOnBackgroundMessageUnsub: jest.Mock;
56+
} {
4957
const mockIsSupported = jest
5058
.spyOn(FirebaseMessagingSWModule, 'isSupported')
5159
.mockResolvedValue(true);
@@ -65,9 +73,12 @@ const arrangeFirebaseMessagingSWMocks = () => {
6573
mockOnBackgroundMessage,
6674
mockOnBackgroundMessageUnsub,
6775
};
68-
};
76+
}
6977

70-
const arrangeFirebaseMessagingMocks = () => {
78+
function arrangeFirebaseMessagingMocks(): {
79+
mockGetToken: jest.SpyInstance;
80+
mockDeleteToken: jest.SpyInstance;
81+
} {
7182
const mockGetToken = jest
7283
.spyOn(FirebaseMessagingModule, 'getToken')
7384
.mockResolvedValue('test-token');
@@ -77,12 +88,14 @@ const arrangeFirebaseMessagingMocks = () => {
7788
.mockResolvedValue(true);
7889

7990
return { mockGetToken, mockDeleteToken };
80-
};
91+
}
8192

8293
describe('createRegToken() tests', () => {
8394
const TEST_TOKEN = 'test-token';
8495

85-
const arrange = () => {
96+
function arrange(): ReturnType<typeof arrangeFirebaseAppMocks> &
97+
ReturnType<typeof arrangeFirebaseMessagingSWMocks> &
98+
ReturnType<typeof arrangeFirebaseMessagingMocks> {
8699
const firebaseMocks = {
87100
...arrangeFirebaseAppMocks(),
88101
...arrangeFirebaseMessagingSWMocks(),
@@ -94,7 +107,7 @@ describe('createRegToken() tests', () => {
94107
return {
95108
...firebaseMocks,
96109
};
97-
};
110+
}
98111

99112
afterEach(() => {
100113
jest.clearAllMocks();
@@ -147,13 +160,15 @@ describe('createRegToken() tests', () => {
147160
});
148161

149162
describe('deleteRegToken() tests', () => {
150-
const arrange = () => {
163+
function arrange(): ReturnType<typeof arrangeFirebaseAppMocks> &
164+
ReturnType<typeof arrangeFirebaseMessagingSWMocks> &
165+
ReturnType<typeof arrangeFirebaseMessagingMocks> {
151166
return {
152167
...arrangeFirebaseAppMocks(),
153168
...arrangeFirebaseMessagingSWMocks(),
154169
...arrangeFirebaseMessagingMocks(),
155170
};
156-
};
171+
}
157172

158173
afterEach(() => {
159174
jest.clearAllMocks();
@@ -193,7 +208,13 @@ describe('deleteRegToken() tests', () => {
193208
});
194209

195210
describe('createSubscribeToPushNotifications() tests', () => {
196-
const arrangeMessengerMocks = () => {
211+
function arrangeMessengerMocks(): {
212+
messenger: ReturnType<
213+
typeof buildPushPlatformNotificationsControllerMessenger
214+
>;
215+
onNewNotificationsListener: jest.Mock;
216+
pushNotificationClickedListener: jest.Mock;
217+
} {
197218
const messenger = buildPushPlatformNotificationsControllerMessenger();
198219

199220
const onNewNotificationsListener = jest.fn();
@@ -213,19 +234,31 @@ describe('createSubscribeToPushNotifications() tests', () => {
213234
onNewNotificationsListener,
214235
pushNotificationClickedListener,
215236
};
216-
};
217-
218-
const arrangeClickListenerMocks = () => {
237+
}
238+
239+
function arrangeClickListenerMocks(): {
240+
mockAddEventListener: jest.SpyInstance;
241+
mockRemoveEventListener: jest.SpyInstance;
242+
} {
243+
// Testing service worker functionality requires using the 'self' global
244+
// eslint-disable-next-line no-restricted-globals
219245
const mockAddEventListener = jest.spyOn(self, 'addEventListener');
246+
// eslint-disable-next-line no-restricted-globals
220247
const mockRemoveEventListener = jest.spyOn(self, 'removeEventListener');
221248

222249
return {
223250
mockAddEventListener,
224251
mockRemoveEventListener,
225252
};
226-
};
227-
228-
const arrange = () => {
253+
}
254+
255+
function arrange(): ReturnType<typeof arrangeFirebaseAppMocks> &
256+
ReturnType<typeof arrangeFirebaseMessagingSWMocks> &
257+
ReturnType<typeof arrangeMessengerMocks> &
258+
ReturnType<typeof arrangeClickListenerMocks> & {
259+
mockOnReceivedHandler: jest.Mock;
260+
mockOnClickHandler: jest.Mock;
261+
} {
229262
const firebaseMocks = {
230263
...arrangeFirebaseAppMocks(),
231264
...arrangeFirebaseMessagingSWMocks(),
@@ -238,17 +271,19 @@ describe('createSubscribeToPushNotifications() tests', () => {
238271
mockOnReceivedHandler: jest.fn(),
239272
mockOnClickHandler: jest.fn(),
240273
};
241-
};
274+
}
242275

243-
const actCreateSubscription = async (mocks: ReturnType<typeof arrange>) => {
276+
async function actCreateSubscription(
277+
mocks: ReturnType<typeof arrange>,
278+
): Promise<() => void> {
244279
const unsubscribe = await createSubscribeToPushNotifications({
245280
messenger: mocks.messenger,
246281
onReceivedHandler: mocks.mockOnReceivedHandler,
247282
onClickHandler: mocks.mockOnClickHandler,
248283
})(mockEnv);
249284

250285
return unsubscribe;
251-
};
286+
}
252287

253288
afterEach(() => {
254289
jest.clearAllMocks();
@@ -288,7 +323,9 @@ describe('createSubscribeToPushNotifications() tests', () => {
288323
expect(mocks.mockRemoveEventListener).toHaveBeenCalled();
289324
});
290325

291-
const arrangeActNotificationReceived = async (testData: unknown) => {
326+
async function arrangeActNotificationReceived(
327+
testData: unknown,
328+
): Promise<ReturnType<typeof arrange>> {
292329
const mocks = arrange();
293330
await actCreateSubscription(mocks);
294331

@@ -303,7 +340,7 @@ describe('createSubscribeToPushNotifications() tests', () => {
303340
firebaseCallback(payload);
304341

305342
return mocks;
306-
};
343+
}
307344

308345
it('should invoke handler when notifications are received', async () => {
309346
const mocks = await arrangeActNotificationReceived(
@@ -351,7 +388,8 @@ describe('createSubscribeToPushNotifications() tests', () => {
351388
notification: { data: notificationData },
352389
});
353390

354-
// Act
391+
// Act - Testing service worker notification click event
392+
// eslint-disable-next-line no-restricted-globals
355393
self.dispatchEvent(mockNotificationEvent);
356394

357395
// Assert Click Notification Event & Handler Calls

packages/notification-services-controller/src/NotificationServicesPushController/web/push-utils.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ declare const self: ServiceWorkerGlobalScope;
2727
// eslint-disable-next-line import-x/no-mutable-exports
2828
export let supportedCache: boolean | null = null;
2929

30-
const getPushAvailability = async () => {
30+
const getPushAvailability = async (): Promise<boolean> => {
31+
// Race condition is acceptable here - worst case is isSupported() is called
32+
// multiple times during initialization, which is harmless for caching a boolean
33+
// eslint-disable-next-line require-atomic-updates
3134
supportedCache ??= await isSupported();
3235
return supportedCache;
3336
};
@@ -129,7 +132,7 @@ async function listenToPushNotificationsReceived(
129132
const unsubscribePushNotifications = onBackgroundMessage(
130133
messaging,
131134
// eslint-disable-next-line @typescript-eslint/no-misused-promises
132-
async (payload: MessagePayload) => {
135+
async (payload: MessagePayload): Promise<void> => {
133136
try {
134137
// MessagePayload shapes are not known
135138
// TODO - provide open-api unfied backend/frontend types
@@ -162,7 +165,7 @@ async function listenToPushNotificationsReceived(
162165
},
163166
);
164167

165-
const unsubscribe = () => unsubscribePushNotifications();
168+
const unsubscribe = (): void => unsubscribePushNotifications();
166169
return unsubscribe;
167170
}
168171

@@ -174,15 +177,15 @@ async function listenToPushNotificationsReceived(
174177
*/
175178
function listenToPushNotificationsClicked(
176179
handler: (e: NotificationEvent, notification: Types.INotification) => void,
177-
) {
178-
const clickHandler = (event: NotificationEvent) => {
180+
): () => void {
181+
const clickHandler = (event: NotificationEvent): void => {
179182
// Get Data
180183
const data: Types.INotification = event?.notification?.data;
181184
handler(event, data);
182185
};
183186

184187
self.addEventListener('notificationclick', clickHandler);
185-
const unsubscribe = () =>
188+
const unsubscribe = (): void =>
186189
self.removeEventListener('notificationclick', clickHandler);
187190
return unsubscribe;
188191
}
@@ -208,11 +211,11 @@ export function createSubscribeToPushNotifications(props: {
208211
notification: Types.INotification,
209212
) => void;
210213
messenger: NotificationServicesPushControllerMessenger;
211-
}) {
212-
return async function (env: PushNotificationEnv) {
214+
}): (env: PushNotificationEnv) => Promise<() => void> {
215+
return async function (env: PushNotificationEnv): Promise<() => void> {
213216
const onBackgroundMessageSub = await listenToPushNotificationsReceived(
214217
env,
215-
async (notification) => {
218+
async (notification): Promise<void> => {
216219
props.messenger.publish(
217220
'NotificationServicesPushController:onNewNotifications',
218221
notification,
@@ -221,7 +224,7 @@ export function createSubscribeToPushNotifications(props: {
221224
},
222225
);
223226
const onClickSub = listenToPushNotificationsClicked(
224-
(event, notification) => {
227+
(event, notification): void => {
225228
props.messenger.publish(
226229
'NotificationServicesPushController:pushNotificationClicked',
227230
notification,
@@ -230,7 +233,7 @@ export function createSubscribeToPushNotifications(props: {
230233
},
231234
);
232235

233-
const unsubscribe = () => {
236+
const unsubscribe = (): void => {
234237
onBackgroundMessageSub?.();
235238
onClickSub();
236239
};

packages/notification-services-controller/src/shared/is-onchain-notification.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import type { OnChainRawNotification } from '../NotificationServicesController';
33
/**
44
* Checks if the given value is an OnChainRawNotification object.
55
*
6-
* @param n - The value to check.
6+
* @param notification - The value to check.
77
* @returns True if the value is an OnChainRawNotification object, false otherwise.
88
*/
99
export function isOnChainRawNotification(
10-
n: unknown,
11-
): n is OnChainRawNotification {
12-
const assumed = n as OnChainRawNotification;
10+
notification: unknown,
11+
): notification is OnChainRawNotification {
12+
const assumed = notification as OnChainRawNotification;
1313

1414
// We don't have a validation/parsing library to check all possible types of an on chain notification
1515
// It is safe enough just to check "some" fields, and catch any errors down the line if the shape is bad.

0 commit comments

Comments
 (0)