Skip to content

Commit 3b79cde

Browse files
Jon-edgej0ntz
authored andcommitted
Add more robust error handling to SwipeChart
1 parent 91d5eda commit 3b79cde

File tree

3 files changed

+68
-41
lines changed

3 files changed

+68
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased (develop)
44

5+
- added: More robust error handling in `SwipeChart` to handle rate limits
56
- added: New Banxa payment methods
67

78
## 4.45.0 (staging)

eslint.config.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ export default [
138138
'src/components/cards/VisaCardCard.tsx',
139139
'src/components/cards/WalletRestoreCard.tsx',
140140
'src/components/cards/WarningCard.tsx',
141-
'src/components/charts/SwipeChart.tsx',
142141

143142
'src/components/common/AnimatedNumber.tsx',
144143
'src/components/common/BlurBackground.tsx',

src/components/charts/SwipeChart.tsx

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -286,49 +286,76 @@ export const SwipeChart: React.FC<Props> = props => {
286286
)
287287
// Start with the free base URL
288288
let fetchUrl = `${COINGECKO_URL}${fetchPath}`
289-
do {
290-
try {
291-
// Construct the dataset query
292-
const response = await fetch(fetchUrl)
293-
const result = await response.json()
294-
const apiError = asMaybe(asCoinGeckoError)(result)
295-
if (apiError != null) {
296-
if (apiError.status.error_code === 429) {
297-
// Rate limit error, use our API key as a fallback
298-
if (
299-
!fetchUrl.includes('x_cg_pro_api_key') &&
300-
ENV.COINGECKO_API_KEY !== ''
301-
) {
302-
fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}`
289+
try {
290+
do {
291+
try {
292+
const response = await fetch(fetchUrl)
293+
294+
// Handle non-OK responses before parsing JSON
295+
if (!response.ok) {
296+
if (response.status === 429) {
297+
if (
298+
!fetchUrl.includes('x_cg_pro_api_key') &&
299+
ENV.COINGECKO_API_KEY !== ''
300+
) {
301+
fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}`
302+
}
303+
// Wait 2 seconds before retrying. It typically takes 1 minute
304+
// before rate limiting is relieved, so even 2 seconds is hasty.
305+
await snooze(2000)
306+
continue
303307
}
304-
// Wait 2 second before retrying. It typically takes 1 minute
305-
// before rate limiting is relieved, so even 2 seconds is hasty.
306-
await snooze(2000)
307-
continue
308+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
308309
}
309-
throw new Error(
310-
`Failed to fetch market data: ${apiError.status.error_code} ${apiError.status.error_message}`
311-
)
312-
}
313310

314-
const marketChartRange = asCoinGeckoMarketChartRange(result)
315-
const rawChartData = marketChartRange.prices.map(pair => ({
316-
x: new Date(pair[0]),
317-
y: pair[1]
318-
}))
319-
const reduced = reduceChartData(rawChartData, selectedTimespan)
320-
321-
setChartData(reduced)
322-
cachedTimespanChartData.set(selectedTimespan, reduced)
323-
setCachedChartData(cachedTimespanChartData)
324-
} catch (e: unknown) {
325-
console.error(JSON.stringify(e))
326-
setErrorMessage(lstrings.error_data_unavailable)
327-
} finally {
328-
setIsFetching(false)
329-
}
330-
break
331-
} while (true)
311+
// Parse JSON safely - use text() first to catch parse errors locally
312+
const text = await response.text()
313+
let result: unknown
314+
try {
315+
result = JSON.parse(text)
316+
} catch {
317+
throw new Error(`Invalid JSON response: ${text.slice(0, 100)}...`)
318+
}
319+
320+
const apiError = asMaybe(asCoinGeckoError)(result)
321+
if (apiError != null) {
322+
if (apiError.status.error_code === 429) {
323+
if (
324+
!fetchUrl.includes('x_cg_pro_api_key') &&
325+
ENV.COINGECKO_API_KEY !== ''
326+
) {
327+
fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}`
328+
}
329+
// Wait 2 seconds before retrying. It typically takes 1 minute
330+
// before rate limiting is relieved, so even 2 seconds is hasty.
331+
await snooze(2000)
332+
continue
333+
}
334+
throw new Error(
335+
`Failed to fetch market data: ${apiError.status.error_code} ${apiError.status.error_message}`
336+
)
337+
}
338+
339+
const marketChartRange = asCoinGeckoMarketChartRange(result)
340+
const rawChartData = marketChartRange.prices.map(pair => ({
341+
x: new Date(pair[0]),
342+
y: pair[1]
343+
}))
344+
const reduced = reduceChartData(rawChartData, selectedTimespan)
345+
346+
setChartData(reduced)
347+
cachedTimespanChartData.set(selectedTimespan, reduced)
348+
setCachedChartData(cachedTimespanChartData)
349+
} catch (e: unknown) {
350+
const message = e instanceof Error ? e.message : String(e)
351+
console.error('SwipeChart fetch error:', message)
352+
setErrorMessage(lstrings.error_data_unavailable)
353+
}
354+
break
355+
} while (true)
356+
} finally {
357+
setIsFetching(false)
358+
}
332359
},
333360
[selectedTimespan, isConnected, fetchAssetId, coingeckoFiat],
334361
'swipeChart'

0 commit comments

Comments
 (0)