Skip to content

Commit 5d26b77

Browse files
committed
fix: prevent JSON parsing crashes on HTML proxy responses with safe fetch helper
1 parent 92fbd13 commit 5d26b77

1 file changed

Lines changed: 49 additions & 21 deletions

File tree

‎src/App.jsx‎

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,51 @@ export default function App() {
110110
return parsed;
111111
};
112112

113+
// Safe fetch helper that handles CORS, proxies, and non-JSON HTML error pages gracefully
114+
const fetchVideoData = async (url) => {
115+
const endpoints = [
116+
`http://localhost:3001/api/video-info?url=${encodeURIComponent(url)}`,
117+
`/api/video-info?url=${encodeURIComponent(url)}`
118+
];
119+
120+
let lastError = null;
121+
122+
for (const endpoint of endpoints) {
123+
try {
124+
const response = await fetch(endpoint, {
125+
headers: { 'Accept': 'application/json' }
126+
});
127+
128+
const contentType = response.headers.get('content-type') || '';
129+
130+
// Guard against HTML error pages (like "The page cannot be found") causing JSON syntax crashes
131+
if (!contentType.includes('application/json')) {
132+
const previewText = await response.text();
133+
console.warn(`Endpoint ${endpoint} returned non-JSON (${response.status}):`, previewText.slice(0, 100));
134+
lastError = new Error(`Server returned non-JSON response (${response.status})`);
135+
continue; // Try next endpoint
136+
}
137+
138+
const data = await response.json();
139+
140+
if (!response.ok) {
141+
throw new Error(data.error || `Server returned error code ${response.status}`);
142+
}
143+
144+
if (!data.metadata) {
145+
throw new Error('Server returned invalid data format.');
146+
}
147+
148+
return data; // Success!
149+
} catch (err) {
150+
lastError = err;
151+
console.warn(`Failed fetching from ${endpoint}:`, err.message);
152+
}
153+
}
154+
155+
throw lastError || new Error('Could not connect to TubeNotes backend service.');
156+
};
157+
113158
// Fetch from Express API
114159
const handleFetchVideo = async (e) => {
115160
if (e) e.preventDefault();
@@ -118,30 +163,13 @@ export default function App() {
118163

119164
const videoId = extractVideoId(videoUrl);
120165
if (!videoId) {
121-
setError('Please enter a valid YouTube video URL.');
166+
setError('Please enter a valid YouTube video URL or ID.');
122167
setIsLoading(false);
123168
return;
124169
}
125170

126171
try {
127-
// 1. Call local backend (proxied via Vite server to port 3001, fallback to direct port 3001)
128-
let response;
129-
try {
130-
response = await fetch(`/api/video-info?url=${encodeURIComponent(videoUrl)}`);
131-
} catch (proxyErr) {
132-
console.warn('Proxy fetch failed, attempting direct backend connection...', proxyErr.message);
133-
response = await fetch(`http://localhost:3001/api/video-info?url=${encodeURIComponent(videoUrl)}`);
134-
}
135-
136-
const data = await response.json();
137-
138-
if (!response.ok) {
139-
throw new Error(data.error || `Backend responded with error status ${response.status}`);
140-
}
141-
142-
if (!data.metadata) {
143-
throw new Error('Received unexpected response format from server.');
144-
}
172+
const data = await fetchVideoData(videoUrl);
145173

146174
// Successfully fetched metadata and transcript
147175
setActiveVideo({
@@ -174,11 +202,11 @@ export default function App() {
174202

175203
} catch (err) {
176204
console.warn('Backend fetch failed:', err.message);
177-
const isNetworkError = err.name === 'TypeError' || err.message.toLowerCase().includes('failed to fetch') || err.message.toLowerCase().includes('network');
205+
const isNetworkError = err.name === 'TypeError' || err.message.toLowerCase().includes('failed to fetch') || err.message.toLowerCase().includes('network') || err.message.toLowerCase().includes('could not connect');
178206

179207
if (isNetworkError) {
180208
setError(
181-
'Could not connect to the local backend server (http://localhost:3001). Please ensure "npm run dev" is active in your terminal. You can still use the app by selecting a pre-loaded Demo Video below, or manually pasting subtitles.'
209+
'Could not connect to the local backend server (http://localhost:3001). Please ensure "npm run dev" is running in your terminal. You can still use the app by selecting a pre-loaded Demo Video below, or manually pasting subtitles.'
182210
);
183211
} else {
184212
setError(err.message || 'An error occurred while fetching video details.');

0 commit comments

Comments
 (0)