From 34787a327039a3390593d565567b30e2bd6796e0 Mon Sep 17 00:00:00 2001 From: lorenzozanee Date: Mon, 31 Aug 2026 13:53:31 +0800 Subject: [PATCH] Bugfix/handle Trackinsight WAF responses in data enhancer Trackinsight now protects its API with AWS WAF which returns JavaScript challenges (non-JSON, non-2xx) instead of JSON. Guard search, funds and holdings fetches against non-OK, non-JSON and invalid JSON responses and return gracefully without throwing. Fixes #5924 --- .../trackinsight/trackinsight.service.ts | 87 ++++++++++++++++++- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts index aa61c048c..6d7db3d14 100644 --- a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts @@ -82,7 +82,32 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { signal: AbortSignal.timeout(requestTimeout) } ) - .then((res) => res.json()) + .then(async (res) => { + if (!res.ok) { + this.logger.debug( + `Trackinsight funds request failed for "${trackinsightSymbol}" with status ${res.status}` + ); + return {}; + } + + const contentType = res.headers.get('content-type') ?? ''; + + if (!contentType.includes('application/json')) { + this.logger.debug( + `Trackinsight funds request returned non-JSON for "${trackinsightSymbol}": ${contentType}` + ); + return {}; + } + + try { + return await res.json(); + } catch { + this.logger.debug( + `Trackinsight funds request returned invalid JSON for "${trackinsightSymbol}"` + ); + return {}; + } + }) .catch(() => { return {}; }); @@ -106,7 +131,32 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { signal: AbortSignal.timeout(requestTimeout) } ) - .then((res) => res.json()) + .then(async (res) => { + if (!res.ok) { + this.logger.debug( + `Trackinsight holdings request failed for "${trackinsightSymbol}" with status ${res.status}` + ); + return {}; + } + + const contentType = res.headers.get('content-type') ?? ''; + + if (!contentType.includes('application/json')) { + this.logger.debug( + `Trackinsight holdings request returned non-JSON for "${trackinsightSymbol}": ${contentType}` + ); + return {}; + } + + try { + return await res.json(); + } catch { + this.logger.debug( + `Trackinsight holdings request returned invalid JSON for "${trackinsightSymbol}"` + ); + return {}; + } + }) .catch(() => { return {}; }); @@ -196,13 +246,42 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { }) { return this.fetchService .fetch( - `${TrackinsightDataEnhancerService.baseUrl}/search-api/search_v2/${symbol}/_/ticker/default/0/3`, + `${TrackinsightDataEnhancerService.baseUrl}/search-api/search_v2/${encodeURIComponent(symbol)}/_/ticker/default/0/3`, { signal: AbortSignal.timeout(requestTimeout) } ) - .then((res) => res.json()) + .then(async (res) => { + if (!res.ok) { + this.logger.debug( + `Trackinsight search failed for "${symbol}" with status ${res.status}` + ); + return undefined; + } + + const contentType = res.headers.get('content-type') ?? ''; + + if (!contentType.includes('application/json')) { + this.logger.debug( + `Trackinsight search returned non-JSON for "${symbol}": ${contentType}` + ); + return undefined; + } + + try { + return await res.json(); + } catch { + this.logger.debug( + `Trackinsight search returned invalid JSON for "${symbol}"` + ); + return undefined; + } + }) .then((jsonRes) => { + if (!jsonRes) { + return undefined; + } + if ( jsonRes['results']?.['count'] === 1 || // Allow exact match