Skip to content

Commit dae978b

Browse files
committed
Handle invalid ssl cert in delivery preview
1 parent b0adbe8 commit dae978b

File tree

9 files changed

+61
-51
lines changed

9 files changed

+61
-51
lines changed

services/backend-api/client/src/features/feed/components/UserFeedLogs/DeliveryPreview/FeedLevelStateDisplay.tsx

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Badge, Box, Button, Flex, HStack, Icon, Stack, Text } from "@chakra-ui/react";
22
import { WarningIcon } from "@chakra-ui/icons";
3-
import { Link } from "react-router-dom";
4-
import { pages } from "../../../../../constants";
5-
import { UserFeedTabSearchParam } from "../../../../../constants/userFeedTabSearchParam";
63
import { getHttpStatusMessage, getGenericErrorMessage } from "./deliveryPreviewUtils";
74

5+
const scrollToRequestHistory = () => {
6+
document.getElementById("request-history")?.scrollIntoView({ behavior: "smooth" });
7+
};
8+
89
export interface FeedState {
910
state: string;
1011
errorType?: string;
@@ -13,15 +14,13 @@ export interface FeedState {
1314

1415
interface FeedLevelStateDisplayProps {
1516
feedState: FeedState;
16-
feedId: string;
1717
}
1818

1919
interface HttpStatusErrorDisplayProps {
2020
statusCode: number;
21-
feedId: string;
2221
}
2322

24-
const HttpStatusErrorDisplay = ({ statusCode, feedId }: HttpStatusErrorDisplayProps) => {
23+
const HttpStatusErrorDisplay = ({ statusCode }: HttpStatusErrorDisplayProps) => {
2524
const statusInfo = getHttpStatusMessage(statusCode);
2625
const StatusIcon = statusInfo.icon;
2726

@@ -67,8 +66,7 @@ const HttpStatusErrorDisplay = ({ statusCode, feedId }: HttpStatusErrorDisplayPr
6766
</Text>
6867
</Stack>
6968
<Button
70-
as={Link}
71-
to={pages.userFeed(feedId, { tab: UserFeedTabSearchParam.Logs })}
69+
onClick={scrollToRequestHistory}
7270
size={{ base: "md", md: "sm" }}
7371
width={{ base: "100%", md: "auto" }}
7472
variant="outline"
@@ -84,10 +82,9 @@ const HttpStatusErrorDisplay = ({ statusCode, feedId }: HttpStatusErrorDisplayPr
8482
interface GenericErrorDisplayProps {
8583
feedState: string;
8684
errorType?: string;
87-
feedId: string;
8885
}
8986

90-
const GenericErrorDisplay = ({ feedState, errorType, feedId }: GenericErrorDisplayProps) => {
87+
const GenericErrorDisplay = ({ feedState, errorType }: GenericErrorDisplayProps) => {
9188
const errorInfo = getGenericErrorMessage(feedState, errorType);
9289

9390
return (
@@ -100,8 +97,7 @@ const GenericErrorDisplay = ({ feedState, errorType, feedId }: GenericErrorDispl
10097
</HStack>
10198
<Text color="whiteAlpha.900">{errorInfo.explanation}</Text>
10299
<Button
103-
as={Link}
104-
to={pages.userFeed(feedId, { tab: UserFeedTabSearchParam.Logs })}
100+
onClick={scrollToRequestHistory}
105101
size={{ base: "md", md: "sm" }}
106102
width={{ base: "100%", md: "auto" }}
107103
variant="outline"
@@ -114,7 +110,7 @@ const GenericErrorDisplay = ({ feedState, errorType, feedId }: GenericErrorDispl
114110
);
115111
};
116112

117-
export const FeedLevelStateDisplay = ({ feedState, feedId }: FeedLevelStateDisplayProps) => {
113+
export const FeedLevelStateDisplay = ({ feedState }: FeedLevelStateDisplayProps) => {
118114
// Note: "unchanged" state is no longer a feed-level state.
119115
// When the feed is unchanged, the backend returns articles with FeedUnchanged outcome.
120116

@@ -123,16 +119,12 @@ export const FeedLevelStateDisplay = ({ feedState, feedId }: FeedLevelStateDispl
123119
feedState.errorType === "bad-status-code" &&
124120
feedState.httpStatusCode
125121
) {
126-
return <HttpStatusErrorDisplay statusCode={feedState.httpStatusCode} feedId={feedId} />;
122+
return <HttpStatusErrorDisplay statusCode={feedState.httpStatusCode} />;
127123
}
128124

129125
if (feedState.state === "fetch-error" || feedState.state === "parse-error") {
130126
return (
131-
<GenericErrorDisplay
132-
feedState={feedState.state}
133-
errorType={feedState.errorType}
134-
feedId={feedId}
135-
/>
127+
<GenericErrorDisplay feedState={feedState.state} errorType={feedState.errorType} />
136128
);
137129
}
138130

services/backend-api/client/src/features/feed/components/UserFeedLogs/DeliveryPreview/deliveryPreviewUtils.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,12 @@ export function getGenericErrorMessage(feedState: string, errorType?: string): G
254254
title: "Failed to Fetch Feed",
255255
explanation: "An unexpected error occurred. Try refreshing in a few minutes.",
256256
};
257+
case "invalid-ssl-certificate":
258+
return {
259+
title: "Invalid SSL Certificate",
260+
explanation:
261+
"The feed server's SSL certificate could not be verified. The certificate may be expired, self-signed, or misconfigured. Contact the feed provider or try an alternative feed URL.",
262+
};
257263
default:
258264
return {
259265
title: "Failed to Fetch Feed",

services/backend-api/client/src/features/feed/components/UserFeedLogs/DeliveryPreview/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export const DeliveryPreviewPresentational = ({
212212
</Box>
213213
)}
214214
{!isLoading && !error && !hasNoConnections && hasFeedLevelState && feedState && (
215-
<FeedLevelStateDisplay feedState={feedState} feedId={feedId} />
215+
<FeedLevelStateDisplay feedState={feedState} />
216216
)}
217217
{!isLoading && !error && !hasNoConnections && !hasNoData && !hasFeedLevelState && (
218218
<Stack spacing={4}>

services/backend-api/client/src/features/feed/components/UserFeedLogs/RequestHistory/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,14 @@ export const RequestHistory = () => {
180180
);
181181

182182
return (
183-
<Stack spacing={4} mb={8} border="solid 1px" borderColor="gray.700" borderRadius="md">
183+
<Stack
184+
id="request-history"
185+
spacing={4}
186+
mb={8}
187+
border="solid 1px"
188+
borderColor="gray.700"
189+
borderRadius="md"
190+
>
184191
<Box>
185192
<Stack px={4} py={4}>
186193
<Heading size="sm" as="h3" m={0} id="request-history-table-title">
Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import type { Meta, StoryObj } from "@storybook/react-vite";
2-
import React from "react";
3-
import { MemoryRouter } from "react-router-dom";
42
import { FeedLevelStateDisplay } from "../../features/feed/components/UserFeedLogs/DeliveryPreview/FeedLevelStateDisplay";
53
import {
64
createFeedState404,
@@ -21,13 +19,6 @@ const meta = {
2119
layout: "padded",
2220
},
2321
tags: ["autodocs"],
24-
decorators: [
25-
(Story: React.ComponentType) => (
26-
<MemoryRouter>
27-
<Story />
28-
</MemoryRouter>
29-
),
30-
],
3122
} satisfies Meta<typeof FeedLevelStateDisplay>;
3223

3324
export default meta;
@@ -38,28 +29,24 @@ type Story = StoryObj<typeof meta>;
3829
export const Http404NotFound: Story = {
3930
args: {
4031
feedState: createFeedState404(),
41-
feedId: "feed-123",
4232
},
4333
};
4434

4535
export const Http403Forbidden: Story = {
4636
args: {
4737
feedState: createFeedState403(),
48-
feedId: "feed-123",
4938
},
5039
};
5140

5241
export const Http429RateLimited: Story = {
5342
args: {
5443
feedState: createFeedState429(),
55-
feedId: "feed-123",
5644
},
5745
};
5846

5947
export const Http5xxServerError: Story = {
6048
args: {
6149
feedState: createFeedState5xx(),
62-
feedId: "feed-123",
6350
},
6451
};
6552

@@ -68,21 +55,18 @@ export const Http5xxServerError: Story = {
6855
export const FetchTimeout: Story = {
6956
args: {
7057
feedState: createFetchTimeout(),
71-
feedId: "feed-123",
7258
},
7359
};
7460

7561
export const FetchFailed: Story = {
7662
args: {
7763
feedState: createFetchFailed(),
78-
feedId: "feed-123",
7964
},
8065
};
8166

8267
export const FetchInternalError: Story = {
8368
args: {
8469
feedState: createFetchInternalError(),
85-
feedId: "feed-123",
8670
},
8771
};
8872

@@ -91,13 +75,11 @@ export const FetchInternalError: Story = {
9175
export const ParseTimeout: Story = {
9276
args: {
9377
feedState: createParseTimeout(),
94-
feedId: "feed-123",
9578
},
9679
};
9780

9881
export const ParseInvalidFormat: Story = {
9982
args: {
10083
feedState: createParseInvalidFormat(),
101-
feedId: "feed-123",
10284
},
10385
};

services/user-feeds-next/src/feed-fetcher/exceptions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ export class FeedRequestTimedOutException extends Error {
5050
}
5151
}
5252

53+
export class FeedRequestInvalidSslCertificateException extends Error {
54+
constructor(message: string) {
55+
super(message);
56+
this.name = "FeedRequestInvalidSslCertificateException";
57+
}
58+
}
59+
5360
export class FeedArticleNotFoundException extends Error {
5461
constructor(message: string) {
5562
super(message);

services/user-feeds-next/src/feed-fetcher/feed-fetcher.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { request } from "undici";
21
import pRetry from "p-retry";
32
import {
43
FeedRequestBadStatusCodeException,
54
FeedRequestFetchException,
65
FeedRequestInternalException,
6+
FeedRequestInvalidSslCertificateException,
77
FeedRequestNetworkException,
88
FeedRequestParseException,
99
FeedRequestServerStatusException,
@@ -32,8 +32,7 @@ export async function fetchFeed(
3232
}
3333
): Promise<FetchFeedResult> {
3434
const serviceHost = options.serviceHost;
35-
let statusCode: number;
36-
let body: { json: () => Promise<unknown> };
35+
let response: Response;
3736

3837
try {
3938
const requestBody = {
@@ -44,10 +43,10 @@ export async function fetchFeed(
4443
stalenessThresholdSeconds: options?.stalenessThresholdSeconds,
4544
hashToCompare: options?.hashToCompare || undefined,
4645
lookupDetails: options?.lookupDetails,
47-
}
48-
const response = await pRetry(
46+
};
47+
response = await pRetry(
4948
async () =>
50-
request(serviceHost, {
49+
fetch(serviceHost, {
5150
method: "POST",
5251
body: JSON.stringify(requestBody),
5352
headers: {
@@ -61,18 +60,15 @@ export async function fetchFeed(
6160
randomize: true,
6261
}
6362
);
64-
65-
statusCode = response.statusCode;
66-
body = response.body;
6763
} catch (err) {
6864
throw new FeedRequestNetworkException(
6965
`Failed to execute request to feed requests API: ${(err as Error).message}`
7066
);
7167
}
7268

7369
return handleFetchResponse({
74-
statusCode,
75-
json: body.json.bind(body),
70+
statusCode: response.status,
71+
json: response.json.bind(response),
7672
});
7773
}
7874

@@ -123,6 +119,12 @@ async function handleFetchResponse({
123119
);
124120
}
125121

122+
if (requestStatus === FeedResponseRequestStatus.InvalidSslCertificate) {
123+
throw new FeedRequestInvalidSslCertificateException(
124+
"Feed server has an invalid SSL certificate"
125+
);
126+
}
127+
126128
if (
127129
requestStatus === FeedResponseRequestStatus.MatchedHash
128130
) {

services/user-feeds-next/src/feed-fetcher/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum FeedResponseRequestStatus {
66
FetchError = "FETCH_ERROR",
77
FetchTimeout = "FETCH_TIMEOUT",
88
MatchedHash = "MATCHED_HASH",
9+
InvalidSslCertificate = "INVALID_SSL_CERTIFICATE",
910
}
1011

1112
interface FeedResponseInternalError {
@@ -16,6 +17,10 @@ interface FeedResponseMatchedHash {
1617
requestStatus: FeedResponseRequestStatus.MatchedHash;
1718
}
1819

20+
interface FeedResponseInvalidSslCertificate {
21+
requestStatus: FeedResponseRequestStatus.InvalidSslCertificate;
22+
}
23+
1924
interface FeedResponseFetchError {
2025
requestStatus: FeedResponseRequestStatus.FetchError;
2126
}
@@ -49,7 +54,8 @@ export type FeedResponse =
4954
| FeedResponseSuccess
5055
| FeedResponseFetchError
5156
| FeedResponseBadStatusCodeError
52-
| FeedResponseMatchedHash;
57+
| FeedResponseMatchedHash
58+
| FeedResponseInvalidSslCertificate;
5359

5460
export interface FeedRequestLookupDetails {
5561
key: string;

services/user-feeds-next/src/feeds/shared-processing.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
FeedRequestBadStatusCodeException,
1111
FeedRequestFetchException,
1212
FeedRequestTimedOutException,
13+
FeedRequestInvalidSslCertificateException,
1314
} from "../feed-fetcher/exceptions";
1415
import {
1516
FeedParseTimeoutException,
@@ -155,6 +156,13 @@ export async function fetchAndParseFeed(
155156
message: err.message,
156157
};
157158
}
159+
if (err instanceof FeedRequestInvalidSslCertificateException) {
160+
return {
161+
status: "fetch-error",
162+
errorType: "invalid-ssl-certificate",
163+
message: err.message,
164+
};
165+
}
158166
throw err;
159167
}
160168

0 commit comments

Comments
 (0)