@@ -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