Skip to content

Commit ddf9652

Browse files
authored
fix(react-email): Crashing when the link or image does not exist (#2016)
1 parent 1aa5797 commit ddf9652

File tree

5 files changed

+83
-44
lines changed

5 files changed

+83
-44
lines changed

.changeset/pretty-tables-repeat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-email": patch
3+
---
4+
5+
Fix crashing when the link or image does not exist

packages/react-email/src/actions/email-validation/check-images.ts

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,33 +103,44 @@ export const checkImages = async (code: string, base: string) => {
103103
});
104104
}
105105

106-
const res = await quickFetch(url);
107-
const hasSucceeded =
108-
res.statusCode?.toString().startsWith('2') ?? false;
109-
110-
result.checks.push({
111-
type: 'fetch_attempt',
112-
passed: hasSucceeded,
113-
metadata: {
114-
fetchStatusCode: res.statusCode,
115-
},
116-
});
117-
if (!hasSucceeded) {
118-
result.status = res.statusCode?.toString().startsWith('3')
119-
? 'warning'
120-
: 'error';
121-
}
106+
let res: IncomingMessage | undefined = undefined;
107+
try {
108+
res = await quickFetch(url);
109+
const hasSucceeded =
110+
res.statusCode?.toString().startsWith('2') ?? false;
111+
result.checks.push({
112+
type: 'fetch_attempt',
113+
passed: hasSucceeded,
114+
metadata: {
115+
fetchStatusCode: res.statusCode,
116+
},
117+
});
118+
if (!hasSucceeded) {
119+
result.status = res.statusCode?.toString().startsWith('3')
120+
? 'warning'
121+
: 'error';
122+
}
122123

123-
const responseSizeBytes = await getResponseSizeInBytes(res);
124-
result.checks.push({
125-
type: 'image_size',
126-
passed: responseSizeBytes < 1_048_576, // 1024 x 1024 bytes
127-
metadata: {
128-
byteCount: responseSizeBytes,
129-
},
130-
});
131-
if (responseSizeBytes > 1_048_576) {
132-
result.status = 'warning';
124+
const responseSizeBytes = await getResponseSizeInBytes(res);
125+
result.checks.push({
126+
type: 'image_size',
127+
passed: responseSizeBytes < 1_048_576, // 1024 x 1024 bytes
128+
metadata: {
129+
byteCount: responseSizeBytes,
130+
},
131+
});
132+
if (responseSizeBytes > 1_048_576 && result.status !== 'error') {
133+
result.status = 'warning';
134+
}
135+
} catch (exception) {
136+
result.checks.push({
137+
type: 'fetch_attempt',
138+
passed: false,
139+
metadata: {
140+
fetchStatusCode: undefined,
141+
},
142+
});
143+
result.status = 'error';
133144
}
134145
} catch (exception) {
135146
result.checks.push({

packages/react-email/src/actions/email-validation/check-links.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use server';
22

3+
import type { IncomingMessage } from 'node:http';
34
import { parse } from 'node-html-parser';
45
import {
56
type CodeLocation,
@@ -67,20 +68,32 @@ export const checkLinks = async (code: string) => {
6768
});
6869
}
6970

70-
const res = await quickFetch(url);
71-
const hasSucceeded =
72-
res.statusCode?.toString().startsWith('2') ?? false;
73-
result.checks.push({
74-
type: 'fetch_attempt',
75-
passed: hasSucceeded,
76-
metadata: {
77-
fetchStatusCode: res.statusCode,
78-
},
79-
});
80-
if (!hasSucceeded) {
81-
result.status = res.statusCode?.toString().startsWith('3')
82-
? 'warning'
83-
: 'error';
71+
let res: IncomingMessage | undefined = undefined;
72+
try {
73+
res = await quickFetch(url);
74+
const hasSucceeded =
75+
res.statusCode?.toString().startsWith('2') ?? false;
76+
result.checks.push({
77+
type: 'fetch_attempt',
78+
passed: hasSucceeded,
79+
metadata: {
80+
fetchStatusCode: res.statusCode,
81+
},
82+
});
83+
if (!hasSucceeded) {
84+
result.status = res.statusCode?.toString().startsWith('3')
85+
? 'warning'
86+
: 'error';
87+
}
88+
} catch (exception) {
89+
result.checks.push({
90+
type: 'fetch_attempt',
91+
passed: false,
92+
metadata: {
93+
fetchStatusCode: undefined,
94+
},
95+
});
96+
result.status = 'error';
8497
}
8598
} catch (exception) {
8699
result.checks.push({

packages/react-email/src/actions/email-validation/quick-fetch.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import http from 'node:http';
33
import https from 'node:https';
44

55
export const quickFetch = (url: URL) => {
6-
return new Promise<IncomingMessage>((resolve) => {
6+
return new Promise<IncomingMessage>((resolve, reject) => {
77
const caller = url.protocol === 'https:' ? https : http;
8-
caller.get(url, (res) => {
9-
resolve(res);
10-
});
8+
caller
9+
.get(url, (res) => {
10+
resolve(res);
11+
})
12+
.on('error', (error) => reject(error));
1113
});
1214
};

packages/react-email/src/components/toolbar/linter.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ export const Linter = ({ rows }: LinterProps) => {
126126
failingCheck.metadata.fetchStatusCode >= 400
127127
? 'The link is broken'
128128
: null}
129+
{failingCheck.type === 'fetch_attempt' &&
130+
failingCheck.metadata.fetchStatusCode === undefined
131+
? 'The link could not be reached'
132+
: null}
129133
{failingCheck.type === 'syntax'
130134
? 'The link is broken due to invalid syntax'
131135
: null}
@@ -180,6 +184,10 @@ export const Linter = ({ rows }: LinterProps) => {
180184
failingCheck.metadata.fetchStatusCode >= 400
181185
? 'The image is broken'
182186
: null}
187+
{failingCheck.type === 'fetch_attempt' &&
188+
failingCheck.metadata.fetchStatusCode === undefined
189+
? 'The image could not be reached'
190+
: null}
183191
{failingCheck.type === 'syntax'
184192
? 'The image is broken due to an invalid source'
185193
: null}

0 commit comments

Comments
 (0)