Skip to content

Commit c273024

Browse files
committed
Merge branch 'main' into 231208-typescript-module-resolution
2 parents 59fe394 + ed7a9db commit c273024

File tree

12 files changed

+175
-152
lines changed

12 files changed

+175
-152
lines changed

packages/notification-services-controller/src/NotificationServicesController/NotificationServicesController.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -284,28 +284,40 @@ export default class NotificationServicesController extends BaseController<
284284
if (!this.#isPushIntegrated) {
285285
return;
286286
}
287-
await this.messagingSystem.call(
288-
'NotificationServicesPushController:enablePushNotifications',
289-
UUIDs,
290-
);
287+
try {
288+
await this.messagingSystem.call(
289+
'NotificationServicesPushController:enablePushNotifications',
290+
UUIDs,
291+
);
292+
} catch (e) {
293+
log.error('Silently failed to enable push notifications', e);
294+
}
291295
},
292296
disablePushNotifications: async (UUIDs: string[]) => {
293297
if (!this.#isPushIntegrated) {
294298
return;
295299
}
296-
await this.messagingSystem.call(
297-
'NotificationServicesPushController:disablePushNotifications',
298-
UUIDs,
299-
);
300+
try {
301+
await this.messagingSystem.call(
302+
'NotificationServicesPushController:disablePushNotifications',
303+
UUIDs,
304+
);
305+
} catch (e) {
306+
log.error('Silently failed to disable push notifications', e);
307+
}
300308
},
301309
updatePushNotifications: async (UUIDs: string[]) => {
302310
if (!this.#isPushIntegrated) {
303311
return;
304312
}
305-
await this.messagingSystem.call(
306-
'NotificationServicesPushController:updateTriggerPushNotifications',
307-
UUIDs,
308-
);
313+
try {
314+
await this.messagingSystem.call(
315+
'NotificationServicesPushController:updateTriggerPushNotifications',
316+
UUIDs,
317+
);
318+
} catch (e) {
319+
log.error('Silently failed to update push notifications', e);
320+
}
309321
},
310322
subscribe: () => {
311323
if (!this.#isPushIntegrated) {

packages/notification-services-controller/src/NotificationServicesController/__fixtures__/mock-raw-notifications.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export function createMockNotificationEthSent(): OnChainRawNotification {
1414
chain_id: 1,
1515
block_number: 17485840,
1616
block_timestamp: '2022-03-01T00:00:00Z',
17-
tx_hash: '0x881D40237659C251811CEC9c364ef91dC08D300C',
17+
tx_hash:
18+
'0xb2256b183f2fb3872f99294ab55fb03e6a479b0d4aca556a3b27568b712505a6',
1819
unread: true,
1920
created_at: '2022-03-01T00:00:00Z',
2021
address: '0x881D40237659C251811CEC9c364ef91dC08D300C',
@@ -48,7 +49,8 @@ export function createMockNotificationEthReceived(): OnChainRawNotification {
4849
chain_id: 1,
4950
block_number: 17485840,
5051
block_timestamp: '2022-03-01T00:00:00Z',
51-
tx_hash: '0x881D40237659C251811CEC9c364ef91dC08D300C',
52+
tx_hash:
53+
'0xb2256b183f2fb3872f99294ab55fb03e6a479b0d4aca556a3b27568b712505a6',
5254
unread: true,
5355
created_at: '2022-03-01T00:00:00Z',
5456
address: '0x881D40237659C251811CEC9c364ef91dC08D300C',
@@ -82,7 +84,8 @@ export function createMockNotificationERC20Sent(): OnChainRawNotification {
8284
chain_id: 1,
8385
block_number: 17485840,
8486
block_timestamp: '2022-03-01T00:00:00Z',
85-
tx_hash: '0x881D40237659C251811CEC9c364ef91dC08D300C',
87+
tx_hash:
88+
'0xb2256b183f2fb3872f99294ab55fb03e6a479b0d4aca556a3b27568b712505a6',
8689
unread: true,
8790
created_at: '2022-03-01T00:00:00Z',
8891
address: '0x881D40237659C251811CEC9c364ef91dC08D300C',
@@ -122,7 +125,8 @@ export function createMockNotificationERC20Received(): OnChainRawNotification {
122125
chain_id: 1,
123126
block_number: 17485840,
124127
block_timestamp: '2022-03-01T00:00:00Z',
125-
tx_hash: '0x881D40237659C251811CEC9c364ef91dC08D300C',
128+
tx_hash:
129+
'0xb2256b183f2fb3872f99294ab55fb03e6a479b0d4aca556a3b27568b712505a6',
126130
unread: true,
127131
created_at: '2022-03-01T00:00:00Z',
128132
address: '0x881D40237659C251811CEC9c364ef91dC08D300C',

packages/notification-services-controller/src/NotificationServicesController/services/feature-announcements.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { mockFetchFeatureAnnouncementNotifications } from '../__fixtures__/mockS
33
import { TRIGGER_TYPES } from '../constants/notification-schema';
44
import { getFeatureAnnouncementNotifications } from './feature-announcements';
55

6+
// Mocked type for testing, allows overwriting TS to test erroneous values
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
type MockedType = any;
9+
610
jest.mock('@contentful/rich-text-html-renderer', () => ({
711
documentToHtmlString: jest
812
.fn()
@@ -20,6 +24,27 @@ describe('Feature Announcement Notifications', () => {
2024
jest.clearAllMocks();
2125
});
2226

27+
it('should return an empty array if invalid environment provided', async () => {
28+
mockFetchFeatureAnnouncementNotifications();
29+
30+
const assertEnvEmpty = async (
31+
override: Partial<typeof featureAnnouncementsEnv>,
32+
) => {
33+
const result = await getFeatureAnnouncementNotifications({
34+
...featureAnnouncementsEnv,
35+
...override,
36+
});
37+
expect(result).toHaveLength(0);
38+
};
39+
40+
await assertEnvEmpty({ accessToken: null as MockedType });
41+
await assertEnvEmpty({ platform: null as MockedType });
42+
await assertEnvEmpty({ spaceId: null as MockedType });
43+
await assertEnvEmpty({ accessToken: '' });
44+
await assertEnvEmpty({ platform: '' });
45+
await assertEnvEmpty({ spaceId: '' });
46+
});
47+
2348
it('should return an empty array if fetch fails', async () => {
2449
const mockEndpoint = mockFetchFeatureAnnouncementNotifications({
2550
status: 500,

packages/notification-services-controller/src/NotificationServicesController/services/feature-announcements.ts

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
2-
import type { Entry, Asset } from 'contentful';
3-
import log from 'loglevel';
2+
import type { Entry, Asset, EntryCollection } from 'contentful';
43

54
import { TRIGGER_TYPES } from '../constants/notification-schema';
65
import { processFeatureAnnouncement } from '../processors/process-feature-announcement';
@@ -37,53 +36,30 @@ export type ContentfulResult = {
3736
items?: TypeFeatureAnnouncement[];
3837
};
3938

40-
const fetchFromContentful = async (
41-
url: string,
42-
retries = 3,
43-
retryDelay = 1000,
44-
): Promise<ContentfulResult | null> => {
45-
let lastError: Error | null = null;
46-
47-
for (let i = 0; i < retries; i++) {
48-
try {
49-
const response = await fetch(url);
50-
if (!response.ok) {
51-
throw new Error(`Fetch failed with status: ${response.status}`);
52-
}
53-
return await response.json();
54-
} catch (error) {
55-
if (error instanceof Error) {
56-
lastError = error;
57-
}
58-
if (i < retries - 1) {
59-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
60-
}
61-
}
62-
}
63-
64-
log.error(
65-
`Error fetching from Contentful after ${retries} retries:`,
66-
lastError,
67-
);
68-
return null;
69-
};
39+
const getFeatureAnnouncementUrl = (env: Env) =>
40+
FEATURE_ANNOUNCEMENT_URL.replace(DEFAULT_SPACE_ID, env.spaceId)
41+
.replace(DEFAULT_ACCESS_TOKEN, env.accessToken)
42+
.replace(DEFAULT_CLIENT_ID, env.platform);
7043

7144
const fetchFeatureAnnouncementNotifications = async (
7245
env: Env,
7346
): Promise<FeatureAnnouncementRawNotification[]> => {
74-
const url = FEATURE_ANNOUNCEMENT_URL.replace(DEFAULT_SPACE_ID, env.spaceId)
75-
.replace(DEFAULT_ACCESS_TOKEN, env.accessToken)
76-
.replace(DEFAULT_CLIENT_ID, env.platform);
77-
const data = await fetchFromContentful(url);
47+
const url = getFeatureAnnouncementUrl(env);
48+
49+
const data = await fetch(url)
50+
.then((r) => r.json())
51+
.catch(() => null);
7852

7953
if (!data) {
8054
return [];
8155
}
8256

8357
const findIncludedItem = (sysId: string) => {
58+
const typedData: EntryCollection<ImageFields | TypeExtensionLinkFields> =
59+
data;
8460
const item =
85-
data?.includes?.Entry?.find((i: Entry) => i?.sys?.id === sysId) ||
86-
data?.includes?.Asset?.find((i: Asset) => i?.sys?.id === sysId);
61+
typedData?.includes?.Entry?.find((i: Entry) => i?.sys?.id === sysId) ||
62+
typedData?.includes?.Asset?.find((i: Asset) => i?.sys?.id === sysId);
8763
return item ? item?.fields : null;
8864
};
8965

@@ -94,6 +70,7 @@ const fetchFeatureAnnouncementNotifications = async (
9470
const imageFields = fields.image
9571
? (findIncludedItem(fields.image.sys.id) as ImageFields['fields'])
9672
: undefined;
73+
9774
const extensionLinkFields = fields.extensionLink
9875
? (findIncludedItem(
9976
fields.extensionLink.sys.id,
@@ -135,10 +112,14 @@ const fetchFeatureAnnouncementNotifications = async (
135112
export async function getFeatureAnnouncementNotifications(
136113
env: Env,
137114
): Promise<INotification[]> {
138-
const rawNotifications = await fetchFeatureAnnouncementNotifications(env);
139-
const notifications = rawNotifications.map((notification) =>
140-
processFeatureAnnouncement(notification),
141-
);
115+
if (env?.accessToken && env?.spaceId && env?.platform) {
116+
const rawNotifications = await fetchFeatureAnnouncementNotifications(env);
117+
const notifications = rawNotifications.map((notification) =>
118+
processFeatureAnnouncement(notification),
119+
);
120+
121+
return notifications;
122+
}
142123

143-
return notifications;
124+
return [];
144125
}

packages/notification-services-controller/src/NotificationServicesController/utils/utils.ts

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import log from 'loglevel';
21
import { v4 as uuidv4 } from 'uuid';
32

43
import {
@@ -425,65 +424,20 @@ export function toggleUserStorageTriggerStatus(
425424
return userStorage;
426425
}
427426

428-
/**
429-
* Attempts to fetch a resource from the network, retrying the request up to a specified number of times
430-
* in case of failure, with a delay between attempts.
431-
*
432-
* @param url - The resource URL.
433-
* @param options - The options for the fetch request.
434-
* @param retries - Maximum number of retry attempts. Defaults to 3.
435-
* @param retryDelay - Delay between retry attempts in milliseconds. Defaults to 1000.
436-
* @returns A Promise resolving to the Response object.
437-
* @throws Will throw an error if the request fails after the specified number of retries.
438-
*/
439-
async function fetchWithRetry(
440-
url: string,
441-
options: RequestInit,
442-
retries = 3,
443-
retryDelay = 1000,
444-
): Promise<Response> {
445-
for (let attempt = 1; attempt <= retries; attempt++) {
446-
try {
447-
const response = await fetch(url, options);
448-
if (!response.ok) {
449-
throw new Error(`Fetch failed with status: ${response.status}`);
450-
}
451-
return response;
452-
} catch (error) {
453-
log.error(`Attempt ${attempt} failed for fetch:`, error);
454-
if (attempt < retries) {
455-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
456-
} else {
457-
throw new Error(
458-
`Fetching failed after ${retries} retries. Last error: ${
459-
error instanceof Error ? error.message : 'Unknown error'
460-
}`,
461-
);
462-
}
463-
}
464-
}
465-
466-
throw new Error('Unexpected error in fetchWithRetry');
467-
}
468-
469427
/**
470428
* Performs an API call with automatic retries on failure.
471429
*
472430
* @param bearerToken - The JSON Web Token for authorization.
473431
* @param endpoint - The URL of the API endpoint to call.
474432
* @param method - The HTTP method ('POST' or 'DELETE').
475433
* @param body - The body of the request. It should be an object that can be serialized to JSON.
476-
* @param retries - The number of retry attempts in case of failure (default is 3).
477-
* @param retryDelay - The delay between retries in milliseconds (default is 1000).
478434
* @returns A Promise that resolves to the response of the fetch request.
479435
*/
480436
export async function makeApiCall<Body>(
481437
bearerToken: string,
482438
endpoint: string,
483439
method: 'POST' | 'DELETE',
484440
body: Body,
485-
retries = 3,
486-
retryDelay = 1000,
487441
): Promise<Response> {
488442
const options: RequestInit = {
489443
method,
@@ -494,5 +448,5 @@ export async function makeApiCall<Body>(
494448
body: JSON.stringify(body),
495449
};
496450

497-
return fetchWithRetry(endpoint, options, retries, retryDelay);
451+
return await fetch(endpoint, options);
498452
}

packages/permission-controller/src/rpc-methods/getPermissions.test.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ describe('getPermissions RPC method', () => {
1414

1515
const engine = new JsonRpcEngine();
1616
engine.push(
17-
async (
17+
(
1818
req: JsonRpcRequest<[]>,
1919
res: PendingJsonRpcResponse<PermissionConstraint[]>,
2020
next,
2121
end,
2222
) => {
23-
await implementation(req, res, next, end, {
23+
// We intentionally do not await this promise; JsonRpcEngine won't await
24+
// middleware anyway.
25+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
26+
implementation(req, res, next, end, {
2427
getPermissionsForOrigin: mockGetPermissionsForOrigin,
2528
});
2629
},
@@ -44,13 +47,16 @@ describe('getPermissions RPC method', () => {
4447

4548
const engine = new JsonRpcEngine();
4649
engine.push(
47-
async (
50+
(
4851
req: JsonRpcRequest<[]>,
4952
res: PendingJsonRpcResponse<PermissionConstraint[]>,
5053
next,
5154
end,
5255
) => {
53-
await implementation(req, res, next, end, {
56+
// We intentionally do not await this promise; JsonRpcEngine won't await
57+
// middleware anyway.
58+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
59+
implementation(req, res, next, end, {
5460
getPermissionsForOrigin: mockGetPermissionsForOrigin,
5561
});
5662
},

packages/permission-controller/src/rpc-methods/requestPermissions.test.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ describe('requestPermissions RPC method', () => {
3232

3333
const engine = new JsonRpcEngine();
3434
engine.push<[RequestedPermissions], PermissionConstraint[]>(
35-
async (req, res, next, end) => {
36-
await implementation(req, res, next, end, {
35+
(req, res, next, end) => {
36+
// We intentionally do not await this promise; JsonRpcEngine won't await
37+
// middleware anyway.
38+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
39+
implementation(req, res, next, end, {
3740
requestPermissionsForOrigin: mockRequestPermissionsForOrigin,
3841
});
3942
},
@@ -102,8 +105,11 @@ describe('requestPermissions RPC method', () => {
102105

103106
const engine = new JsonRpcEngine();
104107
engine.push<[RequestedPermissions], PermissionConstraint[]>(
105-
async (req, res, next, end) => {
106-
await implementation(req, res, next, end, {
108+
(req, res, next, end) => {
109+
// We intentionally do not await this promise; JsonRpcEngine won't await
110+
// middleware anyway.
111+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
112+
implementation(req, res, next, end, {
107113
requestPermissionsForOrigin: mockRequestPermissionsForOrigin,
108114
});
109115
},

0 commit comments

Comments
 (0)